# ERP MVP Интегрированный monorepo ERP MVP v1 для демонстрации базового операционного цикла: справочники, закупки, продажи, складские движения, PDF-заглушки документов, dashboard и demo data. ## Структура ```text erp-mvp/ backend/ frontend/ .env.example docker-compose.yml README.md ``` ## Запуск Опционально можно скопировать пример переменных окружения: ```bash cp .env.example .env ``` Для production обязательно замените `JWT_SECRET`, пароли PostgreSQL и dev credentials. ```bash docker compose up --build ``` После запуска: - Frontend: http://localhost:5173 - Backend health: http://localhost:8080/api/health - PostgreSQL: localhost:5432 ## Backend Backend использует переменные окружения для подключения к PostgreSQL: - `POSTGRES_DB` - `POSTGRES_USER` - `POSTGRES_PASSWORD` - `SPRING_DATASOURCE_URL` - `SPRING_DATASOURCE_USERNAME` - `SPRING_DATASOURCE_PASSWORD` - `CORS_ALLOWED_ORIGINS` - `JWT_SECRET` - `JWT_EXPIRATION_MINUTES` - `ADMIN_EMAIL` - `ADMIN_PASSWORD` - `DEMO_DATA_ENABLED` По умолчанию frontend разрешен с `http://localhost:5173`. ## Backend Foundation Backend содержит базовую инфраструктуру для дальнейшей разработки ERP-модулей: - расширяемую package structure с `common`, `config`, `health` и заготовками `modules`; - единый API response envelope; - global exception handler; - базовые custom exceptions; - pagination DTO; - JPA auditing foundation через `BaseEntity`; - OpenAPI / Swagger UI; - Flyway migrations для системной инициализации. ## Auth + Roles Backend содержит stateless JWT authentication foundation: - таблица `app_users`; - роли `ADMIN`, `MANAGER`, `WAREHOUSE`, `FINANCE`; - BCrypt password hashing; - seed admin user через Spring Boot initializer; - JWT Bearer auth для защищенных `/api/**` endpoint-ов; - Swagger Bearer JWT authorization scheme. Dev credentials по умолчанию: - Email: `admin@erp.local` - Password: `admin12345` Значения можно переопределить через переменные окружения: - `ADMIN_EMAIL` - `ADMIN_PASSWORD` - `JWT_SECRET` - `JWT_EXPIRATION_MINUTES` ## API Response Format Успешный ответ: ```json { "success": true, "data": {}, "error": null, "timestamp": "2026-05-14T10:00:00Z" } ``` Ответ с ошибкой: ```json { "success": false, "data": null, "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "details": [] }, "timestamp": "2026-05-14T10:00:00Z" } ``` ## API URLs - Swagger UI: http://localhost:8080/swagger-ui/index.html - Health check: http://localhost:8080/api/health - OpenAPI JSON: http://localhost:8080/v3/api-docs - Frontend login: http://localhost:5173/login ## Auth Endpoints - `POST /api/auth/login` - `GET /api/auth/me` - `POST /api/auth/logout` - `GET /api/auth/protected-test` Login: ```bash curl -X POST "http://localhost:8080/api/auth/login" \ -H "Content-Type: application/json" \ -d '{"email":"admin@erp.local","password":"admin12345"}' ``` Protected endpoint with JWT: ```bash TOKEN="" curl "http://localhost:8080/api/auth/protected-test" \ -H "Authorization: Bearer $TOKEN" ``` ## Catalog Module Catalog module содержит базовые ERP-справочники без закупок, заказов, остатков и документов: - Products / SKU - Suppliers - B2B Customers - Warehouses Backend endpoints: - `GET /api/catalog/products` - `GET /api/catalog/products/{id}` - `POST /api/catalog/products` - `PUT /api/catalog/products/{id}` - `DELETE /api/catalog/products/{id}` - `GET /api/catalog/suppliers` - `GET /api/catalog/suppliers/{id}` - `POST /api/catalog/suppliers` - `PUT /api/catalog/suppliers/{id}` - `DELETE /api/catalog/suppliers/{id}` - `GET /api/catalog/customers` - `GET /api/catalog/customers/{id}` - `POST /api/catalog/customers` - `PUT /api/catalog/customers/{id}` - `DELETE /api/catalog/customers/{id}` - `GET /api/catalog/warehouses` - `GET /api/catalog/warehouses/{id}` - `POST /api/catalog/warehouses` - `PUT /api/catalog/warehouses/{id}` - `DELETE /api/catalog/warehouses/{id}` List endpoints support: ```text ?page=0&size=20&search=&active=true ``` Role access: - `GET`: any authenticated user. - `POST`, `PUT`, `DELETE`: `ADMIN` and `MANAGER`. - `DELETE` performs soft delete by setting `active=false`. Frontend URLs: - Products: http://localhost:5173/catalog/products - Suppliers: http://localhost:5173/catalog/suppliers - Customers: http://localhost:5173/catalog/customers - Warehouses: http://localhost:5173/catalog/warehouses Create product example: ```bash LOGIN_RESPONSE=$(curl -s -X POST "http://localhost:8080/api/auth/login" \ -H "Content-Type: application/json" \ -d '{"email":"admin@erp.local","password":"admin12345"}') TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.data.accessToken') curl -X POST "http://localhost:8080/api/catalog/products" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{ "sku": "SKU-001", "name": "Demo product", "category": "General", "unit": "pcs", "barcode": "1234567890", "description": "Demo catalog item" }' ``` ## Procurement Module Procurement module содержит простой процесс закупок без продаж, документов и оплат: - purchase order header; - supplier from Catalog; - optional receiving warehouse from Catalog; - product items from Catalog; - quantity, unit price, line total; - status flow and status history. - stock receipt when `ORDERED` moves to `RECEIVED`. Backend endpoints: - `GET /api/procurement/purchase-orders` - `GET /api/procurement/purchase-orders/{id}` - `POST /api/procurement/purchase-orders` - `PUT /api/procurement/purchase-orders/{id}` - `PATCH /api/procurement/purchase-orders/{id}/status` - `GET /api/procurement/purchase-orders/{id}/status-history` - `DELETE /api/procurement/purchase-orders/{id}` List endpoint supports: ```text ?page=0&size=20&search=&status=DRAFT&supplierId=&fromDate=&toDate= ``` Statuses: - `DRAFT` - `APPROVED` - `ORDERED` - `RECEIVED` - `CANCELLED` Allowed transitions: - `DRAFT` to `APPROVED` - `APPROVED` to `ORDERED` - `ORDERED` to `RECEIVED` - `DRAFT` to `CANCELLED` - `APPROVED` to `CANCELLED` - `ORDERED` to `CANCELLED` Role access: - Read: `ADMIN`, `MANAGER`, `WAREHOUSE`, `FINANCE`. - Create/update/cancel: `ADMIN`, `MANAGER`. - Mark received: `ADMIN`, `MANAGER`, `WAREHOUSE`. - `FINANCE` can only read. Frontend URLs: - Purchase orders: http://localhost:5173/procurement/purchase-orders - Purchase order details: http://localhost:5173/procurement/purchase-orders/{id} Create purchase order example: ```bash LOGIN_RESPONSE=$(curl -s -X POST "http://localhost:8080/api/auth/login" \ -H "Content-Type: application/json" \ -d '{"email":"admin@erp.local","password":"admin12345"}') TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.data.accessToken') # Create an active supplier, warehouse, and product first in Catalog, then use their ids below. SUPPLIER_ID="" WAREHOUSE_ID="" PRODUCT_ID="" CREATE_PO_RESPONSE=$(curl -s -X POST "http://localhost:8080/api/procurement/purchase-orders" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d "{ \"supplierId\": \"$SUPPLIER_ID\", \"warehouseId\": \"$WAREHOUSE_ID\", \"expectedDeliveryDate\": \"2026-06-01\", \"notes\": \"Initial procurement request\", \"items\": [ { \"productId\": \"$PRODUCT_ID\", \"quantity\": 10, \"unitPrice\": 1250.00 } ] }") PO_ID=$(echo "$CREATE_PO_RESPONSE" | jq -r '.data.id') curl -X PATCH "http://localhost:8080/api/procurement/purchase-orders/$PO_ID/status" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"status":"APPROVED","comment":"Approved for ordering"}' ``` ## Sales Module Sales module содержит простой B2B customer order process без документов, оплат и внешних интеграций: - customer order header; - customer from Catalog; - optional warehouse from Catalog; - product items from Catalog; - quantity, unit price, line total; - status flow and status history. - stock issue when `IN_PROGRESS` moves to `SHIPPED`. Backend endpoints: - `GET /api/sales/customer-orders` - `GET /api/sales/customer-orders/{id}` - `POST /api/sales/customer-orders` - `PUT /api/sales/customer-orders/{id}` - `PATCH /api/sales/customer-orders/{id}/status` - `GET /api/sales/customer-orders/{id}/status-history` - `DELETE /api/sales/customer-orders/{id}` List endpoint supports: ```text ?page=0&size=20&search=&status=NEW&customerId=&warehouseId=&fromDate=&toDate= ``` Statuses: - `NEW` - `CONFIRMED` - `IN_PROGRESS` - `SHIPPED` - `CLOSED` - `CANCELLED` Allowed transitions: - `NEW` to `CONFIRMED` - `CONFIRMED` to `IN_PROGRESS` - `IN_PROGRESS` to `SHIPPED` - `SHIPPED` to `CLOSED` - `NEW` to `CANCELLED` - `CONFIRMED` to `CANCELLED` - `IN_PROGRESS` to `CANCELLED` Role access: - Read: `ADMIN`, `MANAGER`, `WAREHOUSE`, `FINANCE`. - Create/update/cancel: `ADMIN`, `MANAGER`. - Status changes: `ADMIN`, `MANAGER`. - Warehouse status changes: `CONFIRMED` to `IN_PROGRESS`, `IN_PROGRESS` to `SHIPPED`. - `FINANCE` can only read. Frontend URLs: - Customer orders: http://localhost:5173/sales/customer-orders - Customer order details: http://localhost:5173/sales/customer-orders/{id} Create customer order example: ```bash LOGIN_RESPONSE=$(curl -s -X POST "http://localhost:8080/api/auth/login" \ -H "Content-Type: application/json" \ -d '{"email":"admin@erp.local","password":"admin12345"}') TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.data.accessToken') # Create an active customer and product first in Catalog, then use their ids below. # WAREHOUSE_ID is optional. CUSTOMER_ID="" PRODUCT_ID="" WAREHOUSE_ID="" CREATE_ORDER_RESPONSE=$(curl -s -X POST "http://localhost:8080/api/sales/customer-orders" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d "{ \"customerId\": \"$CUSTOMER_ID\", \"warehouseId\": \"$WAREHOUSE_ID\", \"requestedDeliveryDate\": \"2026-06-10\", \"notes\": \"Initial B2B order\", \"items\": [ { \"productId\": \"$PRODUCT_ID\", \"quantity\": 5, \"unitPrice\": 1800.00 } ] }") ORDER_ID=$(echo "$CREATE_ORDER_RESPONSE" | jq -r '.data.id') curl -X PATCH "http://localhost:8080/api/sales/customer-orders/$ORDER_ID/status" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"status":"CONFIRMED","comment":"Confirmed with customer"}' ``` ## Warehouse Module Warehouse module содержит складские остатки и историю движений без документов, оплат и внешних интеграций: - stock balance by warehouse and product; - stock movement history; - inbound movement when purchase order becomes `RECEIVED`; - outbound movement when customer order becomes `SHIPPED`; - manual stock adjustments; - insufficient stock check before shipping. Backend endpoints: - `GET /api/warehouse/stock-balances` - `GET /api/warehouse/stock-movements` - `POST /api/warehouse/stock-adjustments` Stock balances list supports: ```text ?page=0&size=20&warehouseId=&productId=&search= ``` Stock movements list supports: ```text ?page=0&size=20&warehouseId=&productId=&movementType=INBOUND&sourceType=PURCHASE_ORDER&sourceId=&fromDate=&toDate= ``` Movement types: - `INBOUND` - `OUTBOUND` - `ADJUSTMENT_IN` - `ADJUSTMENT_OUT` Source types: - `PURCHASE_ORDER` - `CUSTOMER_ORDER` - `MANUAL_ADJUSTMENT` Business rules: - `PurchaseOrder` must have `warehouseId` before marking it as `RECEIVED`. - `RECEIVED` creates `INBOUND` stock movement per PO item and increases stock. - `CustomerOrder` must have `warehouseId` before marking it as `SHIPPED`. - `SHIPPED` creates `OUTBOUND` stock movement per SO item and decreases stock. - Shipping is blocked if stock balance does not exist or quantity is insufficient. - Manual `ADJUSTMENT_OUT` is blocked if it would make stock negative. Role access: - Read stock balances/movements: `ADMIN`, `MANAGER`, `WAREHOUSE`, `FINANCE`. - Manual stock adjustment: `ADMIN`, `WAREHOUSE`. - PO receipt: `ADMIN`, `MANAGER`, `WAREHOUSE`. - SO shipment: `ADMIN`, `MANAGER`, `WAREHOUSE`. - `FINANCE` is read-only. Frontend URLs: - Stock balances: http://localhost:5173/warehouse/stock-balances - Stock movements: http://localhost:5173/warehouse/stock-movements Manual adjustment example: ```bash LOGIN_RESPONSE=$(curl -s -X POST "http://localhost:8080/api/auth/login" \ -H "Content-Type: application/json" \ -d '{"email":"admin@erp.local","password":"admin12345"}') TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.data.accessToken') WAREHOUSE_ID="" PRODUCT_ID="" curl -X POST "http://localhost:8080/api/warehouse/stock-adjustments" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d "{ \"warehouseId\": \"$WAREHOUSE_ID\", \"productId\": \"$PRODUCT_ID\", \"type\": \"ADJUSTMENT_IN\", \"quantity\": 25.000, \"comment\": \"Opening stock balance\" }" ``` Mark PO as received: ```bash curl -X PATCH "http://localhost:8080/api/procurement/purchase-orders/$PO_ID/status" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"status":"RECEIVED","comment":"Received into warehouse"}' ``` Mark SO as shipped: ```bash curl -X PATCH "http://localhost:8080/api/sales/customer-orders/$ORDER_ID/status" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"status":"SHIPPED","comment":"Shipped from warehouse"}' ``` ## Documents Module Documents module генерирует простые PDF placeholders для customer orders без email, подписей, оплат и внешних сервисов. Document types: - `INVOICE` - `CONTRACT` - `DELIVERY_NOTE` Backend endpoints: - `GET /api/documents` - `GET /api/documents/{id}` - `GET /api/documents/customer-orders/{customerOrderId}` - `POST /api/documents/customer-orders/{customerOrderId}/generate` - `GET /api/documents/{id}/download` List endpoint supports: ```text ?page=0&size=20&documentType=INVOICE&sourceType=CUSTOMER_ORDER&sourceId=&status=GENERATED&fromDate=&toDate= ``` Business rules: - Customer order must exist. - Documents cannot be generated for `CANCELLED` customer orders. - `DELIVERY_NOTE` can be generated only for `SHIPPED` or `CLOSED` orders. - `INVOICE` and `CONTRACT` can be generated for any status except `CANCELLED`. - Regenerating the same document type for the same customer order updates the existing PDF and keeps the document number. Role access: - Read/list/download: `ADMIN`, `MANAGER`, `WAREHOUSE`, `FINANCE`. - Generate `INVOICE` and `CONTRACT`: `ADMIN`, `MANAGER`, `FINANCE`. - Generate `DELIVERY_NOTE`: `ADMIN`, `MANAGER`, `WAREHOUSE`. Frontend URLs: - Documents: http://localhost:5173/documents - Customer order details documents block: http://localhost:5173/sales/customer-orders/{id} Generate invoice example: ```bash LOGIN_RESPONSE=$(curl -s -X POST "http://localhost:8080/api/auth/login" \ -H "Content-Type: application/json" \ -d '{"email":"admin@erp.local","password":"admin12345"}') TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.data.accessToken') CUSTOMER_ORDER_ID="" GENERATE_DOCUMENT_RESPONSE=$(curl -s -X POST "http://localhost:8080/api/documents/customer-orders/$CUSTOMER_ORDER_ID/generate" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"documentType":"INVOICE"}') DOCUMENT_ID=$(echo "$GENERATE_DOCUMENT_RESPONSE" | jq -r '.data.id') ``` Download PDF example: ```bash curl -L "http://localhost:8080/api/documents/$DOCUMENT_ID/download" \ -H "Authorization: Bearer $TOKEN" \ -o invoice-placeholder.pdf ``` ## Dashboard / Analytics Dashboard module содержит read-only ERP analytics для руководителя без Power BI, внешней BI-системы и изменения бизнес-логики. Backend endpoints: - `GET /api/dashboard/summary` - `GET /api/dashboard/sales` - `GET /api/dashboard/procurement` - `GET /api/dashboard/warehouse` - `GET /api/dashboard/recent-activities` - `GET /api/dashboard/low-stock` Metrics included: - catalog counts: products, active products, suppliers, customers, warehouses; - sales: customer order counts, active orders, total sales amount, status breakdown, daily sales, top customers; - procurement: purchase order counts, active orders, total procurement amount, status breakdown, daily procurement, top suppliers; - warehouse: stock items, quantity on hand, low stock, movements, stock by warehouse, movements by type; - documents: total generated documents and documents by type; - recent activities across customer orders, purchase orders, stock movements, and documents. Query examples: ```text GET /api/dashboard/sales?fromDate=2026-05-01&toDate=2026-05-31 GET /api/dashboard/procurement?fromDate=2026-05-01&toDate=2026-05-31 GET /api/dashboard/recent-activities?limit=20 GET /api/dashboard/low-stock?threshold=10&page=0&size=20 ``` Role access: - `ADMIN` - `MANAGER` - `WAREHOUSE` - `FINANCE` Frontend URL: - Dashboard: http://localhost:5173/dashboard Dashboard sections: - KPI cards - Sales by Status - Procurement by Status - Warehouse Overview - Low Stock Items - Recent Activities Summary curl example: ```bash LOGIN_RESPONSE=$(curl -s -X POST "http://localhost:8080/api/auth/login" \ -H "Content-Type: application/json" \ -d '{"email":"admin@erp.local","password":"admin12345"}') TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.data.accessToken') curl "http://localhost:8080/api/dashboard/summary" \ -H "Authorization: Bearer $TOKEN" ``` Low stock curl example: ```bash curl "http://localhost:8080/api/dashboard/low-stock?threshold=10&page=0&size=20" \ -H "Authorization: Bearer $TOKEN" ``` ## Demo Data Demo seed заполняет dev/demo окружение тестовыми данными для демонстрации ERP без ручной подготовки справочников, заказов, остатков и документов. Seed выключен по умолчанию: ```yaml app: demo-data: enabled: ${DEMO_DATA_ENABLED:false} ``` Включить можно через env: ```bash DEMO_DATA_ENABLED=true ``` В dev `docker-compose.yml` для backend уже задано: ```yaml DEMO_DATA_ENABLED: ${DEMO_DATA_ENABLED:-true} ``` Demo users: - `admin@erp.local` / `admin12345` / `ADMIN` - `manager@erp.local` / `manager12345` / `MANAGER` - `warehouse@erp.local` / `warehouse12345` / `WAREHOUSE` - `finance@erp.local` / `finance12345` / `FINANCE` Seeded data: - catalog: 8 products, 3 suppliers, 3 customers, 2 warehouses; - procurement: 3 purchase orders, including one `RECEIVED` order that creates inbound stock movements; - sales: 4 customer orders, including one `SHIPPED` order that creates outbound stock movements; - warehouse: manual low-stock adjustments for `Water 0.5L` and `Apple Juice 1L`; - documents: invoice, contract and delivery note for shipped order; invoice and contract for confirmed order. Idempotency: - users are matched by email; - products by SKU; - warehouses by code; - suppliers/customers by BIN or company name; - purchase/customer orders by `DEMO-SEED-*` marker in notes; - manual stock movements by source, warehouse, product and comment; - documents reuse the existing unique `(sourceType, sourceId, documentType)` row. How to verify: 1. Run `docker compose up --build`. 2. Open http://localhost:5173/login. 3. Use any demo credential button. 4. Check Dashboard, Catalog, Purchase Orders, Customer Orders, Stock Balances, Stock Movements, and Documents. ## UI / UX Frontend содержит единый ERP-style интерфейс без внешней UI-библиотеки: - общий layout с grouped sidebar и top header; - reusable UI components в `frontend/src/components/ui`; - единые table/loading/error/empty/pagination states; - единые status badges для закупок, продаж, документов и складских движений; - форматтеры в `frontend/src/utils/formatters.ts` для сумм, количеств, дат, статусов, типов документов и движений; - role-aware UI: frontend скрывает недоступные кнопки, backend остается главным источником прав. Available frontend routes: - `/login` - `/dashboard` - `/catalog/products` - `/catalog/suppliers` - `/catalog/customers` - `/catalog/warehouses` - `/procurement/purchase-orders` - `/procurement/purchase-orders/{id}` - `/sales/customer-orders` - `/sales/customer-orders/{id}` - `/warehouse/stock-balances` - `/warehouse/stock-movements` - `/documents` Role-aware UI behavior: - `FINANCE`: read-only UI for catalog, purchase orders, customer orders, warehouse; can generate invoice/contract documents. - `WAREHOUSE`: no catalog create/edit UI; can receive PO, process/ship SO, create warehouse adjustments, generate delivery notes. - `ADMIN` and `MANAGER`: main operational actions for catalog, procurement, sales, and documents. - Warehouse manual `ADJUSTMENT_OUT`, catalog deactivate, PO cancel, and SO cancel use confirmation dialogs. Common UI components: - `Button`, `Input`, `Select`, `Textarea` - `Card`, `Badge`, `PageHeader` - `EmptyState`, `LoadingState`, `ErrorState` - `ConfirmDialog`, `FormField`, `Pagination` ## Debug Error Endpoint Dev-only endpoint для проверки единого формата ошибок: ```bash curl "http://localhost:8080/api/debug/error?type=validation" curl "http://localhost:8080/api/debug/error?type=not-found" curl "http://localhost:8080/api/debug/error?type=business" curl "http://localhost:8080/api/debug/error?type=generic" ``` Endpoint должен быть удален или отключен перед production. ## Useful Commands Run: ```bash docker compose up --build ``` Stop: ```bash docker compose down ``` Reset database: ```bash docker compose down -v docker compose up --build ``` Backend logs: ```bash docker compose logs -f backend ``` Frontend logs: ```bash docker compose logs -f frontend ``` Postgres logs: ```bash docker compose logs -f postgres ``` Server deployment compose: ```bash docker compose -f docker-compose.server.yml up -d --build ``` `docker-compose.server.yml` does not publish app ports directly. It expects an external Docker network named `common_network` and an edge reverse proxy, for example nginx, to route HTTPS traffic to `erp-frontend` and `/api` traffic to `erp-backend`. # ERP MVP Demo Script Pitch: > This MVP demonstrates the core ERP operating loop: catalog data, procurement, sales orders, warehouse stock movements, document generation and management dashboard in one integrated system. 5-7 minute flow: 1. Login as Admin: `admin@erp.local` / `admin12345`. 2. Open Dashboard and show KPI cards for catalog, sales, procurement, warehouse, documents and low stock. 3. Open Catalog -> Products, Suppliers, Customers, Warehouses and show the seeded master data. 4. Open Purchase Orders and open the received PO. 5. Explain that moving PO to `RECEIVED` created inbound stock movements. 6. Open Stock Balances and show updated warehouse quantities. 7. Open Customer Orders and open the shipped order. 8. Explain that moving SO to `SHIPPED` created outbound stock movements and checked available stock. 9. Open Documents and show generated invoice, contract and delivery note placeholders. 10. Download one PDF from Documents or Customer Order details. 11. Return to Dashboard and show recent activities, low stock and updated analytics. ## Manual QA Checklist Backend: - [ ] `/api/health` returns `UP`. - [ ] Swagger opens at http://localhost:8080/swagger-ui/index.html. - [ ] Login works. - [ ] Catalog CRUD works. - [ ] Purchase order flow works. - [ ] Customer order flow works. - [ ] Stock balances update correctly. - [ ] Documents generate and download. - [ ] Dashboard loads. Frontend: - [ ] Login works. - [ ] Sidebar navigation works. - [ ] Dashboard loads. - [ ] Forms save data. - [ ] Status transitions work. - [ ] Errors display properly. - [ ] Download PDF works. - [ ] Logout works. ## Troubleshooting PostgreSQL port already in use: - Stop the local PostgreSQL process or change the `5432:5432` mapping in `docker-compose.yml`. Backend cannot connect to database: - Check `docker compose logs -f backend` and confirm `SPRING_DATASOURCE_URL`, `POSTGRES_DB`, `POSTGRES_USER` and `POSTGRES_PASSWORD` match. Flyway migration failed: - For dev data loss is acceptable, reset with `docker compose down -v` and then `docker compose up --build`. Frontend cannot call backend due to CORS: - Confirm `CORS_ALLOWED_ORIGINS` includes `http://localhost:5173`. JWT expired or unauthorized: - Logout, login again, and check that requests include `Authorization: Bearer `. Demo data not visible: - Ensure `DEMO_DATA_ENABLED=true`, then reset the dev database if it was initialized before enabling demo data. PDF download blocked by browser: - Try downloading from the Documents page again and make sure pop-up/download blocking is not preventing the file save. Database credentials changed but old volume remains: - Run `docker compose down -v` so PostgreSQL can initialize with the new `.env` values. # MVP Scope Included: - Auth and roles. - Catalog. - Procurement. - Sales. - Warehouse stock. - Documents. - Dashboard. - Demo data. Not included in MVP v1: - Real 1C integration. - Real WMS/TMS integration. - Payment module. - Email/WhatsApp sending. - Electronic signature. - Advanced approval workflow. - Advanced BI/Power BI. - Multi-company accounting.