[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>
This commit is contained in:
pqhuy1987
2026-05-13 20:03:28 +07:00
parent eea86fdfe7
commit 036694638e
11 changed files with 4361 additions and 177 deletions

View File

@ -34,45 +34,10 @@ public class ApprovalWorkflow : BaseEntity
// khi tạo version mới (mirror IsActive default), admin có thể unstick.
public bool IsUserSelectable { get; set; }
// ===== Mig 28 (Session 21 turn 4) — 6 advanced options per workflow =====
// Cấu hình "Cấu hình nâng cao" trong Admin Designer. User eOffice render
// dropdown/checkbox theo flag enabled. 4 flag Return* = mode Trả lại (F1).
// 1 flag Skip = Drafter trình thẳng Cấp cuối (F2). 1 flag EditDetails =
// Approver chỉnh Section 2 (F3).
//
// Default backward compat S17: AllowReturnToDrafter=true (mọi workflow cũ
// chạy đúng — fallback "Trả về Drafter" như Session 17 spec). 5 flag còn
// lại default false — admin opt-in per workflow để audit nghiêm.
/// F1 mode 1 — Cho phép Approver Trả lại 1 Cấp trước (lùi pointer trong
/// cùng Step). Phiếu GIỮ Phase=ChoDuyet (peer review chain).
public bool AllowReturnOneLevel { get; set; }
/// F1 mode 2 — Cho phép Approver Trả lại 1 Bước trước (lùi sang Step trước,
/// set level = max của step đó). Phiếu GIỮ Phase=ChoDuyet.
public bool AllowReturnOneStep { get; set; }
/// F1 mode 3 — Cho phép Approver Trả lại Người chỉ định (pick runtime từ
/// list NV ĐÃ DUYỆT trong PeLevelOpinions). Phiếu GIỮ Phase=ChoDuyet, set
/// Step/Level = vị trí của user pick trong workflow.
public bool AllowReturnToAssignee { get; set; }
/// F1 mode 4 — Cho phép Approver Trả lại Người soạn thảo (Drafter). Phiếu
/// đi vào Phase=TraLai, clear pointer (như Session 17 spec). Default TRUE
/// để backward compat — admin có thể unstick force peer review only.
public bool AllowReturnToDrafter { get; set; } = true;
/// F2 — Cho phép Drafter gửi thẳng Cấp cuối (skip mọi Bước/Cấp trung gian).
/// UI eOffice trình duyệt thêm dropdown 2 option ("Gửi tuần tự" default vs
/// "Gửi thẳng Cấp cuối"). BE set CurrentWorkflowStepIndex=maxStep,
/// CurrentApprovalLevelOrder=maxLevel. Audit changelog "Drafter skip C1..N".
public bool AllowDrafterSkipToFinal { get; set; }
/// F3 — Cho phép Approver chỉnh sửa Section 2 (Hạng mục + NCC + Báo giá)
/// khi phase=ChoDuyet + actor match CurrentLevel.ApproverUserId. KHÔNG đụng
/// PE Header (TenGoiThau/Project/Budget). KHÔNG reset workflow. Audit ghi
/// PurchaseEvaluationChangelog cho mỗi field/row thay đổi.
public bool AllowApproverEditDetails { get; set; }
// Mig 28 cũ 6 column workflow-level Allow* đã DROP trong Mig 29 (S21 t5).
// Refactor sang per-NV (per ApprovalWorkflowLevel slot + Users F2). Backfill
// bulk SQL copy workflow → all Levels của workflow trước khi DROP — preserve
// admin config từ S21 t4.
public List<ApprovalWorkflowStep> Steps { get; set; } = new();
}
@ -105,5 +70,33 @@ public class ApprovalWorkflowLevel : BaseEntity
public string? Name { get; set; } // "Cấp 1" — display optional
public Guid ApproverUserId { get; set; } // 1 NV cụ thể duyệt cấp này
// ===== Mig 29 (Session 21 turn 5) — 5 advanced options per slot =====
// Cấu hình quyền duyệt riêng cho TỪNG NV trong slot này. Admin Designer
// tick stick per Level row (KHÔNG còn workflow-level cũ Mig 28).
//
// F1 (4 mode Trả lại) + F3 (Edit Section 2) = quyền của Approver Level.
// F2 (Drafter skip) đã move sang Users.AllowDrafterSkipToFinal (per-Drafter
// user — không liên quan slot Approver).
//
// Backfill Mig 29: copy từ workflow-level Allow* cũ → all Levels của workflow.
// Default backward compat: AllowReturnToDrafter=true (S17 fallback). 4 flag
// còn lại default false (admin opt-in per Level).
/// F1 mode 1 — Lùi 1 Cấp trong cùng Bước. Phase giữ ChoDuyet (peer review).
public bool AllowReturnOneLevel { get; set; }
/// F1 mode 2 — Lùi sang Bước trước Cấp cuối. Phase giữ ChoDuyet.
public bool AllowReturnOneStep { get; set; }
/// F1 mode 3 — Pick runtime từ NV đã ký (PeLevelOpinions). Phase giữ ChoDuyet.
public bool AllowReturnToAssignee { get; set; }
/// F1 mode 4 — Phase=TraLai, clear pointer (S17 fallback). Default TRUE.
public bool AllowReturnToDrafter { get; set; } = true;
/// F3 — Cho phép NV này edit Section 2 (Hạng mục + NCC + Báo giá) lúc đang
/// duyệt. KHÔNG đụng PE Header, KHÔNG reset workflow.
public bool AllowApproverEditDetails { get; set; }
public ApprovalWorkflowStep? Step { get; set; }
}

View File

@ -27,4 +27,13 @@ public class User : IdentityUser<Guid>
// Mỗi inner step yêu cầu user khớp DepartmentId + PositionLevel mới duyệt
// được sub-step đó. Null cho admin/system/external user.
public PositionLevel? PositionLevel { get; set; }
// Mig 29 (Session 21 turn 5) — F2 per-Drafter: cho phép user này (khi đóng
// vai Drafter) gửi PE thẳng Cấp cuối, skip mọi Bước/Cấp trung gian. Workspace
// hiện checkbox "Gửi thẳng Cấp cuối" conditional theo flag này.
//
// Mặc định false (an toàn). Admin set ở User Management page. Backfill
// Mig 29: bulk set TRUE cho user nào từng Drafter PE link workflow có
// workflow.AllowDrafterSkipToFinal=true (preserve admin config S21 t4).
public bool AllowDrafterSkipToFinal { get; set; }
}