All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m42s
Đọc QT-TP-NCC.docx: quy trình 9 bước chỉ áp dụng cho Thầu phụ/NCC/Tổ đội.
Dịch vụ/Mua bán/Nguyên tắc bypass CCM. Thay hardcoded dict bằng policy
registry.
Domain — WorkflowPolicy.cs:
- Record WorkflowPolicy { Name, Description, Transitions, PhaseSla,
ActivePhases } — pure data, testable.
- WorkflowPolicies.Standard: 9-phase full (Thầu phụ/Giao khoán/NCC)
- WorkflowPolicies.SkipCcm: 7-phase (Dịch vụ/Mua bán/Nguyên tắc)
- WorkflowPolicyRegistry.For(type) map ContractType → policy
- WorkflowPolicyRegistry.ForContract(c) override nếu BypassProcurement
AndCCM=true (instance-level escape hatch)
Infrastructure — ContractWorkflowService:
- Xóa hardcoded Transitions/PhaseSla dicts → load từ policy.ForContract
- TransitionAsync: validate qua policy.Transitions thay vì dict local
- Error message include policy.Name để debug dễ hơn
- GetPhaseSla trả SLA từ Standard policy (fallback — SLA hiện tại giống
nhau giữa 2 policy)
Application — ContractDetailDto:
- Field mới `Workflow: WorkflowSummaryDto { PolicyName, Description,
ActivePhases, NextPhases }` — FE dùng để render nút chuyển phase
dynamic + timeline card.
- BuildWorkflowSummary helper trong ContractFeatures.
FE (both apps):
- Type WorkflowSummary + ContractDetail.workflow
- ContractDetailPage xóa hardcoded NEXT_PHASES — dùng
c.workflow.nextPhases từ BE (single source of truth)
- WorkflowSummaryCard: timeline của ActivePhases với check/current/
future states + policy name/description ở header
- Card hiển thị trong sidebar, phía trên "Lịch sử duyệt"
Docs:
- gotchas.md #21 marked RESOLVED (NEXT_PHASES sync không còn cần)
Foundation: sau này admin có thể edit policy qua UI khi chuyển sang DB-
backed policy — nhưng API contract (WorkflowSummaryDto) đã stable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
211 lines
7.9 KiB
Markdown
211 lines
7.9 KiB
Markdown
# Gotchas — SOLUTION_ERP
|
|
|
|
> Bẫy/pitfall đã gặp + cách xử lý. Đọc trước khi debug tương tự để không mất thời gian. Cập nhật liên tục khi gặp bug mới.
|
|
|
|
## 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.
|
|
|
|
**Fix:** Pin `MediatR 12.4.1`. Khi đó `RequestHandlerDelegate<TResponse>` là delegate không tham số (v14 có thêm CancellationToken).
|
|
|
|
### 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 404.
|
|
|
|
**Fix:**
|
|
- Remove `Microsoft.AspNetCore.OpenApi` khỏi Api
|
|
- Downgrade Swashbuckle về `6.9.0`
|
|
|
|
### 3. TypeScript 6 `erasableSyntaxOnly` cấm `enum`
|
|
|
|
**Fix:** Dùng `const + as const + typeof[keyof]` pattern:
|
|
|
|
```ts
|
|
export const SupplierType = { NhaCungCap: 1 } as const
|
|
export type SupplierType = typeof SupplierType[keyof typeof SupplierType]
|
|
```
|
|
|
|
### 4. TypeScript 6 deprecate `baseUrl`
|
|
|
|
**Fix:** Bỏ `baseUrl` trong tsconfig, chỉ giữ `paths`. Paths resolve relative tsconfig location.
|
|
|
|
### 5. Node 22 local vs CI pin 20
|
|
|
|
**Bài học NamGroup:** CI build fail trên Node latest.
|
|
|
|
**Fix:**
|
|
- `package.json` engines: `">=20"` (min, không upper)
|
|
- `.nvmrc` = `20` cho CI
|
|
- GitHub/Gitea Actions: `actions/setup-node@v4` với `node-version: '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`.
|
|
|
|
**Fix:** Tách switch ra ngoài LINQ:
|
|
|
|
```csharp
|
|
var hasPermission = action switch
|
|
{
|
|
"Read" => await query.AnyAsync(p => p.CanRead),
|
|
"Create" => await query.AnyAsync(p => p.CanCreate),
|
|
_ => false,
|
|
};
|
|
```
|
|
|
|
### 7. Design-time DbContext resolve fail
|
|
|
|
**Triệu chứng:** `dotnet ef migrations add` → `Unable to resolve service for type 'DbContextOptions<T>'`.
|
|
|
|
**Fix:** Tạo `IDesignTimeDbContextFactory<ApplicationDbContext>` trong Infrastructure.
|
|
|
|
### 8. `AddDefaultTokenProviders()` không có trong `AddIdentityCore`
|
|
|
|
**Fix:** Bỏ call nếu chưa cần password reset. Khi cần, chuyển `AddIdentity` hoặc add package `Microsoft.AspNetCore.Identity.UI`.
|
|
|
|
## OpenXml / ClosedXML
|
|
|
|
### 9. `SpaceProcessingModeValues` namespace
|
|
|
|
**Fix:** Full path + wrap `EnumValue<>`:
|
|
|
|
```csharp
|
|
textElement.Space = new DocumentFormat.OpenXml.EnumValue<
|
|
DocumentFormat.OpenXml.SpaceProcessingModeValues>(
|
|
DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve);
|
|
```
|
|
|
|
### 10. Placeholder `{{field}}` bị split runs
|
|
|
|
**Vấn đề:** Word hay split text thành nhiều `<w:t>` — placeholder miss khi regex replace.
|
|
|
|
**Fix:** Iterate Paragraph, gom text tất cả `<w:t>` → replace → gán lại text đầu + clear rest. Đã implement trong `DocxRenderer`.
|
|
|
|
### 11. Word COM `SaveAs` PowerShell type conversion
|
|
|
|
**Fix:** Dùng `SaveAs2`:
|
|
|
|
```powershell
|
|
$doc.SaveAs2($outPath, 16) # 16 = wdFormatDocumentDefault
|
|
```
|
|
|
|
### 12. Word COM stuck
|
|
|
|
**Fix:**
|
|
- `$word.DisplayAlerts = 0`
|
|
- Nếu stuck → `Get-Process WINWORD | Stop-Process -Force`
|
|
- Fallback: LibreOffice headless `soffice --headless --convert-to docx`
|
|
|
|
## System.Text.Json
|
|
|
|
### 13. Record deserialization fail với Unicode qua CLI
|
|
|
|
**Triệu chứng:** POST JSON tiếng Việt từ Windows bash/curl → 400 "JSON value could not be converted".
|
|
|
|
**Fix:** Dùng `curl --data-binary @file.json` (file UTF-8). API handle đúng qua axios/Swagger.
|
|
|
|
## File operations
|
|
|
|
### 14. Dropbox sync có thể revert file đang edit
|
|
|
|
**Triệu chứng:** Write thành công, build pass, runtime chạy code cũ.
|
|
|
|
**Fix:** Sau Write quan trọng → Read lại verify. Nếu revert → Write lại.
|
|
|
|
### 15. `.gitignore` wwwroot rules
|
|
|
|
- `wwwroot/uploads/` → **ignore** (user files)
|
|
- `wwwroot/templates/` → **commit** (source of truth)
|
|
- `wwwroot/exports/` → ignore (temp)
|
|
|
|
## Dev workflow
|
|
|
|
### 16. Port conflict khi restart dev server
|
|
|
|
**Fix:** `TaskStop` task cũ, hoặc `netstat -ano | findstr :8082` → `taskkill /F /PID <pid>`.
|
|
|
|
### 17. EF migration 3-file rule
|
|
|
|
Mỗi migration tạo: `{name}.cs` + `{name}.Designer.cs` + `ApplicationDbContextModelSnapshot.cs`. Commit đủ 3.
|
|
|
|
## Claude Code harness quirks
|
|
|
|
### 18. Edit tool "File not read" sau system-reminder
|
|
|
|
**Triệu chứng:** Edit file vừa Read, lỗi "File has not been read yet".
|
|
|
|
**Nguyên nhân:** System reminder interrupt reset read-cache.
|
|
|
|
**Fix:** Read lại file rồi Write/Edit. Hoặc dùng Write (ghi đè full) thay Edit.
|
|
|
|
### 19. Build pass nhưng DI thiếu registration
|
|
|
|
**Triệu chứng:** `dotnet build` → 0 errors nhưng runtime throw `Unable to resolve service`.
|
|
|
|
**Nguyên nhân:** C# compiler chỉ check type, không check DI graph.
|
|
|
|
**Fix:** Sau thêm interface mới + impl → luôn add `services.AddScoped<IX, X>()` trong `DependencyInjection.cs`. Test API start up là OK check.
|
|
|
|
## Contract workflow
|
|
|
|
### 20. Mã HĐ gen 2 lần sau reject → approve lại
|
|
|
|
**Fix:** Check `if (contract.MaHopDong is null)` trước khi gen. Đã implement trong `ContractWorkflowService.TransitionAsync`.
|
|
|
|
### 21. ~~BE adjacency vs FE NEXT_PHASES sync~~ (RESOLVED)
|
|
|
|
**Đã xử lý:** FE không còn hardcode `NEXT_PHASES` nữa. BE expose `contract.workflow.nextPhases` trong `ContractDetailDto` từ `WorkflowPolicyRegistry.ForContract(contract)`. FE render dynamic từ đó — single source of truth.
|
|
|
|
Nếu đổi policy BE: chỉ cần update `WorkflowPolicies.Standard` hoặc `WorkflowPolicies.SkipCcm` trong `Domain/Contracts/WorkflowPolicy.cs`. FE tự reflect.
|
|
|
|
### 22. Race condition gen mã HĐ khi 2 user cùng transition tới DangDongDau
|
|
|
|
**Fix:** `IsolationLevel.Serializable` transaction trong `ContractCodeGenerator`. Không skip.
|
|
|
|
## Permission matrix
|
|
|
|
### 23. Permission update không real-time
|
|
|
|
**Triệu chứng:** Admin tick permission cho role X → user X vẫn thấy menu cũ.
|
|
|
|
**Nguyên nhân:** FE cache menu trong `localStorage`, không auto refetch.
|
|
|
|
**Fix:** User phải logout/login. Phase 3 iteration 2 có thể thêm SignalR push "permission-changed" → FE tự refetch `/menus/me`.
|
|
|
|
### 24. MenuKey typo — không check type
|
|
|
|
**Fix:** Luôn dùng `MenuKeys.Contracts` const (BE) + `MenuKeys.Contracts` (FE `menuKeys.ts`). Không hardcode string.
|
|
|
|
## IIS / Windows Server
|
|
|
|
### 25. `Install-WindowsFeature Web-WebSockets` khóa section `<webSocket>` ở applicationHost
|
|
|
|
**Triệu chứng:** Sau khi install WebSocket feature → TẤT CẢ IIS site có `<webSocket enabled="true" />` trong web.config trả về HTTP 500.19 với error code `0x80070021` "configuration section cannot be used at this path" — kể cả site khác project không liên quan.
|
|
|
|
**Nguyên nhân:** Feature install thêm `<webSocket>` section vào `applicationHost.config` với `overrideModeDefault="Deny"`. Site web.config override section đó → fail.
|
|
|
|
**Fix:** Unlock section ở server level:
|
|
```powershell
|
|
& "$env:SystemRoot\system32\inetsrv\appcmd.exe" unlock config -section:system.webServer/webSocket
|
|
```
|
|
|
|
Tương tự khi dùng URL Rewrite `<serverVariables>` cần unlock `system.webServer/rewrite/allowedServerVariables`.
|
|
|
|
**Cảnh báo co-existence:** Trên VPS shared với project khác, enable feature mới qua `Install-WindowsFeature` có thể làm sập site project khác. Luôn test all site sau mỗi enable.
|
|
|
|
## Checklist debug bug mới
|
|
|
|
1. Build pass không? → fail → check using + package version compat
|
|
2. DI register đủ? → runtime error "Unable to resolve" → add `AddScoped/Singleton`
|
|
3. API log startup có error ẩn? → `tail` output file
|
|
4. File đã persist đúng chưa? → `head -5` verify sau Write
|
|
5. Nếu package exotic → thử downgrade về stable trước
|
|
6. Nếu TS error → check `erasableSyntaxOnly`, `verbatimModuleSyntax`
|
|
7. Nếu EF expression tree → tách logic ra ngoài query
|
|
8. Nếu Unicode CLI → dùng file payload
|
|
9. Nếu workflow 403 → check FE NEXT_PHASES sync BE
|