[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
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.
This commit is contained in:
@ -244,7 +244,7 @@ export function PeListPanel({
|
||||
// hết, user vẫn thấy được phiếu Đã gửi duyệt cùng với tất cả khác. Trade-off
|
||||
// chấp nhận tới khi BE thêm multi-phase param.
|
||||
function statusToPhaseValue(status: PeDisplayStatus): string {
|
||||
if (status === PeDisplayStatus.BanNhap) return String(PurchaseEvaluationPhase.DangSoanThao)
|
||||
if (status === PeDisplayStatus.Nhap) return String(PurchaseEvaluationPhase.DangSoanThao)
|
||||
if (status === PeDisplayStatus.DaDuyet) return String(PurchaseEvaluationPhase.DaDuyet)
|
||||
if (status === PeDisplayStatus.TuChoi) return String(PurchaseEvaluationPhase.TuChoi)
|
||||
return '' // DaGuiDuyet — multi-phase, không filter exact (TODO BE add support)
|
||||
|
||||
@ -122,10 +122,12 @@ export function PurchaseEvaluationsListPage() {
|
||||
<Select value={phase} onChange={e => setParam('phase', e.target.value)}>
|
||||
<option value="">Tất cả trạng thái</option>
|
||||
{Object.values(PeDisplayStatus).map(s => {
|
||||
const phaseValue = s === PeDisplayStatus.BanNhap
|
||||
const phaseValue = s === PeDisplayStatus.Nhap
|
||||
? String(PurchaseEvaluationPhase.DangSoanThao)
|
||||
: s === PeDisplayStatus.DaDuyet
|
||||
? String(PurchaseEvaluationPhase.DaDuyet)
|
||||
: s === PeDisplayStatus.TraLai
|
||||
? String(PurchaseEvaluationPhase.TraLai)
|
||||
: s === PeDisplayStatus.TuChoi
|
||||
? String(PurchaseEvaluationPhase.TuChoi)
|
||||
: '' // DaGuiDuyet — multi-phase, không filter exact (TODO BE)
|
||||
|
||||
@ -7,15 +7,17 @@ export const BudgetPhase = {
|
||||
ChoCCM: 2,
|
||||
ChoCEO: 3,
|
||||
DaDuyet: 4,
|
||||
TraLai: 98,
|
||||
TuChoi: 99,
|
||||
} as const
|
||||
export type BudgetPhase = typeof BudgetPhase[keyof typeof BudgetPhase]
|
||||
|
||||
export const BudgetPhaseLabel: Record<number, string> = {
|
||||
1: 'Đang soạn thảo',
|
||||
1: 'Nháp',
|
||||
2: 'Chờ CCM',
|
||||
3: 'Chờ CEO',
|
||||
4: 'Đã duyệt',
|
||||
98: 'Trả lại',
|
||||
99: 'Từ chối',
|
||||
}
|
||||
|
||||
@ -24,6 +26,7 @@ export const BudgetPhaseColor: Record<number, string> = {
|
||||
2: 'bg-indigo-100 text-indigo-700',
|
||||
3: 'bg-pink-100 text-pink-700',
|
||||
4: 'bg-emerald-100 text-emerald-700',
|
||||
98: 'bg-yellow-100 text-yellow-800',
|
||||
99: 'bg-red-100 text-red-700',
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,8 @@ export const ContractPhase = {
|
||||
DangTrinhKy: 7,
|
||||
DangDongDau: 8,
|
||||
DaPhatHanh: 9,
|
||||
ChoDuyet: 10,
|
||||
TraLai: 98,
|
||||
TuChoi: 99,
|
||||
} as const
|
||||
|
||||
@ -15,7 +17,7 @@ export type ContractPhase = typeof ContractPhase[keyof typeof ContractPhase]
|
||||
|
||||
export const ContractPhaseLabel: Record<number, string> = {
|
||||
1: 'Đang chọn NCC',
|
||||
2: 'Đang soạn thảo',
|
||||
2: 'Nháp',
|
||||
3: 'Đang góp ý',
|
||||
4: 'Đang đàm phán',
|
||||
5: 'Đang in ký',
|
||||
@ -23,12 +25,14 @@ export const ContractPhaseLabel: Record<number, string> = {
|
||||
7: 'Đang trình ký',
|
||||
8: 'Đang đóng dấu',
|
||||
9: 'Đã phát hành',
|
||||
10: 'Đã gửi duyệt',
|
||||
98: 'Trả lại',
|
||||
99: 'Từ chối',
|
||||
}
|
||||
|
||||
export const ContractPhaseColor: Record<number, string> = {
|
||||
1: 'bg-slate-100 text-slate-700',
|
||||
2: 'bg-blue-100 text-blue-700',
|
||||
2: 'bg-slate-100 text-slate-700',
|
||||
3: 'bg-amber-100 text-amber-700',
|
||||
4: 'bg-orange-100 text-orange-700',
|
||||
5: 'bg-purple-100 text-purple-700',
|
||||
@ -36,6 +40,8 @@ export const ContractPhaseColor: Record<number, string> = {
|
||||
7: 'bg-fuchsia-100 text-fuchsia-700',
|
||||
8: 'bg-pink-100 text-pink-700',
|
||||
9: 'bg-emerald-100 text-emerald-700',
|
||||
10: 'bg-amber-100 text-amber-700',
|
||||
98: 'bg-yellow-100 text-yellow-800',
|
||||
99: 'bg-red-100 text-red-700',
|
||||
}
|
||||
|
||||
|
||||
@ -27,20 +27,20 @@ export const PurchaseEvaluationPhase = {
|
||||
ChoCEODuyetNCC: 6, // [LEGACY]
|
||||
DaDuyet: 7,
|
||||
ChoDuyet: 10, // [Mig 21] generic intermediate
|
||||
TraLai: 98, // [LEGACY]
|
||||
TraLai: 98, // Phase riêng — Drafter sửa+gửi lại chạy từ đầu
|
||||
TuChoi: 99,
|
||||
} as const
|
||||
export type PurchaseEvaluationPhase = typeof PurchaseEvaluationPhase[keyof typeof PurchaseEvaluationPhase]
|
||||
|
||||
export const PurchaseEvaluationPhaseLabel: Record<number, string> = {
|
||||
1: 'Đang soạn thảo',
|
||||
1: 'Nháp',
|
||||
2: 'Chờ Purchasing',
|
||||
3: 'Chờ Dự án',
|
||||
4: 'Chờ CCM',
|
||||
5: 'Chờ CEO duyệt PA',
|
||||
6: 'Chờ CEO duyệt NCC',
|
||||
7: 'Đã duyệt',
|
||||
10: 'Đang duyệt',
|
||||
10: 'Đã gửi duyệt',
|
||||
98: 'Trả lại',
|
||||
99: 'Từ chối',
|
||||
}
|
||||
@ -65,15 +65,14 @@ export function isEditablePhase(phase: number): boolean {
|
||||
|| phase === PurchaseEvaluationPhase.TraLai
|
||||
}
|
||||
|
||||
// Display status meta — gom các phase chi tiết thành 4 nhóm hiển thị end-user
|
||||
// friendly. Workflow timeline + workflow service vẫn dùng phase chi tiết.
|
||||
// User 2026-05-07 chỉnh:
|
||||
// - Bản nháp = DangSoanThao (chỉ hiện ở Thao tác workspace, ko Duyệt menu)
|
||||
// - Đã gửi duyệt = bất kỳ phase trung gian (ChoPurchasing/ChoDuAn/ChoCCM/...)
|
||||
// - Đã duyệt = DaDuyet
|
||||
// - Từ chối = TuChoi
|
||||
// Display status meta — 5 trạng thái spec Session 17:
|
||||
// Nháp = DangSoanThao (chưa vào quy trình duyệt)
|
||||
// Đã gửi duyệt = ChoDuyet/legacy intermediate (đang chạy quy trình)
|
||||
// Trả lại = TraLai (có history đã đi qua quy trình, sửa+gửi lại chạy từ đầu)
|
||||
// Đã duyệt = DaDuyet (terminal OK — input cho phiếu khác / in trình ký)
|
||||
// Từ chối = TuChoi (terminal lock — không thao tác gì được nữa)
|
||||
export const PeDisplayStatus = {
|
||||
BanNhap: 'BanNhap',
|
||||
Nhap: 'Nhap',
|
||||
DaGuiDuyet: 'DaGuiDuyet',
|
||||
TraLai: 'TraLai',
|
||||
DaDuyet: 'DaDuyet',
|
||||
@ -82,7 +81,7 @@ export const PeDisplayStatus = {
|
||||
export type PeDisplayStatus = typeof PeDisplayStatus[keyof typeof PeDisplayStatus]
|
||||
|
||||
export const PeDisplayStatusLabel: Record<PeDisplayStatus, string> = {
|
||||
BanNhap: 'Bản nháp',
|
||||
Nhap: 'Nháp',
|
||||
DaGuiDuyet: 'Đã gửi duyệt',
|
||||
TraLai: 'Trả lại',
|
||||
DaDuyet: 'Đã duyệt',
|
||||
@ -90,7 +89,7 @@ export const PeDisplayStatusLabel: Record<PeDisplayStatus, string> = {
|
||||
}
|
||||
|
||||
export const PeDisplayStatusColor: Record<PeDisplayStatus, string> = {
|
||||
BanNhap: 'bg-slate-100 text-slate-700',
|
||||
Nhap: 'bg-slate-100 text-slate-700',
|
||||
DaGuiDuyet: 'bg-amber-100 text-amber-700',
|
||||
TraLai: 'bg-yellow-100 text-yellow-800',
|
||||
DaDuyet: 'bg-emerald-100 text-emerald-700',
|
||||
@ -98,11 +97,11 @@ export const PeDisplayStatusColor: Record<PeDisplayStatus, string> = {
|
||||
}
|
||||
|
||||
export function getPeDisplayStatus(phase: number): PeDisplayStatus {
|
||||
if (phase === PurchaseEvaluationPhase.DangSoanThao) return PeDisplayStatus.BanNhap
|
||||
if (phase === PurchaseEvaluationPhase.DangSoanThao) return PeDisplayStatus.Nhap
|
||||
if (phase === PurchaseEvaluationPhase.DaDuyet) return PeDisplayStatus.DaDuyet
|
||||
if (phase === PurchaseEvaluationPhase.TraLai) return PeDisplayStatus.TraLai
|
||||
if (phase === PurchaseEvaluationPhase.TuChoi) return PeDisplayStatus.TuChoi
|
||||
// Mig 21 ChoDuyet=10 + legacy intermediate 2-6 → all map "Đã gửi duyệt"
|
||||
// ChoDuyet=10 + legacy intermediate 2-6 → all map "Đã gửi duyệt"
|
||||
return PeDisplayStatus.DaGuiDuyet
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user