[CLAUDE] Workflow: wire ApproveV2 + LevelOpinions cho 4 WorkflowApps module (Phase 11 P11-A)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m6s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m6s
Wire full approval workflow V2 cho Leave/OT/Travel/Vehicle — cookie-cutter
mirror Proposal (Mig 38). Trước đây skeleton Phase 1 (Create+List), giờ
ApproveV2 advance-level + UPSERT LevelOpinion + atomic codegen.
Schema (Mig 41 WireWorkflowAppsApprovalV2 — 84→89 tables, pure additive):
- 4 bảng {Leave,Ot,Travel,Vehicle}LevelOpinions (UNIQUE composite + Cascade
parent + Restrict Level — mirror ProposalLevelOpinion)
- 1 bảng WorkflowAppCodeSequences (shared atomic MaDonTu, Prefix-keyed)
- 4 cột RejectedFromStatus (smart return tracking)
- enum ApprovalWorkflowApplicableType.TravelRequest = 9
Application (LeaveOt + TravelVehicle ApprovalFeatures.cs — 30 handler):
- GetById detail (Include LevelOpinions + JOIN Step/Level) · UpdateDraft
- Submit (gen MaDonTu + DaGuiDuyet + level=1, verify ApplicableType per module)
- Approve (verify actor==ApproverUserId OR Admin, UPSERT opinion latest-write-wins,
advance level OR terminal DaDuyet, empty comment → placeholder)
- Reject (→TuChoi) · Return (→TraLai + RejectedFromStatus)
Api: 4 controller +6 route mỗi cái (GET/{id}, PUT/{id}, submit/approve/reject/return)
Infra: DbInitializer seed 4 workflow V2 mẫu (QT-NP/OT/CT/XE-V2-001) → UAT test ngay
FE: WorkflowAppDetailPage.tsx declarative 4-kind (fe-admin+fe-user SHA256 identical)
— workflow status + opinion timeline + action buttons; gỡ banner skeleton + row nav
Tests: +11 WorkflowAppApproveV2Tests (130→141 PASS) — state machine + UPSERT
invariant + guards + codegen + forbidden + placeholder (Leave full + Ot smoke)
Verify: build 0 error · 141 test PASS · FE build ×2 · reviewer checklist
(ApplicableType per-module + cross-module DbSet + [Authorize] — no copy-paste bug)
Known-minor (unreachable): Reject/Return actor-check skip nếu CurrentApprovalLevelOrder
null — nhưng DaGuiDuyet luôn có set (defer hardening).
ItTicket KHÔNG đụng (kanban, no workflow V2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -55,6 +55,7 @@ public enum ApprovalWorkflowApplicableType
|
||||
OtRequest = 6, // G-O4 — Đơn OT
|
||||
VehicleBooking = 7, // G-O5 — Đặt xe công
|
||||
ItTicket = 8, // G-O6 — Ticket CNTT
|
||||
TravelRequest = 9, // G-O4 — Đơn công tác (Travel) — Phase 11 P11-A
|
||||
}
|
||||
|
||||
// Bước = Phòng. 1 quy trình có nhiều bước theo Order.
|
||||
|
||||
@ -19,4 +19,7 @@ public class LeaveRequest : AuditableEntity
|
||||
public WorkflowAppStatus Status { get; set; } = WorkflowAppStatus.Nhap;
|
||||
public Guid? ApprovalWorkflowId { get; set; } // pin ApplicableType=5
|
||||
public int? CurrentApprovalLevelOrder { get; set; }
|
||||
|
||||
public WorkflowAppStatus? RejectedFromStatus { get; set; } // smart return tracking (mirror Proposal)
|
||||
public List<LeaveRequestLevelOpinion> LevelOpinions { get; set; } = new();
|
||||
}
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
using SolutionErp.Domain.ApprovalWorkflowsV2;
|
||||
using SolutionErp.Domain.Common;
|
||||
|
||||
namespace SolutionErp.Domain.Office;
|
||||
|
||||
// Phase 11 P11-A (Mig 41) — Ý kiến cấp duyệt V2 dynamic cho LeaveRequest.
|
||||
// Cookie-cutter mirror ProposalLevelOpinion (Mig 38).
|
||||
//
|
||||
// Mỗi row = 1 (LeaveRequest × ApprovalWorkflowLevel). Service ApproveV2Async sau
|
||||
// khi approve thành công Cấp hiện tại sẽ UPSERT row này (latest-write-wins).
|
||||
// Reject (TraLai/TuChoi) KHÔNG sync.
|
||||
//
|
||||
// UNIQUE composite (LeaveRequestId, ApprovalWorkflowLevelId) — 1 row / level / đơn.
|
||||
// FK Cascade LeaveRequest (wipe khi xoá) + Restrict Level (admin xoá Level chặn).
|
||||
// SignedByUserId track actor thật (có thể Admin override) + denorm FullName.
|
||||
public class LeaveRequestLevelOpinion : AuditableEntity
|
||||
{
|
||||
public Guid LeaveRequestId { get; set; }
|
||||
public Guid ApprovalWorkflowLevelId { get; set; }
|
||||
|
||||
public string? Comment { get; set; } // max 2000 hoặc placeholder
|
||||
public DateTime SignedAt { get; set; }
|
||||
public Guid SignedByUserId { get; set; } // người ký thực sự (có thể Admin thay)
|
||||
public string SignedByFullName { get; set; } = string.Empty; // snapshot denorm
|
||||
|
||||
public LeaveRequest? LeaveRequest { get; set; }
|
||||
public ApprovalWorkflowLevel? Level { get; set; }
|
||||
}
|
||||
@ -19,4 +19,7 @@ public class OtRequest : AuditableEntity
|
||||
public WorkflowAppStatus Status { get; set; } = WorkflowAppStatus.Nhap;
|
||||
public Guid? ApprovalWorkflowId { get; set; }
|
||||
public int? CurrentApprovalLevelOrder { get; set; }
|
||||
|
||||
public WorkflowAppStatus? RejectedFromStatus { get; set; } // smart return tracking (mirror Proposal)
|
||||
public List<OtRequestLevelOpinion> LevelOpinions { get; set; } = new();
|
||||
}
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
using SolutionErp.Domain.ApprovalWorkflowsV2;
|
||||
using SolutionErp.Domain.Common;
|
||||
|
||||
namespace SolutionErp.Domain.Office;
|
||||
|
||||
// Phase 11 P11-A (Mig 41) — Ý kiến cấp duyệt V2 dynamic cho OtRequest.
|
||||
// Cookie-cutter mirror ProposalLevelOpinion (Mig 38).
|
||||
//
|
||||
// Mỗi row = 1 (OtRequest × ApprovalWorkflowLevel). Service ApproveV2Async sau
|
||||
// khi approve thành công Cấp hiện tại sẽ UPSERT row này (latest-write-wins).
|
||||
// Reject (TraLai/TuChoi) KHÔNG sync.
|
||||
//
|
||||
// UNIQUE composite (OtRequestId, ApprovalWorkflowLevelId) — 1 row / level / đơn.
|
||||
// FK Cascade OtRequest (wipe khi xoá) + Restrict Level (admin xoá Level chặn).
|
||||
// SignedByUserId track actor thật (có thể Admin override) + denorm FullName.
|
||||
public class OtRequestLevelOpinion : AuditableEntity
|
||||
{
|
||||
public Guid OtRequestId { get; set; }
|
||||
public Guid ApprovalWorkflowLevelId { get; set; }
|
||||
|
||||
public string? Comment { get; set; } // max 2000 hoặc placeholder
|
||||
public DateTime SignedAt { get; set; }
|
||||
public Guid SignedByUserId { get; set; } // người ký thực sự (có thể Admin thay)
|
||||
public string SignedByFullName { get; set; } = string.Empty; // snapshot denorm
|
||||
|
||||
public OtRequest? OtRequest { get; set; }
|
||||
public ApprovalWorkflowLevel? Level { get; set; }
|
||||
}
|
||||
@ -18,4 +18,7 @@ public class TravelRequest : AuditableEntity
|
||||
public WorkflowAppStatus Status { get; set; } = WorkflowAppStatus.Nhap;
|
||||
public Guid? ApprovalWorkflowId { get; set; }
|
||||
public int? CurrentApprovalLevelOrder { get; set; }
|
||||
|
||||
public WorkflowAppStatus? RejectedFromStatus { get; set; } // smart return tracking (mirror Proposal)
|
||||
public List<TravelRequestLevelOpinion> LevelOpinions { get; set; } = new();
|
||||
}
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
using SolutionErp.Domain.ApprovalWorkflowsV2;
|
||||
using SolutionErp.Domain.Common;
|
||||
|
||||
namespace SolutionErp.Domain.Office;
|
||||
|
||||
// Phase 11 P11-A (Mig 41) — Ý kiến cấp duyệt V2 dynamic cho TravelRequest.
|
||||
// Cookie-cutter mirror ProposalLevelOpinion (Mig 38).
|
||||
//
|
||||
// Mỗi row = 1 (TravelRequest × ApprovalWorkflowLevel). Service ApproveV2Async sau
|
||||
// khi approve thành công Cấp hiện tại sẽ UPSERT row này (latest-write-wins).
|
||||
// Reject (TraLai/TuChoi) KHÔNG sync.
|
||||
//
|
||||
// UNIQUE composite (TravelRequestId, ApprovalWorkflowLevelId) — 1 row / level / đơn.
|
||||
// FK Cascade TravelRequest (wipe khi xoá) + Restrict Level (admin xoá Level chặn).
|
||||
// SignedByUserId track actor thật (có thể Admin override) + denorm FullName.
|
||||
public class TravelRequestLevelOpinion : AuditableEntity
|
||||
{
|
||||
public Guid TravelRequestId { get; set; }
|
||||
public Guid ApprovalWorkflowLevelId { get; set; }
|
||||
|
||||
public string? Comment { get; set; } // max 2000 hoặc placeholder
|
||||
public DateTime SignedAt { get; set; }
|
||||
public Guid SignedByUserId { get; set; } // người ký thực sự (có thể Admin thay)
|
||||
public string SignedByFullName { get; set; } = string.Empty; // snapshot denorm
|
||||
|
||||
public TravelRequest? TravelRequest { get; set; }
|
||||
public ApprovalWorkflowLevel? Level { get; set; }
|
||||
}
|
||||
@ -20,4 +20,7 @@ public class VehicleBooking : AuditableEntity
|
||||
public WorkflowAppStatus Status { get; set; } = WorkflowAppStatus.Nhap;
|
||||
public Guid? ApprovalWorkflowId { get; set; }
|
||||
public int? CurrentApprovalLevelOrder { get; set; }
|
||||
|
||||
public WorkflowAppStatus? RejectedFromStatus { get; set; } // smart return tracking (mirror Proposal)
|
||||
public List<VehicleBookingLevelOpinion> LevelOpinions { get; set; } = new();
|
||||
}
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
using SolutionErp.Domain.ApprovalWorkflowsV2;
|
||||
using SolutionErp.Domain.Common;
|
||||
|
||||
namespace SolutionErp.Domain.Office;
|
||||
|
||||
// Phase 11 P11-A (Mig 41) — Ý kiến cấp duyệt V2 dynamic cho VehicleBooking.
|
||||
// Cookie-cutter mirror ProposalLevelOpinion (Mig 38).
|
||||
//
|
||||
// Mỗi row = 1 (VehicleBooking × ApprovalWorkflowLevel). Service ApproveV2Async sau
|
||||
// khi approve thành công Cấp hiện tại sẽ UPSERT row này (latest-write-wins).
|
||||
// Reject (TraLai/TuChoi) KHÔNG sync.
|
||||
//
|
||||
// UNIQUE composite (VehicleBookingId, ApprovalWorkflowLevelId) — 1 row / level / đơn.
|
||||
// FK Cascade VehicleBooking (wipe khi xoá) + Restrict Level (admin xoá Level chặn).
|
||||
// SignedByUserId track actor thật (có thể Admin override) + denorm FullName.
|
||||
public class VehicleBookingLevelOpinion : AuditableEntity
|
||||
{
|
||||
public Guid VehicleBookingId { get; set; }
|
||||
public Guid ApprovalWorkflowLevelId { get; set; }
|
||||
|
||||
public string? Comment { get; set; } // max 2000 hoặc placeholder
|
||||
public DateTime SignedAt { get; set; }
|
||||
public Guid SignedByUserId { get; set; } // người ký thực sự (có thể Admin thay)
|
||||
public string SignedByFullName { get; set; } = string.Empty; // snapshot denorm
|
||||
|
||||
public VehicleBooking? VehicleBooking { get; set; }
|
||||
public ApprovalWorkflowLevel? Level { get; set; }
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
namespace SolutionErp.Domain.Office;
|
||||
|
||||
// Phase 11 P11-A (Mig 41) — Sequence generator dùng chung cho mã đơn từ (MaDonTu)
|
||||
// của 4 WorkflowApps module (Leave / OT / Travel / VehicleBooking).
|
||||
// Mirror ProposalCodeSequence pattern (Prefix string PK + LastSeq atomic).
|
||||
//
|
||||
// Prefix-keyed per module per năm, vd:
|
||||
// "DT/LR/2026" (Leave) → "DT/LR/2026/001" → "DT/LR/2026/002" → ...
|
||||
// "DT/OT/2026" (OT)
|
||||
// "DT/CT/2026" (Travel — Công tác)
|
||||
// "DX/XE/2026" (VehicleBooking — Đặt xe)
|
||||
// LastSeq reset đầu năm tự nhiên (key Prefix mới). Update atomic qua
|
||||
// SERIALIZABLE transaction trong CodeGen service.
|
||||
public class WorkflowAppCodeSequence
|
||||
{
|
||||
public string Prefix { get; set; } = string.Empty; // PK — "DT/LR/2026"
|
||||
public int LastSeq { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user