Files
solution-erp/docs/changelog/sessions/2026-05-13-1400-s21-turn5-refactor-allow-to-per-nv.md
pqhuy1987 c0af9e05ec
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m18s
[CLAUDE] Docs: S21 t5 Chunk D — chốt refactor Allow* per-NV (Mig 29)
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>
2026-05-13 20:12:21 +07:00

235 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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"