# 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.docx`
> **Raw dump:** [`workflow-raw.md`](workflow-raw.md)
> **Phase 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)

```mermaid
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)

```csharp
// 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 notification
- `IContractCodeGenerator.GenerateAsync(projectId, type, supplierId)` — SERIALIZABLE transaction tránh race
- `SlaExpiryJob : BackgroundService` — 15min, auto-approve quá hạn với Decision=AutoApprove
- `IRealtimeNotifier` (SignalR impl) — push vào group User-{Id} khi Notification created

## 7bis. Policy resolution — versioned workflow

```mermaid
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

1. **Một role chỉ có 1 phase active tại 1 thời điểm** cho 1 HĐ.
2. **Auto-approve nếu quá SLA** — phải log `Decision=AutoApprove` rõ ràng trong `ContractApproval`.
3. **Reject → quay về `DangSoanThao`** — Drafter nhận lại, toàn bộ approval trước đó bị invalidate (kept as history).
4. **Không cho xóa HĐ** đã qua phase 5 (`DangInKy`) — chỉ soft delete.
5. **Mã HĐ** gen theo `forms-spec.md § RG-001` — chỉ gen khi transition sang phase `DangDongDau` (8).
6. **Audit log** — mọi transition đều insert `ContractApprovals` row với actor + timestamp + phase before/after.
7. **Versioned workflow** — `Contract.WorkflowDefinitionId` pin 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ũ.
8. **Chỉ 1 active version per ContractType** — enforce qua business logic trong `CreateWorkflowDefinitionCommand` (atomic deactivate + insert).

## 9. Code pointers (Tier 3)

**Domain:**
- `Domain/Contracts/WorkflowDefinition.cs`
- `Domain/Contracts/WorkflowStep.cs`
- `Domain/Contracts/WorkflowStepApprover.cs` (+ `ApproverKind` enum)
- `Domain/Contracts/WorkflowPolicy.cs` (record + `WorkflowPolicies.Standard/SkipCcm` + `WorkflowPolicyRegistry.FromDefinition` + `ForContract`)

**Application:**
- `Application/Contracts/WorkflowAdminFeatures.cs`:
  - `GetWorkflowAdminOverviewQuery` — landing per-type + active + history
  - `CreateWorkflowDefinitionCommand` — 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-type
- `fe-admin/src/components/workflow/WorkflowDesigner.tsx` — modal Steps + Approvers
- `fe-admin/src/components/workflow/DefinitionCard.tsx` — active + history card
