[CLAUDE] Docs: chốt session Tier 3 feature-complete + versioned workflow
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>
This commit is contained in:
pqhuy1987
2026-04-22 10:25:02 +07:00
parent 91b2da147f
commit fbca83264c
9 changed files with 1363 additions and 479 deletions

View File

@ -98,7 +98,7 @@ Ký hiệu: `R` = read, `W` = write/update draft, `A` = approve (chuyển phase
| 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)
## 7. Data model implication (cho Phase 3 + Tier 3 versioned)
```csharp
// Domain
@ -117,40 +117,157 @@ public enum ContractPhase {
public class Contract : AuditableEntity {
public Guid Id { get; set; }
public string MaHopDong { get; set; } // tự gen theo RG-001
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; } // khi nào phase hiện tại hết hạn
// ...
public List<ContractComment> Comments { get; set; } // thread góp ý phase 3
public List<ContractApproval> Approvals { get; set; } // ai ký phase nào, lúc nào
public List<ContractAttachment> Attachments { get; set; } // scan bản gốc, file export
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 Phase { get; set; }
public Guid ApproverUserId { 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; } // Approve | Reject | AutoApprove
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)` — check guard + update state + tạo approval + notify
- `IContractCodeGenerator.GenerateAsync(projectId, type, supplierId)`dùng SEMAPHORE/transaction tránh race condition
- `ISlaExpiryJob` — hosted service chạy mỗi 15 phút, auto-approve các HĐ quá hạn
- `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** nhưng phải log `Decision=AutoApprove` rõ ràng trong `ContractApproval`.
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 5.
6. **Audit log đầy đủ** — mọi transition đều ghi `AuditLog(entityId, action, oldPhase, newPhase, userId, timestamp, diff)`.
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