[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>
This commit is contained in:
pqhuy1987
2026-04-21 10:59:44 +07:00
parent 25dad7f36f
commit 702411fcc8
85 changed files with 10326 additions and 964 deletions

View File

@ -2,54 +2,75 @@
> **Update rule:** trước khi bắt đầu 1 task → ghi row vào `🔥 In Progress`. Xong → chuyển sang `✅ Recently Done`.
**Last updated:** 2026-04-21
**Last updated:** 2026-04-21 11:00
## 📍 Phase hiện tại: **Phase 0Draft Scaffold** (in progress)
## 📍 Phase hiện tại: **Phase 1Alpha Core** (foundation xong, chờ đợt 2)
## 🔥 In Progress
| Ai | Task | Bắt đầu |
|---|---|---|
| Claude | Hoàn tất Phase 0 — docs + git init | 2026-04-21 |
_(không có — Phase 1 foundation xong, chờ quyết định bước tiếp)_
## ✅ Recently Done (newest on top)
| Ngày | Ai | Task | Commit |
|---|---|---|---|
| 2026-04-21 | Claude | Parse QUY_TRINH → `workflow-contract.md` (9 phase state machine + role matrix) | — |
| 2026-04-21 | Claude | Parse 8 FORM → `forms-spec.md` (catalog + RG-001 code format) | — |
| 2026-04-21 | Claude | Scaffold 2 React + Vite apps (fe-admin :8082, fe-user :8080) + proxy config + Node engines pin | — |
| 2026-04-21 | Claude | Scaffold .NET 10 solution + 4 project (Domain/Application/Infrastructure/Api) + references + packages | — |
| 2026-04-21 | Claude | Tạo cấu trúc thư mục `SOLUTION_ERP/` + MEMORY.md | — |
| 2026-04-21 | Claude | **Phase 1 foundation HOÀN TẤT** — BE (Clean Arch + Identity + JWT + migration) + FE (2 app, Tailwind 4, Router, AuthContext, Login) — E2E login pass qua Vite proxy | (chưa commit, sắp xong) |
| 2026-04-21 | Claude | **Phase 0 HOÀN TẤT** — scaffold + parse FORM/QUY_TRINH + docs + skills + git init | `25dad7f` |
## 🎯 Next up (Phase 0 còn lại)
Session logs:
- [`changelog/sessions/2026-04-21-1045-phase0-scaffold.md`](changelog/sessions/2026-04-21-1045-phase0-scaffold.md)
- [`changelog/sessions/2026-04-21-1100-phase1-foundation.md`](changelog/sessions/2026-04-21-1100-phase1-foundation.md)
- [ ] Tạo file root: `.gitignore`, `README.md`, `global.json`, `docker-compose.yml`
- [ ] Tạo 3 skill folder placeholders (`contract-workflow`, `form-engine`, `permission-matrix`)
- [ ] `git init` + commit đầu tiên `[CLAUDE] Scaffold: khởi tạo SOLUTION_ERP Phase 0`
- [ ] Set Gitea remote (khi user cấp URL)
## 🎯 Next up — Phase 1 đợt 2 (CRUD master + Permission Matrix)
## 📋 Phase 1 — Alpha Core (sắp tới)
### Backend
- [ ] `Domain/Entities/Supplier`, `Project`, `Department` (+ EF configurations)
- [ ] `Application/Suppliers/{Commands,Queries}/*` (Create, Update, Delete, GetById, List)
- [ ] Tương tự cho Project + Department
- [ ] `Api/Controllers/{SuppliersController, ProjectsController, DepartmentsController}`
- [ ] Pagination, search, sort server-side (`GetListQuery` với `PagedResult<T>`)
- [ ] Migration 2: `AddMasterData`
Xem chi tiết ở [`changelog/migration-todos.md`](changelog/migration-todos.md) section **Phase 1**.
### Permission Matrix
- [ ] `Domain/Entities/MenuItem`, `Permission`
- [ ] Seed default menu tree (based on FE screens)
- [ ] `Application/Permissions/Queries/GetMyMenuTreeQuery`
- [ ] `Api/Controllers/{MenusController, RolesController, PermissionsController}`
- [ ] Admin UI: Permission Matrix grid (role × menu × CRUD checkbox)
Summary:
1. Install Tailwind + shadcn/ui cho 2 FE + setup `BrandingProvider`
2. Tạo `BaseEntity`, `AuditableEntity`, `ApplicationDbContext`, migration đầu tiên
3. ASP.NET Identity + JWT endpoint (login, refresh, logout, seed admin)
4. Permission Matrix (Role × MenuKey × CRUD) — copy pattern từ NamGroup skill
5. CRUD Supplier + Project + Contract (draft only, chưa workflow)
6. FE admin: login page + layout + permission guard + 3 CRUD page
7. FE user: login + layout + list HĐ của tôi
### Contract draft (chưa workflow)
- [ ] `Domain/Entities/Contract` skeleton (không state machine)
- [ ] Basic CRUD controller + FE list page
### FE
- [ ] `<PermissionGuard menuKey="Contracts">` + `usePermission()` hook
- [ ] 3 trang CRUD admin (Suppliers / Projects / Departments) với table + modal
- [ ] Route guard theo role (admin-only routes)
## 📊 Thông số sau Phase 1 foundation
- **Backend LOC:** ~400 (Domain 60 + Application 170 + Infrastructure 190 + Api 120)
- **Frontend LOC:** ~450 mỗi app (shared 90%)
- **Build time:** .NET ~4s, FE TS check ~3s mỗi app, Vite dev ~3s ready
- **E2E verified:** Login (fe-admin proxy + fe-user proxy) → API → JWT + user info + /me
## 🚨 Blockers / risks
- **Gitea remote** chưa có URL — chờ user cấp để push code
- **3 file `.doc`** (FO-002.02, .03, .06) không parse được bằng python-docx — cần Word COM hoặc LibreOffice ở Phase 2
- **Node 22 local vs 20 CI** — phải test CI sớm (Phase 5) để tránh surprise
- **Gitea remote** chưa có URL — push sau
- ⚠️ **Swashbuckle 10.x** không tương thích với .NET 10 + Microsoft.OpenApi 2.0 — đã downgrade về 6.9.0. Theo dõi update sau.
- ⚠️ **MediatR 14.x** breaking changes — đã downgrade về 12.4.1. Ok cho Phase 1-5.
- ⚠️ **Microsoft.AspNetCore.OpenApi** đã remove (conflict Swashbuckle 6.9). Nếu sau muốn dùng built-in OpenAPI thì phải chọn 1 trong 2.
- ⚠️ **Design-time DB (`SolutionErp_Design`)** được tạo khi chạy `dotnet ef` — có thể drop an toàn (không chứa data thật)
- ⚠️ **3 file `.doc` FORM** chưa convert được — Phase 2
## 📊 Thông số
## Credentials mặc định
- LOC: ~0 (scaffold chỉ)
- Build time: ~10s (.NET)
- Test coverage: 0% (chưa có test)
```
Email: admin@solutionerp.local
Password: Admin@123456
```
URLs dev:
- API: http://localhost:5443 — Swagger ở `/swagger`
- Admin FE: http://localhost:8082
- User FE: http://localhost:8080

View File

@ -15,75 +15,70 @@
- [x] Parse 8 form → `docs/forms-spec.md`
- [x] Parse quy trình → `docs/workflow-contract.md`
- [x] Viết `docs/{CLAUDE,STATUS,PROJECT-MAP}.md`
- [ ] Viết `.gitignore`, `README.md`, `global.json`, `docker-compose.yml`
- [ ] Tạo placeholder skill folders: `contract-workflow`, `form-engine`, `permission-matrix`
- [ ] `git init` + commit đầu
- [x] Viết `.gitignore`, `README.md`, `global.json`, `docker-compose.yml`
- [x] Tạo placeholder skill folders: `contract-workflow`, `form-engine`, `permission-matrix`
- [x] `git init` + commit đầu (`25dad7f`)
- [ ] Push Gitea remote (chờ URL từ user)
## Phase 1 — Alpha Core (T2-4)
### Backend foundation
### Foundation (đã xong Session 2)
- [ ] `Domain/BaseEntity.cs` (Id, CreatedAt, UpdatedAt, CreatedBy, UpdatedBy)
- [ ] `Domain/AuditableEntity : BaseEntity` (IsDeleted, DeletedAt, DeletedBy)
- [ ] `Domain/ValueObjects/ContractCode.cs` (wrap string theo format RG-001)
- [ ] `Domain/Enums/ContractType.cs`, `ContractPhase.cs`, `ApprovalDecision.cs`
- [ ] `Application/Common/IApplicationDbContext.cs` interface
- [ ] `Application/Common/IDateTime.cs`, `ICurrentUser.cs`
- [ ] `Application/DependencyInjection.cs` — register MediatR, FluentValidation, AutoMapper
- [ ] `Infrastructure/Persistence/ApplicationDbContext.cs` : `IdentityDbContext<User, Role, Guid>`, `IApplicationDbContext`
- [ ] Configurations per entity qua `IEntityTypeConfiguration<T>`
- [ ] `Infrastructure/DependencyInjection.cs`register DbContext, Identity, services
- [ ] `Api/Program.cs` setup: services, Serilog, auth, Swagger, CORS, middleware
- [ ] `Api/Middleware/GlobalExceptionMiddleware.cs`
- [x] `Domain/Common/BaseEntity.cs` (Id Guid, CreatedAt, UpdatedAt, CreatedBy, UpdatedBy)
- [x] `Domain/Common/AuditableEntity.cs` (IsDeleted, DeletedAt, DeletedBy)
- [x] `Domain/Contracts/` Enums: `ContractType`, `ContractPhase` (9 state), `ApprovalDecision`
- [x] `Domain/Identity/User.cs` (IdentityUser<Guid> + FullName + RefreshToken + IsActive)
- [x] `Domain/Identity/Role.cs` (IdentityRole<Guid> + Description)
- [x] `Domain/Identity/AppRoles.cs` — 12 role constants
- [x] `Application/Common/Interfaces/`: IApplicationDbContext, ICurrentUser, IDateTime, IJwtTokenService
- [x] `Application/Common/Exceptions/*`
- [x] `Application/Common/Behaviors/ValidationBehavior.cs`
- [x] `Application/DependencyInjection.cs`MediatR + FluentValidation
- [x] `Infrastructure/Persistence/ApplicationDbContext.cs : IdentityDbContext`
- [x] `Infrastructure/Persistence/Interceptors/AuditingInterceptor.cs`
- [x] `Infrastructure/Persistence/DbInitializer.cs` — seed 12 role + admin
- [x] `Infrastructure/Persistence/DesignTimeDbContextFactory.cs`
- [x] `Infrastructure/Identity/{JwtSettings, JwtTokenService}.cs`
- [x] `Infrastructure/Services/DateTimeService.cs`
- [x] `Infrastructure/DependencyInjection.cs`
- [x] `Api/Services/CurrentUserService.cs`
- [x] `Api/Middleware/GlobalExceptionMiddleware.cs`
- [x] `Api/Controllers/AuthController.cs` (login, refresh, me, logout)
- [x] `Api/Program.cs` (Serilog, JWT, CORS, Swagger, middleware)
- [x] `Api/appsettings.{json, Development.json}` + `launchSettings.json` (port 5443)
- [x] Migration 1 `Init` + apply to `SolutionErp_Dev` LocalDB
- [x] FE: Vite config (Tailwind 4 + proxy + alias)
- [x] FE: `src/{index.css, lib/api.ts, lib/cn.ts, types/auth.ts}` cho 2 app
- [x] FE: `src/contexts/AuthContext.tsx`, `components/{ProtectedRoute, Layout}.tsx`
- [x] FE: `components/ui/{Button, Input, Label}.tsx`
- [x] FE: `pages/LoginPage.tsx`, `pages/DashboardPage.tsx` (admin) + `pages/InboxPage.tsx` (user)
- [x] FE: `App.tsx` với Router + AuthProvider + Toaster
- [x] FE: `main.tsx` với QueryClient (TanStack Query)
- [x] E2E verified: login qua Vite proxy cả 2 app → JWT + user info
### Auth + Identity
### Phase 1 đợt 2 — CRUD master + Permission Matrix (sắp tới)
- [ ] `Domain/Entities/User : IdentityUser<Guid>`, `Role : IdentityRole<Guid>`
- [ ] Migration 1: `Init` (Identity tables)
- [ ] `Application/Auth/Commands/LoginCommand` + handler + validator
- [ ] `Application/Auth/Commands/RefreshTokenCommand`
- [ ] `Api/Controllers/AuthController` (login, refresh, logout, me)
- [ ] JWT config: issuer, audience, key, expiry 1h + refresh 7d
- [ ] Seed admin: `admin@solutionerp.local` / `Admin@123456`
- [ ] Test login → get token → call `/me` OK
### Permission Matrix
- [ ] `Domain/Entities/MenuItem` (Key, Label, ParentKey, Order, Icon)
- [ ] `Domain/Entities/Permission` (RoleId, MenuKey, CanRead, CanCreate, CanUpdate, CanDelete)
- [ ] Seed default menu tree (based on FE screens list)
- [ ] `Application/Permissions/Queries/GetMyMenuTree` — resolve per-user
- [ ] `Api/Controllers/MenusController` + `RolesController` + `PermissionsController`
- [ ] Admin UI: Permission Matrix grid (role × menu × CRUD checkbox)
### CRUD master data
- [ ] `Domain/Entities/Supplier` (Code, Name, TaxCode, Phone, Email, Address, Type: NCC/NTP/TĐ/ĐVDV)
- [ ] `Domain/Entities/Project` (Code, Name, StartDate, EndDate, Manager)
- [ ] `Domain/Entities/Department` (Code, Name, Manager)
- [ ] CQRS + Controller + Migration cho 3 entity
- [ ] FE admin 3 trang CRUD (list, create, edit, delete confirm)
- [ ] Pagination, search, sort server-side
### Contract draft (chưa workflow — chỉ CRUD)
- [ ] `Domain/Entities/Contract` (skeleton: Id, Type, SupplierId, ProjectId, Phase=DangChon, DraftData)
- [ ] API create/update/list/delete draft
- [ ] FE admin: list contracts + filter
- [ ] FE user: "HĐ của tôi" list
### FE setup
- [ ] Install Tailwind CSS cho 2 app + config content paths
- [ ] Install shadcn/ui CLI, init 2 app
- [ ] Install: `@tanstack/react-query`, `react-router-dom`, `axios`, `lucide-react`, `sonner`
- [ ] `src/lib/api.ts` — axios instance + interceptor JWT
- [ ] `src/contexts/AuthContext.tsx` — token từ localStorage
- [ ] `src/components/PermissionGuard.tsx` + `usePermission()` hook
- [ ] Layout shell: sidebar + header + content
- [ ] Route với protected route + role guard
- [ ] Toast notifications (sonner)
- [ ] `Domain/Entities/Supplier` (Code, Name, TaxCode, Phone, Email, Address, Type enum: NCC/NTP/TĐ/ĐVDV)
- [ ] `Domain/Entities/Project` (Code, Name, StartDate, EndDate, ManagerUserId)
- [ ] `Domain/Entities/Department` (Code, Name, ManagerUserId)
- [ ] EF `IEntityTypeConfiguration<T>` cho mỗi entity
- [ ] CQRS CRUD: Create/Update/Delete/GetById/List (với paging) cho 3 entity
- [ ] `Api/Controllers/{SuppliersController, ProjectsController, DepartmentsController}`
- [ ] Migration 2: `AddMasterData`
- [ ] `Domain/Entities/MenuItem` (Key PascalCase, Label, ParentKey, Order, Icon)
- [ ] `Domain/Entities/Permission` (RoleId, MenuKey, CanRead/Create/Update/Delete)
- [ ] Seed default menu tree + permission admin có full access
- [ ] `Application/Permissions/Queries/GetMyMenuTreeQuery` — resolve per-user, cache
- [ ] `Api/Controllers/{MenusController, RolesController, PermissionsController}`
- [ ] Migration 3: `AddPermissions`
- [ ] `Domain/Entities/Contract` skeleton (Id, Type, SupplierId, ProjectId, Phase=DangChon, DraftData JSON)
- [ ] Contract CRUD draft only (không workflow Phase 3)
- [ ] FE: `<PermissionGuard menuKey="Suppliers" action="Update">` + `usePermission()` hook
- [ ] FE Admin: 3 trang CRUD Supplier/Project/Department với table + modal + search/sort
- [ ] FE Admin: Permission Matrix grid page (role × menu × CRUD checkbox)
- [ ] FE User: trang "HĐ của tôi" list + filter
- [ ] Route guard theo role admin-only
- [ ] Update `SolutionErp.slnx` nếu thêm project mới
### Exit criteria Phase 1

View File

@ -0,0 +1,68 @@
# Session 2026-04-21 10:45 — Phase 0 Scaffold complete
**Dev:** Claude (Opus 4.7)
**Duration:** ~45 phút
**Commit:** `25dad7f`
## Làm được
### Scaffold
- Root `SOLUTION_ERP/` + toàn bộ cấu trúc thư mục
- `.NET 10` solution + 4 project Clean Arch (Domain / Application / Infrastructure / Api)
- NuGet packages: MediatR, FluentValidation, AutoMapper, EF Core SqlServer, Identity, JWT Bearer, Swashbuckle, Serilog
- 2 React + Vite + TS apps: `fe-admin` (8082), `fe-user` (8080), proxy `/api → :5443`, Node `>=20`, `.nvmrc=20`
- Root config: `.gitignore`, `global.json` (SDK 10.0.104), `docker-compose.yml` (SQL Server 2022), `README.md`
### Domain analysis
- Parse 8 form HĐ → `docs/forms-spec.md`:
- 5 form parsed OK (FO-002.01, .04, .05, .07, RG-001)
- 3 file `.doc` (FO-002.02, .03, .06) không unzip được → TODO Phase 2 convert qua Word COM / LibreOffice
- Extract **regex mã HĐ** từ RG-001: `{Project}/{Type}/SOL&{Partner}/{Seq}` + 5 biến thể cho PO/framework
- Parse QUY_TRINH → `docs/workflow-contract.md`:
- **9 phase state machine** (DangChon → DangSoanThao → DangGopY → DangDamPhan → DangInKy → DangKiemTraCCM → DangTrinhKy → DangDongDau → DaPhatHanh + TuChoi)
- **Role × Phase matrix** 9 role × 9 phase
- SLA mỗi phase (tổng ~19 ngày)
- Mermaid state diagram + notification triggers + data model implication
### Docs
- `CLAUDE.md` (root pointer)
- `docs/CLAUDE.md` (full context: tech stack + project layout + conventions)
- `docs/STATUS.md` (snapshot)
- `docs/PROJECT-MAP.md` (module map + API namespace + FE screens + flow chính)
- `docs/changelog/migration-todos.md` (roadmap 5 phase, ~100 atomic task)
### Skills (3 placeholder)
- `contract-workflow` (sẽ expand Phase 3)
- `form-engine` (sẽ expand Phase 2)
- `permission-matrix` (sẽ expand Phase 1)
## Quyết định kiến trúc đã chốt
| # | Item | Chốt | Lý do |
|---|---|---|---|
| 1 | Backend | Clean Architecture + CQRS + MediatR + EF migrations | Theo DH_Y_DUOC — dự án mới nên làm đúng bài |
| 2 | DB | SQL Server 2022 | Consistent với NamGroup + DHYD, user chốt |
| 3 | FE | 2 app React 19 + Vite 8 + TS (auto-scaffold) | Vite latest; nếu cần downgrade về React 18 thì làm Phase 1 đầu |
| 4 | Deploy | Windows Server + IIS | User chốt, không Docker |
| 5 | Lang UI | 100% tiếng Việt (code + table name = English) | User chốt |
| 6 | AI service | BỎ luôn | User chốt, không Python |
| 7 | Team | Solo (user + Claude) | Không skill Copilot collaboration |
| 8 | Node | local `>=20`, CI pin `20.x` | Bài học NamGroup (CI fail với Node mới) |
## Handoff cho session tiếp theo
**Phase 1 start:** backend foundation + auth + permission + CRUD master + FE layout + login page.
**Đọc trước khi code:**
1. [docs/STATUS.md](../../STATUS.md) — snapshot
2. [docs/workflow-contract.md](../../workflow-contract.md) — 9 phase (ảnh hưởng Domain model)
3. [docs/forms-spec.md](../../forms-spec.md) — RG-001 code format
4. [docs/changelog/migration-todos.md](../migration-todos.md) section **Phase 1**
**Blocker:**
- ⏳ Chờ user cấp URL Gitea → push remote
**Không ngầm giả định:**
- React 19 vs 18: scaffold ra 19, nếu muốn 18 thì phải downgrade sớm
- Aspose.Words (phí) vs OpenXml (free) cho Phase 2 — chưa quyết
- SignalR cho real-time notification Phase 3 — optional, chưa quyết

View File

@ -0,0 +1,128 @@
# Session 2026-04-21 11:00 — Phase 1 Foundation complete
**Dev:** Claude (Opus 4.7)
**Duration:** ~1h15m
**Base commit:** `25dad7f` (Phase 0)
**Commit target:** ~30 files added/modified
## Làm được
### Backend — Clean Architecture hoàn chỉnh
**Domain layer:**
- `Common/BaseEntity.cs`, `AuditableEntity.cs` (Id Guid + audit fields + soft delete)
- `Contracts/` enums: `ContractType`, `ContractPhase` (9 phase), `ApprovalDecision`
- `Identity/User.cs : IdentityUser<Guid>` + `Role.cs : IdentityRole<Guid>`
- `Identity/AppRoles.cs` — 12 role constants (Admin, Drafter, DeptManager, ProjectManager, Procurement, CostControl, Finance, Accounting, Equipment, Director, AuthorizedSigner, HrAdmin)
**Application layer:**
- Interfaces: `IApplicationDbContext`, `ICurrentUser`, `IDateTime`, `IJwtTokenService`
- Exceptions: `NotFoundException`, `ValidationException`, `ForbiddenException`, `UnauthorizedException`, `ConflictException`
- `ValidationBehavior` (MediatR pipeline — FluentValidation)
- Auth slice: `LoginCommand`, `RefreshTokenCommand`, `GetMeQuery` (CQRS + handlers + validators)
- `DependencyInjection.cs` — MediatR + FluentValidation wire-up
**Infrastructure layer:**
- `Services/DateTimeService.cs` (IDateTime impl)
- `Identity/JwtSettings.cs` + `JwtTokenService.cs` (HS256, refresh token 7d, access 1h)
- `Persistence/ApplicationDbContext.cs : IdentityDbContext<User, Role, Guid>, IApplicationDbContext`
- `Persistence/Interceptors/AuditingInterceptor.cs` — auto CreatedAt/UpdatedAt/DeletedBy + soft delete
- `Persistence/DbInitializer.cs` — apply migrations + seed 12 roles + admin user
- `Persistence/DesignTimeDbContextFactory.cs` — cho `dotnet ef` không cần Program.cs
- `DependencyInjection.cs` — wire DbContext + Identity + JWT service
**Api layer:**
- `Services/CurrentUserService.cs` — impl `ICurrentUser` từ HttpContext
- `Middleware/GlobalExceptionMiddleware.cs` — map exception → ProblemDetails JSON
- `Controllers/AuthController.cs``POST /api/auth/login`, `/refresh`, `GET /me`, `POST /logout`
- `Program.cs` rewrite: Serilog, JWT auth, CORS (2 FE origin), Swagger + Bearer security, DB init
- `appsettings.json` + `appsettings.Development.json` (connection string + JWT)
- `launchSettings.json` update port 5443
**Migration:**
- `Init` migration → `SolutionErp.Infrastructure/Persistence/Migrations/20260421034520_Init.cs`
- Applied to `SolutionErp_Dev` LocalDB ✅
### Frontend — 2 app React 19 + Vite + Tailwind 4
**Shared structure (fe-admin + fe-user):**
- Vite config: port + strictPort + `/api` proxy + `@/*` alias + Tailwind 4 plugin
- `src/index.css` — Tailwind import + theme brand colors (admin=blue, user=emerald)
- `src/lib/api.ts` — axios instance + JWT request interceptor + 401 redirect
- `src/lib/cn.ts` — clsx + tailwind-merge helper
- `src/types/auth.ts` — mirror BE DTOs
- `src/contexts/AuthContext.tsx` — login/logout, localStorage token, bootstrap on mount
- `src/components/ProtectedRoute.tsx` — redirect login nếu chưa auth
- `src/components/Layout.tsx` — sidebar + header, lucide icons, user info + logout
- `src/components/ui/Button.tsx`, `Input.tsx`, `Label.tsx` — mini shadcn (CVA + Tailwind)
- `src/pages/LoginPage.tsx` — form login với toast error
- `src/App.tsx` — Router + AuthProvider + Toaster (sonner)
- `src/main.tsx` — QueryClient (TanStack Query) + StrictMode
**Khác biệt 2 app:**
- fe-admin: port 8082, brand blue, menu [Tổng quan / HĐ / NCC / Users / Settings], route `/dashboard`
- fe-user: port 8080, brand emerald, menu [Chờ xử lý / HĐ của tôi / Tạo HĐ mới], route `/inbox`
- localStorage key riêng (`solution-erp-admin-token` vs `solution-erp-user-token`)
### Packages cài thêm
**Backend:**
- Microsoft.Extensions.Identity.Stores (Domain)
- Microsoft.AspNetCore.Identity.EntityFrameworkCore (Infrastructure)
- System.IdentityModel.Tokens.Jwt (Infrastructure)
- Microsoft.Extensions.DependencyInjection.Abstractions (Application)
- Microsoft.EntityFrameworkCore.Design (Api — `PrivateAssets=all`)
**Frontend (cả 2 app):**
- tailwindcss 4 + @tailwindcss/vite
- axios, @tanstack/react-query, react-router-dom 7, sonner, lucide-react
- clsx, tailwind-merge, class-variance-authority
## Bug gặp phải + fix
| Bug | Root cause | Fix |
|---|---|---|
| `IApplicationDbContext.cs` error CS0234 EntityFrameworkCore | Thừa `using Microsoft.EntityFrameworkCore` nhưng không cần (chỉ có `SaveChangesAsync`) | Bỏ using |
| `ValidationBehavior` ambiguous `ValidationException` | FluentValidation + app custom cùng tên | `using ValidationException = ...Custom.ValidationException;` |
| `AddDefaultTokenProviders()` không có trong `IdentityCore` | Identity Core không include token providers | Remove call — sẽ add lại Phase 4 (password reset) |
| DbContext design-time không resolve được options | `AddDbContext` cần runtime config | Thêm `DesignTimeDbContextFactory` |
| Swagger 404 + IMediator not resolved | **Program.cs không persist** — Write tool ghi nhưng file vẫn là default scaffold (nghi Dropbox revert) | Write lại Program.cs — vấn đề giải quyết |
| `Microsoft.OpenApi.Models` namespace không có | Microsoft.OpenApi 2.0 (breaking change) đi kèm Microsoft.AspNetCore.OpenApi 10 | Remove `Microsoft.AspNetCore.OpenApi` (dùng Swashbuckle thay thế) |
| Swashbuckle 10.1.7 không tương thích OpenApi 2 | Swashbuckle chưa support OpenApi 2.x | Downgrade Swashbuckle → 6.9.0 |
| MediatR 14.1.0 không có `AddMediatR` extension | MediatR v14 refactored | Downgrade → 12.4.1 |
| `baseUrl` deprecated trong TS 6 | TypeScript 6 deprecates baseUrl | Bỏ baseUrl, chỉ giữ paths (works relative to tsconfig location) |
## E2E verified (2026-04-21 10:56)
`POST http://localhost:8082/api/auth/login` (fe-admin Vite proxy) → 200 + JWT
`POST http://localhost:8080/api/auth/login` (fe-user Vite proxy) → 200 + JWT
`GET http://localhost:5443/api/auth/me` với Bearer → 200 + user info
`GET http://localhost:5443/swagger/v1/swagger.json` → 200
✅ Seed admin (`admin@solutionerp.local` / `Admin@123456`) + 12 roles chạy tự động
## Bonus: package constraints ghi lại
Phase 1 đã phát hiện vài package không tương thích .NET 10 / TS 6 / Vite 8:
- Swashbuckle 6.9.0 (không 10.x)
- MediatR 12.4.1 (không 14.x)
- tsconfig không dùng `baseUrl` (TS 6 deprecate)
Nếu Phase 2-5 thấy package mới lại bug, thử **downgrade về stable** trước khi debug.
## Handoff cho session tiếp theo
**Phase 1 đợt 2** — xem STATUS.md section "Next up":
1. CRUD master data (Supplier/Project/Department)
2. Permission Matrix
3. Contract entity draft
**Đọc trước khi code:**
1. [docs/STATUS.md](../../STATUS.md)
2. [docs/workflow-contract.md](../../workflow-contract.md) (domain context)
3. Code vừa viết ở `src/Backend/SolutionErp.*/` + `fe-admin/src/`, `fe-user/src/`
**Blocker vẫn còn:**
- ⏳ Gitea remote URL
**Credentials:**
- `admin@solutionerp.local` / `Admin@123456`