[CLAUDE] Docs: S21 t5 Chunk D — chốt refactor Allow* per-NV (Mig 29)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m18s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m18s
Update docs theo rule §6.5 KEEP narrative:
- `docs/database/schema-diagram.md §14` title "Mig 22-29, S17-21":
- Update ApprovalWorkflows block: note Mig 28 cũ 6 column DROP, refactor per-NV
- Add 5 column Allow* trong ApprovalWorkflowLevels block (inline comment F1+F3)
- Add Users block với F2 AllowDrafterSkipToFinal Mig 29
- `docs/STATUS.md` Last updated S21 t5 + count 28→29 mig. UAT defer test count
unchanged 84.
- `docs/HANDOFF.md` Insert TL;DR S21 t5 đầy đủ (trước S21 t4):
- Trigger UAT feedback "cấu hình cho từng người"
- Q&A 2 lượt chốt scope
- 4 chunk narrative: A BE+Mig 29 + Service refactor → B FE Admin Designer
per-Level → C FE eOffice rename → D Docs
- Pattern reusable: EF migration reorder cho BACKFILL preserve data,
per-NV scope split theo role (Approver Level vs Drafter User)
- State table + Pending User Mgmt F2 UI defer
- NEW session log `docs/changelog/sessions/2026-05-13-1400-s21-turn5-refactor-allow-to-per-nv.md`:
- Code snippets BE/FE refactor
- 5 lessons learned (incl EF reorder pattern + backward compat backfill discipline)
- References file paths
Stats cumulative S21 t5:
- 29 mig (+1 Mig 29 refactor) · 59 tables · ~143 endpoints · 34 FE pages
- 84 test pass (UAT defer test-after §7) · 45 gotcha · 17 memory · 6 skills
- 4 commits S21 t5 cumulative ready push remote
Pending: bro confirm push `eea86fd..HEAD` 4 commits ahead.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -0,0 +1,234 @@
|
||||
# Session 21 turn 5 — 2026-05-13 14:00 — Refactor Allow* sang PER-NV (Mig 29 drop Mig 28)
|
||||
|
||||
**Dev:** Claude Opus 4.7 1M Max (em main solo — Implementer REFUSE per cross-stack reasoning chain rule)
|
||||
**Duration:** ~2h
|
||||
**Base commit:** `eea86fd` (S21 t4 Chunk E Docs)
|
||||
**Commits này turn:** `0366946` (A BE+Mig 29) → `63234b2` (B FE Admin) → `5ccb2a7` (C FE eOffice) → this (D Docs)
|
||||
|
||||
## Trigger
|
||||
|
||||
User feedback sau UAT S21 t4 deploy: "à cấu hình cho từng người nhé (chứ ko phải là cho toàn bộ quy trình duyệt), thêm table vào SQL luôn để cấu hình cho dễ."
|
||||
|
||||
→ Workflow-level Allow* (Mig 28 S21 t4) **chưa fit UX request** — admin muốn config quyền duyệt RIÊNG cho TỪNG NV (per-Level slot trong workflow).
|
||||
|
||||
## Q&A clarify (2 lượt AskUserQuestion)
|
||||
|
||||
### Lượt 1 — Scope per-NV + xử lý Mig 28 cũ:
|
||||
|
||||
| Câu | User chốt |
|
||||
|---|---|
|
||||
| Scope "từng người" | **Per-Level**: 5 flag (4 F1 + 1 F3) gắn slot Designer. F2 per-Drafter user. |
|
||||
| Mig 28 xử lý | **Migrate bốc → per-NV bulk + drop**: copy workflow → Levels của workflow, backfill Users F2 từ PE link, drop 6 column workflow. |
|
||||
|
||||
→ 6 flag split scope theo role natural:
|
||||
- **F1 (4 mode) + F3 (1 flag)** = Approver permission → gắn `ApprovalWorkflowLevels` (1 slot Approver = 1 row Level)
|
||||
- **F2 (1 flag)** = Drafter permission → gắn `Users` (per-Drafter user global)
|
||||
|
||||
## Chunk A — BE schema + Service refactor (`0366946`)
|
||||
|
||||
### Mig 29 `RefactorAdvancedOptionsToPerLevelAndDrafterUser` (4-stage)
|
||||
|
||||
EF auto-generated drop-then-add order WRONG (data loss khi DROP trước BACKFILL).
|
||||
Phải REORDER manual:
|
||||
|
||||
```csharp
|
||||
public override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Stage 1: ADD 5 column ApprovalWorkflowLevels (per slot)
|
||||
AddColumn × 5 (AllowReturn*+AllowApproverEditDetails)
|
||||
|
||||
// Stage 2: ADD 1 column Users (per-Drafter F2)
|
||||
AddColumn AllowDrafterSkipToFinal
|
||||
|
||||
// Stage 3: BACKFILL bulk SQL
|
||||
migrationBuilder.Sql(@"
|
||||
UPDATE l SET l.AllowReturnOneLevel = w.AllowReturnOneLevel, ...
|
||||
FROM ApprovalWorkflowLevels l
|
||||
INNER JOIN ApprovalWorkflowSteps s ON s.Id = l.ApprovalWorkflowStepId
|
||||
INNER JOIN ApprovalWorkflows w ON w.Id = s.ApprovalWorkflowId;
|
||||
");
|
||||
migrationBuilder.Sql(@"
|
||||
UPDATE u SET u.AllowDrafterSkipToFinal = 1
|
||||
FROM Users u WHERE EXISTS (
|
||||
SELECT 1 FROM PurchaseEvaluations pe
|
||||
INNER JOIN ApprovalWorkflows w ON w.Id = pe.ApprovalWorkflowId
|
||||
WHERE pe.DrafterUserId = u.Id AND w.AllowDrafterSkipToFinal = 1
|
||||
);
|
||||
");
|
||||
|
||||
// Stage 4: DROP 6 column workflow-level (Mig 28 cleanup)
|
||||
DropColumn × 6 (Mig 28 fields)
|
||||
}
|
||||
```
|
||||
|
||||
3-file rule complete. Apply LocalDB Dev + Design success.
|
||||
|
||||
### Domain entity
|
||||
|
||||
```csharp
|
||||
// Mig 28 cũ ApprovalWorkflow.cs — REMOVE 6 Allow* field
|
||||
// Mig 29 — entity nguyên thuỷ workflow-level cũ giảm về Code/Name/Version/Active/Selectable only.
|
||||
|
||||
// ApprovalWorkflowLevel.cs — ADD 5 Allow* field
|
||||
public bool AllowReturnOneLevel { get; set; }
|
||||
public bool AllowReturnOneStep { get; set; }
|
||||
public bool AllowReturnToAssignee { get; set; }
|
||||
public bool AllowReturnToDrafter { get; set; } = true; // S17 backward compat
|
||||
public bool AllowApproverEditDetails { get; set; }
|
||||
|
||||
// User.cs — ADD 1 Allow* field
|
||||
public bool AllowDrafterSkipToFinal { get; set; }
|
||||
```
|
||||
|
||||
### Service refactor `ApplyReturnModeAsync`
|
||||
|
||||
```csharp
|
||||
// Resolve currentLevel slot từ pointer
|
||||
ApprovalWorkflowLevel? currentLevel = null;
|
||||
if (evaluation.CurrentWorkflowStepIndex is int csi && csi < stepsOrdered.Count) {
|
||||
var step = stepsOrdered[csi];
|
||||
currentLevel = step.Levels.FirstOrDefault(l => l.Order == evaluation.CurrentApprovalLevelOrder);
|
||||
}
|
||||
|
||||
// Validate Allow* từ Level slot (Admin bypass)
|
||||
if (!isAdmin && currentLevel is not null) {
|
||||
var allowed = mode switch {
|
||||
WorkflowReturnMode.OneLevel => currentLevel.AllowReturnOneLevel,
|
||||
WorkflowReturnMode.OneStep => currentLevel.AllowReturnOneStep,
|
||||
WorkflowReturnMode.Assignee => currentLevel.AllowReturnToAssignee,
|
||||
WorkflowReturnMode.Drafter => currentLevel.AllowReturnToDrafter,
|
||||
_ => false,
|
||||
};
|
||||
if (!allowed) throw new ConflictException($"Cấp Approver hiện tại không bật mode '{mode}'.");
|
||||
}
|
||||
```
|
||||
|
||||
V1 legacy phiếu (no ApprovalWorkflowId) → fallback Drafter behavior tự động.
|
||||
|
||||
### DRAFTER trình refactor
|
||||
|
||||
```csharp
|
||||
if (skipToFinal && evaluation.ApprovalWorkflowId is Guid skipAwId) {
|
||||
if (!isAdmin) {
|
||||
var drafterUser = await userManager.FindByIdAsync(actorUserId.Value.ToString());
|
||||
if (!drafterUser.AllowDrafterSkipToFinal)
|
||||
throw new ConflictException($"User '{drafterUser.FullName}' không được phép gửi thẳng Cấp cuối.");
|
||||
}
|
||||
// ... set pointer = max Step + max Level
|
||||
}
|
||||
```
|
||||
|
||||
### Helper `EnsureEditableForDetailsAsync` refactor
|
||||
|
||||
```csharp
|
||||
// Read level.AllowApproverEditDetails thay vì workflow
|
||||
if (!level.AllowApproverEditDetails)
|
||||
throw new ConflictException($"Cấp Approver hiện tại (Bước {step.Order} / Cấp {levelOrder}) " +
|
||||
"không được cấp quyền chỉnh sửa Section 2.");
|
||||
```
|
||||
|
||||
### DTO refactor
|
||||
|
||||
- `AwLevelDto +5 Allow*`, `AwDefinitionDto -6 Allow*`
|
||||
- `CreateAwLevelInput +5 Allow*`, `CreateAwDefinitionCommand -6 Allow*`
|
||||
- `ApprovalWorkflowOptionsDto`: 5 flag (F2 separate field)
|
||||
- `PurchaseEvaluationDetailBundleDto`:
|
||||
- RENAME `WorkflowOptions → CurrentLevelOptions`
|
||||
- ADD `DrafterAllowSkipToFinal bool`
|
||||
|
||||
GetPe handler populate:
|
||||
- `currentLevelOptions` = 5 Allow* của Cấp hiện tại
|
||||
- `drafterAllowSkipToFinal` = lookup User.AllowDrafterSkipToFinal từ DrafterUserId
|
||||
|
||||
## Chunk B — FE Admin Designer (`63234b2`)
|
||||
|
||||
`ApprovalWorkflowsV2Page.tsx`:
|
||||
|
||||
- Types: `LevelDto +5 Allow*`, `DefinitionDto -6 Allow*`, `EditLevelEntry +5 Allow*`
|
||||
- Factory `makeDefaultLevelEntry(order, userId)` — 4 false + AllowReturnToDrafter=true
|
||||
- `copyFromDefinition` propagate 5 Allow* từ Levels
|
||||
|
||||
UI refactor:
|
||||
- **REMOVE** entire section "Cấu hình nâng cao" workflow-level (amber bg 6 checkbox)
|
||||
- **REPLACE** với info banner violet ngắn:
|
||||
> ⓘ Cấu hình quyền duyệt (Trả lại modes + Edit Section 2) đặt RIÊNG cho từng NV ở mỗi Cấp dưới đây. F2 "Gửi thẳng Cấp cuối" (Drafter) cấu hình ở User Management (mỗi NV global).
|
||||
- **ADD** inline panel mỗi Level entry (NV row) — 5 checkbox grid-cols-2:
|
||||
- Trả về 1 Cấp trước
|
||||
- Trả về 1 Bước trước
|
||||
- Trả về Người chỉ định
|
||||
- Trả về Drafter (mặc định checked)
|
||||
- Cho phép chỉnh sửa Section 2 (col-span-2)
|
||||
- Header "Quyền duyệt NV #N" [10px] uppercase amber-700
|
||||
|
||||
POST body propagate 5 Allow* per slot trong `steps[].levels[].*`.
|
||||
|
||||
F2 UI defer — User Management page sẽ thêm 1 toggle khi admin UAT request.
|
||||
|
||||
## Chunk C — FE eOffice (`5ccb2a7`) mirror 2 app
|
||||
|
||||
Types:
|
||||
- `ApprovalWorkflowOptions` REMOVE allowDrafterSkipToFinal (still 5 flag)
|
||||
- `PeDetailBundle`:
|
||||
- RENAME `workflowOptions → currentLevelOptions`
|
||||
- ADD `drafterAllowSkipToFinal: boolean`
|
||||
|
||||
PeWorkflowPanel.tsx:
|
||||
- RENAME `wfOptions → levelOptions`, read `evaluation.currentLevelOptions`
|
||||
- 4 mode radio render conditional theo levelOptions.allowReturnXxx
|
||||
|
||||
PeDetailTabs.tsx:
|
||||
- F3 approverEditMode: read `currentLevelOptions?.allowApproverEditDetails`
|
||||
- F2 allowSkipToFinal: read `drafterAllowSkipToFinal` (per-user)
|
||||
|
||||
## Chunk D — Docs (this commit)
|
||||
|
||||
- `docs/database/schema-diagram.md §14`: title Mig 22-29 S17-21 + add 5 column Level + 1 column User block
|
||||
- `docs/STATUS.md` S21 t5 + 28→29 mig
|
||||
- `docs/HANDOFF.md` TL;DR đầy đủ trên cùng
|
||||
- Session log (file này)
|
||||
|
||||
## Stats cumulative S21 t5
|
||||
|
||||
| Metric | Trước (S21 t4) | Sau (S21 t5) | Δ |
|
||||
|---|---|---|---|
|
||||
| DB tables | 59 | 59 | 0 |
|
||||
| **Migrations** | 28 | **29** | **+1** (Mig 29 refactor per-NV) |
|
||||
| Endpoints | ~143 | ~143 | 0 (body unchanged, schema-source different) |
|
||||
| FE pages | 34 | 34 | 0 |
|
||||
| Unit tests | 84 | 84 | 0 (UAT defer test-after §7) |
|
||||
| Gotchas | 45 | 45 | 0 |
|
||||
| Memory | 17 | 17 | 0 |
|
||||
| Skills | 6 | 6 | 0 |
|
||||
| Sub-agents | 4 seeds-only | 4 seeds-only | 0 |
|
||||
| **Commits S21 t5** | — | **4** | `0366946` → `63234b2` → `5ccb2a7` → this |
|
||||
|
||||
## Lessons learned
|
||||
|
||||
1. **Reorder EF migration manual khi cần BACKFILL.** EF auto-generate drop-then-add order — fail nếu cần preserve data. Pattern reusable: ADD → BACKFILL SQL → DROP. Test backfill SQL với realistic data trước commit.
|
||||
|
||||
2. **Per-NV permission pattern**. Khi cần config role/permission theo slot user (vd workflow approver level), gắn flag vào table chứa user FK (ApprovalWorkflowLevels có ApproverUserId), KHÔNG gắn parent table (ApprovalWorkflows). Pattern reusable cho future N-stage hoặc HĐ V2.
|
||||
|
||||
3. **Split per-Role vs per-User scope**. F1+F3 thuộc Approver role → per-slot (Level table). F2 thuộc Drafter role → per-User (Users table). Cấu hình natural theo role context, không hardcode 1 scope cho mọi flag.
|
||||
|
||||
4. **Backward compat backfill discipline**. Mig 29 bulk copy preserve admin config Mig 28 → workflow cũ chạy đúng ngay sau deploy. KHÔNG yêu cầu admin reconfig lần đầu (UAT pain point avoided).
|
||||
|
||||
5. **Iteration speed S21 cumulative**: t3 fix bug → t4 add feature workflow-level → t5 refactor per-NV theo UAT feedback. 3 iteration cùng day, mỗi turn dedicated session, no scope creep. Per `feedback_drastic_refactor_scope`.
|
||||
|
||||
## Handoff
|
||||
|
||||
- ✅ All 4 chunk committed local (4 commits)
|
||||
- ⏭ **PENDING bro confirm push remote** — `git push origin main` 4 commits ahead `eea86fd..HEAD`
|
||||
- ⏭ Sau push: CI sẽ trigger (.cs + .tsx + Mig) → 🟩 CICD Monitor spawn smoke verify Mig 29 prod apply + currentLevelOptions returned correctly + drafterAllowSkipToFinal populated cho user backfill
|
||||
|
||||
User next action expected: UAT verify 5 checkbox per Level slot trong Designer. UAT Drafter F2 (BE field sẵn, nhưng User Mgmt UI defer — admin có thể test qua SQL UPDATE Users.AllowDrafterSkipToFinal=1 cho test user nếu cần verify F2 immediate).
|
||||
|
||||
## References
|
||||
|
||||
- BE Mig 29: `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260513130144_RefactorAdvancedOptionsToPerLevelAndDrafterUser.cs`
|
||||
- Domain: `src/Backend/SolutionErp.Domain/ApprovalWorkflowsV2/ApprovalWorkflow.cs` + `Identity/User.cs`
|
||||
- BE Service: `src/Backend/SolutionErp.Infrastructure/Services/PurchaseEvaluationWorkflowService.cs`
|
||||
- BE handlers: `src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationDetailFeatures.cs`
|
||||
- DTO: `src/Backend/SolutionErp.Application/PurchaseEvaluations/Dtos/PurchaseEvaluationDtos.cs`
|
||||
- FE Admin: `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx`
|
||||
- FE eOffice × 2 app: `{fe-admin,fe-user}/src/components/pe/PeWorkflowPanel.tsx` + `PeDetailTabs.tsx` + `types/purchaseEvaluation.ts`
|
||||
- Spec: S21 t4 HANDOFF + user UAT feedback "cấu hình cho từng người"
|
||||
Reference in New Issue
Block a user