# ARCHITECTURE.md ## Project Overview 企业供应链合同管理系统 (中尚鹏 / ZSP) — a monorepo enterprise supply chain contract management system. It manages contracts, deliveries, inventory, finance, purchasing, and reporting for a bulk commodity supply chain business. ### 5 Business Modules (21 Sub-menus) | Module | Sub-menus | |---|---| | 合同管理 (Contract) | 业务合同管理, 中尚鹏合同管理, 公司信息管理 | | 提货管理 (Delivery) | 我要提货, 提货明细, 库存信息, 货权转移, 其他出库 | | 财务管理 (Finance) | 货代费用, 杂费管理, 服务费管理, 发票管理, 结算管理, 对账单, 利润明细 | | 采购管理 (Purchase) | 采购报单, 采购运踪明细, 国外供应商, 采购预算 | | 查询&报表中心 (Reports) | 合同执行情况, 营业明细表, 其他明细表, 商品基础资料, 其他报表 | --- ## Directory Structure ``` zsp-project/ ├── frontend/ # Vue 3 SPA (pure-admin-thin) │ ├── src/ │ │ ├── api/ # Axios API modules (one per domain) │ │ ├── assets/ # Static assets (icons, images, SVG) │ │ ├── components/ # Reusable global components │ │ ├── config/ # Runtime platform configuration │ │ ├── directives/ # Custom Vue directives (auth, perms, copy, etc.) │ │ ├── layout/ # App shell (vertical/horizontal/mix layouts) │ │ ├── plugins/ # Plugin installers (Element Plus, ECharts) │ │ ├── router/ # Vue Router + auto-imported route modules │ │ ├── store/ # Pinia stores (user, app, permission, settings, etc.) │ │ ├── style/ # SCSS/CSS (theme, dark mode, transitions, Tailwind) │ │ ├── utils/ # HTTP client, auth, tree, message, print, etc. │ │ └── views/ # Page components by feature domain │ └── public/ # Static public assets │ ├── backend/ # Go + Gin REST API │ ├── cmd/api/ # Entry point (main.go) │ ├── configs/ # YAML configs (dev, prod) │ ├── internal/ │ │ ├── dto/ # Request/response DTOs │ │ ├── handler/ # HTTP handlers (Gin controllers) │ │ ├── middleware/ # JWT auth middleware │ │ ├── model/ # GORM database models (27 tables) │ │ ├── repository/ # Data access layer (GORM queries) │ │ ├── server/ # Router setup and route definitions │ │ └── service/ # Business logic layer │ ├── pkg/ # Shared packages (config, database, utils) │ ├── test/ # Handler integration tests │ ├── scripts/ # SQL init scripts │ └── data/ # Uploaded files (runtime) │ ├── docs/ # Project documentation │ ├── api/ # API docs and frontend route docs │ ├── deploy/ # Deployment guides, nginx config │ ├── dev/ # Requirements, gap analysis, meeting notes │ └── superpowers/ # Implementation plans and specs │ ├── scripts/ # Test runner, deployment scripts ├── .github/workflows/ # CI pipeline (Go + Vue tests) └── CLAUDE.md # Claude Code project guide ``` --- ## Technology Stack ### Frontend | Technology | Version | Purpose | |---|---|---| | Vue | 3.5 | UI framework (Composition API) | | TypeScript | 5.8 | Type safety | | Vite | 6.3 | Build tool & dev server | | Pinia | 3.0 | State management | | Vue Router | 4.5 | Client-side routing | | Element Plus | 2.9 | UI component library | | Axios | latest | HTTP client | | Tailwind CSS | 4.1 | Utility-first CSS | | ECharts | latest | Data visualization | | Vitest | latest | Test framework | ### Backend | Technology | Version | Purpose | |---|---|---| | Go | 1.25 | Runtime | | Gin | 1.10 | HTTP framework | | GORM | 1.31 | ORM | | MySQL | 8.0 | Relational database | | JWT (golang-jwt) | 4.5 | Authentication tokens | | Viper | 1.21 | Configuration management | | Excelize | 2.9 | Excel import/export | | bcrypt | latest | Password hashing | --- ## Backend Architecture ### Layered Design ``` HTTP Request │ ▼ ┌─────────────────┐ │ Gin Router │ internal/server/route.go — single source of truth for all routes ├─────────────────┤ │ Middleware │ internal/middleware/auth.go — JWT Bearer token validation ├─────────────────┤ │ Handler │ internal/handler/ — request parsing, response writing (thin) ├─────────────────┤ │ Service │ internal/service/ — business logic, orchestration ├─────────────────┤ │ Repository │ internal/repository/ — GORM data access ├─────────────────┤ │ Model │ internal/model/ — GORM structs with table tags └─────────────────┘ │ ▼ MySQL 8.0 ``` **Key pattern**: Manual dependency injection in `cmd/api/main.go` — all repositories, services, and handlers are wired together at startup. Every service and most repositories define a Go interface, enabling mock-based unit testing. ### Startup Sequence (main.go) 1. Load YAML config via Viper (`CONFIG_PATH` env var) 2. Ensure MySQL database exists (create/drop per config) 3. Open GORM DB connection pool 4. `AutoMigrate` all 27 model tables 5. Instantiate repositories → services → handlers (manual DI) 6. Create JWT auth middleware 7. Setup Gin router (`server.SetupRouter`) 8. Start HTTP server (goroutine) 9. Graceful shutdown on SIGINT/SIGTERM ### API Route Groups All routes are under `/api/` prefix with JWT middleware applied globally except for auth endpoints and static file serving. | Route Group | Prefix | Handler | Key Endpoints | |---|---|---|---| | Auth (public) | `/api/auth` | UserHandler | `POST /register`, `POST /login` | | Users | `/api/users` | UserHandler | `GET /:id` | | Contract | `/api/contract` | ContractHandler | CRUD, file upload, folders, batches | | ZSP Contract | `/api/zsp-contract` | ZSPContractHandler | CRUD, details, Excel import/export | | Company | `/api/company` | CompanyHandler | Folders, file upload/CRUD | | Delivery | `/api/apply-delivery` | DeliveryHandler | Delivery applications | | Outbound | `/api/delivery-details` | DeliveryHandler | Outbound orders | | Inventory | `/api/inventory` | DeliveryHandler | Warehouses, inbound, summary | | Ownership | `/api/ownership-transfer` | DeliveryHandler | Ownership transfer CRUD + files | | Finance | `/api/zsp-finances` | ZSPFinancesHandler | Freight, misc, service fees, costs, profits | ### Data Models (27 GORM Tables) | Domain | Tables | |---|---| | User | `users` | | Contract | `contracts`, `contract_files`, `contract_folders`, `contract_batches`, `contract_batch_files` | | ZSP Contract | `zsp_contract_folders`, `zsp_contract_files`, `zsp_contract_details` | | Company | `company_folders`, `company_files` | | Delivery | `delivery_applies`, `delivery_outbounds`, `warehouses`, `inventory_inbounds`, `ownership_transfers`, `ownership_transfer_bls`, `ownership_transfer_files` | | Finance | `zsp_freight_charges`, `zsp_misc_charges`, `zsp_service_fees`, `zsp_cost_breakdowns`, `zsp_profit_records` | | Invoice | `zsp_upstream_invoices`, `zsp_downstream_invoices`, `zsp_freight_misc_invoices` | | Settlement | `zsp_settlements`, `zsp_reconciliations` | Note: Invoice and Settlement models are defined and auto-migrated but not yet wired into handlers/services. ### File Storage Uploaded files are stored on the local filesystem under `backend/data/`: - `./data/contract/` — contract files - `./data/contract/batch/` — batch contract files - `./data/zsp-contract/files/` — ZSP contract files - `./data/company/files/` — company info files Files are served statically via Gin (`/api/contract-files`, `/api/zsp-contract-files`, `/api/company-files`). --- ## Frontend Architecture ### Component Tree ``` App.vue └── ├── # Page content (or login page) └── # Global dialog manager Layout (after login) └── Layout (index.vue) # Three modes: vertical / horizontal / mix ├── Navbar # Top bar (user info, search, fullscreen) ├── Sidebar # Menu (vertical/horizontal/mix variants) ├── MultiTags # Tab-based page navigation ├── # Keep-alive cached page views │ └── Page View # Domain-specific business pages └── Settings Panel # Theme/layout preferences drawer ``` ### Page Composition Pattern Complex pages follow a consistent decomposition: ``` views/system/contract/business-contract/ ├── index.vue # Page assembly (layout, orchestrates sub-components) ├── components/ # Page-specific sub-components │ ├── ContractTable.vue │ ├── EditContractDialog.vue │ ├── FolderPanel.vue │ ├── SearchPanel.vue │ └── UploadDialog.vue ├── hooks/ # Composition API hooks (business logic) │ └── useContract.ts └── types.ts # Page-specific TypeScript types ``` ### Routing Routes use `import.meta.glob` to auto-import all modules from `src/router/modules/`. Two-tier routing: - **Static routes**: Built-in pages (login, error pages, welcome, home) — defined in router modules - **Async routes**: Business pages fetched from backend `/get-async-routes` after login, merged with static routes, filtered by user roles Route structure mirrors the 5 business modules: ``` / → Home /login → Login /system/contract/* → Contract management (3 pages) /system/delivery/* → Delivery management (5 pages) /system/finance/* → Finance management (7 pages) /system/purchase/* → Purchase management (4 pages) /system/query-report/* → Query & reports (5 pages) ``` ### State Management (Pinia) | Store | Key State | |---|---| | `user` | avatar, username, roles, permissions, tokens | | `app` | sidebar state, layout mode, device type | | `permission` | constant/whole menus, flattened routes, keep-alive cache list | | `settings` | title, fixed header, sidebar visibility | | `multiTags` | open tab list, tab cache toggle | | `epTheme` | theme color, light/dark mode | ### API Layer (`src/api/`) Each API module exports typed functions returning `BaseResponse`: ```typescript // Pattern: src/api/contract.ts import { http } from "@/utils/http"; import { baseUrlApi } from "./utils"; export const getContractList = (params?: object) => { return http.request>("get", baseUrlApi("contract"), { params }); }; ``` The HTTP client (`src/utils/http/index.ts`) handles: - Base URL resolution (dev proxy vs production) - JWT token attachment via request interceptor - Automatic token refresh on 401 with request queueing - NProgress loading bar integration ### Authentication Flow ``` Login → POST /api/auth/login → Receive JWT token → Store in cookies + localStorage → Fetch user info + async routes → Build dynamic menu from roles → Navigate to home Token refresh: → 401 response intercepted → Queue subsequent requests → POST /refresh-token → Replay queued requests with new token ``` ### Permission System Two levels: - **Route-level**: `meta.roles` in route config filtered against user roles - **Button-level**: `v-auth` / `v-perms` directives and `` / `` components --- ## Data Flow ``` User Action (UI) → Pinia Store action (optional, for shared state) → API function call (src/api/domain.ts) → Axios request (auto-attaches JWT) → Vite dev proxy (/api → localhost:8080) or nginx proxy (production) → Gin router → JWT middleware → Handler → Service (business logic) → Repository (GORM queries) → MySQL 8.0 ← JSON response { success, data, message } ← Axios interceptor (deserialize, handle 401) ← Component re-render ``` --- ## Authentication & Authorization - **Scheme**: JWT Bearer tokens (HS256, 24-hour expiry) - **Password storage**: bcrypt hashed - **Public endpoints**: `POST /api/auth/register`, `POST /api/auth/login`, static file routes - **Middleware**: Extracts user ID from JWT claims into Gin context; all other routes require valid token - **CORS**: All origins allowed with Authorization header (dev mode) --- ## Testing Strategy ### Backend Tests | Location | Type | Coverage | |---|---|---| | `backend/test/` | Handler integration tests | User, contract, ZSP contract, company, delivery, finance handlers | | `backend/internal/service/` | Service unit tests | User service (with mock repository) | Pattern: Mock service/repository interfaces, use `httptest.NewRecorder` with Gin test mode. ### Frontend Tests | Location | Type | Coverage | |---|---|---| | `src/api/__tests__/` | API module tests | All 12 API modules | | `src/components/business/__tests__/` | Component tests | ConfirmDialog | | `src/store/modules/__tests__/` | Store tests | User store | | `src/utils/__tests__/` | Utility tests | Tree utilities | Framework: Vitest + jsdom + @vue/test-utils. Test helpers in `src/api/test-utils.ts`. ### Test Runner `scripts/run-tests.sh` supports: `backend`, `frontend`, `coverage`, `ci`, `all`. --- ## Deployment ### Development ```bash # Backend (port 8080) cd backend && go run cmd/api/main.go # or with hot-reload cd backend && air -c .air.toml # Frontend (port 5173, proxies /api → 8080) cd frontend && pnpm dev # Docker Compose (backend + MySQL + Air) cd backend && docker-compose -f docker-compose.dev.yml up --build ``` ### Production ```bash # Frontend build cd frontend && pnpm build # → dist/ # Backend build (Docker) cd backend && docker build -t zsp-backend . # Full stack docker-compose --env-file .env.prod up -d ``` Nginx serves the frontend SPA and proxies `/api/` to the Go backend. Configuration at `docs/deploy/nginx.conf`. --- ## CI/CD `.github/workflows/test.yml` — Three-job pipeline triggered on push/PR to `master`/`main`/`dev`: 1. **Backend**: Go 1.25 + MySQL 8 service → `go test ./... -race` 2. **Frontend**: Node 22 + pnpm 9 → `vitest run` + `typecheck` + `eslint` 3. **Summary**: Gate job, fails if either backend or frontend fails --- ## Key Design Decisions 1. **Manual DI over framework**: All wiring in `main.go` keeps dependencies explicit and avoids magic 2. **Interface-based services**: Every service defines an interface for testability via mocks 3. **DTOs at API boundary**: Request/response types in `internal/dto/` decouple API contract from database schema 4. **AutoMigrate for schema**: All 27 models migrated on startup; additive-only schema changes 5. **File storage on local FS**: Uploaded files go to `data/` directory, served statically by Gin 6. **Glob-based route auto-import**: Vite's `import.meta.glob` loads all router modules without manual registration 7. **Token refresh with request queueing**: Axios interceptor queues failed requests during token refresh, replays them after 8. **Dual contract models**: Legacy `Contract` coexists with `ZSPContractFile`/`ZSPContractDetail` for backward compatibility ## Known Issues 1. **Dual contract models** — Legacy `Contract` and new `ZSPContractFile`/`ZSPContractDetail` models coexist with overlapping concerns 2. **API URL inconsistency** — Some routes use `/list`, `/create`, `/update/:id` suffixes while others use direct REST patterns 3. **Incomplete module coverage** — Invoice and Settlement models are in AutoMigrate but lack handlers, services, and repositories 4. **Delivery handler complexity** — `InventorySummary` bypasses the service/repository layer and accesses `*gorm.DB` directly for complex aggregate queries 5. **Hardcoded UI text** — All UI text is hardcoded in Chinese; no i18n infrastructure 6. **Handler-level inconsistency** — Some handlers use proper DTOs, others use inline anonymous structs for request binding 7. **Frontend finance API inconsistency** — Finance API modules use hardcoded `/zsp-finances/` prefix while other modules use `/api/` prefix via `baseUrlApi()`