Files
solution-erp/docs/changelog/sessions/2026-04-21-1200-phase2-form-engine.md
pqhuy1987 5113e4c771 [CLAUDE] Phase2: Form Engine MVP + docs (gotchas, skill, handoff)
Backend Forms:
- Domain/Forms: ContractTemplate (FormCode, Name, ContractType, FileName, StoragePath, Format, FieldSpec JSON, IsActive) + ContractClause
- EF config voi unique FormCode + query filter IsDeleted
- DbSets + IApplicationDbContext update
- Migration AddForms (bang 14 total)
- Packages: DocumentFormat.OpenXml 3.x + ClosedXML 0.105+
- Application/Forms:
  - IFormRenderer interface + RenderResult record
  - FormFeatures.cs: List/Get/Render CQRS
  - IWebHostEnvironmentLocator (abstract IWebHostEnvironment)
- Infrastructure/Forms:
  - DocxRenderer: OpenXml-based placeholder {{field}} replace, handle split runs (gom text tat ca <w:t> trong paragraph, replace, gan lai text dau + clear rest)
  - XlsxRenderer: ClosedXML cell value replace
  - FormRenderer router theo format docx/xlsx
- Api:
  - FormsController: GET /templates (filter type, onlyActive), GET /templates/{id}, POST /templates/{id}/render (return file)
  - WebHostEnvironmentLocator impl
- DbInitializer SeedContractTemplatesAsync: seed 8 template metadata, IsActive=true chi khi file ton tai

Templates vat ly:
- Copy 5 .docx/.xlsx tu FORM/ sang wwwroot/templates/
- 3 .doc (FO-002.02/03/06) chua convert: IsActive=false (Word COM bi stuck luc test, can retry voi DisplayAlerts=0 hoac LibreOffice)
- scripts/convert-doc-to-docx.ps1 (Word COM automation)

Frontend fe-admin:
- types/forms.ts: ContractTemplate + ContractTypeLabel
- pages/forms/FormsPage.tsx: list templates + Render dialog (paste JSON data → download .docx/.xlsx)
- Route /forms them vao App.tsx

Bug fix:
- SpaceProcessingModeValues namespace: wrap EnumValue<> full path
- SaveAs2($path, 16) thay vi SaveAs([ref], [ref]) — PowerShell type issue
- Word COM stuck: kill process, skip .doc cho MVP

Docs (theo yeu cau user):
- docs/gotchas.md MOI: 17 pitfalls nhom theo tech stack / EF Core / OpenXml / JSON / dev workflow
- .claude/skills/form-engine/SKILL.md: placeholder → full spec (algorithm + code pointers + API + limitations)
- .claude/skills/permission-matrix/SKILL.md: placeholder → full spec (BE policy + FE guard + seed + pitfalls)
- docs/HANDOFF.md MOI: brief 5 phut cho session sau (run quickstart + where we are + next steps + file tree + gotchas ref)
- docs/STATUS.md: update cumulative stats + next up Phase 3
- docs/changelog/migration-todos.md: tick Phase 2 iteration 1 items + add iteration 2 list
- docs/changelog/sessions/2026-04-21-1200-phase2-form-engine.md: session log
- CLAUDE.md root: them reference den gotchas + HANDOFF

E2E verified:
- GET /api/forms/templates (onlyActive=false) → 8 templates
- POST /api/forms/templates/{FO-002.05}/render voi data dict → HTTP 200 + file .docx 482KB (Microsoft Word 2007+ OK)

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

5.4 KiB

Session 2026-04-21 12:00 — Phase 2 Form Engine MVP

Dev: Claude (Opus 4.7) Duration: ~1h Base commit: 54d6c9b

Làm được

Chunk D — Forms domain + packages

  • NuGet: DocumentFormat.OpenXml 3.x + ClosedXML 0.105+ (Infrastructure)
  • Domain: Forms/ContractTemplate (FormCode, Name, ContractType, FileName, StoragePath, Format, FieldSpec JSON, IsActive), Forms/ContractClause (Code, Name, Content rich text, Version)
  • EF configs: unique index FormCode, query filter IsDeleted
  • DbSets + IApplicationDbContext update
  • Migration AddForms

Chunk E — Renderer + Application + Controller

  • Application/Forms/Services/IFormRenderer + RenderResult record
  • Infrastructure/Forms/DocxRenderer — OpenXml-based, xử lý placeholder bị split runs (gom text tất cả <w:t> trong paragraph → replace → gán lại vào text đầu)
  • Infrastructure/Forms/XlsxRenderer — ClosedXML-based, replace cell value nếu là text chứa placeholder
  • Infrastructure/Forms/FormRenderer — router theo format docx/xlsx
  • Register IFormRenderer as Singleton trong Infrastructure DI
  • Application/Forms/FormFeatures.cs:
    • ListContractTemplatesQuery (filter type + onlyActive)
    • GetContractTemplateQuery
    • RenderTemplateCommand + Validator + Handler (resolve absolute path qua IWebHostEnvironmentLocator)
  • IWebHostEnvironmentLocator interface trong Application — abstract IWebHostEnvironment, impl ở Api layer (WebHostEnvironmentLocator)
  • Api/Controllers/FormsController: GET templates, GET single, POST render (return file)

Chunk F — Convert + Seed + FE

  • scripts/convert-doc-to-docx.ps1 — Word COM automation. Chạy thử bị stuck (hidden dialog) → killed process. 3 file .doc chưa convert — đánh dấu IsActive=false trong seed.
  • Copy 5 file .docx/.xlsx từ FORM/wwwroot/templates/
  • DbInitializer.SeedContractTemplatesAsync — seed 8 template, check file exists để set IsActive
  • FE: types/forms.ts (ContractTemplate + ContractTypeLabel), pages/forms/FormsPage.tsx — list + render button + Dialog điền JSON data → download file
  • Route /forms add vào App.tsx (menu Layout đã có path sẵn)

E2E verified

GET /api/forms/templates?onlyActive=false → 8 templates (5 active, 3 inactive)
POST /api/forms/templates/{fo-002.05-id}/render
  body: {"benA_tenCongTy": "Solutions Construction", "giaTri": "150,000,000 VND", "ngayKy": "21/04/2026"}
  → HTTP 200, file .docx 482KB (Microsoft Word 2007+ format) — OK mở được bằng Word

TS check fe-admin pass.

Bug gặp + fix

Bug Fix
SpaceProcessingModeValues namespace không tìm thấy Dùng full path + wrap EnumValue<>
Word COM SaveAs([ref]) type conversion error Đổi sang SaveAs2($path, 16)
Word COM stuck (2 process, 164s CPU) Kill process, fallback: skip .doc convert, đánh dấu IsActive=false cho 3 template tương ứng
Edit tool "File has not been read yet" sau system-reminder interrupt Read lại rồi Write full file

Docs updates trong session này

  • docs/gotchas.md (MỚI) — 17 bẫy đã gặp từ Phase 0 → 2, nhóm theo: tech stack constraints, EF Core, OpenXml/ClosedXML, System.Text.Json, file ops, dev workflow
  • .claude/skills/form-engine/SKILL.md — update từ placeholder → full spec với code pointers, algorithm, API, limitations
  • .claude/skills/permission-matrix/SKILL.md — update từ placeholder → full spec với BE policy + FE guard usage + pitfalls
  • docs/STATUS.md — mark Phase 2 MVP done
  • docs/changelog/migration-todos.md — tick Phase 2 items đã xong

Handoff cho session tiếp theo

Phase 2 còn lại (iteration 2)

  • Convert 3 file .doc (retry Word COM với DisplayAlerts=0 + set timeout) HOẶC dùng LibreOffice headless
  • Field spec JSON mỗi template — cho phép FE render dynamic form thay vì điền JSON tay
  • Form builder FE: dynamic render từ fieldSpec → validation → preview → submit
  • Support {{#loop}}...{{/loop}} block (cho table hạng mục lặp ở FO-002.05, FO-002.07)
  • PDF convert via LibreOffice headless (hoặc Aspose nếu mua license)
  • Admin upload template mới qua UI (POST multipart)
  • ContractClause rich text editor (TipTap) cho admin edit FO-002.04

Phase 3 — Workflow (sắp tới)

Xem docs/flows/contract-approval-flow.md.

Các việc lớn:

  • Entity Contract + ContractApproval + ContractComment + ContractAttachment
  • IContractWorkflowService.TransitionAsync() với state guard + role guard
  • IContractCodeGenerator theo RG-001 với transaction SERIALIZABLE
  • SlaExpiryJob hosted service auto-approve
  • Email + in-app notification service
  • FE Inbox + Contract detail + timeline UI

Còn optional (không block Phase 3)

  • Users management FE (tạo user + gán role)
  • Roles CRUD
  • fe-user menu động (hiện tại chưa sync với AuthContext menu pattern từ fe-admin)

Blocker

  • Gitea remote URL

Thông số sau Phase 2 MVP

  • Git commits: 4 (từ scaffold) + sắp thêm 1
  • Backend LOC: ~1900 (thêm ~400 cho Forms)
  • DB tables: 14 (thêm ContractTemplates + ContractClauses)
  • API endpoints: ~23 (thêm Forms 3)
  • FE pages: 6 (thêm Forms)
  • Templates vật lý: 5 .docx/.xlsx trong wwwroot/templates/