924 lines
26 KiB
Markdown
924 lines
26 KiB
Markdown
# 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="<paste-access-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="<supplier-id>"
|
||
WAREHOUSE_ID="<warehouse-id>"
|
||
PRODUCT_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="<customer-id>"
|
||
PRODUCT_ID="<product-id>"
|
||
WAREHOUSE_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="<warehouse-id>"
|
||
PRODUCT_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="<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 <token>`.
|
||
|
||
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.
|