Commit Graph

63 Commits

Author SHA1 Message Date
036694638e [CLAUDE] PE-Workflow: S21 t5 Chunk A — Mig 29 refactor Allow* sang per-NV (per-Level + per-Drafter)
Refactor 6 Allow* options từ workflow-level (Mig 28 S21 t4) sang per-NV scope:
- F1 (4 mode Trả lại) + F3 (Edit Section 2) → 5 flag MOVE xuống
  `ApprovalWorkflowLevels` (per slot Approver, cùng table với ApproverUserId).
- F2 (AllowDrafterSkipToFinal) → MOVE xuống `Users` (per-Drafter user, User Mgmt).

Mig 29 `RefactorAdvancedOptionsToPerLevelAndDrafterUser` — 4-stage migration
(EF auto-generated drop-then-add đã được REORDER manual):
1. ADD 5 column trên `ApprovalWorkflowLevels` (AllowReturnOneLevel/OneStep/
   ToAssignee/ToDrafter[default true]/AllowApproverEditDetails)
2. ADD 1 column trên `Users` (AllowDrafterSkipToFinal default false)
3. BACKFILL bulk SQL (preserve admin config Mig 28):
   - Levels: copy workflow.Allow* → all Levels của workflow (JOIN Steps)
   - Users: SET TRUE cho user nào từng Drafter PE link workflow Allow=true
4. DROP 6 column workflow-level (Mig 28 cleanup)
3-file rule complete. Apply LocalDB Dev + Design success.

Domain entity refactor:
- `ApprovalWorkflow.cs` — REMOVE 6 Allow* field (S21 t4 Mig 28 cũ)
- `ApprovalWorkflowLevel.cs` — ADD 5 Allow* field (F1 + F3)
- `User.cs` — ADD 1 Allow* field (F2 AllowDrafterSkipToFinal)

EF config update:
- `ApprovalWorkflowConfiguration.cs` — remove 6 HasDefaultValue workflow-level,
  add 5 HasDefaultValue per-Level (4 false + 1 AllowReturnToDrafter true S17)

Service refactor `ApplyReturnModeAsync` (`PurchaseEvaluationWorkflowService.cs`):
- Resolve currentLevel slot (CurrentWorkflowStepIndex + CurrentApprovalLevelOrder)
- Read 5 Allow* từ `currentLevel.AllowXxx` thay vì workflow.Allow*
- Admin bypass per-Level flag check (unchanged behavior)
- Drafter mode đặc biệt: check AllowReturnToDrafter của currentLevel (vẫn validate)
- V1 legacy (no V2 schema) → fallback Drafter behavior tự động

DRAFTER trình refactor (`TransitionAsync` skipToFinal branch):
- Permission check moved from workflow-level → `drafterUser.AllowDrafterSkipToFinal`
- Use `userManager.FindByIdAsync(actorUserId)` để get current Drafter user entity
- Admin bypass user flag check (unchanged)

Helper `EnsureEditableForDetailsAsync` refactor:
- Read `level.AllowApproverEditDetails` thay vì workflow.AllowApproverEditDetails
- Error message rõ "Cấp Approver hiện tại (Bước X / Cấp Y)" thay vì "Workflow"

DTO refactor:
- `AwLevelDto` ADD 5 Allow* field (admin Designer GET per-Level)
- `AwDefinitionDto` REMOVE 6 Allow* (no longer workflow-level)
- `CreateAwLevelInput` ADD 5 Allow* param (admin Designer POST per-Level)
- `CreateAwDefinitionCommand` REMOVE 6 Allow* (Steps[].Levels[] now has them)
- `ApprovalWorkflowOptionsDto` chỉ còn 5 flag (F2 removed — separate field)
- `PurchaseEvaluationDetailBundleDto`:
  - rename `WorkflowOptions` → `CurrentLevelOptions` (clearer semantic per-slot)
  - ADD `DrafterAllowSkipToFinal bool` (resolve từ DrafterUserId → User entity)

GetPurchaseEvaluationQueryHandler populate:
- `currentLevelOptions` = 5 Allow* của Cấp hiện tại (null nếu V1 legacy / no pointer)
- `drafterAllowSkipToFinal` = User.AllowDrafterSkipToFinal lookup từ DrafterUserId

Backward compat verified:
- Mig 29 backfill preserve admin config S21 t4 — workflow cũ vẫn chạy đúng
  sau deploy. User chưa từng làm Drafter F2 phải opt-in lần đầu (no auto-set).
- 84 test PASS (58 Domain + 26 Infra unchanged, 3 gotcha #45 guard test backward
  compat signature).

Pending Chunk B/C: FE Admin Designer move 5 checkbox xuống per-Level slot + FE
eOffice read currentLevelOptions + drafterAllowSkipToFinal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 20:03:28 +07:00
c56024ba25 [CLAUDE] PE-Workflow: Chunk B — BE Service + handlers + DTOs (F1+F2+F3)
F1 — 4 mode Trả lại (Service.ApplyReturnModeAsync helper):
- WorkflowReturnMode enum (OneLevel / OneStep / Assignee / Drafter)
- OneLevel: lùi 1 Cấp trong cùng Step (peer review). Bước 1 Cấp 1 → fallback Drafter.
- OneStep: lùi sang Bước trước Cấp cuối. Bước 1 → fallback Drafter.
- Assignee: pick runtime → tìm Step+Level match ApproverUserId trong workflow.
- Drafter: Phase=TraLai clear pointer như S17 (backward compat).
- 3 mode đầu giữ Phase=ChoDuyet, reset SLA 7d. Mode Drafter clear SLA.
- Admin bypass workflow.Allow* flag check. Non-admin → throw ConflictException
  với message rõ "Workflow không bật mode X".

F2 — Drafter skipToFinal (extend DRAFTER trình branch):
- Workflow.AllowDrafterSkipToFinal=true required (non-admin)
- Set CurrentWorkflowStepIndex = Steps.Count-1 + CurrentApprovalLevelOrder = max Level
- Audit comment append "[Drafter gửi thẳng Cấp cuối]"

F3 — Approver edit Section 2 (Detail + NCC + Báo giá):
- New helper `EnsureEditableForDetailsAsync` (extend pattern PurchaseEvaluationDraftGuard):
  - Drafter scope: DangSoanThao OR TraLai (any role, Controller [Authorize] handles)
  - F3 Approver scope: ChoDuyet + workflow.AllowApproverEditDetails=true +
    actor.Id match CurrentLevel.ApproverUserId. Admin bypass flag check.
  - Throw ForbiddenException nếu approver Cấp khác nhau (rõ Bước/Cấp trong message).
- 8 handler switch helper + inject ICurrentUser khi cần:
  - Detail: Add (existing ICurrentUser) / Update + Delete (inject new)
  - Quote: Upsert + Delete (inject new)
  - Supplier: Add (existing) / Update + Delete (inject new + add guard, trước
    đây hoàn toàn KHÔNG có phase guard — bonus security fix)
- Audit: thêm changelog Update/Delete handler (trước đây silent). Khi phase=
  ChoDuyet append " [Approver edit khi đang duyệt]" cho lịch sử rõ ai sửa.

Extension Service `TransitionAsync` signature (backward compat — 3 optional
param thêm cuối + default null/false):
- WorkflowReturnMode? returnMode = null
- Guid? returnTargetUserId = null
- bool skipToFinal = false

TransitionPurchaseEvaluationCommand DTO + Validator + Handler — mirror signature.

DTO extensions:
- ApprovalWorkflowOptionsDto NEW sub-record (6 Allow* flag) cho FE filter
- PurchaseEvaluationDetailBundleDto + WorkflowOptions field (null nếu V1 legacy)
- GetPe handler populate awOptions từ ApprovalWorkflow entity load (Mig 23 path)
- AwDefinitionDto + 6 Allow* field (admin Designer GET overview)
- CreateAwDefinitionCommand + 6 Allow* param (admin Designer POST new version)
- Handler ToDto + entity new() — propagate Allow* end-to-end

Default backward compat: workflow cũ → AllowReturnToDrafter=true (Mig 28 DB
default), 5 flag còn lại false. Phiếu cũ V2 vẫn Trả lại Drafter như S17 sau
deploy — no breaking change.

Verify:
- dotnet build SolutionErp.slnx → 0 err, 2 warn pre-existing DocxRenderer
- 3 regression test gotcha #45 vẫn PASS (backward compat signature change)
- LocalDB Dev + Design đã apply Mig 28 (Chunk A)

Pending Chunk C: FE Admin Designer mirror 2 app (6 checkbox + DTO types).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:57:09 +07:00
0294693a4a [CLAUDE] PE-Workflow: Chunk A — Mig 28 +6 Allow* column ApprovalWorkflow (F1+F2+F3 advanced options)
Domain `ApprovalWorkflow` (Mig 22 — Session 17) thêm 6 boolean cấu hình "Cấu
hình nâng cao" cho admin Designer (F1 trả lại modes + F2 skip cấp cuối + F3
approver edit Section 2):

- AllowReturnOneLevel       (default false) — F1 mode 1 lùi 1 Cấp peer review
- AllowReturnOneStep        (default false) — F1 mode 2 lùi 1 Bước
- AllowReturnToAssignee     (default false) — F1 mode 3 pick runtime từ NV đã duyệt
- AllowReturnToDrafter      (default TRUE)  — F1 mode 4 backward compat S17 fallback
- AllowDrafterSkipToFinal   (default false) — F2 Drafter trình thẳng Cấp cuối
- AllowApproverEditDetails  (default false) — F3 Approver edit HangMuc/NCC/Báo giá

Default backward compat S17: AllowReturnToDrafter=true → mọi workflow cũ chạy
đúng "Trả về Drafter" Phase=TraLai. 5 flag còn lại default false → admin
opt-in per workflow để audit nghiêm.

Mig 28 `AddAdvancedOptionsToApprovalWorkflows`:
- AddColumn × 6 bit NOT NULL DEFAULT 0/1 (3-file rule complete + Designer + Snapshot)
- Apply LocalDB SolutionErp_Dev (runtime) + SolutionErp_Design (ef tooling)

EF config ApprovalWorkflowConfiguration thêm 6 HasDefaultValue match Mig 28
default (backfill rows cũ + ef snapshot consistency).

3 mode Trả lại mới giữ Phase=ChoDuyet, chỉ lùi pointer (peer review chain
sequential). Mode Drafter giữ Phase=TraLai + clear pointer như S17. Behavior
implement trong Chunk B (Service.TransitionAsync extend branches).

Verify:
- dotnet ef migrations add success (no compile error)
- 3-file rule complete: 28 mig × 2 + Snapshot = 57 file Migrations dir
- LocalDB Dev + Design both apply success

Pending Chunk B: BE Service branches + handlers + Controller body extend.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:46:01 +07:00
de0088742f [CLAUDE] PurchaseEvaluation: Chunk A — BE guard target TraLai/TuChoi BẮT BUỘC decision=Reject + 3 regression test
Defense-in-depth chặn FE inconsistency (gotcha #45 — Session 21 turn 3).
Bug pattern: button "← Trả lại" trong PeWorkflowPanel.tsx gửi decision=Approve
khi target=TraLai do `isReject` local var thiếu nhánh TraLai → BE skip Reject
branch → enter APPROVE STEP → ApproveV2Async UPSERT opinion = "đã duyệt" +
advance Cấp. User UAT thấy: "Trả về nhưng hệ thống vẫn duyệt".

BE guard:
- Service `TransitionAsync` thêm early check sau set isAdmin/isSystem
- targetPhase ∈ {TraLai, TuChoi} && decision != Reject → throw ConflictException
- Boundary protection cho mọi caller tương lai (API client / mobile / cron)

Tests (Infra suite +3):
- TransitionAsync_TargetTraLai_WithApproveDecision_Throws_AndDoesNotMutateState
- TransitionAsync_TargetTuChoi_WithApproveDecision_Throws_AndDoesNotMutateState
- TransitionAsync_TargetTraLai_WithRejectDecision_SetsPhaseTraLai (happy path)
+ NoOpNotificationService stub reusable cho future PE service tests

Verify:
- dotnet test SolutionErp.slnx → 84 PASS (58 Domain + 26 Infra = +3 from 81 baseline)
- Build pass (0 err, 2 warn CS8602 pre-existing DocxRenderer)

Pending Chunk B: FE fix PeWorkflowPanel.tsx isReject + dialog isSendBack
mirror 2 app (fe-admin + fe-user) — sync với BE guard rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 09:41:14 +07:00
059bfcbe38 [CLAUDE] FE-Admin+Domain: Chunk C — MenuVisibilityPage + menu key + seed
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
Session 20 turn 7 Chunk C. FE Admin page quản lý Ẩn/Hiện + Đổi tên menu
cho fe-user (eOffice). Admin sidebar fe-admin LUÔN dùng Tên gốc — page này
KHÔNG đụng admin navigation (user Q2=b).

Domain MenuKeys.cs:
  +const MenuVisibility = "MenuVisibility"
  All[] thêm MenuVisibility (giữa Permissions + Workflows)

DbInitializer SeedMenuTreeAsync:
  +leaf (MenuVisibility, "Menu eOffice", System, 94, "Eye")
  Workflows shift Order 94 → 95
  Idempotent — chỉ INSERT nếu chưa có trong DB
  Manual seed Mig 27 LocalDB Dev: INSERT MenuItems + Permissions cho Admin role

FE Admin:
  - types/menu.ts: MenuItem/MenuNode +isVisible bool +displayLabel string|null
  - lib/menuKeys.ts: +MenuVisibility const
  - components/Layout.tsx resolver +MenuVisibility → /system/menu-visibility
  - App.tsx +Route + import MenuVisibilityPage

NEW pages/system/MenuVisibilityPage.tsx (~210 LOC):
  - PageHeader + 4 StatCard (Tổng / Hiển thị / Đã ẩn / Đã đổi tên)
  - Search input (key | label | displayLabel)
  - Table: Key (mono + parentKey ↳) | Tên gốc | Input "Tên hiển thị" inline
    (placeholder "Mặc định: ...") | Toggle Hiển thị/Ẩn (emerald/amber) |
    Lưu (khi dirty) / Khôi phục (khi đã custom)
  - PATCH /menus/{key} body { isVisible, displayLabel } — trim whitespace,
    empty string → null
  - onSuccess: invalidate ['menus', 'all'] + ['my-menu'] + clear draft entry
  - "Khôi phục mặc định" button: PATCH isVisible=true, displayLabel=null
  - Footer hint: nhắc admin sidebar luôn dùng Tên gốc, đổi tên áp eOffice

Verify:
- npm run build × fe-admin pass

Pending Chunk D: FE Layout fe-user filter !isVisible + render displayLabel
Pending Chunk E: Docs S20 turn 7

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:37:47 +07:00
2ea2d27785 [CLAUDE] Infra: Mig 27 — Chunk A MenuItem +IsVisible +DisplayLabel
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
Session 20 turn 7: admin có thể Ẩn/Hiện + Đổi tên hiển thị menu cho fe-user
(eOffice). Admin sidebar luôn giữ Label gốc (user Q2=b "chỉ của eOffice thôi").

Domain MenuItem: +IsVisible bool=true +DisplayLabel string?(200)
EF Configuration: HasDefaultValue(true) + HasMaxLength(200)
Migration 27 AddVisibilityAndDisplayLabelToMenuItems — 3-file rule:
  + AddColumn IsVisible bit NOT NULL DEFAULT 1
  + AddColumn DisplayLabel nvarchar(200) NULL

Verify:
- dotnet build SolutionErp.slnx — 0 warn / 0 err
- dotnet ef database update --connection SolutionErp_Dev — applied OK
- dotnet ef database update SolutionErp_Design — applied OK

Pending: B (BE API) → C (FE admin page) → D (FE user render) → E (Docs)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:29:50 +07:00
90baa8e73c [CLAUDE] PurchaseEvaluation: Chunk B Service V2 hook UPSERT opinion + DTO + GET include
Service `ApproveV2Async` sau khi log approval (Decision=Approve) → UPSERT
row `PurchaseEvaluationLevelOpinions` cho Cấp hiện tại (auto sync ý kiến
từ comment khi duyệt). Reject KHÔNG sync.

Match level theo ApproverUserId của actor (multi-NV cùng Cấp OR-of-N).
Admin override (actor.Id KHÔNG match) → fallback first level — FE detect
SignedByUserId !== Level.ApproverUserId hiển thị "Admin duyệt thay".

Empty/whitespace comment → "(duyệt — không ý kiến)" placeholder (Q4 bonus).

Helper `ResolveActorFullNameAsync(actorUserId, isSystem, ct)` lookup
denorm SignedByFullName từ Users (fallback "(System)" / "(unknown)").

DTO `PurchaseEvaluationLevelOpinionDto` (15 fields):
- StepOrder/StepName/StepDepartmentId/StepDepartmentName (Bước Phòng)
- LevelOrder/LevelName/ApproverUserId/ApproverFullName (Cấp NV)
- Comment/SignedAt/SignedByUserId/SignedByFullName (sign-off)

GetPurchaseEvaluationQueryHandler:
- Include LevelOpinions
- helper BuildLevelOpinionsAsync JOIN ApprovalWorkflows.Steps.Levels +
  Departments + Users → denorm DTO. Empty list cho phiếu V1 / V2 chưa
  có cấp nào duyệt → FE fallback message.

Verify: dotnet build pass + dotnet test 81 pass (no regression).

Chunk C kế tiếp: FE Section 5 dynamic mirror 2 app.
2026-05-09 11:00:01 +07:00
77a30584fc [CLAUDE] PurchaseEvaluation: Mig 26 PeLevelOpinions V2 dynamic — Chunk A Domain + EF
Schema mới cho Section 5 "Ý kiến cấp duyệt" V2 dynamic theo
ApprovalWorkflowsV2 (Mig 22-25). Thay thế Mig 15 cố định 4 box (V1).

Entity `PurchaseEvaluationLevelOpinion : AuditableEntity`:
- (PEId, ApprovalWorkflowLevelId) UNIQUE composite
- Comment nvarchar(2000) — text ý kiến hoặc "(duyệt — không ý kiến)" placeholder (Q4 bonus)
- SignedAt datetime2 (luôn có khi UPSERT từ ApproveV2Async)
- SignedByUserId Guid (NV chính chủ HOẶC Admin override)
- SignedByFullName nvarchar(200) — denorm tránh user bị xóa/đổi tên

EF: FK Cascade Pe + Restrict Level. SignedByUserId KHÔNG nav (denorm only).
Migration 26 `AddPeLevelOpinionsForV2`: 1 CREATE TABLE + 2 FK + 2 index.
3-file rule commit đủ (.cs + Designer + Snapshot).

Apply LocalDB SolutionErp_Dev OK (Mig 25 + 26 catchup).

Verify: dotnet build pass + dotnet test 81 pass (no regression).

Chunk B kế tiếp: Service V2 hook UPSERT auto trong ApproveV2Async.
2026-05-09 10:56:16 +07:00
2a53107602 [CLAUDE] AwV2: Mig 25 +IsUserSelectable + Designer pin toggle + Workspace filter, bỏ "(clone)"
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
Hai yêu cầu UAT 2026-05-08:
1. Bỏ "(clone)" auto-append khi clone version mới — version đã đủ phân biệt.
2. Thêm pin toggle để admin chọn workflows nào cho user pick lúc tạo phiếu.

Migration 25 AddIsUserSelectableToApprovalWorkflows:
- ALTER ApprovalWorkflows ADD IsUserSelectable bit NOT NULL DEFAULT 0
- UPDATE backfill SET IsUserSelectable=1 WHERE IsActive=1 (giữ behavior cũ
  cho active versions, archived = false default — admin tự pin nếu cần)

BE:
- Domain ApprovalWorkflow +property IsUserSelectable
- DTO AwDefinitionDto +field
- CreateAwDefinitionCommandHandler set default true cho version mới
- New SetAwUserSelectableCommand + Handler
- API PATCH /api/approval-workflows-v2/{id}/user-selectable (Workflows.Create policy)
- DbInitializer SeedSampleApprovalWorkflowsV2Async set IsUserSelectable=true

FE Designer (fe-admin):
- DefinitionDto +isUserSelectable
- Badge amber "Pin Cho user chọn" khi true (cạnh Đang áp dụng/Archived)
- Button "Pin/PinOff Ghim cho user / Bỏ ghim" trong action group + mutation toggle
- Auto-fill name khi clone: bỏ "(clone)" suffix → giữ nguyên name

FE Workspace (fe-admin + fe-user):
- approvalWorkflows query filter w.isUserSelectable === true
- User dropdown chỉ thấy workflows admin đã pin

Verify: dotnet build pass · 81 test pass · npm build × 2 pass · Mig 25 apply LocalDB OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:15:23 +07:00
937eb2449c [CLAUDE] Workflow V2: clone leaf Designer + sample seed cho DuyetNccPhuongAn (B)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m16s
Mở rộng V2 schema cho type B mirror type A đã chốt S17. Phần lớn đã chung
qua ApplicableType discriminator — chỉ thêm menu key + sample seed.

Changes:
- MenuKeys.cs: +const ApprovalWorkflowDuyetNccPhuongAnV2 (AwV2_DuyetNccPhuongAn) + add vào All array
- DbInitializer.SeedMenusAsync: +leaf "Duyệt NCC và Giải pháp (Mới)" dưới root ApprovalWorkflowsV2
- DbInitializer +SeedSampleApprovalWorkflowsV2Async: seed QT-DN-PA-V2-001 v01 (1 Bước Phòng CCM × 1 Cấp NV test)
  Idempotent — skip nếu admin đã tạo bất kỳ workflow B nào hoặc thiếu test user
- fe-admin/lib/menuKeys.ts: +AwV2_DuyetNccPhuongAn

KHÔNG đụng:
- Migration (V2 schema chung qua ApplicableType — Mig 22-24 đã hỗ trợ B)
- Service ApproveV2Async (không hardcode type)
- Designer page ApprovalWorkflowsV2Page (TYPE_CODE_TO_INT đã có B=2)
- Layout/App.tsx (regex AwV2_(.+) match dynamic)
- Permission default (admin bypass + role khác không cần Designer access)

Verify: dotnet build pass · 81 test pass · npm build fe-admin pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 18:07:56 +07:00
b41484b702 [CLAUDE] PE Workflow: wire Service V2 (Mig 24) — fix bug duyệt phiếu pin schema mới
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m14s
User báo bug eoffice: phiếu tạo mới không duyệt được + không bắt đc quy
trình mới. Root cause: Mig 23 pin ApprovalWorkflowId vào entity nhưng
Service vẫn đọc WorkflowDefinitionId legacy → match approver theo schema
cũ (Dept+PositionLevel/Role/User) thay vì ApproverUserId V2.

BE Domain — Migration 24 `AddCurrentApprovalLevelOrderToPe`:
- PurchaseEvaluation +CurrentApprovalLevelOrder int? (track Cấp 1/2/3
  đang chờ duyệt trong Step hiện tại khi pin V2). Null khi terminal/V1.
- RejectedAtStepIndex giữ deprecated DB column cho data cũ.

BE Service PE — branch theo schema pin:
- V2 (`ApprovalWorkflowId` set): ApproveV2Async() — load
  ApprovalWorkflows.Steps.Levels Include 3-level. Group Levels by Order
  = Cấp (OR-of-N approvers). Match `actor.Id ∈ levelGroup.ApproverUserId`
  (KHÔNG match Dept+Level/Role/User như V1). Advance:
    Còn cấp tiếp trong Step → levelOrder++
    Hết cấp → idx++, levelOrder=1
    Hết Step → DaDuyet
- V1 legacy (chỉ `WorkflowDefinitionId` set): ApproveV1LegacyAsync() —
  giữ nguyên logic Mig 21 (Dept+PositionLevel match)
- Drafter trình từ Nháp/Trả lại: init CurrentWorkflowStepIndex=0 +
  CurrentApprovalLevelOrder=1 (chỉ khi V2 pin)
- Reject (Trả lại): clear CurrentApprovalLevelOrder=null
- Reject (Từ chối): clear all tracking

BE Synthetic Policy V2:
- `PurchaseEvaluationPolicyRegistry.ForV2Schema()` — simple state machine
  policy (DangSoanThao/TraLai → ChoDuyet/TuChoi; ChoDuyet → ChoDuyet/
  TraLai/TuChoi). Roles="*" cho ChoDuyet branch — Service tự enforce
  ApproverUserId, Policy chỉ expose 3 nút FE.
- GetPurchaseEvaluationByIdQuery handler: ưu tiên ForV2Schema() khi pin
  V2 (FE đọc workflow.nextPhases để show button).

Verify: 81 test pass · BE 0 error · Mig 24 applied cả 2 LocalDB.

Test thử (Drafter eoffice):
1. Designer V2 tạo quy trình QT-DN-V2-001: Bước 1 (Phòng A), Cấp 1 (NV X)
2. Workspace tạo phiếu mới, Select QT-DN-V2-001 → Lưu phiếu + Gửi duyệt
3. Phiếu Phase=ChoDuyet, idx=0, levelOrder=1. NV X login → thấy phiếu
   trong Inbox + duyệt được. Sau approve → idx++, levelOrder reset 1.
4. Cấu hình level mismatch: NV Y khác → thấy ForbiddenException rõ tên.

Logic Contract V2 chưa wire (chỉ PE), defer Session sau khi user UAT PE OK.
2026-05-08 14:54:51 +07:00
0a40c65421 [CLAUDE] PurchaseEvaluation: User chọn quy trình duyệt V2 lúc tạo phiếu (Mig 23)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m11s
User feedback: thay field "Loại quy trình (theo menu — khóa)" disabled
→ Select dropdown cho User pick quy trình ApprovalWorkflowsV2 (Mig 22)
ngay từ workspace tạo mới. Hiển thị "Mã + Tên + Version".

BE Domain:
- PurchaseEvaluation +ApprovalWorkflowId Guid? (nullable, FK Restrict)
- EF Configuration: Index + FK Restrict to ApprovalWorkflows
- Migration 23 `AddApprovalWorkflowIdToPurchaseEvaluation` (1 ALTER +
  1 IX + 1 FK), applied cả _Design + _Dev LocalDB
- Field WorkflowDefinitionId (Mig 21 legacy) giữ song song để Service
  PE chạy logic cũ tới khi Session sau wire qua schema mới

BE Application:
- CreatePurchaseEvaluationCommand +ApprovalWorkflowId? Guid? optional
  param (default null)
- Validate: nếu set, phải tồn tại + ApplicableType khớp PE.Type
  (DuyetNcc=1 → ApprovalWorkflowApplicableType.DuyetNcc, etc)
- Handler set entity.ApprovalWorkflowId từ request
- UpdatePurchaseEvaluationDraftCommand mirror — cho User đổi quy trình
  khi sửa Nháp/Trả lại (validate same)
- PurchaseEvaluationDetailBundleDto +ApprovalWorkflowId/Code/Name/Version
- GetPurchaseEvaluationByIdQuery handler load workflow info join
- Update Phase guard: cho sửa cả DangSoanThao + TraLai (Trả lại =
  editable per Session 17 spec)

FE (cả 2 app mirror):
- types/purchaseEvaluation.ts: PeDetail +approvalWorkflowId/Code/Name/Version
- PeWorkspaceCreateView.tsx:
  - Replace field disabled "Loại quy trình" → Select bắt buộc
  - useQuery `/api/approval-workflows-v2?applicableType=N` filter theo
    defaultType (1=DuyetNcc / 2=DuyetNccPhuongAn)
  - Display option: "QT-DN-V2-001 v01 — Quy trình Duyệt NCC (đang áp dụng)"
  - List cả version active + archived (UAT cần test compare)
  - Empty state hint amber "Chưa có quy trình, vào /system/approval-workflows-v2"
  - canSubmit require approvalWorkflowId set
  - POST payload include approvalWorkflowId

Verify: dotnet build OK · 81 test pass · npm build × 2 OK · Mig 23 applied
cả 2 LocalDB.

Logic Service PE chưa wire qua ApprovalWorkflowId — vẫn pin
WorkflowDefinitionId Mig 21 legacy chạy. Session sau wire Service iterate
ApprovalWorkflowSteps + match approver theo schema V2 + drop legacy.
2026-05-08 14:34:54 +07:00
ff21120c8c [CLAUDE] Workflow: State machine 5 trạng thái — Trả lại = Phase riêng
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m17s
Session 17 spec: chốt 5 trạng thái phiếu PE/HĐ/Budget theo state diagram:
  Nháp ─trình──► Đã gửi duyệt ─approve cấp cuối──► Đã duyệt (terminal)
                              ├─ Trả lại ────────► Trả lại
                              └─ Từ chối ────────► Từ chối (terminal)
  Trả lại ──Drafter sửa+gửi lại──► Đã gửi duyệt (chạy LẠI từ đầu)

Khác Mig 21 (Session 16): bỏ smart-reject jump-back. Trả lại = Phase
RIÊNG (TraLai=98), không revert về DangSoanThao + không jump-back step.
Drafter từ TraLai gửi lại như case Nháp — workflow chạy lại từ Cấp 1
Bước 1 (Option A diagram chốt với user).

BE Domain:
- ContractPhase + TraLai = 98
- BudgetPhase + TraLai = 98
- PurchaseEvaluationPhase: TraLai=98 đổi từ [LEGACY deprecated] thành
  primary state. Comment update enum docs cho cả 3.

BE Policy (PE/HĐ/Budget):
- Reject transitions trỏ về TraLai (thay DangSoanThao)
- Mirror entry transitions: TraLai → next phase (cho Drafter resubmit)
- ActivePhases thêm TraLai
- FromDefinition mirror: TraLai → step.Phase + reject → TraLai
- DefaultSla cho TraLai = same as DangSoanThao

BE Service (PE + Contract):
- Reject branch: target=TuChoi giữ; else set Phase=TraLai, clear
  CurrentWorkflowStepIndex=null
- Bỏ ResumeAfterReject branch + RejectedAtStepIndex/RejectedFromPhase
  assignment (DB column giữ deprecated cho data cũ)
- Drafter trình branch: từ DangSoanThao HOẶC TraLai → ChoDuyet, init
  CurrentWorkflowStepIndex=0 (cùng entry point, chạy lại từ đầu)
- Notification: TraLai when fromPhase=ChoDuyet → "bị trả lại"
- Budget Handler: simplify reject → TraLai, bỏ smart-reject + isResuming

BE Tests update:
- WorkflowPolicyTests: Standard_RejectFromCCM → TraLai (rename + assert)
  + Standard_TraLai_To_DangGopY_Allowed_For_Drafter (new)
- PurchaseEvaluationPolicyTests: BothPolicies_RejectFromCCM → TraLai
  + BothPolicies_TraLai_To_ChoPurchasing_AllowedForDrafter (new theory)
- BudgetPolicyTests: Default_CostControl_ChoCCM_To_TraLai (rename)
  + ActivePhases All6States (was All5) + NextPhasesFrom_TraLai (new)
  + NextPhasesFrom_ChoCEO_Includes_DaDuyet_And_TraLai (rename)
- 77 → 81 test pass (+4 tests TraLai entry point)

FE rename "Bản nháp" → "Nháp" (cả 2 app + types):
- types/purchaseEvaluation.ts: PurchaseEvaluationPhaseLabel 1=Nháp,
  10=Đã gửi duyệt. PeDisplayStatus.BanNhap → Nhap (key + value).
  PhaseLabel/Color cho TraLai update active.
- types/contracts.ts: +ChoDuyet=10, +TraLai=98 const + label/color.
  Phase 2 'Đang soạn thảo' → 'Nháp'.
- types/budget.ts: +TraLai=98 const + label/color. Phase 1 → 'Nháp'.
- PeListPanel + PurchaseEvaluationsListPage filter dropdown: Nhap +
  TraLai map đúng phase value.

BE label maps update consistent:
- ContractExcelExporter PhaseLabel: DangSoanThao → "Nháp" + add ChoDuyet/
  TraLai entries.
- PeWorkflowAdminFeatures + WorkflowAdminFeatures PhaseLabels: same.

Verify: dotnet test 81 pass · npm build × 2 app pass · BE 0 error.

Field RejectedAtStepIndex/RejectedFromPhase giữ DB column (nullable,
không set value mới). Cleanup migration sau.
2026-05-08 14:12:38 +07:00
c847dc0b24 [CLAUDE] Workflow: Mig 22 schema mới ApprovalWorkflowsV2 (Chunk A)
Session 17 — schema riêng UAT trước khi drop legacy WorkflowDefinition.
Cấu trúc 3 bảng theo yêu cầu user:
  Quy trình (Code+Name+ApplicableType)
    Bước (Phòng A — DepartmentId hint)
      Cấp (NV X — ApproverUserId 1 user cụ thể, KHÔNG OR-of-many)

Khác Mig 21: Levels match 1 NV CHÍNH XÁC qua ApproverUserId, không
match group Dept+PositionLevel/Role/User. Service sau UAT iterate
Steps OrderBy Order → Levels OrderBy Order → ApproverUserId duyệt.

Files:
- Domain/ApprovalWorkflowsV2/ApprovalWorkflow.cs (3 entity + enum
  ApplicableType: DuyetNcc/DuyetNccPhuongAn/Contract)
- Infra/Persistence/Configurations/ApprovalWorkflowConfiguration.cs
  (FK Cascade Step→Workflow, Level→Step; Restrict Department + User)
- Infra/Persistence/ApplicationDbContext.cs (3 DbSet)
- Infra/Persistence/DbInitializer.cs (2 menu mới: ApprovalWorkflowsV2
  root dưới System icon Workflow + AwV2_DuyetNcc leaf icon FileCheck)
- Domain/Identity/MenuKeys.cs (2 const + All array)
- Migration 20260508053749_AddApprovalWorkflowsV2 (3 table CREATE +
  2 UNIQUE + 3 index)

Verify:
- Build OK, 77 test pass (54 Domain + 23 Infra) ~3s
- Mig applied cả _Design + _Dev LocalDB

Next chunks:
- B: Application CQRS (Get/Create) + ApprovalWorkflowsV2Controller
- C: FE Designer page /system/approval-workflows-v2/:typeCode
- D: Docs + STATUS update
2026-05-08 12:39:37 +07:00
dbb0089e28 [CLAUDE] Drastic refactor: flat workflow Phòng × Cấp + Migration 21 (Chunk A)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m18s
User chốt drastic refactor — bỏ phase enum hoàn toàn, dùng ChoDuyet=10
đơn nhất + currentStepIndex tracking. Workflow flat list (Phòng × Cấp ×
Approvers). Mỗi PE/HĐ pin WorkflowDefinitionId chạy hết quy trình đó.

Schema (Migration 21 `RefactorWorkflowToFlatModel`):
- Phase enum +ChoDuyet=10 (PE + Contract). Legacy 2-9 + 98 deprecated.
- WorkflowStep + DepartmentId Guid? (FK Restrict) + PositionLevel int?
  (PE + Contract — mirror).
- PE/Contract + CurrentWorkflowStepIndex int? + RejectedAtStepIndex int?
- DROP table PurchaseEvaluationWorkflowStepInnerSteps (Mig 18)
- DROP table WorkflowStepInnerSteps (Mig 20)
- DROP column ContractDeptApproval.InnerStepId (Mig 20)
- DROP column PEDeptApproval.InnerStepId (Mig 18)
- DROP filtered indexes (Mig 19/20) + restore simple unique
  (TargetId, Phase, Dept, Stage) non-filtered

Service rewrite (PE + Contract WorkflowService.TransitionAsync):
- Phase transitions: DangSoanThao → ChoDuyet (Drafter trình, init idx=0)
- ChoDuyet → ChoDuyet (advance idx per approve)
- ChoDuyet → DaDuyet/DaPhatHanh (idx >= steps.Count → terminal)
- ChoDuyet → DangSoanThao (Trả lại — save RejectedAtStepIndex)
- ChoDuyet → TuChoi (Từ chối — khoá vĩnh viễn)
- DangSoanThao + RejectedAtStepIndex → ChoDuyet jump-back to saved idx
- Approver match: actor.Dept == step.Dept AND actor.PositionLevel >=
  step.PositionLevel (OR-of-many cùng cấp/dept = pass) OR
  Approvers.Any(Kind=User AND id match) OR
  Approvers.Any(Kind=Role AND actorRoles contains)
- Admin role bypass policy. Last step done → gen mã HĐ (Contract only)

App CQRS:
- WorkflowStepDto + WorkflowStepInput drop InnerStep, add DepartmentId
  + PositionLevel fields. PE + Contract mirror.

Tests rewrite:
- DROP PeNStageApprovalTests.cs (6 test) + ContractNStageApprovalTests.cs
  (6 test) + PeTwoStageApprovalTests.cs (7 test) — legacy N-stage/2-stage
  no longer applicable
- UPDATE PeWorkflowAdminTests signature to new flat input
- 96 → 77 test pass (drop 19 legacy)

Reference Domain entities removed:
- WorkflowStepInnerStep (Contract)
- PurchaseEvaluationWorkflowStepInnerStep (PE)
- DTOs WorkflowStepInnerStepDto / CreateWorkflowStepInnerStepInput per module

Memory `feedback_drastic_refactor_scope.md` validated: drastic refactor
done in dedicated session với context fresh, scope ~5h actual (planned ~8-10h
with 2x buffer).

Verify:
- dotnet build SolutionErp.slnx 0 error
- dotnet ef database update Mig 21 LocalDB applied OK
- dotnet test 77 pass (54 Domain + 23 Infra)
- 3-file rule: Migration .cs + Designer.cs + Snapshot updated

Pending Chunk B: FE Designer flat UI (PeWorkflowsPage + WorkflowsPage).
Pending Chunk C: FE PeWorkflowPanel + workflow timeline display.
Pending Chunk D: Docs + Skill + Memory + session log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:04:51 +07:00
0d776987e4 [CLAUDE] PE workflow 3-button Duyệt/Trả lại/Từ chối (Task 4)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m6s
User chỉ thị thay 2-button hiện tại bằng 3 hành động rõ ràng:
- Duyệt = forward phase tiếp theo
- Trả lại = về DangSoanThao + Drafter sửa → workflow tự jump tới phase
  đã reject (smart reject Mig 16 pattern + clear N-stage rows)
- Từ chối = phiếu khoá hoàn toàn (Phase=TuChoi → 17 handler Mig 16 lock
  edit). Drafter phải tạo phiếu mới.

Domain (PurchaseEvaluationPolicy.cs):
- NccOnly + NccWithPlan: thêm (X → TuChoi) transition cho mọi phase
  trung gian (ChoPurchasing/ChoCCM/ChoCEODuyetNCC/ChoDuAn/ChoCEODuyetPA)
  với roles của phase đó. Trước đây chỉ DangSoanThao → TuChoi (Drafter).
- FromDefinition expand: mỗi step (trừ DangSoanThao) thêm
  (step.Phase → TuChoi) với roles của step.

Service (PurchaseEvaluationWorkflowService.cs):
- Reject branch tách 2 case:
  * target=TuChoi → giữ nguyên (KHÔNG override + KHÔNG set
    RejectedFromPhase + KHÔNG clear N-stage rows). Phiếu khoá vĩnh viễn.
  * target khác (thường DangSoanThao) → smart reject (set
    RejectedFromPhase + force DangSoanThao + clear N-stage rows).

FE (PeWorkflowPanel.tsx, fe-admin + fe-user mirror):
- next.phases render 3 button rõ ràng:
  * "✓ Duyệt → <label>" brand (forward)
  * "← Trả lại (về Drafter sửa)" red (target=DangSoanThao + isSendBack)
  * "✗ Hủy / Từ chối" red (target=TuChoi)
- Decision logic: target=TuChoi || isSendBack → Reject (2), else Approve (1)
- Dialog confirm:
  * Title rõ theo loại hành động
  * Cancel case: warning red "Phiếu sẽ bị khoá hoàn toàn"
  * SendBack case: hint amber "Phiếu sẽ về Đang soạn thảo, Drafter sửa
    rồi trình lại — workflow tự jump tới phase này"

Tests update + add 1 test mới:
- Reject_Sets_RejectedFromPhase_And_Forces_DangSoanThao →
  Reject_To_DangSoanThao_Sets_RejectedFromPhase_TraLai (rename + change
  target từ TuChoi → DangSoanThao để test Trả lại pattern)
- + Reject_To_TuChoi_Locks_Permanently_No_RejectedFromPhase (NEW test
  Từ chối — phase=TuChoi + RejectedFromPhase null)
- NStage_Reject_Clears_InnerStep_Rows_At_Phase: target TuChoi →
  DangSoanThao (test Trả lại + clear N-stage rows pattern)

Verify:
- dotnet build 0 error
- dotnet test 95 → **96 pass** (+1 test mới Từ chối)
- npm build fe-admin + fe-user pass

Pending Task 2: Sample data seed N-stage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:25:44 +07:00
e247b67681 [CLAUDE] Infra: ContractWorkflowService N-stage logic mirror PE (Chunk C)
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
Refactor TransitionAsync mirror PurchaseEvaluationWorkflowService Mig 18
N-stage pattern:
- Reject branch: clear N-stage approval rows tại fromPhase (resume sẽ
  approve lại từ inner đầu)
- Load definition with InnerSteps eager + assign outer scope
- Department approval block split:
  * hasInnerSteps=true → N-stage logic:
    - Yêu cầu actor có DeptId + PositionLevel set (else throw 403)
    - Match firstPending (Order asc IsRequired) same dept + (exact level
      OR canBypass + level≥)
    - exact match: upsert 1 row InnerStepId, IsBypassed=false
    - bypass: batch upsert NV+PP+TP cùng dept ≤ actor (audit IsBypassed
      cho cấp dưới)
    - Recheck stillPending → BLOCK + log Approval/Changelog "duyệt cấp X
      (còn Y pending)"
    - All done → fall through phase transition
  * hasInnerSteps=false → Legacy 2-stage Mig 16 (giữ nguyên với
    InnerStepId=null filter)

Backward compat 100%: workflow Contract no InnerSteps configured →
service fallback legacy 2-stage NV.Review/TPB.Confirm. Tests 89 pass —
no regression.

Verify: dotnet build 0 error, dotnet test 89 pass.

Pending Chunk D: ContractNStageApprovalTests 6 test mirror PE pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:04:16 +07:00
951ffa3ed8 [CLAUDE] Domain+Infra: Migration 20 Contract workflow inner steps mirror PE (Chunk A)
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
Mirror PE N-stage Mig 18+19 pattern sang Contract module. Gộp 1 migration
(CREATE TABLE + ALTER + filtered unique alter) thay vì tách 2 như PE.

Schema:
- entity WorkflowStepInnerStep (Domain/Contracts/) — Order, DeptId,
  PositionLevel, Name, SlaDays, IsRequired
- WorkflowStep nav +InnerSteps List
- ContractDepartmentApproval +InnerStepId Guid? FK Restrict
- CREATE TABLE WorkflowStepInnerSteps (no Contract prefix vì module dùng
  entity gốc Workflow* từ Mig 8)
- DROP UX_ContractDeptApprovals_Contract_Phase_Dept_Stage cũ
- RECREATE filtered: WHERE InnerStepId IS NULL (legacy 2-stage Mig 16)
  + new filtered UNIQUE (ContractId, Phase, InnerStepId) WHERE InnerStepId
  IS NOT NULL (N-stage)
- 3 INDEX: IX_InnerStepId, IX_(StepId, Order), IX_DeptId

Backward compat 100%: workflow Contract no InnerSteps configured →
service fallback legacy 2-stage. Data legacy InnerStepId=null vẫn enforce
unique cũ qua filtered index.

Note: Budget defer (chưa có versioned WorkflowDefinition entity —
hardcoded BudgetPolicy.Default). Cần migration AddBudgetVersionedWorkflow
trước khi mirror N-stage Budget.

Verify:
- dotnet build SolutionErp.slnx 0 error
- dotnet ef database update LocalDB applied OK
- dotnet test 89 pass (54 + 35) — no regression

Pending Chunk B: WorkflowAdminFeatures.cs DTO/Input/Validator/Handler
extend mirror PE Chunk B pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:00:45 +07:00
0c62e241d0 [CLAUDE] Infra: PE workflow service N-stage logic + Migration 19 unique filter (Chunk C)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m2s
Migration 19 `AlterPeDeptApprovalsUniqueFilteredForInnerSteps` — fix
UNIQUE constraint conflict khi N-stage có nhiều inner step cùng dept:
- Drop UX_PEDeptApprovals_PE_Phase_Dept_Stage (Mig 16)
- Recreate filtered: UNIQUE (PEId, Phase, Dept, Stage) WHERE InnerStepId IS NULL
  (legacy 2-stage rows giữ nguyên invariant)
- New filtered: UNIQUE (PEId, Phase, InnerStepId) WHERE InnerStepId IS NOT NULL
  (N-stage 1 row per inner step per phase)

PurchaseEvaluationWorkflowService.TransitionAsync refactor:
- Load definition with InnerSteps eager (.Include ThenInclude)
- Reject branch: clear N-stage approval rows tại fromPhase (resume sẽ
  approve lại từ inner step đầu — clean state)
- Department approval block split:
  * hasInnerSteps=true → N-stage logic:
    - Yêu cầu actor có DepartmentId + PositionLevel set (else throw 403)
    - Match firstPending inner step (Order asc, IsRequired only):
      same dept AND (exact PositionLevel OR canBypass + level ≥ pending)
    - exact match: upsert 1 row (Stage=Confirm, InnerStepId=that)
    - bypass: batch upsert NV+PP+TP cùng dept ≤ actor level (audit
      IsBypassed=true cho cấp dưới skip)
    - Recheck stillPending → BLOCK transition + log Approval/Changelog
      "đã duyệt cấp X (còn Y cấp pending)"
    - All done → fall through phase transition
  * hasInnerSteps=false → Legacy 2-stage (Mig 16) — giữ nguyên logic
    NV.Review/TPB.Confirm + InnerStepId=null filter

Backward compat: workflow cũ (no InnerSteps configured) chạy đúng logic
2-stage Mig 16. Data legacy InnerStepId=null vẫn match unique cũ qua
filtered index. Tests 83 pass — no regression.

Verify:
- dotnet build SolutionErp.slnx 0 error
- dotnet ef database update LocalDB applied Mig 19
- dotnet test 83 pass

Pending Chunk D: Tests N-stage workflow (~6-7 test mới: sequential pass /
bypass cùng dept / reject reset / resume jump-back / legacy fallback).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:20:17 +07:00
13ab533fe7 [CLAUDE] Domain+Infra: Migration 18 PE workflow inner steps + User.PositionLevel (Chunk A)
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
N-stage workflow approval — mỗi WorkflowStep cha (= 1 phase) cấu hình
được chuỗi InnerSteps con theo Department × PositionLevel với Order
sequential. Phase 9+ feature, mở rộng từ 2-stage Mig 16.

Schema:
- enum PositionLevel { NhanVien=1, PhoPhong=2, TruongPhong=3 } (Domain/Identity)
- ALTER Users + PositionLevel int? NULL (admin/system user vẫn null)
- CREATE TABLE PurchaseEvaluationWorkflowStepInnerSteps:
  Id PK, PurchaseEvaluationWorkflowStepId FK Cascade,
  Order int, DepartmentId FK Restrict, PositionLevel int,
  Name nvarchar(200), SlaDays int?, IsRequired bit
- ALTER PurchaseEvaluationDepartmentApprovals + InnerStepId Guid? FK Restrict
  (null cho data legacy 2-stage Review/Confirm Mig 16)

Backward compat: step KHÔNG có InnerSteps → service fallback logic
2-stage Stage=Review|Confirm cũ (Chunk C). Data Mig 16 hiện có giữ
nguyên, InnerStepId=null.

Verify:
- dotnet build SolutionErp.slnx pass (0 error, 2 pre-existing warning DocxRenderer)
- dotnet ef database update LocalDB applied OK
- dotnet test SolutionErp.slnx 83 pass (54 Domain + 29 Infra) — no regression
- 3-file rule: Migration.cs + Designer.cs + Snapshot updated

Pending Chunk B: Application CQRS — extend CreatePeWorkflowDefinitionCommand
với InnerSteps DTO + UpdateUserPositionLevelCommand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:11:42 +07:00
ecd5f7e9d9 [CLAUDE] Domain+Infra: Migration 17 — manual budget fields cho PE + HĐ
Chunk 1/5 — DB schema + Domain layer cho fallback "user nhập số tiền ngân sách
tay khi chưa link Budget entity" (UAT request 2026-05-07). Áp cho cả PE + HĐ
(mirror logic per user Q3 chốt).

Migration 17 `AddManualBudgetFieldsToPeAndContract` — 4 ALTER:
  - PurchaseEvaluations.BudgetManualName  nvarchar(200) NULL
  - PurchaseEvaluations.BudgetManualAmount decimal(18,2) NULL
  - Contracts.BudgetManualName            nvarchar(200) NULL
  - Contracts.BudgetManualAmount          decimal(18,2) NULL

Validation Q2: cả 2 cùng null OK (PE/HĐ chưa có ngân sách gì cả). KHÔNG XOR
với BudgetId — tạm thời cho phép cả 2 cùng có (BE prefer BudgetId nếu set vì
có Phase=DaDuyet guarantee, manual chỉ là fallback hiển thị/note).

Files:
  ~ Domain/PurchaseEvaluations/PurchaseEvaluation.cs — 2 property mới
  ~ Domain/Contracts/Contract.cs — 2 property mới
  ~ Infrastructure/Persistence/Configurations/PurchaseEvaluationConfiguration.cs
    — HasMaxLength(200) + HasPrecision(18,2)
  ~ Infrastructure/Persistence/Configurations/ContractConfiguration.cs — same
  + Migration 17 .cs + .Designer.cs (3-file rule per ef-core-migration skill)
  ~ ApplicationDbContextModelSnapshot.cs (auto-overwrite)

Verify:
  - dotnet ef migrations add → 3 file gen sạch (4 AddColumn Up + 4 DropColumn Down)
  - dotnet ef database update → applied LocalDB OK
  - dotnet test SolutionErp.slnx → 83 pass (54 Domain + 29 Infra) — không regression

Next: Chunk 2 App CQRS Create/Update commands + DTO + AutoMapper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:30:59 +07:00
b6f5a16420 [CLAUDE] Infra+App+Api+FE: Chunk E4 — HĐ 2-stage dept approval (mirror PE)
Mở rộng 2-stage logic từ PE sang Contract workflow (Migration 16 đã có schema):

BE Service:
- ContractWorkflowService thêm UserManager<User> DI
- Mirror logic 2-stage từ PurchaseEvaluationWorkflowService.TransitionAsync
  Sau policy guard, trước gen mã HĐ:
  - User.DepartmentId != null + actor không admin/system + KHÔNG resume
    - DeptManager (TPB) → Stage=Confirm trực tiếp
    - CanBypassReview=true → Stage=Confirm + IsBypassed=true
    - Else (NV) → Stage=Review only, BLOCK transition
  - Insert ContractDepartmentApproval row (UPSERT theo UNIQUE)
  - Block transition khi chưa có Stage=Confirm:
    - Insert ContractApproval (FromPhase=ToPhase=fromPhase, [Review NV] comment)
    - Insert ContractChangelog "đã review, chờ TPB confirm"
    - Notify TPB cùng dept (UserManager filter DeptManager role)
    - Return early — phase KHÔNG đổi

App + Api:
- ContractDepartmentApprovalFeatures.cs (List query mirror PE)
- ContractsController endpoint GET /contracts/{id}/department-approvals

FE (cả fe-admin + fe-user):
- types/contracts.ts thêm ApprovalStage const + ContractDepartmentApproval type
- WorkflowHistoryPanel section "Tiến trình duyệt 2-cấp phòng ban":
  - Group by phase × dept, show Review NV + Confirm TPB
  - Highlight amber "chờ TPB confirm" khi current phase có Review chưa Confirm
  - Badge fuchsia "bypass" khi NV.CanBypassReview=true
  - Insert giữa WorkflowSummaryCard và Lịch sử duyệt
- Mirror cả 2 app (rule §3.9)

Use case mirror PE: HĐ ở phase DangGopY (P.CCM) — nv.cao (NV) duyệt thì
phase KHÔNG đổi (Review only), chờ ccm.tran (TPB) confirm mới sang DangXetDuyet.

Build: BE pass + FE pass cả 2 + 77 test pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:43:05 +07:00
3c4931687a [CLAUDE] App+Api+Docs: Chunk E1 — List endpoint + Bypass-review + Notify TPB + chốt session 8
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m15s
3 endpoint mới + Notify TPB + Docs update để chốt session 8.

Application:
- PurchaseEvaluationDepartmentApprovalFeatures.cs (NEW):
  * ListPeDepartmentApprovalsQuery + DTO PeDepartmentApprovalDto
  * Join Departments (lấy Name) + lookup Users.FullName denorm cho FE timeline
- UserFeatures.cs: SetUserBypassReviewCommand + Handler dùng UserManager.UpdateAsync
- IApplicationDbContext: thêm DbSet<User> Users + DbSet<Role> Roles (cần cho lookup)

Api:
- PurchaseEvaluationsController: GET /api/purchase-evaluations/{id}/department-approvals
- UsersController: PATCH /api/users/{id}/bypass-review (Authorize Users.Update)

Infra:
- PurchaseEvaluationWorkflowService: notify TPB cùng dept khi NV review.
  Query db.Users.Where(DeptId match + IsActive) → UserManager.GetRolesAsync
  filter DeptManager → notifications.NotifyAsync. Best effort fail non-critical.

Docs:
- STATUS.md: Recently Done thêm row session 8 + Phase header update
  count 52→55 tables, 15→16 migrations, 128→131 endpoints
- HANDOFF.md: TL;DR session 8 + 8 cảnh báo session 9 (FE chưa làm,
  test flow anh Kiệt, smart reject test, lock edit test, ...)
- migration-todos.md: Phase 9 done section đầy đủ 3 ràng buộc + pending Chunk E-bis
- CLAUDE.md: count 52→55 + migration 16 description
- session log: 2026-05-04-1230-chot-session-8-2-stage-dept-approval.md (full report)

Verify final:
- Build pass 0 warning 0 error
- 77 unit test pass (54 Domain + 23 Infra)
- Migration 16 applied LocalDB OK + schema verified

Total session 8 cumulative: 5 commit per-chunk:
- 5fe61cc (A: Migration 16 schema)
- 14f3c9f (B: Lock edit guards 17 handler)
- 9747f8c (C: Smart reject + Resume 3 module)
- a532ba6 (D: PE 2-stage logic)
- (current E1: List + Notify + Bypass + Docs)

Pending Chunk E-bis (defer cho session 9 sau UAT PE):
- FE Workflow Panel hiển thị 2-stage timeline
- FE UserManager toggle CanBypassReview
- HĐ + Budget 2-stage extension
- Tests Phase 3 mini cho 2-stage Service-layer logic

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 12:42:47 +07:00
a532ba6fc3 [CLAUDE] Infra: Chunk D — PE 2-stage dept approval (đóng bug anh Kiệt báo)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m22s
Ràng buộc 3 (Phase 9) scope tối giản: chỉ PE workflow trước. Đóng bug
"NV duyệt được hết phase" anh Kiệt báo trong chat FDC.

Logic 2-stage trong PurchaseEvaluationWorkflowService.TransitionAsync,
chèn sau policy guard, trước phase transition:

1. Detect approving phase với role thuộc phòng ban:
   - decision == Approve
   - target != DangSoanThao && != TuChoi
   - Không reject + không resume + không admin/system
   - actorUserId != null + actor.DepartmentId != null

2. Stage detection:
   - DeptManager (TPB) → Stage=Confirm trực tiếp (TPB tự confirm được)
   - User.CanBypassReview=true → Stage=Confirm + IsBypassed=true (NV bypass)
   - Else (NV thường) → Stage=Review only

3. Upsert PurchaseEvaluationDepartmentApproval row:
   - UNIQUE (PEId, PhaseAtApproval, DepartmentId, Stage) đảm bảo 1 row
   - UPDATE in-place khi user click Duyệt lần 2 (đổi comment)
   - ApproverRoleSnapshot: "TPB" / "NV(bypass)" / "NV" denorm cho audit

4. Check Stage=Confirm tồn tại cho (PEId, fromPhase, deptId):
   - hasConfirm = vừa insert Stage=Confirm OR đã có sẵn
   - !hasConfirm → BLOCK phase transition:
     * Insert PEApproval row (FromPhase=ToPhase=fromPhase, Decision=Approve,
       Comment="[Review NV] ...") để track audit
     * Insert Changelog "NV X đã review phase Y, chờ TPB confirm"
     * Return early — Phase KHÔNG đổi
   - hasConfirm → tiếp tục normal phase transition logic

5. Skip 2-stage hoàn toàn khi:
   - Decision=Reject (smart reject Chunk C đã handle)
   - Resume after reject (target đã pinned)
   - Admin role hoặc System (auto-approve)
   - actorUserId == null hoặc actor.DepartmentId == null

Bug fix verified theo flow anh Kiệt:
- User long.chau (NV.PRO, role=Procurement, DepartmentId=PRO) tạo phiếu
- long.chau click Duyệt phase ChoPurchasing → ChoCCM:
  - actor.DepartmentId=PRO → 2-stage logic active
  - role=Procurement, không có DeptManager → Stage=Review
  - hasConfirm=false → BLOCK transition
  - Insert PEDeptApproval(PE, ChoPurchasing, PRO, Review)
  - Phase giữ nguyên ChoPurchasing
- TPB.PRO (tra.bui có role DeptManager + DeptId=PRO) click Duyệt:
  - role=DeptManager → Stage=Confirm
  - hasConfirm=true (vừa insert) → ALLOW transition
  - Phase chuyển ChoPurchasing → ChoCCM
- NV CCM lặp pattern tương tự ở phase ChoCCM
- Cuối cùng CEO/AuthSigner duyệt ChoCEODuyetNCC → DaDuyet (CEO không thuộc
  dept cụ thể nên bypass 2-stage)

Pending Chunk E:
- TODO notify TPB cùng dept khi NV review (best effort, chưa implement)
- List endpoint GET /api/purchase-evaluations/{id}/department-approvals
  cho FE hiển thị progress 2-stage
- UserManager API PATCH /api/users/{id}/bypass-review
- FE Workflow Panel update + UserManager toggle

HĐ + Budget 2-stage scope sẽ làm sau khi PE verify UAT (per default chốt
trước đó).

Verify:
- Build pass (2 warning DocxRenderer cũ)
- 77 unit test pass — Domain policy chưa đụng

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 12:26:18 +07:00
9747f8cbf5 [CLAUDE] Infra+App: Chunk C — Smart reject + Resume after reject (3 module)
Ràng buộc 2 (Phase 9): khi reject, trả về Drafter (DangSoanThao) + lưu phase
nguồn. Drafter sửa lại + trình lại → quay về phase đã reject (skip phase
trung gian).

Logic flow:

1. Reject (Decision=Reject):
   - entity.RejectedFromPhase = currentPhase  // snapshot phase đang reject
   - targetPhase override = DangSoanThao       // force về Drafter
   - Approval row: FromPhase=X, ToPhase=DangSoanThao, Decision=Reject
   - Notification cho Drafter

2. Resume after reject (Decision=Approve, fromPhase=DangSoanThao,
   RejectedFromPhase != null):
   - targetPhase override = entity.RejectedFromPhase!.Value
   - entity.RejectedFromPhase = null  // clear field
   - Skip policy guard (Drafter có quyền trình lại sau khi sửa)
   - Approval row: FromPhase=DangSoanThao, ToPhase=ResumePhase, Decision=Approve

3. Normal transition (chưa reject hoặc đã clear):
   - Logic cũ giữ nguyên — policy guard check + transition

Pattern unified cho 3 module:
- ContractWorkflowService.TransitionAsync: 2 case detect + override
- PurchaseEvaluationWorkflowService.TransitionAsync: tương tự
- TransitionBudgetCommandHandler.Handle: tương tự (Budget không có service riêng,
  logic ở handler)

Files:
- src/Backend/SolutionErp.Infrastructure/Services/ContractWorkflowService.cs
- src/Backend/SolutionErp.Infrastructure/Services/PurchaseEvaluationWorkflowService.cs
- src/Backend/SolutionErp.Application/Budgets/BudgetFeatures.cs

Verify:
- Build pass (2 warning DocxRenderer cũ, không liên quan)
- 77 unit test pass — Domain policy không đổi, tests giữ nguyên

Note: Approval history giờ track đầy đủ cycle reject→sửa→resume:
  Approval 1: DangGopY → DangSoanThao, Decision=Reject (CCM reject)
  Approval 2: DangSoanThao → DangGopY, Decision=Approve (Drafter resume)

UI có thể detect "đã từng reject" qua RejectedFromPhase != null hoặc
qua Approval history (Decision=Reject row gần nhất). Hiển thị banner
đỏ "Phiếu đã bị reject từ phase X, lý do: Y" cho Drafter.

Smart reject hoàn tất Ràng buộc 2. Còn Ràng buộc 3 (2-stage dept approval)
ở Chunk D.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 12:20:21 +07:00
5c200978cb [CLAUDE] Domain+Infra: Migration 16 — 2-stage dept approval + smart reject schema
Chunk A của feature 2-stage department approval (Phase 9). 3 ràng buộc gộp
1 migration để rollback atomic.

Schema changes:

1. Smart reject (3 ALTER):
   - Contracts.RejectedFromPhase int NULL
   - PurchaseEvaluations.RejectedFromPhase int NULL
   - Budgets.RejectedFromPhase int NULL
   Lưu phase nguồn khi reject để Drafter trình lại quay về đúng phase
   (skip phase trung gian đã duyệt) thay vì đi tuần tự từ DangSoanThao.

2. Bypass per-user (1 ALTER):
   - Users.CanBypassReview bit NOT NULL DEFAULT 0
   Khi true → NV được duyệt thay TPB (skip Stage Review, đẩy thẳng
   Stage Confirm). Audit qua DepartmentApproval.IsBypassed=true.

3. 2-stage dept approval (3 CREATE TABLE):
   - ContractDepartmentApprovals
   - PurchaseEvaluationDepartmentApprovals
   - BudgetDepartmentApprovals

   Schema (chung):
   - Id, *Id FK Cascade, PhaseAtApproval int, DepartmentId FK
   - Stage enum (1=Review NV, 2=Confirm TPB)
   - ApproverUserId, ApproverRoleSnapshot, Comment, ApprovedAt
   - IsBypassed bit (mark NV bypass)
   - AuditableEntity (CreatedAt/By/UpdatedAt/By/IsDeleted/...)

   Indexes:
   - UNIQUE (TargetId, PhaseAtApproval, DepartmentId, Stage)
   - Single: TargetId, DepartmentId, ApproverUserId

Files:
- Domain: 4 entity update (Contract/PE/Budget/User add field) + 1 enum mới
  ApprovalStage + 3 entity DepartmentApproval mới
- Infrastructure: 3 EntityConfiguration update + 1 file mới
  DepartmentApprovalsConfiguration với 3 config classes
- IApplicationDbContext: thêm 3 DbSet
- ApplicationDbContext: thêm 3 DbSet
- Migration 16: 3 file (.cs + Designer.cs + Snapshot.cs) — 3-file rule

Verify:
- Build pass (2 warning DocxRenderer cũ, không liên quan)
- 77 unit test pass (54 Domain + 23 Infra) — Domain policy chưa update,
  test pass nguyên không regression

Note: KHÔNG khác PurchaseEvaluationDepartmentOpinion (Migration 15) —
Opinion là sign-off block "Ý kiến 4 phòng ban" trên header phiếu.
DepartmentApproval mới là 2-stage approval workflow per phase.

Tổng sau Migration 16: 55 bảng (52+3), 16 migration. Chunk B-E sẽ implement
Application + FE + Tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 12:11:53 +07:00
5d94bb449a [CLAUDE] PE: Workflow designer admin UI + Ý kiến 4 phòng ban (P1 Session 5)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m51s
==== Task 1: PE Workflow Designer admin ====

BE (mirror Contract WorkflowAdminFeatures pattern):
- Application/PurchaseEvaluations/PeWorkflowAdminFeatures.cs ~250 LOC:
  - GetPeWorkflowAdminOverviewQuery → list 2 EvaluationType (DuyetNcc / DuyetNccPhuongAn) với Active + History versions + count phiếu đang dùng
  - CreatePeWorkflowDefinitionCommand + Validator: auto-increment Version per Code, deactivate Active cũ trong cùng EvaluationType (1 active per type invariant)
  - DTOs: PeWorkflowStepApproverDto / PeWorkflowStepDto / PeWorkflowDefinitionDto / PeWorkflowTypeSummaryDto / PeWorkflowAdminOverviewDto
  - Phase validation 1..7 (state thường, không bao gồm 99=TuChoi)
- Api/Controllers/PeWorkflowsController.cs: 2 endpoint GET /api/pe-workflows + POST. Reuse policy "Workflows.Read" + "Workflows.Create" (admin chung quyền cho cả 2 nhóm WF).

FE:
- pages/system/PeWorkflowsPage.tsx ~500 LOC mirror WorkflowsPage:
  - Landing 2-card grid khi /system/pe-workflows (chưa pick type)
  - TypePanel khi /system/pe-workflows/:typeCode (DuyetNcc / DuyetNccPhuongAn)
  - DefinitionCard read-only view với active badge + version + steps + approvers (Role/User chip)
  - PeWorkflowDesigner dialog: clone từ existing, edit Code/Name/Description, add/remove steps, +Role / +User approvers per step, save → version mới + deactivate cũ
- App.tsx route /system/pe-workflows + /system/pe-workflows/:typeCode
- Layout đã có resolver PeWf_<Code> → /system/pe-workflows/<code> từ session 3

==== Task 2: Ý kiến 4 phòng ban PE ====

Domain:
- PurchaseEvaluationDepartmentOpinion entity (AuditableEntity) — PEId + Kind + Opinion text + SignedAt + UserId + UserName denorm
- PeDepartmentKind enum (PheDuyet / Ccm / MuaHang / SmPm)
- PE entity + collection navigation DepartmentOpinions

Infrastructure:
- PurchaseEvaluationDepartmentOpinionConfiguration EF: UNIQUE(PEId, Kind) — max 1 row per phòng ban per phiếu (UPDATE in-place)
- ApplicationDbContext + IApplicationDbContext DbSet
- Migration 15 AddPurchaseEvaluationDepartmentOpinions (15 migration total / 52 DB tables)

Application:
- PeDepartmentOpinionFeatures.cs: UpsertPeDepartmentOpinionCommand (sign=true → set SignedAt+UserId, sign=false chỉ lưu text giữ chữ ký cũ) + DeletePeDepartmentOpinionCommand
- DTO bundle update: + DepartmentOpinions list trong PurchaseEvaluationDetailBundleDto
- GetPurchaseEvaluationQueryHandler load DepartmentOpinions + KindLabel resolution

API:
- POST /api/purchase-evaluations/{id}/opinions (upsert)
- DELETE /api/purchase-evaluations/{id}/opinions/{kind}

FE:
- types/purchaseEvaluation.ts: + PeDepartmentKind enum + PeDepartmentKindLabel + PeDepartmentOpinion type + departmentOpinions vào bundle
- PeDetailTabs Section "5. Ý kiến 4 phòng ban (sign-off)" — 2x2 grid OpinionBox per kind:
  - Read mode (readOnly menu Duyệt): hiển thị text + chữ ký
  - Edit mode: textarea + 2 button "Lưu text" / "Lưu & Ký"
  - Badge "Đã ký" emerald + tên người ký + ngày khi signedAt != null

==== Task 3: User seed verify ====

Seed `SeedDemoUsersAsync` đã match đúng user list authoritative (5 PRO TPB+NV / 7 CCM TPB+NV / 1 ISO / 1 CEO) từ prior commit. DbInitializer reconcile sẽ tự sync khi API restart. Typo trong list user (soluttions / trương) đã fixed sensibly trong seed.

==== Build verify ====
- dotnet build clean (0 error)
- fe-admin TS build pass (1 module mới PeWorkflowsPage)
- fe-user TS build pass (PE detail mirror)

Total: 8 file mới (BE 4 + FE 1 + Migration 2 + 1 Domain) + 13 file modified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 11:17:14 +07:00
a05c57b081 [CLAUDE] Domain+App+Api: Module Ngan sach (Budget) - 4 bang + workflow simple
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m11s
User request: 'Them cho tao 4 bang luu ve ngan sach: Header / Chi tiet
/ Quy trinh duyet / Lich su thay doi'.

Domain (5 file + 1 enum):
 - Budget (header) — Aggregate root, AuditableEntity. Field: MaNganSach,
   TenNganSach, Description, NamNganSach, ProjectId FK, DepartmentId?,
   DrafterUserId, Phase (BudgetPhase 5-state), TongNganSach (sum auto
   tu Details), SlaDeadline, SlaWarningSent.
 - BudgetDetail — flat row pattern (GroupCode/GroupName + Item +
   KhoiLuong/DonGia/ThanhTien). 18,4 precision KhoiLuong, 18,2 money.
 - BudgetApproval — workflow history (FromPhase/ToPhase/Decision/Comment)
 - BudgetChangelog — audit log unified (EntityType: Header/Detail/Workflow)
 - BudgetPhase enum 5 state: DangSoanThao(1) → ChoCCM(2) → ChoCEO(3) →
   DaDuyet(4) | TuChoi(99)
 - BudgetPolicy hardcoded (no versioned WF, simple default per user
   confirm 'tam thoi don gian'): Drafter/DeptManager → CCM → CEO/
   AuthorizedSigner. Reject path back to DangSoanThao.

Migration 14 AddBudgets:
 - 4 bang moi: Budgets + BudgetDetails + BudgetApprovals + BudgetChangelogs
 - Index: Phase+IsDeleted, ProjectId, NamNganSach, SlaDeadline,
   MaNganSach unique filtered. Cascade delete child.
 - +2 cot FK ngoai bang (per user 'lien ket ca 3'):
   * Contracts.BudgetId Guid? + index
   * PurchaseEvaluations.BudgetId Guid? + index
   Cho phep doi chieu chi phi HD/PE vs ngan sach goi thau.

Application CQRS (BudgetFeatures.cs ~340 line):
 - CreateBudget + UpdateBudgetDraft + TransitionBudget + ListBudgets
   (filter Phase/Project/Year + search + paging) + GetBudget bundle
   (Header + Details + Approvals + Workflow summary)
 - DeleteBudget (only DangSoanThao/TuChoi)
 - AddBudgetDetail + UpdateBudgetDetail + DeleteBudgetDetail (auto
   recompute TongNganSach = sum Details.ThanhTien)
 - ListBudgetChangelogs

Api: BudgetsController 11 endpoint REST /api/budgets:
 - GET /  /{id}  /{id}/changelogs
 - POST /  /{id}/transitions  /{id}/details
 - PUT /{id}  /{id}/details/{detailId}
 - DELETE /{id}  /{id}/details/{detailId}

DbContext + IApplicationDbContext: 4 DbSet new (Budgets/Details/
Approvals/Changelogs).

MenuKeys + DbInitializer: 4 menu key (Budgets root + Bg_List/Create/
Pending leaves) seed dau order=27 'Ngan sach' icon Wallet. Auto-grant
admin permission via SeedAdminPermissionsAsync (MenuKeys.All).

MaNganSach format don gian 'NS-YYYYMM-NNNN' Random.Shared (chua atomic
sequence - user said 'tam thoi chua co').

Workflow chua versioned, hardcode BudgetPolicy.Default. Tuong lai admin
config qua UI: them BudgetWorkflowDefinition tables tuong tu PE.
2026-04-28 11:37:45 +07:00
8097892d20 [CLAUDE] Infra: them 14 demo users Solutions company (PRO 5 + CCM 7 + ISO 1 + CEO 1)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m11s
User cung cap danh sach nhan vien thuc te. Add vao SeedDemoUsersAsync,
idempotent (FindByEmail trong reconcile pattern). Pass = User@123456
(matching demo pattern, policy 12 char + digit + lower + upper + special).

PRO (5):
 - tra.bui — Bui Le Thuy Tra — TP Cung ung (Procurement+DeptManager)
 - phuong.nguyen — Nguyen Thi Bich Phuong (Procurement+Drafter)
 - thanh.lethanh — Le Thanh Binh (Procurement+Drafter)
 - long.chau — Chau Ta Kim Long (Procurement+Drafter)
 - duy.nguyen — Nguyen Van Duy (Procurement+Drafter)

CCM (7):
 - chuong.phan — Phan Van Chuong — TP Kiem soat Chi phi (CostControl+DeptManager)
 - binh.le — Le Van Binh (CostControl)
 - luu.tran — Tran Xuan Luu (CostControl)
 - nguyen.ho — Ho Thi Nu Nguyen (CostControl)
 - anh.nguyen — Nguyen Thi Kim Anh (CostControl)
 - tring.le — Le Tu Dang Trinh (CostControl) — giu literal user provided
 - truong.le — Le Tran Dang Truong (CostControl)

ISO (1):
 - long.nguyen — Nguyen Hoang Chanh Long — Dep HRA, role HrAdmin (dong dau HD)

CEO (1):
 - truong.nguyen — Nguyen Van Truong — Dep BOD, role Director (ASCII fix tu 'truong.nguyen' co dau)

Email typo fix:
 - 'phuong.nguyen@soluttions.com' → 'phuong.nguyen@solutions.com.vn'
 - 'truong.nguyen' co dau → 'truong.nguyen' ASCII (KHONG conflict voi truong.le CCM)

Reconcile pattern dam bao: neu user da exist (email collision), update
dept/position/role thay vi error. Pass khong overwrite cho existing user.
2026-04-28 10:51:56 +07:00
a336997cfe [CLAUDE] PE: section Bang so sanh + rename demo email @solutions.com.vn
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m10s
PART A: Section 'Bang so sanh' (file tong ho so so sanh)

User request: 'theo them cho thong tin ve Bang so sanh, cho dinh kem
file so sanh tong len'.

BE:
 - PurchaseEvaluationAttachmentPurpose.ComparisonTable = 4 (new enum value)
   Backend validator IsInEnum pass, khong can migration (int column).

FE types (2 app):
 - PeAttachmentPurpose.ComparisonTable + Label '4: Bang so sanh'.

FE PeDetailTabs:
 - Them section thu 4 'Bang so sanh (file tong)' sau 'Hang muc + Bao gia'.
 - Component GeneralAttachmentsSection: upload KHONG truyen supplierRowId
   (BE luu NULL) → purpose=ComparisonTable default. Filter attachments
   co supplierRowId===null de render.
 - Card layout khac SupplierAttachmentsCell: full-width card + brand color
   + purpose chip + date. Upload button to hon ([+ Tai len bang so sanh]).
 - readOnly hide upload + delete, giu download.

PART B: Demo email rebrand @solutionerp.local → @solutions.com.vn

User request: 'tao nguoi dung demo theo email cua ben nay'.

BE DbInitializer:
 - Rename 18 email in source: AdminEmail const + 17 demo users
   (bod/pm/ccm/pro/fin/act/equ/hra/qs/nv) — keep password + role unchanged.
 - Them BackfillUserEmailDomainAsync (idempotent): scan user co email
   @solutionerp.local, rename sang @solutions.com.vn, update Email +
   NormalizedEmail + UserName + NormalizedUserName. Skip neu co conflict
   user da ton tai voi email moi. Chay truoc SeedAdmin de tranh tao
   duplicate admin.

Admin permission tao user da co san qua /system/users page.
Comment input khi duyet da co san o PeWorkflowPanel (Ghi chu tuy chon
Textarea) + ContractDetailContent (Yeu cau sua / Duyet tiep dialog).
2026-04-24 15:08:00 +07:00
7ee105df12 [CLAUDE] PE: rename menu + workflow Phuong An -> Giai phap
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m27s
User request session 3 mở đầu: đổi label menu:
  'Quy trình Duyệt NCC - Phương Án' -> 'Duyệt NCC và Giải pháp'
  'Quy trình Duyệt NCC' -> 'Duyệt NCC' (bỏ prefix 'Quy trình')

Giữ nguyên (không breaking):
  - Enum value PurchaseEvaluationType.DuyetNccPhuongAn = 2
  - Menu key Pe_DuyetNccPhuongAn_* (FK ref)
  - typeCode string 'DuyetNccPhuongAn' (URL routing)
  - Policy name 'NccWithPlan' + WorkflowDefinition.Code QT-DN-B

Changed (display-only):
  - DbInitializer SeedMenuTreeAsync peTypeLabels: 2 entry
  - DbInitializer SeedPurchaseEvaluationWorkflowsAsync WF name B
  - PurchaseEvaluationPolicy.cs NccWithPlan Description
  - fe-admin + fe-user types PurchaseEvaluationTypeLabel[2]

Backfill existing DB (seed idempotent skip-if-exists không update):
  - MenuItems Pe_DuyetNcc group + Pe_DuyetNccPhuongAn group + PeWf_* leaf
    → UPDATE Label nếu khác expected
  - WorkflowDefinitions QT-DN-B v01 → UPDATE Name Replace 'Phương Án' → 'và Giải pháp'
  Run mỗi start API — idempotent, fast no-op sau lần đầu.
2026-04-24 10:43:26 +07:00
c48ac2116d [CLAUDE] PurchaseEvaluation: demo seed 4 phieu + MaPhieu atomic sequence + Pe_* perm defaults
Polish session tiep cua PE module skeleton (commit 2c6f0ca..3990066):
3 task A (MISSING in MVP) khac STATUS.md In Progress:

1. Demo PE data seed (SeedDemoPurchaseEvaluationsAsync)
   - 4 phieu varied A/B x phase: A-001 DangSoanThao (mo), A-002
     ChoCEODuyetNCC (winner+9 quotes), A-003 DaDuyet (chua tao HD,
     PaymentTerms JSON), B-001 ChoDuAn (5-step giua chung).
   - Idempotent: skip-if-[DEMO]-exists.
   - Approval history dung policy A (3-step) hoac B (5-step).

2. MaPhieu atomic sequence — Migration 13
   - Format PE/{YYYY}/{TypeLetter}/{Seq:D3} (vd PE/2026/A/001).
   - PurchaseEvaluationCodeSequence entity (Prefix PK).
   - IPurchaseEvaluationCodeGenerator + impl SERIALIZABLE
     transaction (mirror ContractCodeGenerator 1:1).
   - Replace Random.Shared trong CreatePurchaseEvaluationCommandHandler.
   - Migration AddPurchaseEvaluationCodeSequences (1 bang).

3. Pe_* permission defaults
   - SeedPurchaseEvaluationPermissionDefaultsAsync — 7 role business x 9 menu key.
   - Drafter/DeptManager/Procurement: R+C+U; CostControl/PM/Director/AuthorizedSigner: R+U.
   - DeptManager them Delete (xoa nhap).
   - Idempotent per-(roleId x menuKey).

Build: 0 error, 2 warning (pre-existing DocxRenderer).

Files: 4 new + 8 modified (1 migration + entity + generator + DI + 2 ctx + 2 features).

Resolves: STATUS.md In Progress §A — 3 item PE MISSING.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 10:41:17 +07:00
0048a2e83a [CLAUDE] Docs: STATUS + HANDOFF domain migration E2E done (huypham.vn → solutions.com.vn)
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 29s
2026-04-24 10:25:41 +07:00
a6676658a4 [CLAUDE] Infra: SeedDemoUsersAsync robust reconcile pattern + 16 demo users
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m48s
User feedback: prod chỉ thấy admin user, 13 demo seed cũ chưa apply →
fix robust + add thêm.

## Vấn đề trước

- Skip-if-exists pattern: nếu user nào đó tạo fail trước đó → never retry
- Không log error chi tiết khi CreateAsync hoặc AddToRoleAsync fail
- Không tự sửa khi user exists nhưng thiếu dept/position/roles (drift)
- 1 exception abort cả batch

## Fix: RECONCILE PATTERN

Mỗi run, per-user:
- **Nếu chưa có** → CreateAsync + AddToRoleAsync với detailed error log
- **Nếu đã có** → verify + fix nếu drift:
  * DepartmentId mismatch → update
  * Position mismatch → update
  * FullName mismatch → update
  * !IsActive → reactivate
  * Roles missing → AddToRoleAsync supplement
- **Try-catch per-user** → 1 fail không abort 15 còn lại
- **Detailed logging** — counts created/fixed/failed cuối cùng

## 16 demo users (3 thêm)

Trước 13 → giờ 16:
- BOD (3) — bod.huynh + bod.le + **bod.tran** (Phó GĐ Kỹ thuật NĐUQ thứ 2)
- PM (2) — pm.nguyen + **pm.le** (GĐ Vinhomes Ocean Park)
- TPB (6) — CCM/PRO/FIN/ACT/EQU/HRA — 1 mỗi (giữ nguyên)
- Drafter (5) — qs.hoang + qs.ngo + nv.cao + nv.dinh + **nv.truong**
  (NV CCM, làm cho ccm.tran)

Bổ sung: NĐUQ thứ 2 (test multi-AuthorizedSigner), PM thứ 2 (test
multi-project), Drafter trong CCM (test cùng dept với CCM TPB).

## Build

dotnet build BE pass (0 error).

## Note deploy

Khi deploy lên prod:
- HĐ user đã có → reconcile (sửa dept/position/role nếu drift)
- 13 user cũ chưa được tạo → CreateAsync với password User@123456
- 3 user mới (bod.tran, pm.le, nv.truong) → tạo mới
- Log: "Demo users reconcile xong: X created, Y fixed, Z failed.
  (Total demo: 16, password=User@123456 — ⚠ rotate prod)"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 10:20:43 +07:00
4678d192e2 [CLAUDE] App+Api: PurchaseEvaluation CQRS + Controller + WorkflowService
Application (4 file, ~900 lines):
 - IPurchaseEvaluationWorkflowService + PurchaseEvaluationDtos
 - PurchaseEvaluationFeatures: Create / UpdateDraft / Transition / List /
   Inbox / GetDetail (bundle Suppliers+Details+Quotes+Approvals+Workflow) /
   Delete / ListChangelogs. IDOR filter role-based phase eligibility.
 - PurchaseEvaluationSupplierFeatures: Add / Update / Remove supplier
   (N:M Phiếu × Supplier). Block remove nếu còn Quote FK reference.
 - PurchaseEvaluationDetailFeatures: Add/Update/Delete hạng mục +
   Upsert/Delete Quote + SelectWinner (set SelectedSupplierId).

Infrastructure:
 - PurchaseEvaluationWorkflowService: policy load pinned definition →
   guard role + transition rules. Emit Notification drafter khi
   state-change. Tạo PurchaseEvaluationApproval + Changelog row.

Api:
 - PurchaseEvaluationsController ~15 endpoint: CRUD phiếu, N:M supplier,
   hạng mục CRUD, Quote upsert, SelectWinner, Changelog list.
   Route /api/purchase-evaluations.

DI: đăng ký IPurchaseEvaluationWorkflowService scoped.

Skip MVP: Attachments upload, Admin PeWorkflows designer UI (sẽ phase sau
— framework versioned WF table đã sẵn, designer pattern copy từ HĐ).
2026-04-23 16:43:47 +07:00
2c6f0cabfb [CLAUDE] Domain+Infra: PurchaseEvaluation module — 10 bảng + 2 workflow seed (migration 12)
Module Duyệt NCC (tiền-HĐ): phiếu trình duyệt so sánh giá N NCC × M hạng
mục trước khi ký HĐ. 2 quy trình: A DuyetNcc (3-step: Purchasing→CCM→CEO),
B DuyetNccPhuongAn (5-step: Purchasing→DựÁn→CCM→CEO PA→CEO NCC).

Domain (7 core + 3 workflow admin):
 - PurchaseEvaluation (header, AuditableEntity, pin WorkflowDefinitionId,
   SelectedSupplierId, PaymentTerms JSON, ContractId? FK kế thừa)
 - PurchaseEvaluationSupplier (N:M Phiếu × Supplier + contact + payment term)
 - PurchaseEvaluationDetail (hạng mục + ngân sách, group A.I/A.II/...)
 - PurchaseEvaluationQuote (báo giá per NCC per hạng mục + IsSelected)
 - PurchaseEvaluationApproval (workflow history, reuse ApprovalDecision)
 - PurchaseEvaluationChangelog (audit log, reuse ChangelogAction)
 - PurchaseEvaluationAttachment (file upload — báo giá NCC + spec...)
 - PurchaseEvaluationWorkflowDefinition/Step/StepApprover (config y như HĐ,
   tách table riêng vì Phase là PurchaseEvaluationPhase enum riêng)

Policy:
 - PurchaseEvaluationPolicy record + PurchaseEvaluationPolicies.NccOnly/
   NccWithPlan (default hardcoded) + FromDefinition(def) build runtime policy
   từ DB admin-authored. Default SLA: soạn 3d, step 1-2d, CEO 1d.

EF: 10 configurations với index phase+isDeleted, SupplierId, ProjectId,
SlaDeadline, WorkflowDefinitionId, ContractId. UX index (PeId, SupplierId)
+ (DetailId, SupplierId). HasQueryFilter soft delete cho header.

Migration 12 AddPurchaseEvaluations tạo 10 bảng. Idempotent seed:
 - SeedMenuTreeAsync +13 menu item (Pe_* root + 2 group + 6 action leaf
   + PeWorkflows root + 2 admin leaf)
 - SeedPurchaseEvaluationWorkflowsAsync seed QT-DN-A-v01 + QT-DN-B-v01
2026-04-23 16:37:55 +07:00
bcdc00706c [CLAUDE] Infra: Mở rộng seed master (15 NCC + 8 Project) + backfill demo HĐ
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m51s
User feedback: thêm master data NCC + Project + cập nhật HĐ đang thiếu/
trùng nhau.

## SeedDemoMasterDataAsync — per-code idempotent

Trước: skip toàn bộ nếu Suppliers + Projects table đã có row → không add
được seed mới khi expand list.

Sau: per-Code check — add từng item nếu Code chưa tồn tại, skip nếu có.
Cho phép expand seed list theo thời gian mà không clobber data admin
đã add manual.

## 15 demo suppliers (5 SupplierType × 3 entities)

- NhaCungCap (5): VLXD-ABC, Xi măng Hà Tiên, Thép Hòa Phát, Cadivi
  Điện, Tiền Phong Cấp thoát nước
- NhaThauPhu (4): Thăng Long XD, Cô Công Cơ khí, MEP Hà Nội, Next Stage
  Nội thất
- ToDoi (3): Hoàng Nam (cốp pha), Bắc Ninh (xây trát), Hà Trang (ốp lát)
- DonViDichVu (3): Clean Pro, Hồng Phát Vận chuyển, Long An Bảo vệ
- ChuDauTu (3): Vingroup, Sun Group, Masterise Homes

Đầy đủ TaxCode + Phone + Email + ContactPerson + Address.

## 8 demo projects (đa dạng quy mô + giai đoạn)

- FLOCK01 (sẵn) + SKYGARDEN (sẵn) + INDUSTRIAL (sẵn)
- VHOMES-OP (Vinhomes Ocean Park 3, 350B)
- ECOPARK-VL (Ecopark Văn Lâm, 180B)
- BWTOWER (Văn phòng BW Tower 25 tầng, 95B)
- WAREHOUSE-LB (Kho vận Long Biên, 32B)
- RESORT-PQ (Resort 5* Phú Quốc 250 phòng, 220B)

## Backfill demo HĐ supplier + project

Vấn đề trước: 7 demo HĐ tất cả dùng cùng 1 supplier (FirstOrDefault)
+ cùng 1 project → list nhìn không thực tế.

DemoSupplierByType + DemoProjectByType maps mới:
| ContractType | Supplier | Project |
|---|---|---|
| HopDongThauPhu | NTP-XD (thầu phụ) | FLOCK01 |
| HopDongGiaoKhoan | TD-NEHOANG (tổ đội) | FLOCK01 |
| HopDongNhaCungCap | NCC-XIMANG | VHOMES-OP |
| HopDongDichVu | DV-VANCHUYEN | RESORT-PQ |
| HopDongMuaBan | NCC-DIEN | BWTOWER |
| HopDongNguyenTacNCC | NCC-THEP | ECOPARK-VL |
| HopDongNguyenTacDichVu | DV-CLEAN | WAREHOUSE-LB |

SeedDemoContractsAsync 2 path:
1. Lần đầu (no [DEMO] HĐ): tạo mới với supplier+project diverse per type
2. Đã có [DEMO] HĐ: gọi BackfillDemoContractsSupplierProjectAsync —
   loop từng demo HĐ, update supplier_id + project_id nếu mismatch với
   map (idempotent, log count updated)

Match đúng business: HĐ thầu phụ → NTP, HĐ NCC → NCC supplier, HĐ DV →
DV supplier, HĐ chủ đầu tư (BypassCCM) → CDT supplier (admin tự gán).

## Build

dotnet build BE pass (0 error)

## Note deploy

Lần CI/CD deploy `e8XXXXX` lên prod sẽ:
- Add 10 supplier mới + 5 project mới (existing 5+3 giữ nguyên)
- Backfill 7 demo HĐ → switch sang đúng supplier + project per type
- Log: "Seeded 10 demo suppliers", "Seeded 5 demo projects",
  "Backfill [DEMO] contracts supplier+project: 6 updated."
  (6 vì FLOCK01 vẫn dùng cho ThauPhu/GiaoKhoan unchanged)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:54:39 +07:00
4edcd588d8 [CLAUDE] Domain+Infra: User-kind approver runtime guard + Warning 20% SLA
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m41s
## User-kind approver guard

Trước: WorkflowDefinition Designer cho admin pick User cụ thể vào step
approver, nhưng runtime guard bỏ qua (User-kind treat như DeptManager
fallback per skill doc).

Bây giờ: enable đầy đủ. WorkflowPolicy + UserTransitions parallel dict
(default null cho hardcoded Standard/SkipCcm, populated qua
FromDefinition khi WorkflowStepApprover Kind=User).

IsTransitionAllowed signature update: (from, to, actorRoles, actorUserId?)
- Check Role first (existing behavior)
- Fallback User-kind: actorUserId.ToString() có trong UserTransitions[(from,to)]?

ContractWorkflowService.TransitionAsync dùng IsTransitionAllowed thay
inline check. Error message thêm "{N} user explicit" nếu policy có
User-kind approvers cho transition đó.

FromDefinition cũng update: nếu step CHỈ có User-kind (không Role),
không fallback DeptManager nữa — guard sẽ check user-level. Chỉ
fallback DeptManager nếu step thiếu cả 2.

## Warning 20% SLA

SlaExpiryJob.ProcessWarningsAsync mới — chạy trước ProcessAsync
(auto-approve quá hạn):
- Pull Contracts WHERE !SlaWarningSent && SlaDeadline > now &&
  Phase NOT IN (DaPhatHanh, TuChoi, DangDongDau)
- Per phase, threshold = 20% × default SLA (vd Soạn thảo 7 ngày → 33.6h
  remaining trigger warning; In ký 1 ngày → 4.8h)
- Compute remaining = SlaDeadline - now; nếu remaining <= threshold
  + còn slot → notify Drafter via INotificationService
- Set SlaWarningSent = true để chỉ warning 1 lần per phase (reset trong
  TransitionAsync khi chuyển phase mới)
- NotificationType.SlaWarning (đã có trong enum) + title icon ⚠

## Build

dotnet build BE pass (0 error)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:11:34 +07:00
8bc9565127 [CLAUDE] Infra: SeedDemoContractsAsync — 7 HĐ varied phases để UAT-ready
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m39s
User feedback: làm hết task pending. Phase 1: seed sample HĐ data để admin
login thấy ngay E2E hoạt động full (không cần tự tạo từ đầu).

## Idempotent guard

Skip nếu đã có bất kỳ HĐ nào tên bắt đầu "[DEMO]" — admin có thể xóa
demo + create thật bình thường, restart không clobber.

## 7 demo HĐ — covering 7 ContractTypes + 4-5 phases khác nhau

| # | Type | Final Phase | Tên HĐ | Giá trị | Details |
|---|---|---|---|---|---|
| 1 | ThauPhu | DangSoanThao | Thi công móng + cột tầng 1 — FLOCK 01 | 850M | 3 hạng mục (đào, đổ BT, lắp thép) |
| 2 | GiaoKhoan | DangGopY | Khoán nhân công xây + trát + sơn | 320M | 3 công việc + 2 comments demo |
| 3 | NhaCungCap | DangInKy | Cung cấp xi măng + sắt thép Q2/2026 | 1.2B | 3 SP (xi măng, thép D14/D18) |
| 4 | DichVu | DangTrinhKy | Thuê cẩu tháp 6 tháng | 540M | 1 DV (cẩu tháp Liebherr) |
| 5 | MuaBan | DaPhatHanh | Mua máy phát điện 250kVA | 850M | 2 SP có VAT 10% |
| 6 | NguyenTacNCC | DangGopY | HĐ nguyên tắc cung cấp vật tư 2026 | 0 (framework) | 2 SP với khung giá min/max |
| 7 | NguyenTacDV | DangSoanThao | HĐ nguyên tắc bảo trì TB 2026 | 0 (framework) | 1 DV với SLA |

## Workflow simulation

Loop transition phases tới finalPhase, insert ContractApproval row mỗi
phase với ApproverUserId mapping đúng role:
- DangKiemTraCCM → ccm.tran (CostControl)
- DangTrinhKy + DangDongDau → bod.huynh (Director)
- DaPhatHanh → hra.dang (HrAdmin)
- Khác → qs.hoang (Drafter — soạn thảo + đàm phán + in ký)

SkipCcm policy (DichVu/MuaBan/NguyenTacNcc/NguyenTacDv) bỏ DangKiemTraCCM
khỏi flow.

Mã HĐ auto gen qua codeGen.GenerateAsync (RG-001 format đầy đủ).

## Demo data gồm

- 7 Contracts (Header)
- ~14 Details rows (3+3+3+1+2+2+1)
- ~30 ContractApprovals (workflow history per phase)
- 2 ContractComments (demo conversation CCM↔Drafter)

## Build

dotnet build BE pass (0 error)

## Login test sau deploy

Admin → /my-contracts hoặc /contracts → thấy 7 [DEMO] HĐ với mã RG-001
+ varied phases. Click bất kỳ HĐ → Panel 2 thấy Tổng quan + Chi tiết +
Lịch sử duyệt full content.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:06:34 +07:00
330d529c92 [CLAUDE] Domain+App+Infra: Role ShortName + User Department/Position + 13 demo users (migration 11)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m53s
User feedback: chi tiết hóa Users/Phòng ban + gán roles. Roles label
tiếng Việt có Mã (ShortName) + Tên đầy đủ (Description).

## Entity changes

### Role (Domain/Identity/Role.cs)
+ ShortName (max 50)  — Mã viết tắt VN: QTV/BOD/CCM/PRO/FIN/...
+ Description (đã có) — Tên đầy đủ VN
- Identity Name = code English giữ nguyên (FK + [Authorize] attr)

### User (Domain/Identity/User.cs)
+ DepartmentId Guid? FK Departments (Restrict — không xóa dept nếu user reference)
+ Position string? max 200 — chức vụ free text

## Migration 11: AddRoleShortNameAndUserDepartment

3-file rule. Apply LocalDB OK. DB total: 36 tables (không tăng — chỉ
thêm cột vào existing).

## Seed VN labels (12 roles)

| Code | ShortName | Description |
|---|---|---|
| Admin | QTV | Quản trị viên hệ thống |
| Drafter | NV.PB | Nhân viên phòng ban (soạn thảo HĐ) |
| DeptManager | TPB | Trưởng phòng ban |
| ProjectManager | PM | Giám đốc dự án |
| Procurement | PRO | Phòng Cung ứng |
| CostControl | CCM | Phòng Kiểm soát chi phí |
| Finance | FIN | Phòng Tài chính |
| Accounting | ACT | Phòng Kế toán |
| Equipment | EQU | Phòng Thiết bị |
| Director | BOD | Ban Giám đốc |
| AuthorizedSigner | NĐUQ | Người được Ủy quyền ký HĐ |
| HrAdmin | HRA | Phòng Nhân sự - Hành chính |

SeedRolesAsync idempotent + backfill (existing role thiếu ShortName/
Description → update).

## Seed 13 demo users

Default password: User@123456 (warn log để rotate prod). Coverage full
org chart:
- bod.huynh (Tổng GĐ — Director, BOD dept)
- bod.le (Phó GĐ NĐUQ — AuthorizedSigner, BOD dept)
- pm.nguyen (PM FLOCK 01 — ProjectManager, PM dept)
- ccm.tran (TPB CCM — CostControl + DeptManager, CCM dept)
- pro.pham (TPB PRO — Procurement + DeptManager, PRO dept)
- fin.do, act.vu, equ.bui, hra.dang (TPB respective dept)
- qs.hoang, qs.ngo (Drafter — QS dept)
- nv.cao, nv.dinh (Drafter — PRO/FIN dept)

Idempotent (skip nếu email đã tồn tại).

## DTOs + Commands updated

- RoleDto + ShortName field
- UserDto + DepartmentId/DepartmentName/Position
- CreateUserCommand + DepartmentId/Position params (defaults null)
- UpdateUserCommand + DepartmentId/Position
- ListUsersQueryHandler load dept names denormalize per page
- UpdateUserCommandHandler set UpdatedAt

## Note

FE updates (UsersPage dept dropdown + role label VN) ở commit kế tiếp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 14:24:12 +07:00
e27c54702a [CLAUDE] Domain+App+Api: 4 master catalogs cho Details (migration 10)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m48s
User feedback: thêm Master Data cho phần Chi tiết (line items) — autocomplete
khi user nhập field thay vì gõ tay free text.

## 4 entities mới (Domain/Master/Catalogs/)

| Entity | Bảng | Dùng cho HĐ Detail |
|---|---|---|
| UnitOfMeasure | UnitsOfMeasure | Tất cả 7 type (DonViTinh) |
| MaterialItem | MaterialItems | NCC + Mua bán + Nguyên tắc NCC (MaSP/TenSP) |
| ServiceItem | ServiceItems | Dịch vụ + Nguyên tắc DV (MaDichVu/TenDichVu) |
| WorkItem | WorkItems | Thầu phụ + Giao khoán (HangMuc/MaCongViec) |

Common pattern: Code unique (filter IsDeleted=0) + Name + Category +
DefaultUnit + AuditableEntity (soft delete) + IsActive flag.

## Migration 10: AddMasterCatalogs

3-file rule (gotcha #17). Total DB: 32 → 36 tables. Apply LocalDB OK.

## Seed defaults (idempotent — skip per-table nếu có row)

- 20 UnitsOfMeasure: m2, m3, kg, tấn, lít, ngc (ngày công), giờ, gói, ...
- 15 MaterialItems demo: xi măng PCB40, cát vàng, đá 1x2, thép D10, gạch
  4 lỗ, sơn lót, ống PVC...
- 10 ServiceItems demo: vận chuyển, bảo trì, tư vấn, kiểm định, vệ sinh...
- 15 WorkItems demo: đào móng, đổ bê tông, xây tường, trát, lát gạch,
  sơn nước, lắp điện, lắp nước, thấm chống...

## CQRS (Application/Master/Catalogs/CatalogsFeatures.cs ~290 dòng)

Mỗi catalog 5 handlers (List filter q+category, Create với unique code
guard, Update, Delete soft). FluentValidation max length per spec EF.

## Controller (Api/Controllers/CatalogsController.cs)

13 endpoints:
- GET  /api/catalogs/{units|materials|services|work-items}
- POST /api/catalogs/{kind}                  (Admin role)
- PUT  /api/catalogs/{kind}/{id}             (Admin role)
- DELETE /api/catalogs/{kind}/{id}           (Admin role)

Read open cho mọi role (FE Details add form autocomplete cần list).

## Menu (5 mới)

- Catalogs (group, Master parent, order 24, "Library" icon)
  - CatalogUnits "Đơn vị tính" (Ruler)
  - CatalogMaterials "Vật tư / SP" (Package)
  - CatalogServices "Dịch vụ" (Wrench)
  - CatalogWorkItems "Hạng mục công việc" (ListChecks)

Admin auto-grant tất cả CRUD action (qua SeedAdminPermissionsAsync
loop MenuKeys.All).

## Build

dotnet build BE pass (0 error)

## Note

FE admin page CatalogsPage + datalist autocomplete trong Details form
sẽ ở commit kế tiếp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 12:21:39 +07:00
51449d6b9d [CLAUDE] App+Infra: Mã HĐ gen ngay tại CreateContract + backfill HĐ legacy
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m45s
User feedback: HĐ phải có mã ngay khi tạo (không đợi đến DangDongDau như
cũ). HĐ đã tạo trước đây nhưng chưa có mã → backfill tự động.

## Thay đổi

### CreateContractCommandHandler (App)

- Inject IContractCodeGenerator
- Load supplier + project FULL (cần Code, không chỉ check tồn tại như trước)
- Call codeGenerator.GenerateAsync TRƯỚC khi db.Contracts.Add — entity
  chưa tracked nên GenerateAsync internal SaveChangesAsync chỉ save SEQ
  (không kèm contract chưa tracked)
- Set entity.MaHopDong = result trước khi Add → INSERT contract đã có mã
- Changelog summary include mã: "Tạo HĐ {mã} — {tên}"

### Trade-off documented

- Mã gen sớm → HĐ TuChoi sẽ "wasted" 1 mã (gap trong sequence)
- Acceptable vì user cần mã reference vào tài liệu/giấy tờ ngay từ đầu

### ContractWorkflowService.TransitionAsync (Infra)

- Giữ logic cũ `if MaHopDong is null → gen` ở DangDongDau
- Update comment: nominal flow skip vì mã đã có; defensive cho HĐ legacy
  hoặc HĐ tạo bằng path khác (seed/import)

### DbInitializer.BackfillContractCodesAsync (Infra)

- Chạy 1 lần trước WarnDefaultAdminPasswordAsync
- Idempotent: count Contracts WHERE MaHopDong IS NULL → skip nếu 0
- Loop từng HĐ: load supplier+project → GenerateAsync → SaveChangesAsync
- Skip + log warning nếu missing supplier/project (legacy data corruption)
- Try-catch per HĐ, log success/failed count cuối cùng

## Build

dotnet build BE pass (0 error, 2 pre-existing DocxRenderer warning)

## Note

Khi deploy lên prod, DbInitializer chạy startup → backfill HĐ cũ tự động.
Log line "Backfill mã HĐ: X HĐ thiếu mã, đang gen..." sẽ xuất hiện ở
Logs/log-{date}.txt để verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:36:59 +07:00
71c035d31e [CLAUDE] App+Infra: IChangelogService + log Workflow/Contract/Comment/Attachment
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m26s
User decision B (log cả 3): mọi thay đổi liên quan HĐ ghi vào unified
ContractChangelogs để render tab Lịch sử FE.

## IChangelogService (Application/Common/Interfaces/)

5 methods:
- LogContractChangeAsync — Header insert/update
- LogDetailChangeAsync — line item insert/update/delete
- LogWorkflowTransitionAsync — phase change (parallel với ContractApprovals)
- LogCommentAddedAsync — góp ý mới
- LogAttachmentAsync — upload/delete file

KHÔNG SaveChanges trong service — caller chịu trách nhiệm save atomic
cùng business changes (pattern giống INotificationService).

## ChangelogService impl

- Resolve actor qua ICurrentUser → UserManager.FindByIdAsync
- Denormalize UserName (FullName ?? Email) cho log readable
- null UserId = system action (vd SLA auto-approve)
- DI: AddScoped trong DependencyInjection.cs

## Wiring vào handlers hiện tại

- ContractWorkflowService.TransitionAsync — LogWorkflowTransitionAsync
  sau khi insert ContractApproval
- CreateContractCommandHandler — LogContractChangeAsync(Insert)
- UpdateContractDraftCommandHandler — diff GiaTri/TenHopDong/NoiDung/
  TemplateId trước update, log update với fieldChangesJson nếu có thay đổi
- AddCommentCommandHandler — LogCommentAddedAsync
- UploadContractAttachmentCommandHandler — LogAttachmentAsync(Insert)
- DeleteContractAttachmentCommandHandler — LogAttachmentAsync(Delete)

Build: dotnet build BE pass (0 error, 2 pre-existing warning trong
DocxRenderer.cs)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:12:51 +07:00
70810e1b34 [CLAUDE] Domain+Infra: 7 ContractType-specific Details + ContractChangelog (migration 9)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m37s
User decision: Option B — bảng riêng cho mỗi loại HĐ (chuẩn nhất, schema
chuyên biệt). Plus: ContractChangelog audit log thống nhất Header /
Detail / Workflow / Comment / Attachment.

## 8 entities mới

### Details (7) — Domain/Contracts/Details/

| Bảng | Loại HĐ | Field đặc trưng |
|---|---|---|
| ThauPhuDetails | 1 (Thầu phụ) | HangMuc, KhoiLuong, DonGia, ThoiGianHoanThanh |
| GiaoKhoanDetails | 2 (Giao khoán) | MaCongViec, KhoiLuong, YeuCauKyThuat |
| NhaCungCapDetails | 3 (NCC) | MaSP, ThongSoKyThuat, SoLuong, ThoiGianGiao, XuatXu |
| DichVuDetails | 4 (Dịch vụ) | MaDichVu, ThoiGian, TuNgay/DenNgay |
| MuaBanDetails | 5 (Mua bán) | MaSP, SoLuong, DonGia, ThueVAT (%), XuatXu |
| NguyenTacNccDetails | 6 (Nguyên tắc NCC) | NhomSP, DonGiaToiThieu/ToiDa, DieuKienGiaoHang |
| NguyenTacDvDetails | 7 (Nguyên tắc DV) | LoaiDichVu, DonGiaToiThieu/ToiDa, PhamViDichVu, SLA |

Common base `ContractDetailBase`: ContractId FK + Order + ThanhTien
decimal(18,2) + GhiChu nvarchar(1000) + audit (BaseEntity).

### ContractChangelog (1) — Domain/Contracts/

Unified audit log. Khác ContractApprovals (workflow-only, dùng cho guard
logic) — Changelog là VIEW LAYER cho user đọc lịch sử thao tác:
- EntityType enum: Contract | Detail | Workflow | Comment | Attachment
- Action enum: Insert | Update | Delete | Transition
- PhaseAtChange snapshot
- UserId + UserName denormalize (log readable)
- Summary human-readable + FieldChangesJson [{Field, Old, New}]
- ContextNote (comment kèm theo)

## EF Configurations

ContractDetailsConfiguration.cs (1 file gộp 7 IEntityTypeConfiguration):
- ToTable + HasMaxLength + HasPrecision per type
- HasOne(Contract).WithMany(<TypeDetails>) cascade delete
- IX (ContractId, Order) cho load timeline

ContractChangelogConfiguration.cs:
- Cascade delete khi Contract xóa
- IX (ContractId, CreatedAt) timeline + IX (ContractId, EntityType) filter

## DbContext + IApplicationDbContext

+ 8 DbSet mới (7 Details + ContractChangelogs).

## Migration 9: AddContractDetailsAndChangelog

3-file rule (gotcha #17): .cs + .Designer.cs + ApplicationDbContextModel
Snapshot.cs đầy đủ. Applied LocalDB SolutionErp_Dev OK — 24 + 8 = 32 bảng
total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:08:42 +07:00
f216169039 [CLAUDE] FE-Admin+Domain+Infra+App: Workflows tab → sidebar menu items
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m37s
User request: 7 tab trong /system/workflows thành menu items riêng.

Domain:
- MenuKeys.WorkflowTypeLeaf(code) helper — `Wf_<TypeCode>` pattern

Infrastructure (DbInitializer):
- Seed 7 leaves dưới Workflows group (order 95..101), label matches
  ContractType (HĐ Thầu phụ / Giao khoán / NCC / Dịch vụ / Mua bán /
  Nguyên tắc NCC / Nguyên tắc Dịch vụ). Idempotent.

Application (GetMyMenuTreeQuery):
- Generalized inherit-perm logic: descendants of Contracts AND Workflows
  inherit parent CanRead flag. Single Workflows.Read grant → all 7
  Wf_* leaves visible; no per-leaf permission rows needed.

FE Layout (admin):
- resolvePath: Wf_<Code> → /system/workflows/<code>. Ct_* still hidden
  on admin side.

FE App.tsx:
- New route /system/workflows/:typeCode?

FE WorkflowsPage:
- Removed horizontal tab bar; type selection now comes từ URL param.
- Landing view (no param): 3-col grid card per type với active version
  badge — so admin có visual overview khi click top-level Workflows
  group without selecting a type.
- TYPE_CODE_TO_INT map drives URL→int conversion.

Result: click `Quy trình HĐ > HĐ Mua bán` trong sidebar → opens
/system/workflows/MuaBan directly với designer scoped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 09:49:42 +07:00
e7e5f2d066 [CLAUDE] Domain+Infra+App+Api+FE-Admin: versioned workflow per ContractType
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 1m32s
User yêu cầu: mỗi loại HĐ có quy trình riêng với admin add roles + users
vào từng bước. Khi tạo version mới → HĐ tương lai chạy theo, HĐ cũ giữ
version cũ.

Domain:
- WorkflowDefinition (Code + Version + ContractType + IsActive + Steps)
- WorkflowStep (Order + Phase + Name + SlaDays + Approvers)
- WorkflowStepApprover (Kind: Role/User + AssignmentValue)
- Contract.WorkflowDefinitionId — pinned at creation
- WorkflowPolicyRegistry.FromDefinition() — build runtime policy từ DB

Infrastructure:
- EF config + migration AddVersionedWorkflows (3 table mới)
- DbInitializer.SeedWorkflowDefinitionsAsync: v01 per 7 ContractType,
  steps sinh từ hardcoded WorkflowPolicies (Role approvers).
- ContractWorkflowService.TransitionAsync: load pinned WorkflowDefinition
  → FromDefinition(), fallback cho HĐ cũ không có pin.

Application:
- CreateContractCommand pin WorkflowDefinitionId = active version cho type
- ContractFeatures.Get(id): load pinned def cho workflow summary
- WorkflowAdminFeatures: GetWorkflowAdminOverviewQuery (7 types + active
  + history + ContractsUsingCount), CreateWorkflowDefinitionCommand
  (validate payload, auto-increment version, deactivate old).

Api:
- GET /api/workflows trả overview
- POST /api/workflows tạo version mới (deactivate old)

FE /system/workflows:
- Tabs per 7 ContractType, mỗi tab hiện active version + lịch sử
- DefinitionCard: steps với badge role/user + SLA + archived indicator
  hiện "N HĐ còn chạy" cho version cũ
- WorkflowDesigner modal: form code/name/desc + danh sách steps
  (phase/name/SLA) + approvers (+ Role hoặc + User). Drop step ok.
  Clone từ version hiện tại để tạo v02 có điểm start sensible.
- Amber banner: HĐ cũ không bị ảnh hưởng khi tạo version mới

Invariants được giữ:
- Unique (Code, Version) index
- Chỉ 1 version IsActive per ContractType tại 1 thời điểm
- Set default sẽ auto xóa override → respect legacy override table
- Role-kind approvers drive transition guards; User-kind fallback
  DeptManager role cho v1 (user-level targeting = iteration 2)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:57:41 +07:00
5e0f3801a1 [CLAUDE] Move nested-type menu → fe-user; Admin workflow config page
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m41s
User clarified: menu loại HĐ 3-level (Danh sách/Thao tác/Duyệt) thuộc
fe-user. Admin có page riêng để config quy trình per loại HĐ.

fe-admin Layout:
- filterForAdmin() drops Ct_* entries (hide nested type menu).
- Admin sidebar giờ về lại đơn giản: Dashboard / Master / Hợp đồng
  (leaf) / Forms / Reports / System.

fe-user Layout:
- Dynamic menu tree từ /menus/me (thay fixed USER_MENU hardcoded).
- Recursive MenuNodeRenderer (top-level expanded, nested collapsed).
- resolvePath user-specific: Ct_*_List → /my-contracts?type=X,
  Ct_*_Create → /contracts/new?type=X, Ct_*_Pending → /inbox?type=X.
- filterForUser drops admin-only entries (Master/System/Forms/Reports).
- Static USER_FIXED_TOP prepends "Hộp thư" leaf → /inbox.
- MyContractsPage + InboxPage đọc ?type=X param, filter client-side.

Workflow config (Admin side):
- Domain: WorkflowTypeAssignment entity (ContractType → PolicyName
  override). Registry.ForContractWithOverrides() prefer DB override
  else default.
- Infrastructure: EF config + migration AddWorkflowTypeAssignments,
  unique index trên ContractType. ContractWorkflowService load
  overrides dict mỗi transition. ContractFeatures load overrides khi
  build WorkflowSummaryDto.
- Application: GetWorkflowAdminOverviewQuery returns 7 types × current
  policy + available policies. SetWorkflowAssignmentCommand validate
  policy name tồn tại; nếu = default thì delete override (no stale row).
- Api: GET /api/workflows + PUT /api/workflows/{contractType}
  với policy "Workflows.Read" + "Workflows.Update".
- Menu: new key `Workflows` dưới System, label "Quy trình HĐ".
- FE /system/workflows: 7 card per type, dropdown Standard/SkipCcm +
  'Đã override' badge khi khác default, phase sequence timeline,
  explanation banner ở top. Iteration 2 note: admin-authored custom
  policies.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:41:05 +07:00
48e91fe7ca [CLAUDE] Domain+Infra+App+FE-Admin: per-ContractType nested sidebar menu
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m48s
User request: mỗi loại HĐ có menu riêng với 3 action Danh sách /
Thao tác / Duyệt.

Sidebar giờ 3-level under "Hợp đồng":
  Hợp đồng (group, expandable)
  ├── HĐ Thầu phụ (sub-group)
  │   ├── Danh sách → /contracts?type=1
  │   ├── Thao tác  → /contracts/new?type=1
  │   └── Duyệt      → /contracts?type=1&pendingMe=1
  ├── HĐ Giao khoán (sub-group)
  ├── HĐ NCC / Dịch vụ / Mua bán / Nguyên tắc NCC / Nguyên tắc DV
  └── ... (7 types × 4 = 28 new menu items)

BE:
- MenuKeys.cs: ContractTypeCodes array + helpers ContractTypeGroup/
  List/Create/Pending → key format Ct_<TypeCode>[_<Action>]
- DbInitializer.SeedMenuTreeAsync: loop seeds 28 entries under Contracts
- GetMyMenuTreeQuery.BuildChildren: descendants of `Contracts` inherit
  parent permission (avoid adding 28 rows to Permissions table per role)

FE:
- Layout.tsx recursive: MenuNodeRenderer dispatches group vs leaf by
  depth; nested groups collapsed by default (top-level expanded).
  Deeper levels get smaller padding/text + left border guide.
- Pattern-based resolvePath: Ct_<Type>_<Action> → URL with query.
- Contract type code → int map (matches Domain ContractType enum).
- ContractsListPage reads ?type + ?pendingMe, filters client-side.
  Header title + description reflect active filter. "← Tất cả loại"
  quick-reset button.
- ContractCreatePage new cho admin (copy từ fe-user), pre-select type
  từ ?type URL param.
- App.tsx route /contracts/new → ContractCreatePage.

Pure navigation UX; no new permissions needed. Admin + any role with
Contracts.Read see full menu; leaves click-through to filtered views.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:25:00 +07:00
6197c841bb [CLAUDE] App+Infra+FE-Admin: seed master data + MyDashboard widgets
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m48s
Task 1 — Seed master data unblock UAT/demo:
- DbInitializer.SeedDepartmentsAsync: 9 departments từ QT-TP-NCC.docx
  (PM/QS/CCM/PRO/FIN/ACT/EQU/HRA/BOD) — reference data không phải demo.
- DbInitializer.SeedDemoMasterDataAsync: 5 demo suppliers (NCC VLXD, NTP
  Xây dựng, TĐ Hoàng Nam, DV Clean, CĐT Vingroup — covers cả 5
  SupplierType) + 3 demo projects (FLOCK01, SkyGarden, Industrial).
  Chỉ seed nếu tables empty — respect admin's real data khi họ add.

Task 2 — Roles CRUD đã có sẵn trong UsersPage (Shield icon button mở
dialog gán 12 roles từ AppRoles.cs). Skip.

Task 3 — MyDashboard role-specific widgets:
- GetMyDashboardQuery (Reports): returns DraftsInProgress (tôi là
  Drafter + phase soạn thảo), PendingMyApproval (phase eligible role
  tôi + không phải tôi drafter), DueSoon 24h, Overdue, DraftsTotalValue.
- Endpoint GET /api/reports/my-dashboard.
- FE MyDashboardRow ở đầu DashboardPage: 4 card hover → navigate.
  Admin ẩn row nếu tất cả = 0 (ERP noise reduction).
  'Đang soạn thảo' + 'Chờ tôi duyệt' clickable → /contracts?filter=...
  (filter param để wire lần sau; row hiện chưa implement).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:06:28 +07:00
cae4d84830 [CLAUDE] Domain+Infra+App+FE: dynamic workflow policy per ContractType
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m42s
Đọc QT-TP-NCC.docx: quy trình 9 bước chỉ áp dụng cho Thầu phụ/NCC/Tổ đội.
Dịch vụ/Mua bán/Nguyên tắc bypass CCM. Thay hardcoded dict bằng policy
registry.

Domain — WorkflowPolicy.cs:
- Record WorkflowPolicy { Name, Description, Transitions, PhaseSla,
  ActivePhases } — pure data, testable.
- WorkflowPolicies.Standard: 9-phase full (Thầu phụ/Giao khoán/NCC)
- WorkflowPolicies.SkipCcm: 7-phase (Dịch vụ/Mua bán/Nguyên tắc)
- WorkflowPolicyRegistry.For(type) map ContractType → policy
- WorkflowPolicyRegistry.ForContract(c) override nếu BypassProcurement
  AndCCM=true (instance-level escape hatch)

Infrastructure — ContractWorkflowService:
- Xóa hardcoded Transitions/PhaseSla dicts → load từ policy.ForContract
- TransitionAsync: validate qua policy.Transitions thay vì dict local
- Error message include policy.Name để debug dễ hơn
- GetPhaseSla trả SLA từ Standard policy (fallback — SLA hiện tại giống
  nhau giữa 2 policy)

Application — ContractDetailDto:
- Field mới `Workflow: WorkflowSummaryDto { PolicyName, Description,
  ActivePhases, NextPhases }` — FE dùng để render nút chuyển phase
  dynamic + timeline card.
- BuildWorkflowSummary helper trong ContractFeatures.

FE (both apps):
- Type WorkflowSummary + ContractDetail.workflow
- ContractDetailPage xóa hardcoded NEXT_PHASES — dùng
  c.workflow.nextPhases từ BE (single source of truth)
- WorkflowSummaryCard: timeline của ActivePhases với check/current/
  future states + policy name/description ở header
- Card hiển thị trong sidebar, phía trên "Lịch sử duyệt"

Docs:
- gotchas.md #21 marked RESOLVED (NEXT_PHASES sync không còn cần)

Foundation: sau này admin có thể edit policy qua UI khi chuyển sang DB-
backed policy — nhưng API contract (WorkflowSummaryDto) đã stable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 21:46:31 +07:00