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

11 KiB
Raw Blame History

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:

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

// 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

// 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

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

// 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 036694663234b25ccb2a7 → 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 remotegit 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"