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>
188 lines
7.8 KiB
Markdown
188 lines
7.8 KiB
Markdown
# 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:
|
|
|
|
```ts
|
|
// ❌ 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:
|
|
|
|
```csharp
|
|
// ❌
|
|
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 add` → `Unable 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<>`:
|
|
|
|
```csharp
|
|
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):
|
|
|
|
```powershell
|
|
$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 :8082` → `taskkill /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
|