Đọ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>
7.9 KiB
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.OpenApikhỏi Api - Downgrade Swashbuckle về
6.9.0
3. TypeScript 6 erasableSyntaxOnly cấm enum
Fix: Dùng const + as const + typeof[keyof] pattern:
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.jsonengines:">=20"(min, không upper).nvmrc=20cho CI- GitHub/Gitea Actions:
actions/setup-node@v4vớinode-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:
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<>:
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:
$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:
& "$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
- Build pass không? → fail → check using + package version compat
- DI register đủ? → runtime error "Unable to resolve" → add
AddScoped/Singleton - API log startup có error ẩn? →
tailoutput file - File đã persist đúng chưa? →
head -5verify sau Write - Nếu package exotic → thử downgrade về stable trước
- Nếu TS error → check
erasableSyntaxOnly,verbatimModuleSyntax - Nếu EF expression tree → tách logic ra ngoài query
- Nếu Unicode CLI → dùng file payload
- Nếu workflow 403 → check FE NEXT_PHASES sync BE