Files
solution-erp/docs/flows/contract-creation-flow.md
pqhuy1987 49a5f57a50 [CLAUDE] Docs: database-guide + 6 flow diagrams
docs/database/database-guide.md:
- Conventions (naming, data types, audit fields, soft delete)
- Schema hien tai (Identity tables sau migration Init) + seed 12 role + admin
- Schema planned: Phase 1 dot 2 (Supplier/Project/Department + Permission Matrix)
- Schema planned: Phase 3 (Contract + Approval + Comment + Attachment + Template + Clause + CodeSequence)
- Mermaid ERD cho tung phase
- Migration workflow (create/apply/revert)
- Index strategy + unique indexes
- Backup/restore SQL
- Common pitfalls + SQL cheatsheet

docs/flows/ — 6 flow documentation:
- README.md: index
- auth-flow.md: login/refresh/me/logout (IMPLEMENTED, sequence + edge cases + security checklist)
- permission-flow.md: Phase 1 dot 2 - Role x MenuKey x CRUD resolution + FE guard + BE policy
- contract-creation-flow.md: Phase 2 - Drafter flow chon template -> fill -> preview -> save draft
- contract-approval-flow.md: Phase 3 - state machine 9 phase chi tiet + reject flow + timeline UI
- form-render-flow.md: Phase 2 - OpenXml + ClosedXML + LibreOffice PDF convert
- sla-expiry-flow.md: Phase 3 - BackgroundService auto-approve qua SLA + warning notify

Update references:
- CLAUDE.md (root): them 2 row Tai lieu quan trong
- docs/CLAUDE.md: update project layout voi flows/ + database/
- docs/STATUS.md: log docs addition
- docs/changelog/migration-todos.md: tick Phase 0 docs items

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 11:15:28 +07:00

9.1 KiB

Contract Creation Flow

Status: 📝 Planned (Phase 2) Actors: Drafter (QS/NV.PB) Entry: User mở /contracts/new ở fe-user

1. Tổng quan

Drafter chọn loại HĐ → chọn template form → điền field động → preview → lưu draft. Sau đó có thể tiếp tục soạn hoặc submit lên phase DangGopY (xem contract-approval-flow.md).

2. Sequence

sequenceDiagram
    actor D as Drafter
    participant FE as fe-user<br/>/contracts/new
    participant API as ContractsController
    participant CMD as CreateContractCommandHandler
    participant TMPL as FormsController<br/>.GetTemplate
    participant REN as IFormRenderer
    participant DB
    participant FS as File Storage<br/>(wwwroot/uploads)

    D->>FE: Click "Tạo HĐ mới"

    D->>FE: Step 1 — Chọn loại HĐ<br/>(ContractType dropdown)
    FE->>API: GET /api/forms/templates?type={contractType}
    API->>DB: SELECT ContractTemplates WHERE Type = ? AND IsActive = 1
    API-->>FE: Template list<br/>[{id, formCode, name, fieldSpec}]

    D->>FE: Step 2 — Chọn template (vd FO-002.05 Giao khoán)
    FE->>TMPL: GET /api/forms/templates/{id}
    TMPL->>DB: SELECT template + FieldSpec JSON
    TMPL-->>FE: fieldSpec (array of {name, type, required, ...})

    FE->>FE: Dynamic form builder render form<br/>theo fieldSpec

    D->>FE: Step 3 — Điền field (NCC, dự án,<br/>giá trị, hạng mục, nghiệm thu…)

    D->>FE: Click "Preview"
    FE->>API: POST /api/forms/render<br/>{templateId, data}
    API->>REN: DocxRenderer.Render(template, data)
    REN->>REN: Load .docx template<br/>replace placeholder {{field}}<br/>with values
    REN->>FS: Write temp file uploads/preview/{guid}.docx
    REN-->>API: (bytes + temp path)
    API-->>FE: 200 file blob (inline hoặc Content-Disposition: inline)
    FE->>FE: Display PDF preview<br/>(convert server-side via LibreOffice)

    D->>FE: Step 4 — Click "Lưu draft"
    FE->>API: POST /api/contracts<br/>{type, supplierId, projectId,<br/>templateId, draftData}
    API->>CMD: Send(CreateContractCommand)
    CMD->>CMD: Validate (FluentValidation)<br/>supplier exists, project active...
    CMD->>DB: INSERT Contracts (Phase=DangSoanThao,<br/>DraftData=JSON.serialize)
    CMD-->>API: ContractId
    API-->>FE: 201 Created + Location header

    FE->>D: Redirect /contracts/{id}<br/>toast "Đã lưu draft"

3. API chi tiết

GET /api/forms/templates?type=HopDongGiaoKhoan

Response:

[
  {
    "id": "a0b1-...",
    "formCode": "FO-002.05",
    "name": "Hợp đồng Giao khoán",
    "contractType": 2
  }
]

GET /api/forms/templates/{id}

Response:

{
  "id": "a0b1-...",
  "formCode": "FO-002.05",
  "name": "Hợp đồng Giao khoán",
  "fieldSpec": {
    "sections": [
      {
        "title": "Thông tin hai bên",
        "fields": [
          { "name": "benA_tenCongTy", "label": "Tên Bên A", "type": "string", "required": true },
          { "name": "benA_diaChi", "label": "Địa chỉ Bên A", "type": "text" },
          { "name": "benA_maSoThue", "label": "MST Bên A", "type": "string", "pattern": "^[0-9]{10,13}$" },
          { "name": "benB_supplierId", "label": "NCC (Bên B)", "type": "reference", "table": "Suppliers" }
        ]
      },
      {
        "title": "Giá trị hợp đồng",
        "fields": [
          { "name": "giaTri", "label": "Giá trị (VND)", "type": "decimal", "required": true, "min": 0 },
          { "name": "ngayKy", "label": "Ngày ký dự kiến", "type": "date" }
        ]
      }
    ]
  }
}

POST /api/forms/render

Request:

{
  "templateId": "a0b1-...",
  "data": {
    "benA_tenCongTy": "Công ty TNHH Xây dựng Solutions",
    "benA_diaChi": "123 Đường ABC, TP.HCM",
    "benB_supplierId": "d9e2-...",
    "giaTri": 150000000,
    "ngayKy": "2026-05-10"
  }
}

Response: binary stream .docx (or .pdf nếu convert).

POST /api/contracts

Request:

{
  "type": 2,
  "supplierId": "d9e2-...",
  "projectId": "p7a1-...",
  "departmentId": "dp3b-...",
  "templateId": "a0b1-...",
  "giaTri": 150000000,
  "draftData": { /* full field values */ }
}

Response:

{
  "id": "c5d6-...",
  "phase": "DangSoanThao",
  "createdAt": "2026-04-21T10:00:00Z"
}

4. Validation rules (FluentValidation)

public class CreateContractCommandValidator : AbstractValidator<CreateContractCommand>
{
    public CreateContractCommandValidator(IApplicationDbContext db)
    {
        RuleFor(x => x.Type).IsInEnum();
        RuleFor(x => x.SupplierId).NotEmpty().MustAsync(SupplierExists);
        RuleFor(x => x.ProjectId).NotEmpty().MustAsync(ProjectActive);
        RuleFor(x => x.TemplateId).NotEmpty().MustAsync(TemplateActive);
        RuleFor(x => x.GiaTri).GreaterThan(0);
        RuleFor(x => x.DraftData).NotNull();
        // Template field validation — đọc FieldSpec + validate draftData match
        RuleFor(x => x).CustomAsync(async (cmd, ctx, ct) =>
        {
            var tpl = await db.ContractTemplates.FindAsync(cmd.TemplateId, ct);
            var spec = JsonSerializer.Deserialize<FormFieldSpec>(tpl.FieldSpec);
            // check required fields present in draftData
        });
    }
}

5. Side effects

Action Side effect
POST /contracts INSERT Contracts (Phase=DangSoanThao)
AuditInterceptor: set CreatedAt, CreatedBy = Drafter.Id
(Phase 3) Emit ContractCreatedEvent domain event
POST /forms/render preview Write temp file wwwroot/uploads/preview/{guid}.docx (auto cleanup sau 1h)
POST /forms/render với persist=true Save final file vào ContractAttachments với Purpose='Export'

6. UI wireframe (fe-user)

/contracts/new
┌────────────────────────────────────────────────────┐
│  Tạo hợp đồng mới                                  │
├────────────────────────────────────────────────────┤
│  Bước 1/4: Chọn loại hợp đồng                      │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐            │
│  │ Trọn gói │ │ Giao     │ │ Mua bán  │ ...        │
│  │ NC+VT    │ │ khoán    │ │          │            │
│  └──────────┘ └──────────┘ └──────────┘            │
├────────────────────────────────────────────────────┤
│  Bước 2/4: Chọn template                           │
│  ○ FO-002.02 Trọn gói nhân công + vật tư          │
│  ● FO-002.05 Giao khoán ← đã chọn                 │
├────────────────────────────────────────────────────┤
│  Bước 3/4: Điền thông tin                          │
│  [Thông tin 2 bên]                                 │
│    Tên Bên A: [Công ty TNHH ...]                  │
│    NCC (Bên B): [Autocomplete → chọn NCC]         │
│  [Giá trị HĐ]                                      │
│    Giá trị: [150,000,000  VND]                    │
│  ...                                               │
├────────────────────────────────────────────────────┤
│  Bước 4/4: Preview + Lưu                           │
│  [PDF preview inline]                              │
│  [← Quay lại] [💾 Lưu draft] [🚀 Submit góp ý]    │
└────────────────────────────────────────────────────┘

7. Edge cases

Case Xử lý
NCC chưa có trong DB FE modal "Tạo NCC mới" inline → callback back vào form
Template bị deactivate khi đang soạn Warning + cho phép tiếp tục với template cũ
Draft bị trùng (user nhấn Save 2 lần) Idempotency key header — return existing contract
User mất kết nối khi preview FE retry + show spinner; nếu fail 3 lần → disable button + show cached data
Giá trị HĐ vượt ngân sách dự án Warning (không block) — sẽ check lại ở phase CCM Review
Template thiếu field bắt buộc FormValidator block Save + highlight field đỏ

8. Performance

  • Template load 1 lần + cache TanStack Query {queryKey: ['template', id], staleTime: 10min}
  • Preview render: có thể tốn 1-3s cho .docx lớn → show spinner + allow cancel
  • Save draft < 500ms (chỉ INSERT 1 row)

9. Liên quan