All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m33s
- Session log 2026-04-22-0300 (A→K): attachment, SignalR, form builder, PDF, dynamic + versioned workflow, nested menu, 3-panel permissions, seed master, brand identity, content polish, Gitea fix - STATUS: Tier 3 feature-complete snapshot + cumulative stats (24 tables, ~50 endpoints, 8 migrations); next-up = UAT + Email SMTP (blocked) + rotate creds + SQL backup schedule - HANDOFF: rewrite brief cho session mới — phase 5 prod done, Tier 3 đóng gói, quick sanity-check 2 app, versioned workflow quick ref, file active hiện trạng, git state - migration-todos: tick Tier 3 items (attachment/realtime/form builder/ PDF/dynamic+versioned workflow/nested menu) + thêm iter-3 versioned workflow section + post-launch list - schema-diagram: +5 table (Notifications, WorkflowTypeAssignments, WorkflowDefinitions, WorkflowSteps, WorkflowStepApprovers); indexes mới, cardinality FK restrict cho pinned policy, truy vấn tiêu biểu - workflow-contract: +section 7bis resolution order, 7ter admin designer flow, updated data model + code pointers Tier 3 - PROJECT-MAP: module map post-Tier-3 (3 box mới Notification/ Attachment/Branding + Infra/DevOps box), API namespace đầy đủ, architectural wins 5 điểm - contract-workflow skill: versioned workflow section, policy resolution code snippet, admin designer flow, code pointers Tier 3, tier 4+ backlog - gotchas +7 bẫy mới (#26-32): SignalR WebSocket headers, interceptor 2-phase pattern, LibreOffice mirror 404, PS 5.1 UTF-16 GITHUB_PATH, PS 5.1 diacritics parse, Dialog size TS, NavLink end query-params Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
13 KiB
13 KiB
Workflow — Quy trình trình ký Hợp đồng TP/NCC/Tổ đội
Nguồn:
QUY_TRINH/QT TRINH KY HOP DONG TP-NCC.docxRaw dump:workflow-raw.mdPhase 3 deliverable: Implement state machine + role guard + SLA timer + notification
1. Phạm vi
Áp dụng cho Hợp đồng/Phụ lục HĐ ký với: Thầu phụ (NTP) · Nhà cung cấp (NCC) · Tổ đội (TĐ). Tham chiếu ISO 9001.
2. Glossary (viết tắt)
| Mã | Nghĩa | Role trong system |
|---|---|---|
| BOD | Ban Giám đốc | Director |
| NĐUQ | Người được ủy quyền | AuthorizedSigner |
| CCM | Phòng Kiểm soát Chi phí | CostControl |
| PRO | Phòng Cung ứng | Procurement |
| FIN | Phòng Tài chính | Finance |
| ACT | Phòng Kế toán | Accounting |
| EQU | Phòng Thiết bị | Equipment |
| HRA | Nhân sự - Hành chính (đóng dấu) | HR_Admin |
| PB | Phòng ban | Department |
| BCH CT | Ban chỉ huy công trường | SiteCommand |
| PD | Giám đốc Thi công | ProjectDirector |
| PM | Giám đốc Dự án | ProjectManager |
| TPB | Trưởng Phòng ban | DeptManager |
| TBP | Trưởng Bộ phận | SectionLeader |
| QS/NV.PB | QS công trường / Nhân viên PB | Drafter (người soạn) |
| NTP/NCC/TĐ | Đối tác ký HĐ | Partner (external — không login) |
3. State Machine (9 phase)
stateDiagram-v2
[*] --> DangChon: Tạo mới
DangChon --> DangSoanThao: Chọn NCC xong (chuyển Drafter)
DangSoanThao --> DangGopY: Gửi email góp ý (7d)
DangGopY --> DangDamPhan: Nhận xong comment (7d)
DangDamPhan --> DangInKy: Thỏa thuận xong (7d)
DangInKy --> DangKiemTraCCM: Đã ký nháy QS/PD/TPB, chuyển CCM (1d)
DangKiemTraCCM --> DangTrinhKy: CCM ký nháy xong (3d)
DangTrinhKy --> DangDongDau: BOD/NĐUQ ký duyệt (1d)
DangDongDau --> DaPhatHanh: HRA đóng dấu xong
DaPhatHanh --> [*]: PB scan + gửi bản gốc + lưu Filing
DangSoanThao --> TuChoi: Cancel
DangGopY --> DangSoanThao: Revise
DangKiemTraCCM --> DangSoanThao: CCM reject
DangTrinhKy --> DangSoanThao: BOD reject
TuChoi --> [*]
4. Chi tiết từng phase
| # | Phase (state) | Role thực hiện | Input | Output | SLA | Form liên quan |
|---|---|---|---|---|---|---|
| 1 | DangChon — Lựa chọn NTP/NCC |
PB / BCH CT |
Yêu cầu công việc | Chốt đối tác | — | (theo Quy trình Cung ứng SOL-PRO-SP-001) |
| 2 | DangSoanThao — Soạn thảo HĐ |
Drafter (QS/NV.PB) + TBP/TPB check |
Template HĐ | Dự thảo HĐ | 7 ngày | FO-002.02/.03/.04/.05/.06 (chọn loại phù hợp) |
| 3 | DangGopY — Góp ý nội dung |
PD/PM/PRO/CCM/FIN/ACT/EQU |
Dự thảo | Comment (email → hệ thống) | 7 ngày | (đính kèm comment thread) |
| 4 | DangDamPhan — Đàm phán |
Drafter + TBP/TPB/PD/PM |
Comment | Bản cuối thỏa thuận | 7 ngày | — |
| 5 | DangInKy — In + đối tác ký |
Drafter + NTP/NCC/TĐ (external) + PD/PM/TPB ký nháy |
Bản cuối | 2 mặt, có chữ ký đối tác + ký nháy nội bộ | 1 ngày | FO-002.01 (cover approval) |
| 6 | DangKiemTraCCM — CCM kiểm tra |
CCM (TP.CCM ký nháy) |
HĐ đã ký nháy | HĐ CCM approved | 3 ngày | FO-002.01 |
| 7 | DangTrinhKy — BOD ký duyệt |
BOD hoặc NĐUQ |
HĐ CCM approved | HĐ ký duyệt | 1 ngày | FO-002.01 |
| 8 | DangDongDau — Đóng dấu |
HRA/ISO |
HĐ đã ký | HĐ có dấu | — | — |
| 9 | DaPhatHanh — Phát hành + lưu trữ |
PB/BCH CT + CCM |
HĐ có dấu | Scan + bản gốc gửi NCC + lưu Filing System | — | — |
Tổng SLA: ~19 ngày (phase 2-7) cho 1 HĐ hoàn chỉnh.
5. Role × Phase Matrix (quyền xem/thao tác)
Ký hiệu: R = read, W = write/update draft, A = approve (chuyển phase tiếp), - = không có quyền.
| Role \ Phase | 1.Chọn | 2.Soạn | 3.GópÝ | 4.ĐàmPhán | 5.InKý | 6.CCMCheck | 7.BODKý | 8.ĐóngDấu | 9.PhátHành |
|---|---|---|---|---|---|---|---|---|---|
| Drafter (QS/NV.PB) | R | W,A | R | W,A | W,A | R | R | R | W |
| TBP/TPB | R | R,A | R,A | R,A | R,A | R | R | R | R |
| PD/PM | R | R | W,A | R,A | R,A | R | R | R | R |
| PRO/EQU/FIN/ACT | A | R | W,A | R | - | - | - | - | R |
| CCM | - | R | W,A | R | R | W,A | R | R | W |
| BOD/NĐUQ | - | - | - | - | - | - | A | - | R |
| HRA | - | - | - | - | - | - | - | W,A | R |
| Admin (system) | R | R | R | R | R | R | R | R | R (+ override) |
Guard rule:
- Đặc biệt: HĐ với Chủ đầu tư → có thể bypass phase 3 + 6 (không cần PRO/CCM góp ý + kiểm tra). Cờ
BypassProcurementAndCCM: boolở entity HĐ. - Quá SLA mỗi phase không action → auto-approve (chuyển phase tiếp). Gửi notification warning khi còn 2h.
- Nếu cần kéo dài → bộ phận kiểm tra phải comment vào "Ý kiến" trước khi SLA hết.
6. Notification triggers
| Event | Người nhận | Kênh |
|---|---|---|
Chuyển DangSoanThao → DangGopY |
Tất cả role góp ý (PD/PM/PRO/CCM/FIN/ACT) | email + in-app |
Chuyển DangKiemTraCCM |
CCM |
email + in-app |
Chuyển DangTrinhKy |
BOD + NĐUQ |
email + in-app (high priority) |
| Quá SLA 80% thời gian | Role đang giữ phase | in-app warning |
| Quá SLA → auto-approve | Drafter + role giữ phase | email + in-app (log audit) |
Reject (quay về DangSoanThao) |
Drafter | email + in-app |
7. Data model implication (cho Phase 3 + Tier 3 versioned)
// Domain
public enum ContractPhase {
DangChon = 1,
DangSoanThao,
DangGopY,
DangDamPhan,
DangInKy,
DangKiemTraCCM,
DangTrinhKy,
DangDongDau,
DaPhatHanh,
TuChoi
}
public class Contract : AuditableEntity {
public Guid Id { get; set; }
public string? MaHopDong { get; set; } // tự gen theo RG-001
public ContractType Type { get; set; } // HDTP, HDGK, NCC, HDDV...
public ContractPhase Phase { get; set; }
public Guid SupplierId { get; set; }
public Guid ProjectId { get; set; }
public decimal GiaTri { get; set; }
public bool BypassProcurementAndCCM { get; set; }
public DateTime? SlaDeadline { get; set; }
public bool SlaWarningSent { get; set; }
// Tier 3: pin policy version at create-time cho immutability
public Guid? WorkflowDefinitionId { get; set; }
public List<ContractComment> Comments { get; set; }
public List<ContractApproval> Approvals { get; set; }
public List<ContractAttachment> Attachments { get; set; }
}
public class ContractApproval {
public Guid ContractId { get; set; }
public ContractPhase FromPhase { get; set; }
public ContractPhase ToPhase { get; set; }
public Guid? ApproverUserId { get; set; } // null = system (SLA auto)
public DateTime? ApprovedAt { get; set; }
public ApprovalDecision Decision { get; set; } // Pending | Approve | Reject | AutoApprove
public string? Comment { get; set; }
}
// ==================== Tier 3: versioned workflow ====================
public class WorkflowDefinition : AuditableEntity {
public Guid Id { get; set; }
public string Code { get; set; } = ""; // "QT-MB", "QT-TP", "QT-NCC", ...
public int Version { get; set; } // 1, 2, 3, ... auto-increment per Code
public bool IsActive { get; set; } // chỉ 1 = true per ContractType
public ContractType ContractType { get; set; }
public string Name { get; set; } = ""; // "Quy trình Mua bán v02"
public string? Description { get; set; }
public List<WorkflowStep> Steps { get; set; } = new();
}
public class WorkflowStep {
public Guid Id { get; set; }
public Guid WorkflowDefinitionId { get; set; }
public int Order { get; set; } // thứ tự step trong định nghĩa
public ContractPhase Phase { get; set; } // target phase
public string Name { get; set; } = ""; // "Kiểm tra CCM"
public int SlaDays { get; set; } // SLA ngày cho phase này
public List<WorkflowStepApprover> Approvers { get; set; } = new();
}
public class WorkflowStepApprover {
public Guid Id { get; set; }
public Guid WorkflowStepId { get; set; }
public ApproverKind Kind { get; set; } // Role | User
public string AssignmentValue { get; set; } = ""; // RoleName hoặc UserId Guid string
}
public enum ApproverKind { Role = 1, User = 2 }
Service chính:
IContractWorkflowService.TransitionAsync(contractId, targetPhase, userId, comment)— resolve policy, check guard, update state, tạo approval, emit notificationIContractCodeGenerator.GenerateAsync(projectId, type, supplierId)— SERIALIZABLE transaction tránh raceSlaExpiryJob : BackgroundService— 15min, auto-approve quá hạn với Decision=AutoApproveIRealtimeNotifier(SignalR impl) — push vào group User-{Id} khi Notification created
7bis. Policy resolution — versioned workflow
sequenceDiagram
participant User as Actor
participant API as ContractsController
participant WF as ContractWorkflowService
participant DB as WorkflowDefinitions
User->>API: POST /contracts/{id}/transitions {targetPhase}
API->>WF: TransitionAsync(id, targetPhase, userId, comment)
alt Contract.WorkflowDefinitionId != null (Tier 3 pinned)
WF->>DB: Include(Steps.Approvers).First(Id == wfId)
DB-->>WF: def
WF->>WF: policy = Registry.FromDefinition(def)
else Admin override in WorkflowTypeAssignments
WF->>DB: Find(ContractType == c.Type)
DB-->>WF: override
WF->>WF: policy = Registry.ByName(override.PolicyName)
else Legacy fallback
WF->>WF: policy = Registry.For(c.Type) // hardcoded Standard/SkipCcm
end
WF->>WF: check (from, to) ∈ policy.Transitions
WF->>WF: check actor.Roles ∩ allowedRoles != ∅
WF->>DB: UPDATE Phase + INSERT ContractApproval + INSERT Notifications
WF-->>API: 200
7ter. Admin designer flow (tạo version mới)
Admin → /system/workflows → click type "HĐ Mua bán"
→ /system/workflows/MuaBan
→ thấy active version QT-MB-v01 + history
→ click "Tạo phiên bản mới" → Designer modal (có thể Clone từ v01)
- Code: QT-MB (auto-fill)
- Version: v02 (auto-compute max+1)
- Name + Description
- Steps (repeatable):
[Order 1] Phase=2 (DangSoanThao) SLA=7 days Approvers: +Role Drafter, +Role DeptManager
[Order 2] Phase=3 (DangGopY) SLA=7 days Approvers: +Role ProjectManager, +User {userId}
...
- Save → POST /api/workflows
BE: auto Version=max+1, deactivate QT-MB-v01.IsActive=0, insert v02.IsActive=1, atomic
→ trở về /system/workflows/MuaBan → v02 active, v01 archived "N HĐ còn chạy"
→ HĐ cũ pin v01 vẫn chạy v01 (Contract.WorkflowDefinitionId không đổi)
→ HĐ mới tạo sau đây sẽ pin v02
8. Business rules summary
- Một role chỉ có 1 phase active tại 1 thời điểm cho 1 HĐ.
- Auto-approve nếu quá SLA — phải log
Decision=AutoApproverõ ràng trongContractApproval. - Reject → quay về
DangSoanThao— Drafter nhận lại, toàn bộ approval trước đó bị invalidate (kept as history). - Không cho xóa HĐ đã qua phase 5 (
DangInKy) — chỉ soft delete. - Mã HĐ gen theo
forms-spec.md § RG-001— chỉ gen khi transition sang phaseDangDongDau(8). - Audit log — mọi transition đều insert
ContractApprovalsrow với actor + timestamp + phase before/after. - Versioned workflow —
Contract.WorkflowDefinitionIdpin tại create-time, không update sau đó. Admin tạo version mới ảnh hưởng HĐ tương lai, HĐ cũ giữ version cũ. - Chỉ 1 active version per ContractType — enforce qua business logic trong
CreateWorkflowDefinitionCommand(atomic deactivate + insert).
9. Code pointers (Tier 3)
Domain:
Domain/Contracts/WorkflowDefinition.csDomain/Contracts/WorkflowStep.csDomain/Contracts/WorkflowStepApprover.cs(+ApproverKindenum)Domain/Contracts/WorkflowPolicy.cs(record +WorkflowPolicies.Standard/SkipCcm+WorkflowPolicyRegistry.FromDefinition+ForContract)
Application:
Application/Contracts/WorkflowAdminFeatures.cs:GetWorkflowAdminOverviewQuery— landing per-type + active + historyCreateWorkflowDefinitionCommand— auto Version + atomic deactivate old
Infrastructure:
Infrastructure/Services/ContractWorkflowService.cs—LoadPolicyAsync(contractId)resolution order
Api:
Api/Controllers/WorkflowsController.cs— GET overview, GET per-type, POST create-version
FE-Admin:
fe-admin/src/pages/system/WorkflowsPage.tsx— URL-driven, landing + per-typefe-admin/src/components/workflow/WorkflowDesigner.tsx— modal Steps + Approversfe-admin/src/components/workflow/DefinitionCard.tsx— active + history card