Commit Graph

20 Commits

Author SHA1 Message Date
b75448e711 [CLAUDE] FE-User+FE-Admin: 3-panel layout cho danh sách HĐ
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m50s
Redesign trang Danh sách HĐ (Ct_*_List menu fe-user + /contracts admin)
thành 3-panel: List | Detail content | Workflow + lịch sử duyệt. Selected
HĐ giữ qua URL ?id= (bookmarkable + back/forward navigation work).

## Components mới (reuse cho cả 3-panel embedded + fullpage detail)

### fe-user/src/components/contracts/
- ContractDetailContent.tsx — Panel 2 body: header sticky (title + phase
  + actions Yêu cầu sửa/Duyệt) + Info section + Comments thread + form
  thêm góp ý + Attachments. Transition Dialog inline. Prop optional
  onBack — render arrow back button (fullpage) hoặc skip (embedded).
- WorkflowHistoryPanel.tsx — Panel 3: WorkflowSummaryCard (timeline
  policy current+next) + Lịch sử duyệt (approvals: phase from→to + actor
  + timestamp + comment).

### fe-admin/src/components/contracts/
- ContractDetailContent.tsx — variant admin có thêm Phòng ban + Bypass
  CCM trong Info section. Invalidate ['contracts'] khi transition.
- WorkflowHistoryPanel.tsx — identical fe-user.

## Trang refactored

### fe-user
- MyContractsPage.tsx — bỏ DataTable, dùng 3-panel grid
  lg:grid-cols-[320px_1fr_360px] h-[calc(100vh-4rem)]:
    Panel 1: search box + list compact (mã/tên/NCC/phase/SLA/giá), click
             update ?id= active highlight ring-brand
    Panel 2: detail content embedded
    Panel 3: workflow + history
  Mobile (<lg): chỉ Panel 1 visible, click row navigate fullpage
  /contracts/:id (UX khả dụng, không nhồi 3 panel màn hình hẹp).
  URL state: ?type=X (filter loại) + ?id= (selected) + ?q= (search).
- ContractDetailPage.tsx — slim version dùng ContractDetailContent +
  WorkflowHistoryPanel, giữ deep link /contracts/:id work.

### fe-admin
- ContractsListPage.tsx — 3-panel + filter phase + pagination compact
  trong Panel 1 footer. URL state: ?type, ?pendingMe, ?id, ?q, ?phase,
  ?page (full bookmarkable). Title hiển thị loại HĐ + count badge.
- ContractDetailPage.tsx — slim version giống fe-user.

## Build verified

- fe-user: tsc -b + vite build pass (1888 modules, 1.08MB JS)
- fe-admin: tsc -b + vite build pass (1903 modules, 1.15MB JS)

Note: npm install resolved @microsoft/signalr 8.0.7 → 8.0.17 (within
^8.0.7 caret), reverted package.json + lock changes do bump không phải
scope task này. Dev tiếp theo run npm install sẽ tự re-resolve.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 09:04:46 +07:00
f216169039 [CLAUDE] FE-Admin+Domain+Infra+App: Workflows tab → sidebar menu items
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m37s
User request: 7 tab trong /system/workflows thành menu items riêng.

Domain:
- MenuKeys.WorkflowTypeLeaf(code) helper — `Wf_<TypeCode>` pattern

Infrastructure (DbInitializer):
- Seed 7 leaves dưới Workflows group (order 95..101), label matches
  ContractType (HĐ Thầu phụ / Giao khoán / NCC / Dịch vụ / Mua bán /
  Nguyên tắc NCC / Nguyên tắc Dịch vụ). Idempotent.

Application (GetMyMenuTreeQuery):
- Generalized inherit-perm logic: descendants of Contracts AND Workflows
  inherit parent CanRead flag. Single Workflows.Read grant → all 7
  Wf_* leaves visible; no per-leaf permission rows needed.

FE Layout (admin):
- resolvePath: Wf_<Code> → /system/workflows/<code>. Ct_* still hidden
  on admin side.

FE App.tsx:
- New route /system/workflows/:typeCode?

FE WorkflowsPage:
- Removed horizontal tab bar; type selection now comes từ URL param.
- Landing view (no param): 3-col grid card per type với active version
  badge — so admin có visual overview khi click top-level Workflows
  group without selecting a type.
- TYPE_CODE_TO_INT map drives URL→int conversion.

Result: click `Quy trình HĐ > HĐ Mua bán` trong sidebar → opens
/system/workflows/MuaBan directly với designer scoped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 09:49:42 +07:00
5e0f3801a1 [CLAUDE] Move nested-type menu → fe-user; Admin workflow config page
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m41s
User clarified: menu loại HĐ 3-level (Danh sách/Thao tác/Duyệt) thuộc
fe-user. Admin có page riêng để config quy trình per loại HĐ.

fe-admin Layout:
- filterForAdmin() drops Ct_* entries (hide nested type menu).
- Admin sidebar giờ về lại đơn giản: Dashboard / Master / Hợp đồng
  (leaf) / Forms / Reports / System.

fe-user Layout:
- Dynamic menu tree từ /menus/me (thay fixed USER_MENU hardcoded).
- Recursive MenuNodeRenderer (top-level expanded, nested collapsed).
- resolvePath user-specific: Ct_*_List → /my-contracts?type=X,
  Ct_*_Create → /contracts/new?type=X, Ct_*_Pending → /inbox?type=X.
- filterForUser drops admin-only entries (Master/System/Forms/Reports).
- Static USER_FIXED_TOP prepends "Hộp thư" leaf → /inbox.
- MyContractsPage + InboxPage đọc ?type=X param, filter client-side.

Workflow config (Admin side):
- Domain: WorkflowTypeAssignment entity (ContractType → PolicyName
  override). Registry.ForContractWithOverrides() prefer DB override
  else default.
- Infrastructure: EF config + migration AddWorkflowTypeAssignments,
  unique index trên ContractType. ContractWorkflowService load
  overrides dict mỗi transition. ContractFeatures load overrides khi
  build WorkflowSummaryDto.
- Application: GetWorkflowAdminOverviewQuery returns 7 types × current
  policy + available policies. SetWorkflowAssignmentCommand validate
  policy name tồn tại; nếu = default thì delete override (no stale row).
- Api: GET /api/workflows + PUT /api/workflows/{contractType}
  với policy "Workflows.Read" + "Workflows.Update".
- Menu: new key `Workflows` dưới System, label "Quy trình HĐ".
- FE /system/workflows: 7 card per type, dropdown Standard/SkipCcm +
  'Đã override' badge khi khác default, phase sequence timeline,
  explanation banner ở top. Iteration 2 note: admin-authored custom
  policies.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:41:05 +07:00
48e91fe7ca [CLAUDE] Domain+Infra+App+FE-Admin: per-ContractType nested sidebar menu
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m48s
User request: mỗi loại HĐ có menu riêng với 3 action Danh sách /
Thao tác / Duyệt.

Sidebar giờ 3-level under "Hợp đồng":
  Hợp đồng (group, expandable)
  ├── HĐ Thầu phụ (sub-group)
  │   ├── Danh sách → /contracts?type=1
  │   ├── Thao tác  → /contracts/new?type=1
  │   └── Duyệt      → /contracts?type=1&pendingMe=1
  ├── HĐ Giao khoán (sub-group)
  ├── HĐ NCC / Dịch vụ / Mua bán / Nguyên tắc NCC / Nguyên tắc DV
  └── ... (7 types × 4 = 28 new menu items)

BE:
- MenuKeys.cs: ContractTypeCodes array + helpers ContractTypeGroup/
  List/Create/Pending → key format Ct_<TypeCode>[_<Action>]
- DbInitializer.SeedMenuTreeAsync: loop seeds 28 entries under Contracts
- GetMyMenuTreeQuery.BuildChildren: descendants of `Contracts` inherit
  parent permission (avoid adding 28 rows to Permissions table per role)

FE:
- Layout.tsx recursive: MenuNodeRenderer dispatches group vs leaf by
  depth; nested groups collapsed by default (top-level expanded).
  Deeper levels get smaller padding/text + left border guide.
- Pattern-based resolvePath: Ct_<Type>_<Action> → URL with query.
- Contract type code → int map (matches Domain ContractType enum).
- ContractsListPage reads ?type + ?pendingMe, filters client-side.
  Header title + description reflect active filter. "← Tất cả loại"
  quick-reset button.
- ContractCreatePage new cho admin (copy từ fe-user), pre-select type
  từ ?type URL param.
- App.tsx route /contracts/new → ContractCreatePage.

Pure navigation UX; no new permissions needed. Admin + any role with
Contracts.Read see full menu; leaves click-through to filtered views.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:25:00 +07:00
cae4d84830 [CLAUDE] Domain+Infra+App+FE: dynamic workflow policy per ContractType
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>
2026-04-21 21:46:31 +07:00
e45909712b [CLAUDE] App+Infra+FE-Admin: DynamicForm + .doc/.xls auto-convert on upload
Some checks failed
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
ea9ab5e352 [CLAUDE] App+Infra+Api+FE: SignalR realtime notifications E2E
All checks were successful
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)
All checks were successful
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
Some checks failed
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
All checks were successful
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
4abb5596d5 [CLAUDE] FE-Admin+FE-User: brand identity từ Solutions logo
All checks were successful
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
49c0ddc8f4 [CLAUDE] App+Domain+Infra+Api+FE: Notifications module end-to-end
All checks were successful
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
2b6f91c2b2 [CLAUDE] FE: TopBar + NotificationBell + UserMenu — ERP shell foundation
Some checks failed
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
Some checks failed
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)
All checks were successful
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
Some checks failed
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
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
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