Files
solution-erp/docs/changelog/sessions/2026-04-21-1100-phase1-foundation.md
pqhuy1987 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

6.9 KiB

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.csPOST /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
  2. docs/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