[CLAUDE] App+Api: S34 Plan 3 Item 3 BE 5 satellite CRUD scaffold (15 endpoint)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m36s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m36s
Phase 1.5 Item 3 BE — Implementer Case 2 cookie-cutter 5-entity scaffold
mirror parent EmployeeProfile CRUD pattern (Pattern 12-ter NEW saved).
New file: Application/Hrm/EmployeeSatelliteFeatures.cs (621 LOC):
- 5 region (#region {EntityName}) — WorkHistory/Education/FamilyRelation/Skill/Document
- Each region: Create/Update/Delete Command + Validator + Handler
- Verify parent EmployeeProfile.Exists pattern AnyAsync(!IsDeleted)
- Soft delete IsDeleted=true + DeletedAt=UtcNow + DeletedBy=currentUser
- Validator nullable enum IsInEnum().When(HasValue) pattern
Modified file: Api/Controllers/EmployeesController.cs (70 → 234 LOC, +15 endpoint):
- 5 region × 3 verb (POST/PUT/DELETE) = 15 satellite endpoint
- Path per satellite:
- /api/employees/{id}/work-history
- /api/employees/{id}/education
- /api/employees/{id}/family-relations
- /api/employees/{id}/skills
- /api/employees/{id}/documents
- Per-action policy: Hrm_HoSo.Create/Update/Delete (override class-level Read)
- BadRequest guard: id != cmd.{EmployeeProfileId|Id} → "ID không khớp"
Document satellite metadata-only — file upload IFileStorage body wire defer S35.
Verify:
- dotnet build PASS (2 warn DocxRenderer baseline, 0 error)
- dotnet test 130/130 PASS (58 Domain + 72 Infra baseline preserve, no test add)
- Endpoints count: ~154 → 169 (+15)
- LOC delta: +785 BE (621 features + 164 controller)
Pattern 12-ter NEW (Implementer MEMORY): 5× satellite CRUD scaffold cookie-cutter
within-module (different from Pattern 12-bis cross-module mirror). Reusable
cho future N-satellite parent (vd Contract attachments, PE quotes, Budget details).
Defer S35:
- FE inline edit forms 5 satellite section (~1.5h em main solo)
- Test bundle satellite CRUD (~30 phút Implementer Case 3)
- IFileStorage Document body upload wire (multipart form-data)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -182,6 +182,27 @@ Khi spec yêu cầu "move page X từ fe-admin → fe-user" hoặc ngược lạ
|
||||
- Token cost ~20k (under budget 25k). Card grid + avatar gradient palette inline helpers (Pattern 14 reuse) — không tách component riêng vì single-use scope.
|
||||
- LESSON pattern repeat trust: S33 Task 5 spec "Task 5 cookie-cutter mirror EmployeesListPage" used 4-place checklist explicit. S34 G-O1 Task 3 spec follow same template → execute 0 ambiguity. Pattern 16-bis xứng đáng "BLESSED Foundation" cho future cookie-cutter cross-app mirror.
|
||||
|
||||
### Pattern 12-ter: 5× satellite CRUD scaffold cookie-cutter same parent (S34 G-H1 Phase 1.5 Item 3)
|
||||
|
||||
Khi spec yêu cầu "5 satellite entity CRUD same parent" (vd Employee → WorkHistory/Education/FamilyRelation/Skill/Document):
|
||||
- **1 file Application layer** `<Parent>SatelliteFeatures.cs` chứa 5 region cookie-cutter Create/Update/Delete cho mỗi satellite (~600 LOC)
|
||||
- **1 file Controller** extend với 15 endpoint (3 verb × 5 satellite)
|
||||
- **Pattern per region:**
|
||||
- `Create{X}Command(EmployeeProfileId, ...)` → `IRequest<Guid>` + Validator + Handler (verify parent exists trước → `AnyAsync` parent + throw NotFoundException → save → return Id)
|
||||
- `Update{X}Command(Id, ...)` → `IRequest` + Validator + Handler (FirstOrDefaultAsync `!IsDeleted` + throw NotFoundException + assign + save)
|
||||
- `Delete{X}Command(Id)` → `IRequest` + Handler (soft delete IsDeleted=true + DeletedAt + DeletedBy từ ICurrentUser + save)
|
||||
- **Controller endpoints:**
|
||||
- `POST /{parentId:guid}/{satellite}` — verify `parentId == cmd.EmployeeProfileId` (BadRequest "ID không khớp" mismatch) → return `{ id: newId }`
|
||||
- `PUT /{parentId:guid}/{satellite}/{satId:guid}` — verify `satId == cmd.Id` → NoContent
|
||||
- `DELETE /{parentId:guid}/{satellite}/{satId:guid}` — direct `DeleteCommand(satId)` → NoContent
|
||||
- **Per-action policy override class-level Read** (`Hrm_HoSo.Create/Update/Delete`)
|
||||
- **Verify parent exists pattern**: `AnyAsync(x => x.Id == ... && !x.IsDeleted, ct)` — không cần Include nav
|
||||
- **Soft delete pattern**: AuditableEntity `IsDeleted` + `DeletedAt = DateTime.UtcNow` + `DeletedBy = currentUser.UserId` (inject ICurrentUser)
|
||||
|
||||
Bài học S34 Plan 3 Phase 1.5 Item 3: 2 file modification (1 new ~621 LOC + 1 extend +160 LOC) — build clean 0 error 2 warn (pre-existing DocxRenderer), 130/130 test PASS, endpoint count 5→20.
|
||||
|
||||
Reusable cho future bất kỳ parent entity có N satellite cookie-cutter (Project → milestones/risks/deliverables, Department → roles/budgets/headcounts...). Polymorphic discriminator field (vd EmployeeSkill.Kind) treat as regular required field — không cần special handling.
|
||||
|
||||
### Pattern 12-bis: Cross-module entity cookie-cutter mirror (S29 Plan B Chunk C — Mig 33)
|
||||
|
||||
Khi spec yêu cầu "mirror entity X từ PE module sang Contract module" (vd LevelOpinions / DepartmentApproval / ManualBudgetFields):
|
||||
|
||||
Reference in New Issue
Block a user