[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.
This commit is contained in:
pqhuy1987
2026-05-08 14:54:51 +07:00
parent 0a40c65421
commit b41484b702
7 changed files with 4096 additions and 79 deletions

View File

@ -43,12 +43,17 @@ public class PurchaseEvaluation : AuditableEntity
// Flat workflow tracking (Session 16 — Migration 21):
// - CurrentWorkflowStepIndex: 0-based index của step đang chờ approver
// (khi Phase=ChoDuyet). Null khi DangSoanThao/DaDuyet/TuChoi.
// - RejectedAtStepIndex: snapshot CurrentWorkflowStepIndex tại Trả lại.
// Drafter resume → restore CurrentWorkflowStepIndex (jump-back).
// (khi Phase=ChoDuyet). Null khi DangSoanThao/DaDuyet/TuChoi/TraLai.
// - RejectedAtStepIndex: [DEPRECATED Session 17] snapshot index tại Trả lại.
// Field giữ DB column cho data cũ — Service không set value mới.
public int? CurrentWorkflowStepIndex { get; set; }
public int? RejectedAtStepIndex { get; set; }
// V2 schema tracking (Session 17 — Migration 24):
// - CurrentApprovalLevelOrder: Cấp đang chờ duyệt (1/2/3) trong Step
// hiện tại khi pin ApprovalWorkflowId. Null khi V1 legacy hoặc terminal.
public int? CurrentApprovalLevelOrder { get; set; }
public List<PurchaseEvaluationSupplier> Suppliers { get; set; } = new();
public List<PurchaseEvaluationDetail> Details { get; set; } = new();
public List<PurchaseEvaluationQuote> Quotes { get; set; } = new();

View File

@ -156,6 +156,52 @@ public static class PurchaseEvaluationPolicyRegistry
public static PurchaseEvaluationPolicy ForEvaluation(PurchaseEvaluation ev) =>
For(ev.Type);
// Session 17 — synthetic policy cho phiếu pin schema V2 (ApprovalWorkflowsV2).
// Workflow chạy theo state machine 5 trạng thái + iterate Steps/Levels —
// Phase enum chỉ dùng (DangSoanThao/TraLai/ChoDuyet/DaDuyet/TuChoi). Service
// tự handle advance level/step bên trong ChoDuyet, FE chỉ cần biết:
// DangSoanThao/TraLai → ChoDuyet (trình) | TuChoi (huỷ)
// ChoDuyet → ChoDuyet (advance) | TraLai (trả lại) | TuChoi (từ chối)
public static PurchaseEvaluationPolicy ForV2Schema()
{
var transitions = new Dictionary<(PurchaseEvaluationPhase, PurchaseEvaluationPhase), string[]>
{
// Drafter trình từ Nháp HOẶC gửi lại từ Trả lại — cùng entry point
[(PurchaseEvaluationPhase.DangSoanThao, PurchaseEvaluationPhase.ChoDuyet)] = [AppRoles.Drafter, AppRoles.DeptManager],
[(PurchaseEvaluationPhase.DangSoanThao, PurchaseEvaluationPhase.TuChoi)] = [AppRoles.Drafter, AppRoles.DeptManager],
[(PurchaseEvaluationPhase.TraLai, PurchaseEvaluationPhase.ChoDuyet)] = [AppRoles.Drafter, AppRoles.DeptManager],
[(PurchaseEvaluationPhase.TraLai, PurchaseEvaluationPhase.TuChoi)] = [AppRoles.Drafter, AppRoles.DeptManager],
// ChoDuyet — Service guard match approver ApproverUserId, Policy chỉ
// expose 3 nút cho FE (Duyệt forward / Trả lại / Từ chối). Roles "*"
// để guard không block; Service tự enforce ApproverUserId match.
[(PurchaseEvaluationPhase.ChoDuyet, PurchaseEvaluationPhase.ChoDuyet)] = ["*"],
[(PurchaseEvaluationPhase.ChoDuyet, PurchaseEvaluationPhase.TraLai)] = ["*"],
[(PurchaseEvaluationPhase.ChoDuyet, PurchaseEvaluationPhase.TuChoi)] = ["*"],
};
var sla = new Dictionary<PurchaseEvaluationPhase, TimeSpan?>
{
[PurchaseEvaluationPhase.DangSoanThao] = TimeSpan.FromDays(3),
[PurchaseEvaluationPhase.TraLai] = TimeSpan.FromDays(3),
[PurchaseEvaluationPhase.ChoDuyet] = TimeSpan.FromDays(7),
[PurchaseEvaluationPhase.DaDuyet] = null,
[PurchaseEvaluationPhase.TuChoi] = null,
};
return new PurchaseEvaluationPolicy(
Name: "V2-Schema",
Description: "Schema mới ApprovalWorkflowsV2 — Service iterate Steps/Levels theo workflow pin.",
Transitions: transitions,
PhaseSla: sla,
ActivePhases:
[
PurchaseEvaluationPhase.DangSoanThao,
PurchaseEvaluationPhase.TraLai,
PurchaseEvaluationPhase.ChoDuyet,
PurchaseEvaluationPhase.DaDuyet,
PurchaseEvaluationPhase.TuChoi,
]);
}
// Build policy from persisted admin-authored definition (mirror
// WorkflowPolicyRegistry.FromDefinition for HĐ).
public static PurchaseEvaluationPolicy FromDefinition(PurchaseEvaluationWorkflowDefinition def)