[CLAUDE] Domain+App+Api+Infra: Plan B G-H1 Task 4+6 — Hrm CQRS 5 endpoint + Permission menu
Phase 10.1 G-H1 Phase 2 — Task 4 (BE CQRS + REST endpoint) + Task 6
(Permission menu seed) cumulative. Foundation BE side complete cho Hồ sơ
Nhân sự module — FE Task 5 + Reviewer Task 7 + CICD verify next.
## Task 4 — BE CQRS + Controller (3 file new, Implementer Case 2)
src/Backend/SolutionErp.Application/Hrm/EmployeeFeatures.cs (~450 LOC):
- CreateEmployeeProfileCommand + Validator + Handler
- Verify User.Id exists qua UserManager.FindByIdAsync
- UNIQUE 1-1 check: throw ConflictException nếu User đã có EmployeeProfile
- EmployeeCode auto-gen qua IEmployeeCodeGenerator (NV/{YYYY}/{Seq:D4})
- 50+ field assignment từ Command record
- UpdateEmployeeProfileCommand + Validator + Handler (mutable fields, UserId+EmployeeCode immutable)
- DeleteEmployeeProfileCommand + Handler (soft delete IsDeleted=true)
- GetEmployeeProfileQuery + Handler (Include 5 satellite collection)
- ListEmployeesQuery + Handler (paged + JOIN Users+Departments, filter Status/DepartmentId/Search)
src/Backend/SolutionErp.Application/Hrm/Dtos/EmployeeDtos.cs (~110 LOC):
- EmployeeProfileListItemDto (Id, EmployeeCode, UserId, FullName/Email/Department JOIN, Status, Phone, HireDate)
- EmployeeProfileDetailDto (full 50+ field + 5 satellite collection)
- 5 satellite DTO: EmployeeWorkHistoryDto + EmployeeEducationDto +
EmployeeFamilyRelationDto + EmployeeSkillDto + EmployeeDocumentDto
src/Backend/SolutionErp.Api/Controllers/EmployeesController.cs (~70 LOC):
- 5 REST endpoint: GET list / GET detail / POST / PUT / DELETE
- Class-level [Authorize] only Phase 1 (per-action policy Hrm_HoSo_View/Create/
Edit/Delete defer Phase 1.5 per Reviewer recommend)
- Route prefix /api/employees
## Task 6 — Permission menu Hrm_HoSo* (em main solo, 2 file mod)
src/Backend/SolutionErp.Domain/Identity/MenuKeys.cs (+10 LOC):
- +2 const: Hrm root group + HrmHoSo leaf
- Update All[] array → SeedAdminPermissionsAsync auto-grant Admin role CRUD
src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs (+4 LOC):
- SeedMenuTreeAsync +2 entry:
- (Hrm, "Nhân sự", null, 28, "UserCircle") — root group
- (HrmHoSo, "Hồ sơ Nhân sự", Hrm, 1, "ContactRound") — leaf
- Order=28 between Budgets=27 và Contracts=30+ (no collision)
- INFRASTRUCTURE menu seed (NOT gated DemoSeed:Disabled — em main verified
outside gate block per gotcha #51 lesson, mirror Plan B Task 3b
SeedDemoEmployeeProfilesAsync placement)
## Reviewer ae752c0 verdict: PASS Smart Friend 6× clean
- 0 critical, 0 major, 3 minor defer Phase 1.5 (per-action policy + bool
partial update + IDateTimeProvider injection)
- Cumulative Smart Friend track: S22 #44 + S25 #48 + S29 Plan CA ≥12 + S29
Plan B ApplicableType + S33 Plan C BW clean + S33 Plan B Phase 2 clean
- gotcha #51 INFRASTRUCTURE seed gate compliance: ✓
- gotcha #50 Layout staticMap mirror: ✓ (Task 5 commit next)
## Verify
- dotnet build: 0 err 0 warn (1.72s)
- dotnet test: 120/120 PASS baseline preserved
- Endpoint claim verified grep 0 mock marker, 5 mediator.Send real
Pattern 12-bis cross-module entity cookie-cutter mirror PE→Hrm reinforced 4×
cumulative (S29 Plan B Contract Chunk C + S33 Task 3 entity scaffold + Task
3b seed + Task 4 CQRS).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -0,0 +1,62 @@
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SolutionErp.Application.Common.Models;
|
||||
using SolutionErp.Application.Hrm;
|
||||
using SolutionErp.Application.Hrm.Dtos;
|
||||
using SolutionErp.Domain.Hrm;
|
||||
|
||||
namespace SolutionErp.Api.Controllers;
|
||||
|
||||
// Phase 10.1 G-H1 Task 4 (S33) — REST endpoint cho Hồ sơ Nhân sự.
|
||||
// 5 main endpoint Phase 1 minimal: List / Get / Create / Update / Delete.
|
||||
// Satellite endpoint (WorkHistory/Education/FamilyRelation/Skill/Document
|
||||
// CRUD) DEFER Phase 1.5.
|
||||
//
|
||||
// Class-level [Authorize] only — em main Task 6 wire per-action policy
|
||||
// "Hrm_HoSo_View/Create/Edit/Delete" sau khi seed MenuKeys.
|
||||
[ApiController]
|
||||
[Route("api/employees")]
|
||||
[Authorize]
|
||||
public class EmployeesController(IMediator mediator) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<PagedResult<EmployeeProfileListItemDto>>> List(
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20,
|
||||
[FromQuery] string? search = null,
|
||||
[FromQuery] bool sortDesc = true,
|
||||
[FromQuery] EmployeeStatus? status = null,
|
||||
[FromQuery] Guid? departmentId = null,
|
||||
CancellationToken ct = default)
|
||||
=> Ok(await mediator.Send(new ListEmployeesQuery(status, departmentId)
|
||||
{ Page = page, PageSize = pageSize, Search = search, SortDesc = sortDesc }, ct));
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<ActionResult<EmployeeProfileDetailDto>> Get(Guid id, CancellationToken ct)
|
||||
=> Ok(await mediator.Send(new GetEmployeeProfileQuery(id), ct));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<object>> Create(
|
||||
[FromBody] CreateEmployeeProfileCommand cmd, CancellationToken ct)
|
||||
{
|
||||
var id = await mediator.Send(cmd, ct);
|
||||
return CreatedAtAction(nameof(Get), new { id }, new { id });
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> Update(
|
||||
Guid id, [FromBody] UpdateEmployeeProfileCommand cmd, CancellationToken ct)
|
||||
{
|
||||
if (id != cmd.Id) return BadRequest(new { detail = "ID không khớp" });
|
||||
await mediator.Send(cmd, ct);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
await mediator.Send(new DeleteEmployeeProfileCommand(id), ct);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user