Files
solution-erp/docs/gotchas.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

7.8 KiB

Gotchas — SOLUTION_ERP

Các bẫy, pitfall đã gặp + cách xử lý. Đọc trước khi debug tương tự để không mất thời gian.

Tech stack constraints (.NET 10 + TS 6 + Vite 8)

1. MediatR 14.x không tương thích → pin 12.4.1

Triệu chứng: Unable to resolve service for type 'MediatR.IMediator'AddMediatR vẫn chạy nhưng không register IMediator.

Nguyên nhân: MediatR v14 (late 2025) refactored, extension methods khác.

Fix: Downgrade <PackageReference Include="MediatR" Version="12.4.1" />. Khi đó RequestHandlerDelegate<TResponse> là delegate không tham số (v14 có thêm CancellationToken param).

2. Swashbuckle 10.x + Microsoft.OpenApi 2.x breaking change

Triệu chứng: Build fail The type or namespace 'Models' does not exist in 'Microsoft.OpenApi'. Swagger endpoint 404.

Nguyên nhân: .NET 10 template auto-cài Microsoft.AspNetCore.OpenApi 10 → pull Microsoft.OpenApi 2.0 → namespace Microsoft.OpenApi.Models đã bị remove.

Fix:

  • Remove Microsoft.AspNetCore.OpenApi khỏi Api
  • Downgrade Swashbuckle về 6.9.0 (compatible với OpenApi 1.x)

3. TypeScript 6 erasableSyntaxOnly cấm enum

Triệu chứng: TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled. khi dùng enum.

Nguyên nhân: Vite 8 scaffold bật erasableSyntaxOnly: true — enum sinh runtime code nên bị cấm.

Fix: Dùng const + as const + typeof[keyof] pattern:

// ❌ Không được
export enum SupplierType { NhaCungCap = 1 }

// ✅ OK
export const SupplierType = { NhaCungCap: 1, NhaThauPhu: 2 } as const
export type SupplierType = typeof SupplierType[keyof typeof SupplierType]

4. TypeScript 6 deprecate baseUrl

Triệu chứng: TS5101: Option 'baseUrl' is deprecated.

Fix: Bỏ baseUrl trong tsconfig.app.json, chỉ giữ paths. Paths resolve relative to tsconfig location.

5. Node 22 local nhưng CI phải pin 20

Bài học NamGroup: CI build fail trên Node latest, phải downgrade. Dev local dùng Node 22 thoải mái.

Fix:

  • package.json engines: ">=20" (min only, không upper bound)
  • .nvmrc = 20 (CI dùng)
  • GitHub/Gitea Actions: actions/setup-node@v4 với node-version-file: '.nvmrc' hoặc hardcode '20.x'

EF Core 10

6. Expression tree không support switch expression

Triệu chứng: CS8514: An expression tree may not contain a switch expression khi viết .AnyAsync(p => action switch { ... }).

Fix: Tách switch ra ngoài, mỗi case gọi query riêng:

// ❌
var hasPermission = await query.AnyAsync(p => action switch { "Read" => p.CanRead, ... });

// ✅
var hasPermission = action switch
{
    "Read" => await query.AnyAsync(p => p.CanRead),
    "Create" => await query.AnyAsync(p => p.CanCreate),
    ...
};

7. Design-time DbContext resolve fail

Triệu chứng: dotnet ef migrations addUnable to resolve service for type 'DbContextOptions<ApplicationDbContext>'.

Fix: Tạo IDesignTimeDbContextFactory<ApplicationDbContext> trong Infrastructure — EF CLI sẽ dùng factory này thay vì chạy full Host.

8. AddDefaultTokenProviders() không tồn tại trong AddIdentityCore

Triệu chứng: Build fail IdentityBuilder does not contain AddDefaultTokenProviders.

Nguyên nhân: AddIdentityCore là minimal variant, không include token providers (password reset, email confirmation).

Fix: Bỏ call AddDefaultTokenProviders() nếu chưa cần. Khi cần password reset (Phase 4), chuyển sang AddIdentity hoặc add package Microsoft.AspNetCore.Identity.UI.

OpenXml / ClosedXML (Form Engine Phase 2)

9. SpaceProcessingModeValues.Preserve namespace không tìm thấy

Triệu chứng: CS0103: The name 'SpaceProcessingModeValues' does not exist.

Fix: Dùng full path DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve và wrap trong EnumValue<>:

textElement.Space = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.SpaceProcessingModeValues>(
    DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve);

10. Placeholder {{field}} bị split giữa 2 <w:t> elements

Vấn đề: Word hay split text thành nhiều run (style, typo check), placeholder {{giaTri}} có thể bị chia thành {{gia + Tri}} nằm trong 2 <w:t> khác nhau → regex replace miss.

Fix (đã implement trong DocxRenderer): Iterate theo Paragraph, gom text của mọi <w:t> trong cùng paragraph → replace → gán lại vào <w:t> đầu + clear rest. Giữ run style của text đầu.

11. Word COM SaveAs PowerShell type conversion error

Triệu chứng: Cannot convert "..." value of type "psobject" to type "Object" khi gọi $doc.SaveAs([ref]$outPath, [ref]16).

Fix: Dùng SaveAs2 (không đòi ref parameters):

$doc.SaveAs2($outPath, 16)  # 16 = wdFormatDocumentDefault (.docx)

12. Word COM stuck/hang

Triệu chứng: Script chạy không xong, process WINWORD.EXE còn nhưng CPU idle hoặc high.

Nguyên nhân: Hidden dialog (activation, recovery, template warning) block COM.

Fix:

  • Set $word.DisplayAlerts = 0 trước khi mở file
  • Nếu stuck → Get-Process WINWORD | Stop-Process -Force
  • Fallback: dùng LibreOffice headless soffice --headless --convert-to docx file.doc

System.Text.Json (ASP.NET Core 10)

13. Record constructor deserialization fail với Unicode

Triệu chứng: POST JSON chứa ký tự tiếng Việt từ Windows bash/curl CLI → 400 "JSON value could not be converted to ... CreateSupplierCommand. Path: $.name".

Nguyên nhân: Encoding CLI không đúng UTF-8 khi pass vào curl -d '{...}'.

Fix:

  • Test qua file: curl --data-binary @payload.json (file lưu UTF-8 thật)
  • Không phải bug backend — API handle UTF-8 đúng qua axios/Swagger

File operations

14. Dropbox sync có thể "revert" file đang edit

Triệu chứng: Write file thành công, build pass, nhưng file thực tế vẫn là nội dung cũ.

Case cụ thể (Phase 1): Program.cs Write thành công nhưng runtime chạy với default scaffold code.

Fix: Sau Write file quan trọng → Read lại hoặc head -5 để xác nhận nội dung. Nếu phát hiện revert → Write lại ngay.

15. .gitignore wwwroot/uploads/ vs wwwroot/templates/

Quy ước:

  • wwwroot/uploads/ignore (user-uploaded files, không commit)
  • wwwroot/templates/commit (template files là source of truth, phải version control)
  • wwwroot/exports/ → ignore (rendered output, tạm)

Dev workflow

16. Port conflict khi restart dev server

Triệu chứng: npm run dev fail với Port 8082 is in use.

Nguyên nhân: Background task trước chưa kill hẳn.

Fix: TaskStop task cũ, hoặc kill process listening port: netstat -ano | findstr :8082taskkill /F /PID <pid>.

17. EF migration tạo 3 file, COMMIT ĐỦ

Quy tắc: Mỗi migration tạo:

  • {timestamp}_{Name}.cs — up/down
  • {timestamp}_{Name}.Designer.cs — model snapshot lúc đó
  • ApplicationDbContextModelSnapshot.cs — current snapshot (update mỗi lần)

Commit đủ 3 file. Nếu thiếu, team khác dotnet ef database update sẽ fail.

Checklist khi gặp bug mới

  1. Build có pass không? Nếu fail → check using + package version
  2. Log API startup có error ẩn không? → tail output file
  3. File đã persist đúng chưa? → head -5 verify
  4. Nếu là package compat → thử downgrade về stable (không dùng preview/latest)
  5. Nếu là TS error exotic → check tsconfig flags (erasableSyntaxOnly, verbatimModuleSyntax)
  6. Nếu là EF expression tree error → tách logic ra ngoài query