[CLAUDE] Docs: chốt session Tier 3 feature-complete + versioned workflow
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m33s
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:
@ -1,6 +1,6 @@
|
||||
# Schema Diagram — Luồng DB SOLUTION_ERP
|
||||
|
||||
> ERD đầy đủ + mối quan hệ 19 table sau Phase 3. Mermaid render ở VS Code / GitHub / Gitea.
|
||||
> ERD đầy đủ + mối quan hệ **24 table** sau Tier 3 (Notifications + Versioned workflows). Mermaid render ở VS Code / GitHub / Gitea.
|
||||
|
||||
## 1. Full ERD
|
||||
|
||||
@ -22,6 +22,7 @@ erDiagram
|
||||
Departments ||--o{ Contracts : "drafted-in"
|
||||
Users ||--o{ Contracts : "drafter"
|
||||
ContractTemplates ||--o{ Contracts : "uses"
|
||||
WorkflowDefinitions ||--o{ Contracts : "pinned-policy"
|
||||
|
||||
Contracts ||--o{ ContractApprovals : "history"
|
||||
Contracts ||--o{ ContractComments : "thread"
|
||||
@ -29,6 +30,11 @@ erDiagram
|
||||
Users ||--o{ ContractApprovals : "approved-by"
|
||||
Users ||--o{ ContractComments : "author"
|
||||
|
||||
WorkflowDefinitions ||--o{ WorkflowSteps : "has"
|
||||
WorkflowSteps ||--o{ WorkflowStepApprovers : "allowed-by"
|
||||
|
||||
Users ||--o{ Notifications : "recipient"
|
||||
|
||||
Users {
|
||||
uniqueidentifier Id PK
|
||||
nvarchar FullName "200"
|
||||
@ -126,6 +132,7 @@ erDiagram
|
||||
uniqueidentifier DepartmentId FK
|
||||
uniqueidentifier DrafterUserId FK
|
||||
uniqueidentifier TemplateId FK
|
||||
uniqueidentifier WorkflowDefinitionId FK "pinned policy, nullable"
|
||||
decimal GiaTri "18,2"
|
||||
bit BypassProcurementAndCCM
|
||||
datetime2 SlaDeadline
|
||||
@ -170,6 +177,54 @@ erDiagram
|
||||
int LastSeq
|
||||
datetime2 UpdatedAt
|
||||
}
|
||||
|
||||
Notifications {
|
||||
uniqueidentifier Id PK
|
||||
uniqueidentifier RecipientUserId FK
|
||||
int Type "ContractTransition/CommentAdded/SlaExpired/..."
|
||||
nvarchar Title "200"
|
||||
nvarchar Body "2000"
|
||||
nvarchar Link "500"
|
||||
bit IsRead
|
||||
datetime2 ReadAt
|
||||
datetime2 CreatedAt
|
||||
}
|
||||
|
||||
WorkflowTypeAssignments {
|
||||
uniqueidentifier Id PK
|
||||
int ContractType "UK"
|
||||
nvarchar PolicyName "50 Standard/SkipCcm"
|
||||
datetime2 UpdatedAt
|
||||
uniqueidentifier UpdatedBy FK
|
||||
}
|
||||
|
||||
WorkflowDefinitions {
|
||||
uniqueidentifier Id PK
|
||||
nvarchar Code "100 QT-MB / QT-TP / ..."
|
||||
int Version "auto-increment per Code"
|
||||
bit IsActive "chi 1 active per ContractType"
|
||||
int ContractType
|
||||
nvarchar Name "200"
|
||||
nvarchar Description "500"
|
||||
datetime2 CreatedAt
|
||||
uniqueidentifier CreatedBy FK
|
||||
}
|
||||
|
||||
WorkflowSteps {
|
||||
uniqueidentifier Id PK
|
||||
uniqueidentifier WorkflowDefinitionId FK
|
||||
int Order
|
||||
int Phase "target ContractPhase int"
|
||||
nvarchar Name "200"
|
||||
int SlaDays
|
||||
}
|
||||
|
||||
WorkflowStepApprovers {
|
||||
uniqueidentifier Id PK
|
||||
uniqueidentifier WorkflowStepId FK
|
||||
int Kind "1=Role, 2=User"
|
||||
nvarchar AssignmentValue "200 RoleName or UserId Guid string"
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Luồng dữ liệu chính (data flow diagram)
|
||||
@ -182,18 +237,27 @@ flowchart TB
|
||||
P --> MI[MenuItems]
|
||||
end
|
||||
|
||||
subgraph MASTER ["📋 Master data (admin CRUD)"]
|
||||
subgraph MASTER ["📋 Master data (admin CRUD + seed demo)"]
|
||||
S[Suppliers]
|
||||
PR[Projects]
|
||||
DE[Departments]
|
||||
end
|
||||
|
||||
subgraph FORMS ["📄 Form templates (seed)"]
|
||||
subgraph FORMS ["📄 Form templates (admin upload)"]
|
||||
CT[ContractTemplates]
|
||||
CC[ContractClauses]
|
||||
end
|
||||
|
||||
subgraph CONTRACT ["📝 Contract workflow (Phase 3 core)"]
|
||||
subgraph WORKFLOW ["⚙️ Versioned workflow (admin designer)"]
|
||||
WD[WorkflowDefinitions]
|
||||
WS[WorkflowSteps]
|
||||
WSA[WorkflowStepApprovers]
|
||||
WTA[WorkflowTypeAssignments legacy override]
|
||||
WD --> WS
|
||||
WS --> WSA
|
||||
end
|
||||
|
||||
subgraph CONTRACT ["📝 Contract workflow"]
|
||||
C[Contracts]
|
||||
CA[ContractApprovals]
|
||||
CCM[ContractComments]
|
||||
@ -201,39 +265,48 @@ flowchart TB
|
||||
CCS[ContractCodeSequences]
|
||||
end
|
||||
|
||||
subgraph NOTIFY ["🔔 Notification module"]
|
||||
N[Notifications]
|
||||
end
|
||||
|
||||
U -.Drafter.-> C
|
||||
S --> C
|
||||
PR --> C
|
||||
DE --> C
|
||||
CT --> C
|
||||
WD -.pinned at create.-> C
|
||||
C --> CA
|
||||
C --> CCM
|
||||
C --> CAT
|
||||
C -.gen when DangDongDau.-> CCS
|
||||
C -.transition event.-> N
|
||||
CCM -.comment added.-> N
|
||||
U -.recipient.-> N
|
||||
```
|
||||
|
||||
## 3. Vòng đời 1 HĐ — data changes
|
||||
## 3. Vòng đời 1 HĐ — data changes (với versioned workflow)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Create[POST /contracts]
|
||||
Create --> C1["Contracts INSERT<br/>Phase=2, SLA=+7d"]
|
||||
Create[POST /contracts type=5]
|
||||
Create --> PickWD["SELECT TOP 1 WorkflowDefinition<br/>WHERE ContractType=5 AND IsActive=1<br/>→ Id=wf-v02"]
|
||||
PickWD --> C1["Contracts INSERT<br/>Phase=2, SLA=+7d, WorkflowDefinitionId=wf-v02"]
|
||||
|
||||
Transition1[Transition 2→3]
|
||||
Transition1 --> C2["UPDATE Phase=3<br/>INSERT ContractApprovals"]
|
||||
Transition1 --> LoadPolicy["Load wf-v02.Steps.Approvers<br/>WorkflowPolicyRegistry.FromDefinition(def)"]
|
||||
LoadPolicy --> Guard["Check allowed roles for<br/>(from=2, to=3)"]
|
||||
Guard --> C2["UPDATE Phase=3<br/>INSERT ContractApprovals<br/>INSERT Notifications bulk"]
|
||||
|
||||
Comment[POST /comments]
|
||||
Comment --> C3[INSERT ContractComments]
|
||||
Comment --> C3["INSERT ContractComments<br/>INSERT Notifications"]
|
||||
|
||||
Transition2[Transition 3→4→5→6→7]
|
||||
Transition2 --> C4["UPDATE Phase + SlaDeadline<br/>INSERT ContractApprovals"]
|
||||
NewVersion[Admin creates QT-MB-v03]
|
||||
NewVersion --> NV1["INSERT WorkflowDefinition v03 IsActive=1<br/>UPDATE v02 SET IsActive=0 (atomic)"]
|
||||
NV1 -.->|HĐ cũ không ảnh hưởng| C1
|
||||
|
||||
Transition3[Transition 7→8 BOD ký]
|
||||
Transition3 --> CG["ContractCodeGenerator<br/>SERIALIZABLE tran<br/>UPSERT ContractCodeSequences"]
|
||||
CG --> C5["UPDATE Contract<br/>SET MaHopDong='FLOCK 01/HĐGK/SOL&PVL/01',<br/>Phase=8"]
|
||||
|
||||
Transition4[Transition 8→9 HRA phát hành]
|
||||
Transition4 --> C6["UPDATE Phase=9, SlaDeadline=NULL<br/>INSERT ContractApprovals"]
|
||||
Transition3 --> CG["ContractCodeGenerator SERIALIZABLE<br/>UPSERT ContractCodeSequences"]
|
||||
CG --> C5["UPDATE Contract<br/>SET MaHopDong, Phase=8<br/>INSERT Notifications"]
|
||||
```
|
||||
|
||||
## 4. Index strategy
|
||||
@ -245,12 +318,19 @@ flowchart LR
|
||||
| Contracts | `IX_Contracts_SupplierId` | Filter HĐ theo NCC |
|
||||
| Contracts | `IX_Contracts_ProjectId` | Filter HĐ theo dự án |
|
||||
| Contracts | `IX_Contracts_SlaDeadline` | SLA expiry job query |
|
||||
| Contracts | `IX_Contracts_WorkflowDefinitionId` | Pinned policy lookup |
|
||||
| ContractApprovals | `IX_ContractApprovals_ContractId_ApprovedAt` | Timeline query theo HĐ |
|
||||
| ContractComments | `IX_ContractComments_ContractId_CreatedAt` | Thread load |
|
||||
| ContractAttachments | `IX_ContractAttachments_ContractId` | Attachments load |
|
||||
| Suppliers/Projects/Departments | `UX_{Table}_Code` | Unique business code |
|
||||
| Permissions | `UX_Permissions_RoleId_MenuKey` | 1 row / role / menu |
|
||||
| MenuItems | `IX_MenuItems_ParentKey` | Tree query |
|
||||
| Notifications | `IX_Notifications_RecipientUserId_IsRead_CreatedAt` | Bell badge unread count + list |
|
||||
| WorkflowDefinitions | `UX_WorkflowDefinitions_Code_Version` | Unique version per code |
|
||||
| WorkflowDefinitions | `IX_WorkflowDefinitions_ContractType_IsActive` | Active policy lookup |
|
||||
| WorkflowSteps | `IX_WorkflowSteps_WorkflowDefinitionId_Order` | Load steps ordered |
|
||||
| WorkflowStepApprovers | `IX_WorkflowStepApprovers_WorkflowStepId` | Approver load |
|
||||
| WorkflowTypeAssignments | `UX_WorkflowTypeAssignments_ContractType` | 1 override per type (legacy) |
|
||||
|
||||
Chi tiết + cheatsheet SQL: [`database-guide.md`](database-guide.md).
|
||||
|
||||
@ -263,9 +343,13 @@ Chi tiết + cheatsheet SQL: [`database-guide.md`](database-guide.md).
|
||||
| Contract → ContractAttachment | 1 - N | Cascade | File vật lý vẫn còn trong `wwwroot/uploads/`, cleanup riêng |
|
||||
| Supplier → Contract | 1 - N | Restrict | Không xóa Supplier nếu còn HĐ tham chiếu |
|
||||
| Project → Contract | 1 - N | Restrict | Tương tự |
|
||||
| **WorkflowDefinition → Contract** | 1 - N | **Restrict** | **KHÔNG cascade** → HĐ cũ pin version cũ không bị xóa khi admin archive |
|
||||
| Role → Permission | 1 - N | Cascade | Xóa role → clear permissions |
|
||||
| MenuItem → Permission | 1 - N | Cascade | — |
|
||||
| MenuItem → MenuItem (self-ref) | 1 - N | Restrict | Parent-child menu |
|
||||
| **WorkflowDefinition → WorkflowStep** | 1 - N | Cascade | Delete def → remove steps (chỉ khi no Contract tham chiếu) |
|
||||
| **WorkflowStep → WorkflowStepApprover** | 1 - N | Cascade | — |
|
||||
| **User → Notification (RecipientUserId)** | 1 - N | Cascade | — |
|
||||
|
||||
## 6. Soft delete behavior
|
||||
|
||||
@ -278,47 +362,89 @@ Entity list áp dụng:
|
||||
- Supplier, Project, Department
|
||||
- Contract
|
||||
- ContractTemplate, ContractClause
|
||||
- WorkflowDefinition (admin archive = `IsActive=false`, xóa logic chỉ khi muốn scrub)
|
||||
|
||||
KHÔNG soft delete (cascade hoặc keep):
|
||||
- ContractApproval, ContractComment, ContractAttachment — cascade khi Contract xóa
|
||||
- Permission, MenuItem — cascade khi Role xóa
|
||||
- ContractCodeSequence — không bao giờ xóa (giữ history seq)
|
||||
- Notifications — không soft delete, chỉ `IsRead` flag (giữ history ngắn hạn, cleanup job sau)
|
||||
- WorkflowStep / WorkflowStepApprover — cascade khi WorkflowDefinition xóa
|
||||
- Identity tables (Users, Roles, ...) — Identity không support soft delete built-in
|
||||
|
||||
## 7. Truy vấn tiêu biểu
|
||||
|
||||
### Inbox HĐ chờ role của tôi
|
||||
### Inbox HĐ chờ role của tôi (với versioned workflow)
|
||||
|
||||
```sql
|
||||
-- Tương đương GetMyInboxQuery
|
||||
-- Server filter (API): HĐ chờ role eligible phase theo pinned policy
|
||||
-- Thực tế ContractsController resolve policy runtime:
|
||||
-- def = db.WorkflowDefinitions.Include(Steps.Approvers).Where(Id == contract.WorkflowDefinitionId).FirstOrDefault()
|
||||
-- policy = def != null ? Registry.FromDefinition(def) : Registry.For(contract.Type)
|
||||
-- phase eligible = policy.Transitions.Where(t => t.From == contract.Phase && t.AllowedRoles.Intersect(myRoles).Any())
|
||||
-- SQL tương đương:
|
||||
SELECT c.Id, c.MaHopDong, c.TenHopDong, c.Phase, c.SlaDeadline, s.Name AS SupplierName, p.Name AS ProjectName
|
||||
FROM Contracts c
|
||||
INNER JOIN Suppliers s ON c.SupplierId = s.Id
|
||||
INNER JOIN Projects p ON c.ProjectId = p.Id
|
||||
WHERE c.IsDeleted = 0
|
||||
AND c.Phase IN (/* phase eligible cho role hiện tại */)
|
||||
AND c.Phase IN (/* phase eligible computed theo pinned workflow */)
|
||||
ORDER BY c.SlaDeadline ASC;
|
||||
```
|
||||
|
||||
### Dashboard stats
|
||||
### Pick active workflow tại create-time
|
||||
|
||||
```sql
|
||||
SELECT TOP 1 Id
|
||||
FROM WorkflowDefinitions
|
||||
WHERE ContractType = @Type AND IsActive = 1
|
||||
ORDER BY [Version] DESC;
|
||||
```
|
||||
|
||||
### Tạo version mới (atomic)
|
||||
|
||||
```sql
|
||||
BEGIN TRAN;
|
||||
|
||||
-- Step 1: deactivate current active
|
||||
UPDATE WorkflowDefinitions
|
||||
SET IsActive = 0
|
||||
WHERE ContractType = @Type AND IsActive = 1;
|
||||
|
||||
-- Step 2: compute next version
|
||||
DECLARE @NextVersion INT = (SELECT ISNULL(MAX([Version]), 0) + 1 FROM WorkflowDefinitions WHERE Code = @Code);
|
||||
|
||||
-- Step 3: insert new active version
|
||||
INSERT WorkflowDefinitions (Id, Code, [Version], IsActive, ContractType, Name, Description, CreatedAt, CreatedBy)
|
||||
VALUES (NEWID(), @Code, @NextVersion, 1, @Type, @Name, @Description, GETUTCDATE(), @UserId);
|
||||
|
||||
-- Step 4: insert steps + approvers (batch)
|
||||
-- ...
|
||||
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
### Dashboard stats (MyDashboard user-specific)
|
||||
|
||||
```sql
|
||||
-- Tổng + active + overdue
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM Contracts WHERE IsDeleted = 0) AS Total,
|
||||
(SELECT COUNT(*) FROM Contracts WHERE IsDeleted = 0 AND Phase NOT IN (9, 99)) AS Active,
|
||||
(SELECT COUNT(*) FROM Contracts WHERE IsDeleted = 0 AND Phase NOT IN (9, 99) AND SlaDeadline < GETUTCDATE()) AS Overdue;
|
||||
(SELECT COUNT(*) FROM Contracts
|
||||
WHERE DrafterUserId = @Me AND IsDeleted = 0 AND Phase NOT IN (9, 99)) AS DraftsInProgress,
|
||||
(SELECT COUNT(*) FROM Contracts
|
||||
WHERE Phase IN (/* eligible phases cho role tôi */) AND IsDeleted = 0) AS PendingMyApproval,
|
||||
(SELECT COUNT(*) FROM Contracts
|
||||
WHERE IsDeleted = 0 AND SlaDeadline BETWEEN GETUTCDATE() AND DATEADD(DAY, 2, GETUTCDATE())) AS DueSoon,
|
||||
(SELECT COUNT(*) FROM Contracts
|
||||
WHERE IsDeleted = 0 AND SlaDeadline < GETUTCDATE() AND Phase NOT IN (9, 99)) AS Overdue,
|
||||
(SELECT ISNULL(SUM(GiaTri), 0) FROM Contracts
|
||||
WHERE DrafterUserId = @Me AND Phase = 2) AS DraftsTotalValue;
|
||||
```
|
||||
|
||||
-- By phase
|
||||
SELECT Phase, COUNT(*) FROM Contracts WHERE IsDeleted = 0 GROUP BY Phase;
|
||||
### Notifications unread count (bell badge)
|
||||
|
||||
-- Top 5 NCC
|
||||
SELECT TOP 5 c.SupplierId, s.Name, COUNT(*) AS Cnt, SUM(c.GiaTri) AS TotalValue
|
||||
FROM Contracts c
|
||||
INNER JOIN Suppliers s ON c.SupplierId = s.Id
|
||||
WHERE c.IsDeleted = 0
|
||||
GROUP BY c.SupplierId, s.Name
|
||||
ORDER BY COUNT(*) DESC;
|
||||
```sql
|
||||
SELECT COUNT(*) FROM Notifications
|
||||
WHERE RecipientUserId = @Me AND IsRead = 0;
|
||||
```
|
||||
|
||||
### Gen mã HĐ atomic
|
||||
@ -327,32 +453,63 @@ ORDER BY COUNT(*) DESC;
|
||||
BEGIN TRAN;
|
||||
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
|
||||
|
||||
MERGE ContractCodeSequences AS tgt
|
||||
USING (SELECT @Prefix AS Prefix) AS src ON tgt.Prefix = src.Prefix
|
||||
WHEN MATCHED THEN UPDATE SET LastSeq = LastSeq + 1, UpdatedAt = GETUTCDATE()
|
||||
WHEN NOT MATCHED THEN INSERT (Prefix, LastSeq, UpdatedAt) VALUES (@Prefix, 1, GETUTCDATE());
|
||||
UPDATE ContractCodeSequences
|
||||
SET LastSeq = LastSeq + 1, UpdatedAt = GETUTCDATE()
|
||||
WHERE Prefix = @Prefix;
|
||||
|
||||
IF @@ROWCOUNT = 0
|
||||
INSERT ContractCodeSequences (Prefix, LastSeq, UpdatedAt)
|
||||
VALUES (@Prefix, 1, GETUTCDATE());
|
||||
|
||||
SELECT LastSeq FROM ContractCodeSequences WHERE Prefix = @Prefix;
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
(EF Core impl dùng `UPDATE + IF ROWCOUNT = 0 INSERT` thay MERGE — tương đương nhưng an toàn hơn với SERIALIZABLE).
|
||||
|
||||
## 8. Migration lịch sử
|
||||
|
||||
| # | Migration | Tables added |
|
||||
| # | Migration | Tables added / changed |
|
||||
|---|---|---|
|
||||
| 1 | `Init` | 7 Identity tables |
|
||||
| 2 | `AddMasterData` | Suppliers, Projects, Departments |
|
||||
| 3 | `AddPermissions` | MenuItems, Permissions |
|
||||
| 4 | `AddForms` | ContractTemplates, ContractClauses |
|
||||
| 5 | `AddContractsWorkflow` | Contracts, ContractApprovals, ContractComments, ContractAttachments, ContractCodeSequences |
|
||||
| 6 | `AddNotifications` | Notifications |
|
||||
| 7 | `AddWorkflowTypeAssignments` | WorkflowTypeAssignments (admin override legacy) |
|
||||
| 8 | `AddVersionedWorkflows` | WorkflowDefinitions, WorkflowSteps, WorkflowStepApprovers + Contracts.WorkflowDefinitionId FK |
|
||||
|
||||
Tổng: **19 bảng** (+ `__EFMigrationsHistory` hệ thống).
|
||||
Tổng: **24 bảng** (+ `__EFMigrationsHistory` hệ thống).
|
||||
|
||||
## 9. Liên quan
|
||||
## 9. Versioned workflow invariants
|
||||
|
||||
```
|
||||
1. UNIQUE (WorkflowDefinitions.Code, Version)
|
||||
→ không 2 row cùng Code + Version (enforce qua IX unique)
|
||||
|
||||
2. Chỉ 1 WorkflowDefinition.IsActive = true per ContractType tại 1 thời điểm
|
||||
→ enforce qua CreateWorkflowDefinitionCommand: UPDATE deactivate trước INSERT, cùng transaction
|
||||
|
||||
3. Contract.WorkflowDefinitionId pinned at create → không update sau đó
|
||||
→ CreateContractCommandHandler pick active version 1 lần, save
|
||||
|
||||
4. ON DELETE Restrict FK Contract.WorkflowDefinitionId → WorkflowDefinitions.Id
|
||||
→ admin không thể DELETE WorkflowDefinition nếu còn Contract pin
|
||||
→ admin archive = set IsActive=false thôi, row vẫn tồn tại
|
||||
|
||||
5. Runtime policy resolution order (ContractWorkflowService):
|
||||
a. If contract.WorkflowDefinitionId NOT NULL → load def → FromDefinition
|
||||
b. Else if admin override ở WorkflowTypeAssignments for contract.Type → Registry.ByName
|
||||
c. Else → Registry.For(contract.Type) (hardcoded Standard/SkipCcm)
|
||||
|
||||
6. WorkflowStepApprover.Kind
|
||||
- 1=Role: AssignmentValue là RoleName (Domain/Identity/AppRoles constants)
|
||||
- 2=User: AssignmentValue là UserId Guid string
|
||||
- Runtime guard hiện tại chỉ dùng Role-kind (User-kind data model ready, enable iter sau)
|
||||
```
|
||||
|
||||
## 10. Liên quan
|
||||
|
||||
- [`database-guide.md`](database-guide.md) — conventions + migration workflow + cheatsheet đầy đủ
|
||||
- [`../architecture.md`](../architecture.md) — layered architecture + data flow
|
||||
- [`../workflow-contract.md`](../workflow-contract.md) — state machine spec
|
||||
- [`../workflow-contract.md`](../workflow-contract.md) — state machine spec + versioned
|
||||
- [`../flows/`](../flows/) — sequence diagrams
|
||||
|
||||
Reference in New Issue
Block a user