[CLAUDE] Contract: K3 SP-002 8 workflow nhom KHKK-N1-8 + port level-finalize + designer (0-mig) + 5 test
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 6m10s

Seed 8 workflow type-10 theo ROSTER LIVE prod (QT-DN-V2-001, lead verify tay
@SQLEXPRESS: 3 tram PRO->CCM->CEO, 11 nguoi, resolve EMAIL, idempotent per-Code;
co Finalize CHI cap chuong.phan). Port nhanh level-finalize mirror PE :866-876
(EndedByLevelFinalize K2-de-K3-tieu + ApplyApprovedValuesOnFinalize truoc Phase;
opt-out applyLevelFinalize 3-tang interface/service/controller, default true).
Sweep ep-false va-3: GO CREATE ep-false Finalize type-10 (OG-9 supersede PHA-VO
S155); GIU PUT-Conflict (luat versioning CHUNG - spec liet nham, khai lech);
GIU ep-false SkipToFinal type-10 (K3-test O-1 chong config-lie mới) + FE disable
o SkipToFinal cho KHKK (gate-K3 F-2). Rao (v) pin-dung-nhom regex KHKK-N[1-8].
+8 MenuKeys AwV2_KhkkN* (All 56->64, policies 224->256 may-dem) + 8 menu-row +
resolvePath fe-admin phep-am Khkk_ 0-hit. 5 test K3 (T-a..T-e, anti-tautology).
Gate K3 PASS-WITH-FLAGS 8f/0-blocker (sub-reviewer-gate-k3.md): F-1 checkbox
opt-out man duyet = KHAI HANDOFF dau-viec-#1 lan sau (tram CEO tam thoi qua
Designer bo co); F-2 va; F-3 dong truoc do. Suite 614/0 (45D+569I). K3-BE 2 luot
engine-exit -> che nho + lead-solo (bai: task-hep-Write-som song).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-31 18:21:39 +07:00
parent d6cb5469b3
commit 15349e8ee0
15 changed files with 766 additions and 83 deletions

View File

@ -34,16 +34,17 @@ namespace SolutionErp.Infrastructure.Services;
//
// 3 ĐIỂM ADAPT:
// (a) Finalize choke-point: helper `ApplyApprovedValuesOnFinalize` gọi ở MỌI nhánh set
// `DaDuyet` — xem khối LUẬT ngay trên helper. 🔴 KHAI RÕ: service này có **ĐÚNG MỘT**
// nhánh set DaDuyet, KHÔNG có site thứ hai kiểu admin-override của nguồn copy
// (`ContractWorkflowService.cs:190-193` gán `contract.Phase = targetPhase` qua BIẾN).
// Lý do: hợp-đồng transition KHKK chỉ nhận 4 action cố định, KHÔNG có tham số
// `targetPhase` ⇒ Admin muốn kết thúc phiếu vẫn phải đi đúng đường `approve`
// (Admin chỉ được miễn vế "có tên trong Cấp", không được miễn choke-point).
// Đó chính là write-path 2 mà test T3 đo.
// (b) 🔴 KHÔNG PORT 2 nhánh kết thúc sớm (`AllowApproverFinalize`, `CeoApprovalThreshold`)
// và cũng KHÔNG port `AllowApproverSkipToFinal` (`:318-352` nguồn copy) — chủ dự án
// phán "PHÁ VỠ" @S155. W3 chỉ đường THƯỜNG: PMH → CCM → CEO ký thật.
// `DaDuyet` — xem khối LUẬT ngay trên helper. 🔄 [K3 S165] service NAY có **HAI** nhánh
// set DaDuyet (đường-thường Cấp-cuối + level-finalize dưới đây), CẢ HAI qua choke-point;
// vẫn KHÔNG có site gán-qua-BIẾN kiểu nguồn copy (`ContractWorkflowService.cs:190-193`):
// hợp-đồng transition KHKK chỉ nhận 4 action cố định, KHÔNG có tham số `targetPhase`
// ⇒ Admin kết thúc phiếu vẫn đi đúng đường `approve` (miễn vế "có tên trong Cấp",
// KHÔNG min choke-point). Tự-kiểm 2 lệnh grep ở khối LUẬT phải cùng khớp.
// (b) 🔄 [K3 S165 — OG-9 SUPERSEDE "PHÁ VỠ" @S155] Owner @S164 đảo phán quyết (verbatim:
// "chỗ budget chốt chặn là a Chương… bài toán trần budget xem như đóng") ⇒ ĐÃ PORT
// nhánh `AllowApproverFinalize` (level-finalize, OPT-OUT khuôn PE S96 — cờ seed cho
// TP.CCM Phan Văn Chương @K3). VẪN KHÔNG port `CeoApprovalThreshold` (ngưỡng máy —
// NĐUQ = human-gate vĩnh viễn, OG-3) và `AllowApproverSkipToFinal` giữ nguyên không port.
// (c) Trả lại = `TraLai` TOÀN PHẦN (về người soạn, xoá con-trỏ; gửi lại chạy LẠI từ Bước 1
// Cấp 1). 4 return-mode per-level (`ApprovalWorkflow.cs:116-129`) CỐ Ý chưa đọc —
// mở thêm mode là đổi hành vi duyệt, thuộc quyền lead.
@ -87,6 +88,7 @@ public class ContractSigningPlanWorkflowService(
Guid planId,
string action,
string? comment,
bool applyLevelFinalize = true,
CancellationToken ct = default)
{
// Rào action đứng TRƯỚC mọi truy vấn: gõ sai tên hành động là lỗi dữ liệu gửi lên,
@ -121,7 +123,7 @@ public class ContractSigningPlanWorkflowService(
break;
case "approve":
await ApproveV2Async(plan, actorId, isAdmin, normalizedComment, ct);
await ApproveV2Async(plan, actorId, isAdmin, normalizedComment, applyLevelFinalize, ct);
break;
case "return":
@ -242,7 +244,8 @@ public class ContractSigningPlanWorkflowService(
// approve — approver Cấp đang chờ duyệt (OR-of-N)
// =====================================================================
private async Task ApproveV2Async(
ContractSigningPlan plan, Guid actorId, bool isAdmin, string? comment, CancellationToken ct)
ContractSigningPlan plan, Guid actorId, bool isAdmin, string? comment,
bool applyLevelFinalize, CancellationToken ct)
{
if (plan.Phase != ContractSigningPlanPhase.ChoDuyet)
throw new ConflictException("Chỉ duyệt được phiếu đang ở trạng thái Đã gửi duyệt.");
@ -264,6 +267,27 @@ public class ContractSigningPlanWorkflowService(
var fromPhase = plan.Phase;
// ---- [K3 S165 — OG-3/OG-9 port level-finalize, mirror PE `:866-876`] Cấp có cờ
// `AllowApproverFinalize` (seed K3: TP.CCM Phan Văn Chương, Bước 2 Cấp 2) được KẾT THÚC
// quy trình TẠI ĐÂY thay vì trình tiếp CEO. OPT-OUT khuôn PE S96: client gửi
// `applyLevelFinalize=false` (người duyệt BỎ tick "Cấp này KẾT THÚC") ⇒ rơi xuống
// advance thường. Supersede "PHÁ VỠ" @S155 theo lời owner @S164 (xem 3-ĐIỂM-ADAPT (b)).
// Cờ RUNTIME `EndedByLevelFinalize` = Mig 71 K2 đẻ — K3 tiêu thụ, HẾT ghost-window.
if (actingLevel.AllowApproverFinalize && applyLevelFinalize)
{
plan.EndedByLevelFinalize = true;
ApplyApprovedValuesOnFinalize(plan); // LUẬT choke-point: TRƯỚC khi gán Phase
plan.Phase = ContractSigningPlanPhase.DaDuyet;
plan.CurrentWorkflowStepIndex = null;
plan.CurrentApprovalLevelOrder = null;
await LogSigningPlanTransitionAsync(
plan, fromPhase, plan.Phase, actorId, ApprovalDecision.Approve, comment,
actingLevel.Id,
$"Duyệt Bước {currentIdx + 1} Cấp {currentLevelOrder} — KẾT THÚC TẠI CẤP (level-finalize), kế hoạch ĐÃ DUYỆT, chốt giá",
ct);
return;
}
// ---- Advance con-trỏ ĐÔI: còn Cấp trong Bước → Cấp++; hết Cấp → Bước kế (Cấp 1);
// hết Bước → FINALIZE.
if (currentLevelOrder < maxLevelOrder)