e45909712b
[CLAUDE] App+Infra+FE-Admin: DynamicForm + .doc/.xls auto-convert on upload
...
Deploy SOLUTION_ERP / build-deploy (push) Failing after 1m17s
Tier 3 iter2 — form builder UI dùng FieldSpec thay raw JSON textarea.
FE:
- DynamicForm component — parse FieldSpec JSON (record of FieldDef với
label/type/required/placeholder/hint/options) và render inputs
dynamic theo type: text/textarea/number/date/currency/select.
- FormsPage render dialog thêm toggle Form ↔ JSON (segmented control).
Mặc định Form mode khi template có FieldSpec, JSON mode khi không.
Khi mở dialog cho row khác, reset formValues + chọn đúng default mode.
- parseFieldSpec helper trả { spec, error } — UI báo lỗi nếu JSON
không parse được, fallback JSON textarea.
BE — generalize converter thành IDocumentConverter:
- IPdfConverter → IDocumentConverter (ConvertAsync(bytes, src, tgt, ct))
— đủ gánh cả pdf, docx, xlsx targets.
- LibreOfficeDocumentConverter — 1 shell-out pattern cho mọi conversion
(docx→pdf, doc→docx, xls→xlsx, xlsx→pdf), target arg truyền vào
--convert-to.
- ExportTemplatePdfCommand update dùng "pdf" target.
Auto-convert .doc/.xls trên upload:
- Validator accept thêm .doc/.xls (thêm note "sẽ tự convert").
- UploadContractTemplateCommandHandler: nếu ext là doc/xls → read stream
→ converter.ConvertAsync → lưu file .docx/.xlsx thay vì format gốc.
File rendering pipeline (DocxRenderer/XlsxRenderer) chỉ support docx/
xlsx — convert đảm bảo consistent.
- Display FileName preserve original name nhưng đổi extension.
Unblock 3 file .doc legacy template — admin giờ upload .doc bình thường,
system tự convert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 21:35:05 +07:00
6bbd894d96
[CLAUDE] App+Infra+Api+FE-Admin: PDF export (LibreOffice headless)
...
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m33s
Pipeline: template.docx → FormRenderer fill placeholders → LibreOffice
soffice --headless --convert-to pdf → PDF byte[] → File() stream to
browser.
Clean-arch split:
- Application: IPdfConverter abstraction (swap to QuestPDF/Aspose later
without touching caller).
- Infrastructure: LibreOfficePdfConverter — shells out to soffice.exe
path from config (Pdf:SofficePath, default
`C:\Program Files\LibreOffice\program\soffice.exe` on Windows).
Per-request temp workDir để tránh filename collision + -env:
UserInstallation isolate mỗi conversion (chống "soffice already
running" khi concurrent). Timeout 60s (configurable). Best-effort
cleanup. Kill entire process tree nếu timeout.
- Application: ExportTemplatePdfCommand — reuses existing FormRenderer
+ pipes bytes through IPdfConverter. Same data dict signature as
Render để UI code share.
- Api: POST /api/forms/templates/{id}/export-pdf (same JSON body as
/render, returns PDF stream).
FE:
- useExport hook chung cho 2 endpoints (DRY render + export-pdf mutations)
- Render dialog thêm nút "Tải PDF" (outline variant) cạnh "Tải file gốc".
Disabled khi mutation khác đang chạy.
- Hướng dẫn dialog nâng cấp: "file gốc để edit Word/Excel, PDF để
in/gửi không chỉnh sửa được".
Ops: scripts/install-libreoffice.ps1 — silent MSI install 25.8.6 cho
VPS (đã chạy trên prod).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 21:28:31 +07:00
166d26c1d8
[CLAUDE] App+Api+FE-Admin: Form template builder (upload + edit + delete)
...
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m44s
Admin giờ có thể quản lý template HĐ hoàn toàn qua UI — không cần dev
đụng vào file system hay seed data.
BE (FormFeatures.cs + FormsController.cs):
- UploadContractTemplateCommand (multipart): validate FormCode
(regex [A-Za-z0-9._-]+, unique), file <= 10MB, ext .docx/.xlsx,
FieldSpec phải là JSON hợp lệ hoặc null. Ghi file vào
wwwroot/templates/{formCode}_{guid:N}.{ext} để tránh collision
+ path traversal.
- UpdateContractTemplateCommand: sửa metadata + FieldSpec + IsActive
(không đụng file — chỉ DB).
- DeleteContractTemplateCommand: soft delete qua IsActive=false
(historical contracts ref template này vẫn resolve).
- Endpoints: POST /api/forms/templates (multipart),
PUT /api/forms/templates/{id}, DELETE /api/forms/templates/{id}.
RequestSizeLimit 12MB (validator caps 10MB).
FE (FormsPage.tsx admin):
- PageHeader action button "Upload template" mở dialog mới
- Row actions: Download (render existing), Pencil (edit), Trash (xóa
confirm) thay vì chỉ có 1 nút Render — row hover reveals clearly
- Upload dialog: file picker với file: pseudo-element brand styled,
FormCode (required, font-mono), Tên, Loại HĐ select, Mô tả,
FieldSpec JSON textarea với placeholder example
- Edit dialog: same fields minus file (FormCode disabled, edit chỉ
cập nhật metadata), có checkbox Kích hoạt
- Shared form submit handler — same dialog cho upload (__new) + edit
Foundation sẵn cho form builder thật (render UI từ FieldSpec JSON
đang là text field — iteration sau sẽ parse + render form dynamic).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 21:15:35 +07:00
ea9ab5e352
[CLAUDE] App+Infra+Api+FE: SignalR realtime notifications E2E
...
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m43s
Clean-arch split:
- Application: IRealtimeNotifier (PushToUserAsync, abstraction)
- Api: NotificationHub (/hubs/notifications, [Authorize]) +
SignalRNotifier impl với IHubContext<NotificationHub>, uses
Clients.User(userId) (default provider resolves NameIdentifier="sub")
- Infrastructure: NotificationPushInterceptor — SaveChangesInterceptor
capture Notification entities state=Added trong SavingChanges,
push qua IRealtimeNotifier trong SavedChanges sau khi commit thành
công. Zero caller changes — handlers chỉ cần db.Add(Notification).
Attached vào ApplicationDbContext cùng với AuditingInterceptor.
Auth:
- JWT config thêm OnMessageReceived event: read ?access_token= từ
query string khi path = /hubs/* (WebSockets không set headers).
- SignalRNotifier singleton (stateless, chỉ delegate IHubContext).
FE (both apps):
- @microsoft/signalr 8.0.7 vào package.json.
- lib/realtime.ts: singleton connection với lazy start + automatic
reconnect [0,2s,5s,10s,15s] + accessTokenFactory lấy từ localStorage.
- NotificationBell: useEffect subscribe 'notification-created' khi
isAuthenticated. On push: invalidate query + toast.message. Fallback
polling giảm từ 30s → 60s (realtime cover gap).
- AuthContext.logout: dynamic import stopConnection() — avoid leaking
auth'd socket across users.
Result: ERP-grade feel. Contract transition → Drafter nhận toast ngay
trong vòng 100-300ms (same-origin WebSocket), không cần F5 hay polling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 20:56:37 +07:00
dc3f09b8d4
[CLAUDE] FE: drop unused Button import (fixes TS6133 CI error)
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m38s
2026-04-21 20:42:48 +07:00
c8d0070770
[CLAUDE] App+Infra+Api+FE: Attachment upload E2E
...
Deploy SOLUTION_ERP / build-deploy (push) Failing after 1m40s
Foundation file-storage:
- IFileStorage interface (Application) — SaveAsync/OpenReadAsync/
DeleteAsync/Exists. Future swap cho S3/Azure Blob không đổi caller.
- LocalFileStorage (Infrastructure) — resolve Uploads:RootPath từ
config, path-traversal guard (resolved full path phải stay in root),
tự tạo directory khi save.
- DI: singleton (stateless).
- Config: dev "uploads", prod "C:\inetpub\solution-erp\uploads".
CQRS:
- UploadContractAttachmentCommand: validate size <=20MB + MIME whitelist
(pdf, doc/docx, xls/xlsx, png/jpg/jpeg/webp). Sanitize filename
(strip path components + invalid FS chars + leading dots). Storage
path: contracts/{contractId}/{attId}_{safeFileName}.
- DownloadContractAttachmentQuery: trả Stream + FileName + ContentType.
- DeleteContractAttachmentCommand: best-effort file delete sau DB remove
(orphan cleanup job có thể sweep sau).
Api:
- POST /api/contracts/{id}/attachments — multipart/form-data, field
'file' + form fields 'purpose' + 'note'. RequestSizeLimit 25MB
(validator enforces 20MB).
- GET /api/contracts/{id}/attachments/{attId}/download — File() stream.
- DELETE /api/contracts/{id}/attachments/{attId}.
FE ContractAttachmentsSection (both apps, identical):
- Drag-drop zone với dragging highlight (brand-500 border + brand-50 bg)
- Purpose selector (DraftExport / ScannedSigned / SealedCopy / Other)
- List có icon per MIME (FileText/Image/File), filename, metadata
(purpose · size · createdAt), download button (fetch blob + trigger
browser save với auth header), delete button (confirm dialog)
- Empty state hint về use-case ("bản scan HĐ đã ký ở phase In ký…")
Integrated vào cả 2 ContractDetailPage — ngay dưới phần comments,
trước sidebar lịch sử duyệt.
Unblock E2E workflow: users giờ có thể upload bản scan ký (DangInKy),
scan đóng dấu (DangDongDau) — phase transitions có bằng chứng thật.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 20:37:35 +07:00
346bd5d644
[CLAUDE] FE: content polish — typography + PageHeader + Button/Input/Table
...
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m54s
Typography base (both apps):
- 14px base, line-height 1.55, letter-spacing -0.003em — Be Vietnam
Pro render chắc hơn, dấu tiếng Việt rõ hơn
- h1/h2 tracking tighter (-0.018em), h1 weight 700, h2 weight 600
- Tabular numbers trong <table> (tự động align cột số)
PageHeader:
- Title text-[22px] thay vì text-xl — hierarchy mạnh hơn
- Border-bottom + pb-5 thay flat layout — content-area rõ vùng
- Description leading-relaxed + slate-500 — dễ đọc hơn
Button:
- shadow-sm + color-tinted shadow (brand/20, red/20) cho primary/
danger — có chiều sâu
- active:translate-y-[0.5px] micro-press feedback
- Ring offset 2 (thay 1) + offset-white — focus ring tách rõ
Input/Select/Textarea:
- h-9 thay h-10 — phù hợp dense table layouts
- shadow-[inset_0_1px_0_...] — inset highlight tinh tế
- Focus: border-brand-500 + ring-brand-500/20 — 2 lớp chỉ báo
- Disabled: bg-slate-50 + opacity-70 — rõ disabled state
DataTable:
- rounded-xl + shadow-sm + border-200/80 — card feel nhẹ nhàng hơn
- Header: UPPERCASE text-[11px] tracking-wider — ERP enterprise look
- Row hover: bg-brand-50/40 (thay slate-50) — brand-tinted hover
- Padding tăng từ px-3 py-2 → px-4 py-2.5 — breathing room
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 16:16:53 +07:00
bf1fbe3870
[CLAUDE] FE: favicon dùng chữ 'S' thật từ logo Solutions
...
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m4s
Pipeline:
- Pixel-crop chữ 'S' script từ logo.png (x=0..86, y=0..100 — trước
underline + tagline)
- Scale lên 2x thành 172x200 trên canvas 256x256 transparent,
center có padding ~12%
- Save thành mark.png (6.8KB, transparent bg)
- Embed base64 vào favicon.svg với nền trắng rounded-r-48 (contrast
với chữ S xanh brand) — scale mọi size
- index.html thêm apple-touch-icon + alternate PNG cho browsers
không support SVG favicon
Kết quả: favicon giờ là glyph 'S' script thật của Solutions, không
phải font-rendered text nữa. Contrast trắng-xanh dễ nhận ra ở size nhỏ.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 16:10:05 +07:00
4abb5596d5
[CLAUDE] FE-Admin+FE-User: brand identity từ Solutions logo
...
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m54s
Lấy logo gốc từ template docx (SOL-CCM-FO-002.05) và brand color
exact pixel-sampled #1F7DC1 từ chữ "Solutions".
Thay đổi:
- logo.png (407x145, từ header docx) đặt vào /public cả 2 app
- favicon.svg: "S" trắng trên nền vuông brand blue bo góc
- index.css: palette brand-50..900 generate quanh #1F7DC1 + accent
red-500/600 cho ® mark + font Be Vietnam Pro (Google Fonts,
designed cho tiếng Việt, diacritics đẹp) với fallback Inter
+ JetBrains Mono cho font-mono + tùy chỉnh scrollbar
- Layout sidebar: logo.png 32px + "Admin"/"ERP" subtitle (thay
text "SOLUTION ERP" đơn điệu)
- LoginPage: gradient background brand-50 + 2 decorative orbs
blur, rounded-2xl card + backdrop-blur, big logo 56px + subtitle
tracking-[0.2em]
- index.html: lang="vi", title "Solutions ERP · Admin" / "Solutions
ERP", theme-color #1F7DC1 cho mobile address bar, preconnect
fonts.gstatic.com để load Google Fonts nhanh hơn
Tất cả màu hardcoded trong component đã dùng `brand-600` → tự
map sang palette mới, không cần đổi logic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 15:57:45 +07:00
c73e3f904b
[CLAUDE] FE: login error message hướng dẫn diagnose khi Network Error
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m55s
2026-04-21 15:46:04 +07:00
49c0ddc8f4
[CLAUDE] App+Domain+Infra+Api+FE: Notifications module end-to-end
...
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m43s
Domain:
- Notification entity + NotificationType enum (stable ints)
- Nullable RefId cho correlation (contract, user, ...)
Infrastructure:
- NotificationConfiguration: bảng Notifications, index theo (UserId, ReadAt)
- NotificationService: ghi vào DbContext, không SaveChanges (để caller quyết
định unit-of-work — đảm bảo atomic với domain mutation)
- EF migration AddNotifications
Application:
- INotificationService (Notify + NotifyMany)
- CQRS: ListMyNotifications / GetMyUnreadCount / MarkRead / MarkAllRead
Api:
- NotificationsController: GET /api/notifications + unread-count + mark-read
Integration:
- ContractWorkflowService emit notification tới Drafter khi HĐ chuyển phase
(skip nếu actor chính là Drafter). Title + type theo phase đích:
DaPhatHanh → ContractPublished, TuChoi → ContractRejected, khác →
ContractPhaseTransition.
FE:
- Both NotificationBell (admin + user) dùng /api/notifications thật
(thay cho derived-from-inbox MVP trước đó). 30s refetch, click mark-read,
'Đọc hết' bulk action.
Foundation sẵn cho SignalR push + email outbox sau này — chỉ cần mở rộng
NotificationService mà không đổi caller.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 15:24:09 +07:00
6c0e20649a
[CLAUDE] FE-Admin: PermissionsPage improved (search + stats + column bulk toggle + empty state)
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m47s
2026-04-21 15:17:58 +07:00
2b6f91c2b2
[CLAUDE] FE: TopBar + NotificationBell + UserMenu — ERP shell foundation
...
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
Kiến trúc Layout giờ tách thành [sidebar] [topbar + content], foundation
để scale thêm module trong tương lai (HR, Accounting, Inventory...).
TopBar: title placeholder + NotificationBell + UserMenu (initials avatar
+ role badges + logout). UserMenu thay cho bottom-of-sidebar (cleaner).
NotificationBell:
- fe-admin: cảnh báo SLA (HĐ quá/sắp quá hạn, 24h window)
- fe-user: hộp thư chờ xử lý (items trong /contracts/inbox)
- refetchInterval: 60s
- Placeholder cho SignalR/email notifications thật sẽ thay bằng
/api/notifications endpoint ở Tier 3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 15:16:15 +07:00
2e43799046
[CLAUDE] FE: EmptyState component + MyContracts CTA empty state
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
2026-04-21 15:13:43 +07:00
c1c23619de
[CLAUDE] FE: DataTable skeleton rows khi loading (thay 'Đang tải…' text đơn giản)
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m36s
2026-04-21 15:06:40 +07:00
290936a0ca
[CLAUDE] CICD+FE-Admin+FE-User: deploy pool-state guard + SlaTimer component
...
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
CICD: check app pool state before Stop-WebAppPool (idempotent).
FE: new SlaTimer component with color-coded countdown (emerald/amber/red)
and progress bar. Two variants:
- inline: used in list tables (Inbox, Contracts list x2, MyContracts)
- full: used in ContractDetail card with progress bar + deadline timestamp
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 15:04:44 +07:00
b1a4571c86
[CLAUDE] VPS setup scripts + SSL + runner + FE prod config + master runbook
...
Deploy SOLUTION_ERP / build-backend (push) Failing after 9s
Deploy SOLUTION_ERP / build-fe-admin (push) Has been cancelled
Deploy SOLUTION_ERP / build-fe-user (push) Has been cancelled
Deploy SOLUTION_ERP / deploy-iis (push) Has been cancelled
Scripts moi (PowerShell admin trên VPS Windows Server):
- setup-sql-db.ps1: tao DB SolutionErp + grant db_owner cho vrapp (user shared voi VIETREPORT). Idempotent.
- setup-iis-sites.ps1: app pool SolutionErp-Api (NoManagedCode + AlwaysRunning + no idle) + 3 site (SolutionErp-Api/Admin/User) voi host header, C:\inetpub\solution-erp\{api,fe-admin,fe-user,logs,uploads}. Placeholder index.html + SPA web.config voi URL rewrite fallback + security headers. Firewall rule. ACL grant AppPool identity Modify. Naming prefix SolutionErp-* tranh conflict VIETREPORT.
- setup-ssl.ps1: download win-acme v2.2.9 → issue cert Let's Encrypt 3 domain (api/admin/user.huypham.vn) qua HTTP-01 challenge + auto install IIS binding + HTTP→HTTPS redirect + scheduled task 90d renew.
- setup-gitea-runner.ps1: download act_runner.exe → register voi Gitea git.baocaogiaoduc.vn, install Windows service, labels windows-latest,self-hosted,windows,x64 (cho phep share voi VIETREPORT).
FE production config:
- fe-admin/.env.production + fe-user/.env.production: VITE_API_BASE_URL=https://api.huypham.vn
- fe-admin/src/lib/api.ts + fe-user/src/lib/api.ts: BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? '') + '/api'
- Dev: empty prefix → /api qua Vite proxy :5443
- Prod: https://api.huypham.vn/api (cross-origin CORS da config AllowedOrigins)
Docs:
- docs/guides/vps-setup.md MOI (master runbook): prereq, 4 script chay theo thu tu, set 5 Gitea secrets, first deploy, appsettings.Production.json pattern (file hoac user-secrets), smoke test 3 curl, post go-live checklist (doi admin password, rotate secrets chat-exposed, backup schedule, disable Swagger prod, monitor logs), table co-existence VIETREPORT
- CLAUDE.md root: add vps-setup.md reference
Gitea repo da setup (extern):
- https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp (private)
- Secrets set via API: IIS_HOST=103.124.94.38, IIS_USER=Administrator, DB_CONNECTION (voi vrapp password), JWT_SECRET placeholder
- CON THIEU: IIS_PASSWORD (Windows admin — user cung cap), JWT_SECRET real value (64-char tu vps-jwt-key.txt — user update qua Gitea UI)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 13:37:34 +07:00
11e61c9c39
[CLAUDE] Phase5.1: Security headers + account lockout + Users management
...
Security hardening:
- Api/Middleware/SecurityHeadersMiddleware MOI: remove server fingerprint (Server, X-Powered-By, ...), add X-Content-Type-Options:nosniff, X-Frame-Options:DENY, Referrer-Policy:strict-origin-when-cross-origin, Permissions-Policy (disable geolocation/mic/cam/payment), X-Permitted-Cross-Domain-Policies:none, CSP (default-src 'self' + img data: + style inline for Tailwind + frame-ancestors 'none'). Skip CSP tren /swagger (dung inline script).
- Program.cs wire UseMiddleware SecurityHeadersMiddleware first in pipeline
- Infrastructure/DependencyInjection Identity options:
- Password.RequiredLength config-driven (Identity:Password:RequiredLength, default 8 dev, override 12+ prod)
- Lockout: DefaultLockoutTimeSpan (15min), MaxFailedAccessAttempts (5), AllowedForNewUsers=true — all config-driven
- LoginCommandHandler: IsLockedOutAsync check truoc → throw voi deadline message, AccessFailedAsync khi sai password, ResetAccessFailedCountAsync khi login thanh cong
Users management:
- Application/Users/UserFeatures.cs: 8 CQRS (ListUsersQuery paging+search, GetUserQuery, CreateUserCommand + Validator, UpdateUserCommand voi self-disable protection, AssignRolesCommand voi self-demote protection (khong tu go Admin), ResetPasswordCommand (invalidate refresh token + unlock), UnlockUserCommand)
- UserDto: Id, Email, FullName, IsActive, IsLocked (computed tu LockoutEnd), CreatedAt, Roles
- Api/Controllers/UsersController: 7 endpoint (Users.Read/Create/Update policies):
- GET / (list paged), GET /{id}, POST /, PUT /{id}, PUT /{id}/roles, POST /{id}/reset-password, POST /{id}/unlock
- using alias ValidationException = Application.Common.Exceptions.ValidationException (fix ambiguity voi FluentValidation)
Frontend fe-admin:
- types/users.ts MOI: User type + AVAILABLE_ROLES 12 role (match BE AppRoles.cs) + RoleLabel Vietnamese
- pages/system/UsersPage.tsx MOI:
- DataTable columns: Email (mono), FullName, Roles (badge chips voi Vietnamese label), IsActive (CheckCircle/XCircle), IsLocked (KeyRound red), CreatedAt
- Actions per row (PermissionGuard Users.Update wrap): Gan role (Shield icon → Dialog grid 12 checkbox), Reset password (KeyRound → Dialog voi warning user se bi logout), Unlock (Unlock icon, chi hien khi isLocked), Toggle active (XCircle/CheckCircle)
- Create user dialog: email + fullName + password (min 8) + grid 12 role checkbox
- Route /system/users vao App.tsx
E2E verified:
- Security headers present tren moi response (check qua curl -I)
- POST /api/users voi roles: [Drafter] → 201 + id
- GET /api/users → paged voi 2 user (admin + new test.drafter)
- TS check fe-admin → pass
- dotnet build → 0 errors
Docs:
- docs/STATUS.md: Phase 5.1 xong, cumulative BE 3700 LOC, 42 endpoints, 17 FE pages
- docs/HANDOFF.md: phase table update row Phase 5.1, last updated timestamp
- docs/changelog/migration-todos.md: tick 6 items Phase 5.1 + 4 items remaining (IDOR, deps scan, admin warning, Roles CRUD)
- docs/changelog/sessions/2026-04-21-1630-phase5-1-security-users.md: session log
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
EOF
2026-04-21 13:06:46 +07:00
f3fb3fd565
[CLAUDE] Phase5 prep: production infra + deploy scripts + 4 guides + FE refresh token
...
Backend production infra:
- Packages: Serilog.Sinks.File, HealthChecks.EntityFrameworkCore (RateLimiting built-in .NET 10)
- appsettings.Production.json MOI: placeholder __SET_VIA_SECRETS__, AllowedOrigins, Serilog File sink rolling daily retention 30d, RateLimit config
- appsettings.json + Development.json: them Serilog WriteTo Console
- Program.cs REWRITE:
- Serilog ReadFrom.Configuration (prod file / dev console)
- Rate limiter: policy auth-login 5/min/IP (AuthController.Login) + GlobalLimiter 300/min/IP
- Health checks: /health/live liveness (empty predicate) + /health/ready DB probe (AddDbContextCheck)
- HSTS production 1 year
- CORS origins from config AllowedOrigins (default dev 2 localhost)
- AuthController.Login gắn [EnableRateLimiting("auth-login")]
Deploy scripts:
- scripts/deploy-iis.ps1: stop pool → backup current → clean+extract artifact → start pool → health check loop 30s timeout → rollback instruction if fail
- scripts/backup-sql.ps1: BACKUP DATABASE voi INIT+COMPRESSION+CHECKSUM + retention 30d auto cleanup
- .gitea/workflows/deploy.yml MOI: 4 job build BE (Windows) + build 2 FE (Ubuntu, pin .nvmrc 20) + deploy-iis qua WinRM PSSession (secrets IIS_HOST/USER/PASSWORD/JWT_SECRET/DB_CONNECTION)
Docs guides MOI (4 file):
- deployment-iis.md: prereqs (IIS features, Hosting Bundle, SQL, WinRM) + setup lan dau (app pool, 3 site, HTTPS win-acme, user-secrets) + deploy hang ngay (CI/CD + manual) + rollback + monitoring + troubleshooting + SPA web.config sample
- cicd.md: pipeline overview 4 job, secrets setup, runner Windows+Ubuntu, branch strategy, build optimizations, common CI/CD issues
- security-checklist.md: OWASP top 10 2021 mapping voi status + pre go-live checklist + incident response
- runbook.md: daily ops (health/logs), restart/rollback, DB backup/restore/migration revert, user management (reset password, unlock, disable), monitoring (CPU/disk/connection pool), deployment checklist, common gotcha
Frontend refresh token (ca 2 app fe-admin + fe-user):
- lib/api.ts REWRITE: them REFRESH_KEY, axios response interceptor 401 → POST /auth/refresh → retry request goc. Queue pattern cho nhieu request song song chi 1 refresh call chay. Skip retry /auth/login + /auth/refresh tranh infinite loop. _retry flag tren original config.
- contexts/AuthContext.tsx: luu+xoa REFRESH_KEY trong login/logout
E2E verified:
- GET /health/live → 200 Healthy
- GET /health/ready → 200 Healthy (DB probe)
- Rate limit flood 7 POST /auth/login → #1-5 HTTP 400 (cred sai) + #6-7 HTTP 429 Too Many Requests ✅
- TS check fe-admin + fe-user → pass
- dotnet build → 0 errors
Docs updates:
- docs/STATUS.md: Phase 5 prep done, next Phase 5 deploy production + Phase 5.1 security hardening, cumulative stats 8 commits
- docs/HANDOFF.md: phase table them Phase 5 prep row, file tree update voi guides + scripts + workflows, git state commit 8
- docs/changelog/migration-todos.md: tick Phase 5 prep items (12 items done) + Phase 5 deploy items remaining + Phase 5.1 security hardening list
- docs/changelog/sessions/2026-04-21-1530-phase5-prep.md: session log chi tiet
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 12:57:12 +07:00
fe7ad8e4a3
[CLAUDE] Phase4: Report MVP + Docs Consolidation (rules, architecture, schema-diagram)
...
Backend Report:
- Application/Reports/Dtos/DashboardStatsDto: 5 KPI + PhaseCount + SupplierCount + ProjectCount + MonthlyValue
- Application/Reports/Queries/GetDashboardStats handler: total/active/overdue/published this month/totalValueActive + byPhase + top 5 NCC/du an + 12 thang monthly (fill zero khi thang empty)
- Application/Reports/Services/IContractExcelExporter interface
- Infrastructure/Reports/ContractExcelExporter: ClosedXML workbook 10 cot, header style bold+blue, number format #,##0, formula SUM, auto-fit, freeze header
- Application/Reports/Commands/ExportContractsToExcelCommand: filter phase/supplier/project/date range
- Api/Controllers/ReportsController: GET /reports/dashboard, GET /reports/contracts/export
- DI register IContractExcelExporter (Scoped)
Frontend fe-admin:
- types/reports.ts: DashboardStats type
- components/BarChart.tsx: generic horizontal bar chart — chi Tailwind, khong thu vien ngoai
- pages/DashboardPage.tsx REWRITE: 5 KPI card (FileText/TrendingUp/AlertTriangle/CheckCircle2/Coins) + by-phase bar + monthly 12-month chart + top 5 NCC + top 5 du an + skeleton loader
- pages/ReportsPage.tsx MOI: filter phase/fromDate/toDate → export Excel button
- Route /reports vao App.tsx
E2E verified:
- GET /api/reports/dashboard → 200 voi day du KPI + monthly fill 12 thang
- GET /api/reports/contracts/export → 200 xlsx 7229 bytes (Microsoft Excel 2007+)
Docs consolidation (theo yeu cau user):
- docs/rules.md MOI: 9 section coding conventions (ngon ngu UI/code/DB/docs, BE Clean Arch, CQRS+MediatR, Validation FluentValidation, Error handling, Async, Entity rules, DI, Package pinning, FE React/TS erasableSyntaxOnly, path alias, TanStack Query, Permission guard, Toast+error, DB convention, Git commit format, Docs structure, Testing, Security)
- docs/architecture.md MOI: layered overview ASCII art, request lifecycle (1 POST/api/contracts qua 10 step), workflow state machine 9 phase, permission model, data flow sequence diagram 4 actor (Drafter/Manager/CCM/BOD/HRA), deployment architecture Phase 5, skill library, non-functional table
- docs/database/schema-diagram.md MOI: full ERD 19 table mermaid + data flow diagram + vong doi 1 HD (create → 7 transition → gen ma → publish) + index strategy table + relationship cardinality + soft delete behavior + SQL queries (inbox/dashboard/gen ma) + migration history
- docs/gotchas.md UPDATE: 17 → 26 pitfalls, them section "Claude Code harness quirks" (Edit File not read, DI build pass nhung runtime fail) + "Contract workflow" (ma HD gen 2 lan, BE-FE NEXT_PHASES sync, race condition) + "Permission matrix" (cache real-time, MenuKey typo)
- docs/STATUS.md: Phase 4 MVP done, docs entry points section liet ke het, next Phase 5 Production
- docs/HANDOFF.md: phase table them Phase 4 row, file tree update voi Reports, test points day du, git state commit 7
- docs/changelog/migration-todos.md: tick Phase 4 MVP items + them iteration 2 list
- docs/changelog/sessions/2026-04-21-1430-phase4-report.md: session log voi thong so cumulative (BE 3100 LOC, 30 docs)
- CLAUDE.md root: update Tai lieu quan trong section them rules.md, architecture.md, schema-diagram.md, .claude/skills (13 links now)
Bug fix:
- TS unused import ContractPhaseLabel trong DashboardPage
- DI thieu register IContractExcelExporter — build pass but runtime would fail (added)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 12:42:46 +07:00
7e957a7654
[CLAUDE] Phase3: Workflow MVP — 9-phase state machine + code gen + FE Inbox/Detail
...
Backend Contracts domain (5 entities):
- Contract aggregate: Phase (9 enum), SlaDeadline, MaHopDong, BypassProcurementAndCCM, DraftData, SlaWarningSent
- ContractApproval: FromPhase → ToPhase, ApproverUserId (null = system auto-approve), Decision, Comment
- ContractComment: thread theo Phase current
- ContractAttachment: FileName + StoragePath + Purpose (DraftExport/ScannedSigned/SealedCopy)
- ContractCodeSequence: Prefix PK + LastSeq — atomic gen
EF configs:
- Unique MaHopDong filtered [MaHopDong] IS NOT NULL
- Indexes: Phase+IsDeleted, SupplierId, ProjectId, SlaDeadline, ContractId+ApprovedAt, ContractId+CreatedAt
- Cascade delete Approvals/Comments/Attachments khi Contract xoa
- Query filter IsDeleted
- Migration AddContractsWorkflow (DB 19 tables)
Workflow service:
- IContractWorkflowService.TransitionAsync:
- Adjacency check qua Transitions Dict<(from,to), roles[]> (12 transitions)
- Role guard: user phai co role ∈ allowed
- Admin bypass (role Admin pass moi check)
- System bypass (userId=null + Decision=AutoApprove → cho SLA job sau nay)
- Bypass CCM: BypassProcurementAndCCM=true cho phep DangInKy → DangTrinhKy skip phase 6
- Gen ma HD khi chuyen DangDongDau (idempotent — khong gen lai neu da co)
- Reset SlaDeadline = UtcNow + PhaseSla
- Insert ContractApproval row
Code generator (RG-001):
- 7 format theo ContractType: HDTP / HDGK / NCC / HDDV / MB + 2 framework (year prefix)
- BeginTransactionAsync(Serializable) + ContractCodeSequences UPSERT → atomic
- Idempotent: neu MaHopDong da co thi skip
CQRS (8 feature, ContractFeatures.cs):
- CreateContractCommand + Validator + Handler (set SlaDeadline = +7d)
- UpdateContractDraftCommand (chi khi Phase=DangSoanThao)
- TransitionContractCommand (delegate → WorkflowService)
- AddCommentCommand (phase = hien tai)
- ListContractsQuery (PagedResult + filter phase/supplier/project/search)
- GetMyInboxQuery (map Phase → actor roles, filter theo role user)
- GetContractQuery (detail + approvals + comments + attachments + resolve user names)
- DeleteContractCommand (soft, block > DangInKy)
Controller:
- ContractsController 8 endpoint: GET list/inbox/detail, POST create/transition/comment, PUT update, DELETE
Frontend fe-admin (2 page moi):
- types/contracts.ts: ContractPhase const + Label + Color maps + types
- components/PhaseBadge.tsx
- pages/contracts/ContractsListPage.tsx: filter phase + search + click → detail
- pages/contracts/ContractDetailPage.tsx: 2-col layout (info+comments | timeline), action dialog select target phase + comment
Frontend fe-user (4 page moi + 14 file shared):
- cp 14 file shared tu fe-admin (menuKeys, types/*, DataTable, PhaseBadge, Dialog, Textarea, Select, apiError, usePermission, PermissionGuard)
- AuthContext update: load menu tu /menus/me + cache
- Layout: menu fixed 3 muc + user info + roles display
- InboxPage: list HD cho role user xu ly (sort theo SLA)
- ContractCreatePage: form chon loai + template + NCC + du an + gia tri + bypass CDT
- ContractDetailPage: duplicate fe-admin pattern (convention)
- MyContractsPage: list HD cua toi
- App.tsx: 4 route moi
E2E verified:
- Setup Supplier + Project
- POST /contracts → 201 + phase=2
- POST /contracts/{id}/transitions x7 → di het 9 phase
- Final: MaHopDong = "FLOCK 01/HĐGK/SOL&PVL2026/01" dung format RG-001
- Approvals: 7 rows audit day du
Docs:
- .claude/skills/contract-workflow/SKILL.md: placeholder → full spec voi state machine, SLA table, role matrix, 7 code format, code pointers, API, E2E workflow, pitfalls
- docs/changelog/sessions/2026-04-21-1330-phase3-workflow.md: session log
- docs/STATUS.md: Phase 3 MVP done, next Phase 4
- docs/HANDOFF.md: update phase status + file tree + commit log + testing points
- docs/changelog/migration-todos.md: tick Phase 3 MVP items + add iteration 2 list
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 12:26:09 +07:00
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
54d6c9ba52
[CLAUDE] Phase1.2: CRUD Master + Permission Matrix + FE admin pages
...
Backend:
- Domain/Master: Supplier (+ SupplierType 5 loai), Project, Department (AuditableEntity)
- Domain/Identity: MenuItem, Permission, MenuKeys const (12 menu)
- EF Configurations voi unique Code + query filter IsDeleted
- DbSets + IApplicationDbContext interface update
- Application: PagedResult + PagedRequest generic
- Application/Master CQRS CRUD 3 entity (Create/Update/Delete/Get/List voi paging search sort)
- Application/Permissions: GetMyMenuTree (union OR role, filter tree), ListMenuItems, ListPermissionsByRole, UpsertPermission (guard admin khong tu giam quyen), ListRoles
- Api/Authorization: MenuPermissionRequirement + Handler (Admin bypass, query DB)
- Program.cs: register 48 policy {menu}.{action} tu MenuKeys x Actions
- Api/Controllers: Suppliers, Projects, Departments, Menus, Roles, Permissions
- DbInitializer: seed 12 menu + admin full CRUD permissions
- Migration AddMasterData + AddPermissions
Frontend (fe-admin):
- Types: menuKeys.ts const, menu.ts (MenuNode/Role/Permission), master.ts (Supplier/Project/Department + SupplierType const-object)
- AuthContext: load menu from /menus/me, cache localStorage, refreshMenu()
- usePermission hook + PermissionGuard component (wrap button)
- UI kit them: Dialog (modal overlay), Textarea, Select
- Generic: DataTable (column config, sortable, loading, empty) + Pagination
- PageHeader component
- apiError helper extract message tu ProblemDetails
- Layout rewrite: render menu dong tu AuthContext.menu (MenuGroup collapsible + NavLink + lucide icon map)
- Pages: master/Suppliers, master/Projects, master/Departments (CRUD + search + sort + paging + Dialog form)
- Page system/Permissions: ma tran Role x MenuKey x CRUD checkbox (tick tu dong PUT upsert)
- App.tsx them 4 route moi
Bug fix:
- MenuPermissionHandler: EF expression tree khong support switch expression -> tach switch ra ngoai AnyAsync
- TS erasableSyntaxOnly khong cho enum -> SupplierType const-object pattern (typeof[keyof])
E2E verified via Vite proxy:
- GET /menus/me -> 6 root + 6 child nodes (12 menus)
- GET /roles -> 12 roles
- POST/GET/PUT/DELETE /suppliers -> full CRUD, soft delete OK
- tsc -b fe-admin pass
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 11:30:14 +07:00
702411fcc8
[CLAUDE] Phase1: foundation - BE Clean Arch + Identity + JWT + 2 FE React + login E2E
...
Backend (.NET 10):
- Domain: BaseEntity/AuditableEntity, ContractType/Phase/ApprovalDecision enums, User/Role (Identity<Guid>), AppRoles (12 const)
- Application: IApplicationDbContext/ICurrentUser/IDateTime/IJwtTokenService, custom exceptions, ValidationBehavior (MediatR pipeline), Auth CQRS (Login/Refresh/Me), DependencyInjection
- Infrastructure: ApplicationDbContext (IdentityDbContext), AuditingInterceptor (auto audit + soft delete), DbInitializer (seed 12 role + admin), DesignTimeDbContextFactory, JwtTokenService, DateTimeService, DI
- Api: CurrentUserService, GlobalExceptionMiddleware (ProblemDetails), AuthController, Program.cs rewrite (Serilog + JWT + CORS + Swagger), appsettings + launchSettings (port 5443)
- Migration Init applied to SolutionErp_Dev LocalDB
Frontend (React 19 + Vite 8 + Tailwind 4):
- fe-admin (:8082 blue) + fe-user (:8080 emerald) - shared structure, khac menu + brand color
- Tailwind 4 via @tailwindcss/vite plugin, theme brand colors
- AuthContext (localStorage token), ProtectedRoute, Layout (sidebar + header)
- UI kit: Button/Input/Label (CVA + Tailwind)
- LoginPage voi toast error, DashboardPage/InboxPage placeholder
- Axios interceptor: auto Bearer + 401 redirect
- TanStack Query client, React Router 7, Sonner toast
Package downgrades (do .NET 10 / TS 6 compat):
- MediatR 14 -> 12.4.1 (v14 breaking changes)
- Swashbuckle 10 -> 6.9.0 (v10 khong tuong thich OpenApi 2)
- Removed Microsoft.AspNetCore.OpenApi (conflict voi Swashbuckle)
E2E verified: POST /api/auth/login qua Vite proxy ca 2 FE -> JWT + user info
Credentials seed: admin@solutionerp.local / Admin@123456
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 10:59:44 +07:00
25dad7f36f
[CLAUDE] Scaffold: khoi tao SOLUTION_ERP Phase 0
...
- .NET 10 Clean Architecture: Domain/Application/Infrastructure/Api (4 project)
- 2 React + Vite + TS app: fe-admin (:8082), fe-user (:8080) voi proxy /api
- Node engines >=20, .nvmrc = 20 cho CI (bai hoc NamGroup)
- SQL Server 2022 qua docker-compose (dev)
- Parse 8 FORM -> docs/forms-spec.md (catalog + ma HD format RG-001)
- Parse QUY_TRINH -> docs/workflow-contract.md (9 phase state machine + role matrix)
- docs: CLAUDE.md, STATUS.md, PROJECT-MAP.md, migration-todos.md (roadmap 5 phase)
- .claude/skills: 3 placeholder (contract-workflow, form-engine, permission-matrix)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 10:37:34 +07:00