29 KiB
MASTER_CONTEXT - MetallKart ERP
Bible du projet - Memoire persistante de l'agent ARCHITECT
Derniere mise a jour: 09/03/2026 | Version: 1.0
1. VISION PRODUIT
Entreprise: MetallKart (Тверь / Tver, Russie) Activite: Fabricant d'equipements pour entrepots et equipements industriels depuis 2007. Objectif ERP: Remplacer la gestion manuelle (Excel, papier) par un ERP integre couvrant fournisseurs, produits, commandes, stock, facturation et rapports.
Contraintes cles:
- Deployable sur serveur local en Russie (independant de services cloud externes)
- Standalone: ne depend PAS de n8n pour fonctionner
- Conforme aux normes comptables russes (ИНН, НДС 20%)
- Multilingue: code=anglais, UI/messages=russe, documentation=francais
Utilisateurs cibles:
- Direction (ADMIN) - acces complet
- Comptabilite (COMPTABLE) - factures, rapports
- Commercial (COMMERCIAL) - commandes, clients
- Production (PRODUCTION) - stock, mouvements
- Employes (USER) - consultation
2. ARCHITECTURE TECHNIQUE
Stack
| Composant | Technologie | Version |
|---|---|---|
| Runtime | Node.js (ESM, type:module) | 20.x+ |
| Framework | Fastify | 4.x |
| ORM | Prisma | 5.x |
| Base de donnees | PostgreSQL | 16 |
| Authentification | @fastify/jwt + bcryptjs | JWT |
| Validation | Zod | 3.x |
| Langage | TypeScript | 5.x |
| Dev runner | tsx | latest |
Infrastructure
- Serveur: VPS Hostinger, 48 GB RAM
- Port applicatif: 3001
- Host: 0.0.0.0
- DB staging:
postgresql://metallkart:MK_PG_2026_staging!@172.19.0.5:5432/erp_staging
Git & CI/CD
- Gitea: depot local sur le VPS
- GitHub: depot prive (francegruart) - backup
- Remotes:
origin(Gitea) +github(GitHub) - CI/CD: Claude Code CLI via
send_task.sh(n8n orchestrateur leger) - Backup: cron quotidien a 2h00, git push vers les deux remotes
Structure du projet
/opt/erp/metallkart-erp/
├── CLAUDE.md # Instructions Claude Code CLI (lu automatiquement)
├── MASTER_CONTEXT.md # Ce fichier (bible projet)
├── package.json
├── tsconfig.json
├── prisma/
│ └── schema.prisma # Schema DB (source of truth)
├── src/
│ ├── server.ts # Point d'entree Fastify
│ ├── seed.ts # Seed DB initial
│ ├── plugins/
│ │ ├── prisma.ts # Plugin connexion DB
│ │ └── jwt.ts # Plugin authentification JWT
│ └── modules/
│ ├── auth/ # Authentification
│ │ ├── auth.routes.ts
│ │ └── auth.service.ts
│ ├── suppliers/ # Fournisseurs
│ │ ├── suppliers.routes.ts
│ │ ├── suppliers.service.ts
│ │ └── suppliers.schemas.ts
│ └── products/ # Produits
│ ├── products.routes.ts
│ ├── products.service.ts
│ └── products.schemas.ts
3. SCHEMA DE DONNEES
3.1 Modeles existants (implementes)
User
model User {
id String @id @default(uuid())
email String @unique
password String
name String
role Role @default(USER)
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("users")
}
enum Role {
ADMIN
COMPTABLE
COMMERCIAL
PRODUCTION
USER
}
Supplier
model Supplier {
id Int @id @default(autoincrement())
name String
inn String @unique
email String?
phone String?
address String?
contactPerson String? @map("contact_person")
active Boolean @default(true)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
products Product[]
@@map("suppliers")
}
Product
model Product {
id Int @id @default(autoincrement())
name String
sku String @unique
description String?
category String?
unit String @default("шт")
price Decimal
cost Decimal?
weight Decimal?
active Boolean @default(true)
supplierId Int? @map("supplier_id")
supplier Supplier? @relation(fields: [supplierId], references: [id])
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("products")
}
3.2 Modeles planifies
Order (Commande)
model Order {
id Int @id @default(autoincrement())
orderNumber String @unique @map("order_number") // Format: MK-YYYYMMDD-NNN
clientName String @map("client_name")
clientPhone String? @map("client_phone")
clientEmail String? @map("client_email")
clientInn String? @map("client_inn") // ИНН client (optionnel)
status OrderStatus @default(DRAFT)
totalAmount Decimal @default(0) @map("total_amount")
notes String?
createdBy String @map("created_by")
creator User @relation(fields: [createdBy], references: [id])
items OrderItem[]
invoice Invoice?
movements InventoryMovement[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("orders")
}
enum OrderStatus {
DRAFT // Brouillon
CONFIRMED // Confirmee
IN_PRODUCTION // En production
READY // Prete
SHIPPED // Expediee
DELIVERED // Livree
CANCELLED // Annulee
}
OrderItem (Ligne de commande)
model OrderItem {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
productId Int @map("product_id")
product Product @relation(fields: [productId], references: [id])
quantity Decimal
unitPrice Decimal @map("unit_price")
totalPrice Decimal @map("total_price")
@@map("order_items")
}
Inventory (Stock)
model Inventory {
id Int @id @default(autoincrement())
productId Int @unique @map("product_id")
product Product @relation(fields: [productId], references: [id])
location String @default("Основной склад") // Emplacement
quantity Decimal @default(0)
minQuantity Decimal @default(0) @map("min_quantity") // Seuil alerte
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("inventory")
}
InventoryMovement (Mouvement de stock)
model InventoryMovement {
id Int @id @default(autoincrement())
productId Int @map("product_id")
product Product @relation(fields: [productId], references: [id])
type MovementType
quantity Decimal
reason String?
orderId Int? @map("order_id")
order Order? @relation(fields: [orderId], references: [id])
createdBy String @map("created_by")
creator User @relation(fields: [createdBy], references: [id])
createdAt DateTime @default(now()) @map("created_at")
@@map("inventory_movements")
}
enum MovementType {
IN // Entree (reception fournisseur)
OUT // Sortie (expedition commande)
ADJUSTMENT // Ajustement (inventaire)
RETURN // Retour (client ou fournisseur)
}
Invoice (Facture)
model Invoice {
id Int @id @default(autoincrement())
invoiceNumber String @unique @map("invoice_number") // Format: МК-YYYY-NNN
orderId Int @unique @map("order_id")
order Order @relation(fields: [orderId], references: [id])
clientName String @map("client_name")
clientInn String? @map("client_inn")
subtotal Decimal // HT
vatRate Decimal @default(20) @map("vat_rate") // НДС 20%
vatAmount Decimal @map("vat_amount") // Montant НДС
totalAmount Decimal @map("total_amount") // TTC
status InvoiceStatus @default(DRAFT)
issuedAt DateTime? @map("issued_at")
dueAt DateTime? @map("due_at")
paidAt DateTime? @map("paid_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("invoices")
}
enum InvoiceStatus {
DRAFT // Brouillon
SENT // Envoyee
PAID // Payee
OVERDUE // En retard
CANCELLED // Annulee
}
3.3 Diagramme des relations
┌──────────┐ ┌───────────┐ ┌──────────────┐
│ User │ │ Supplier │ │ Product │
│──────────│ │───────────│ │──────────────│
│ id (PK) │ │ id (PK) │ │ id (PK) │
│ email │ │ name │──1:N─→│ supplierId │
│ password │ │ inn │ │ name │
│ name │ │ email │ │ sku │
│ role │ │ phone │ │ price │
│ active │ │ address │ │ unit │
└────┬─────┘ │ active │ │ active │
│ └───────────┘ └──┬───┬───────┘
│ │ │
│ createdBy │ │ productId
│ │ │
┌────┴─────────┐ ┌──────────────┐ ┌──┴───┴──────────┐
│ Order │ │ OrderItem │ │ Inventory │
│──────────────│ │──────────────│ │─────────────────│
│ id (PK) │ │ id (PK) │ │ id (PK) │
│ orderNumber │──→│ orderId (FK) │ │ productId (FK) │
│ clientName │ │ productId(FK)│ │ location │
│ status │ │ quantity │ │ quantity │
│ totalAmount │ │ unitPrice │ │ minQuantity │
│ createdBy(FK)│ │ totalPrice │ └─────────────────┘
└──┬───┬───────┘ └──────────────┘
│ │
│ │ orderId ┌──────────────────────┐
│ │ │ InventoryMovement │
│ │ ┌──────────────┐ │──────────────────────│
│ └──→│ Invoice │ │ id (PK) │
│ │──────────────│ │ productId (FK) │
│ │ id (PK) │ │ type (enum) │
│ │ invoiceNumber│ │ quantity │
│ │ orderId (FK) │ │ reason │
│ │ clientName │ │ orderId (FK, opt) │
│ │ vatRate (20%)│ │ createdBy (FK User) │
│ │ status │ └──────────────────────┘
│ └──────────────┘ ↑
│ │
└───────────────────────────────────────┘
orderId (optionnel)
Resume des relations:
Supplier1:NProduct(un fournisseur a plusieurs produits)User1:NOrder(un utilisateur cree plusieurs commandes)Order1:NOrderItem(une commande a plusieurs lignes)Product1:NOrderItem(un produit dans plusieurs lignes)Order1:1Invoice(une commande = une facture)Product1:1Inventory(un produit = une ligne stock)Product1:NInventoryMovement(un produit a plusieurs mouvements)Order1:NInventoryMovement(une commande genere des mouvements)User1:NInventoryMovement(un utilisateur enregistre des mouvements)
4. MODULES - ETAT DETAILLE
4.1 Module Auth (/api/v1/auth)
Statut: Implemente (08/03/2026)
Fichiers: auth.routes.ts, auth.service.ts (pas de fichier schemas separe)
Endpoints
| Methode | Route | Auth | Description |
|---|---|---|---|
POST |
/login |
Non | Connexion email/mot de passe |
GET |
/me |
Oui | Profil utilisateur connecte |
POST |
/refresh |
Non | Renouveler le token JWT |
POST |
/logout |
Oui | Deconnexion (symbolique) |
Schemas Zod (inline dans routes)
// loginSchema
{ email: z.string().email(), password: z.string().min(6) }
// refreshSchema
{ refreshToken: z.string() }
Comportements service
- login: Cherche user par email → bcrypt.compare → retourne
{ accessToken, refreshToken, user }. RefreshToken expire en 30 jours. Erreur 401 si user inexistant, inactif ou mot de passe incorrect. - me: Retourne user sans le champ password. Erreur 404 si user inexistant.
- refresh: Verifie le refreshToken avec
JWT_REFRESH_SECRET→ retourne nouveau{ accessToken }. Erreur 401 si token invalide/expire. - logout: Retourne
{ message: 'Выход выполнен' }(pas d'invalidation cote serveur).
4.2 Module Suppliers (/api/v1/suppliers)
Statut: Implemente (09/03/2026) - commit b56e371
Fichiers: suppliers.routes.ts, suppliers.service.ts, suppliers.schemas.ts
Endpoints (tous authentifies)
| Methode | Route | Code retour | Description |
|---|---|---|---|
GET |
/ |
200 | Liste paginee avec recherche et filtres |
GET |
/:id |
200 | Detail d'un fournisseur |
POST |
/ |
201 | Creer un fournisseur |
PUT |
/:id |
200 | Modifier un fournisseur |
DELETE |
/:id |
204 | Supprimer un fournisseur |
Schemas Zod
// createSupplierSchema
{
name: z.string().min(1, 'Название обязательно'),
inn: z.string().regex(/^\d{10}(\d{2})?$/, 'ИНН должен содержать 10 или 12 цифр'),
email: z.string().email('Некорректный email').optional().nullable(),
phone: z.string().optional().nullable(),
address: z.string().optional().nullable(),
contactPerson: z.string().optional().nullable(),
active: z.boolean().optional()
}
// updateSupplierSchema = createSupplierSchema.partial()
// supplierQuerySchema
{
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
search: z.string().optional(), // recherche sur name et inn (insensible casse)
active: z.enum(['true', 'false']).optional()
}
Comportements service
- list: Pagination (defaut: page=1, limit=20), recherche insensible a la casse sur
nameetinn, filtreactive. Tri parcreatedAt DESC. Retourne{ data, total, page, limit, totalPages }. - getById: Retourne le fournisseur ou erreur 404
'Поставщик не найден'. - create: Verifie unicite INN → erreur 409 si doublon.
- update: Verifie existence (404) puis unicite INN si modifie (409).
- delete: Verifie existence (404) puis supprime (hard delete).
4.3 Module Products (/api/v1/products)
Statut: Implemente (09/03/2026) - commit 072b747
Fichiers: products.routes.ts, products.service.ts, products.schemas.ts
Endpoints (tous authentifies)
| Methode | Route | Code retour | Description |
|---|---|---|---|
GET |
/ |
200 | Liste paginee avec recherche et filtres |
GET |
/:id |
200 | Detail d'un produit (avec fournisseur) |
POST |
/ |
201 | Creer un produit |
PUT |
/:id |
200 | Modifier un produit |
DELETE |
/:id |
204 | Supprimer un produit |
Schemas Zod
// createProductSchema
{
name: z.string().min(1, 'Название обязательно'),
sku: z.string().min(1, 'Артикул обязателен'),
description: z.string().optional().nullable(),
category: z.string().optional().nullable(),
unit: z.enum(['шт', 'кг', 'м', 'м2', 'м3', 'компл']).default('шт'),
price: z.coerce.number().positive('Цена должна быть положительной'),
cost: z.coerce.number().positive('Себестоимость должна быть положительной').optional().nullable(),
weight: z.coerce.number().positive('Вес должен быть положительным').optional().nullable(),
active: z.boolean().optional(),
supplierId: z.coerce.number().int().positive().optional().nullable()
}
// updateProductSchema = createProductSchema.partial()
// productQuerySchema
{
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
search: z.string().optional(), // recherche sur name et sku
category: z.string().optional(), // filtre exact sur categorie
active: z.enum(['true', 'false']).optional(),
supplierId: z.coerce.number().int().positive().optional()
}
Comportements service
- list: Pagination, recherche insensible a la casse sur
nameetsku, filtrescategory,active,supplierId. Inclut relationsupplier { id, name }. Tri parcreatedAt DESC. - getById: Inclut
supplier { id, name }. Erreur 404'Продукт не найден'. - create: Verifie unicite SKU → erreur 409 si doublon. Inclut supplier dans le retour.
- update: Verifie existence (404) puis unicite SKU si modifie (409).
- delete: Verifie existence (404) puis supprime (hard delete).
4.4 Endpoint Health
GET /health→{ status: 'ok' }(pas d'authentification)
5. CONVENTIONS DE CODAGE
Pattern module (3 fichiers)
Chaque module src/modules/<nom>/ contient:
<nom>.schemas.ts- Schemas Zod + types exportes (Create*Input,Update*Input,*Query)<nom>.service.ts- Classe service avec methodes CRUD, interactions Prisma<nom>.routes.ts- Fonction async registrant les routes Fastify, validation Zod en preHandler
Patterns Zod
// Schema creation: tous les champs requis
export const createXxxSchema = z.object({ ... });
// Schema update: .partial() du schema creation
export const updateXxxSchema = createXxxSchema.partial();
// Schema query: pagination + filtres avec z.coerce
export const xxxQuerySchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
// ... filtres specifiques
});
// Types inferes
export type CreateXxxInput = z.infer<typeof createXxxSchema>;
Patterns service
export class XxxService {
constructor(private prisma: PrismaClient) {}
async list(query: XxxQuery) { /* pagination, where, orderBy createdAt desc */ }
async getById(id: number) { /* findUnique ou throw 404 */ }
async create(input: CreateXxxInput) { /* unicite check, create */ }
async update(id: number, input: UpdateXxxInput) { /* getById + unicite check + update */ }
async delete(id: number) { /* getById + delete */ }
}
Patterns routes
export async function xxxRoutes(server: FastifyInstance) {
const service = new XxxService(server.prisma);
// Hook global: authentification requise pour toutes les routes du module
server.addHook('preHandler', server.authenticate);
server.get('/', async (request, reply) => {
const query = xxxQuerySchema.parse(request.query);
// ...
});
}
Conventions de nommage
- Tables DB: snake_case pluriel via
@@map("nom_tables") - Colonnes DB: snake_case via
@map("nom_colonne") - Modeles Prisma: PascalCase singulier
- Champs Prisma: camelCase
- Variables/fonctions TS: camelCase
- Classes TS: PascalCase
- Enums: UPPER_SNAKE_CASE
- Routes API:
/api/v1/<module>(kebab-case si necessaire) - Fichiers: kebab-case ou nom_module.type.ts
Gestion des erreurs
// Validation Zod
try { schema.parse(data); }
catch (e) { reply.status(400).send({ error: 'Validation', details: e.flatten() }); }
// Erreurs metier
reply.status(404).send({ error: 'Поставщик не найден' });
reply.status(409).send({ error: 'Поставщик с таким ИНН уже существует' });
6. REGLES METIER RUSSES
ИНН (INN - Numero d'identification fiscale)
- Personnes morales: 10 chiffres exactement
- Entrepreneurs individuels: 12 chiffres exactement
- Validation regex:
/^\d{10}(\d{2})?$/ - Obligatoire pour les fournisseurs
- Optionnel pour les clients (champ
clientInndans Order/Invoice) - Unique par fournisseur dans la base
НДС (TVA russe)
- Taux standard: 20%
- Calcul:
subtotal * vatRate / 100 = vatAmount - Total TTC:
subtotal + vatAmount = totalAmount - Certains produits peuvent etre exoneres (taux 0% ou 10%) - a gerer au niveau produit si necessaire
Formats de numerotation
- Commandes:
MK-YYYYMMDD-NNN(ex: MK-20260310-001) - Factures:
МК-YYYY-NNN(ex: МК-2026-001) - prefixe en cyrillique - SKU produits: format libre, unique
Unites de mesure
| Code | Signification | Utilisation |
|---|---|---|
| шт | Pieces (штуки) | Equipements, accessoires |
| кг | Kilogrammes | Materiaux metalliques |
| м | Metres lineaires | Profiles, tubes |
| м2 | Metres carres | Toles, panneaux |
| м3 | Metres cubes | Volumes |
| компл | Ensemble/kit (комплект) | Kits complets |
Devises
- Devise principale: Rouble russe (₽ / RUB)
- Les montants sont stockes en
Decimal(precision Prisma par defaut)
7. DECISIONS ARCHITECTURALES (ADR)
ADR-001: Application standalone (09/03/2026)
Contexte: L'ERP doit fonctionner de maniere autonome sur un serveur en Russie. Decision: Application Node.js/Fastify standalone. n8n n'est utilise que comme orchestrateur leger (envoi de commandes texte). Le code ne transite JAMAIS par des webhooks n8n. Consequence: Deploiement simple, pas de dependance externe.
ADR-002: Claude Code CLI comme moteur de developpement (09/03/2026)
Contexte: Besoin d'automatiser le developpement avec des agents IA.
Decision: Claude Code CLI ecrit directement sur le filesystem. Pipeline: n8n declenche → send_task.sh → Claude Code CLI execute.
Consequence: Code de haute qualite, iterable rapidement.
ADR-003: Soft delete par defaut (planifie)
Contexte: Les donnees metier ne doivent jamais etre perdues definitivement.
Decision: Tous les modeles ont un champ active: Boolean @default(true). Le "delete" bascule active = false au lieu de supprimer physiquement.
Statut: A IMPLEMENTER. Actuellement les modules suppliers et products font du hard delete. Migration vers soft delete prevue lors du prochain refactoring.
ADR-004: Pipeline 3 agents (planifie)
Contexte: Besoin d'un processus de developpement structure et fiable. Decision: Pipeline a 3 niveaux:
- ARCHITECT - Lit MASTER_CONTEXT, planifie, produit les specs detaillees
- CODER - Execute les specs, ecrit le code, fait les migrations
- REVIEWER - Valide le code, teste, verifie la conformite Statut: En cours de mise en place.
8. ERREURS CONNUES ET SOLUTIONS
Prisma migrate dev echoue en mode non-interactif
Probleme: prisma migrate dev demande une confirmation interactive quand il detecte un drift.
Solution: Utiliser prisma db push a la place pour le developpement automatise.
npx prisma db push
npx prisma generate
EADDRINUSE: Port 3001 deja occupe
Probleme: Le serveur Fastify ne demarre pas car le port est deja utilise. Solution:
fuser -k 3001/tcp
# puis relancer le serveur
HOST_TERMINAL tue les processus enfants
Probleme: Quand le terminal se ferme, les processus lances sont tues.
Solution: Utiliser systemd-run pour les processus long:
systemd-run --user --scope tsx src/server.ts
Claude Code CLI: pas de flag --cwd
Probleme: Claude Code CLI ne supporte pas --cwd pour changer de repertoire.
Solution: Faire cd avant de lancer la commande.
Claude Code CLI: pas de --dangerously-skip-permissions en root
Probleme: Le flag --dangerously-skip-permissions est desactive quand on execute en tant que root.
Solution: Configurer les permissions correctement via les settings ou executer sous un utilisateur non-root.
Timeout des commandes longues
Probleme: Les builds TypeScript ou migrations peuvent timeout.
Solution: Augmenter le timeout dans les configurations ou utiliser run_in_background.
9. CHANGELOG
| Date | Module | Commit | Description |
|---|---|---|---|
| 08/03/2026 | auth | 3e75772 |
Squelette initial: server.ts, plugins, module auth (login, register, JWT refresh) |
| 09/03/2026 | - | 9ad37a3 |
Mise a jour CLAUDE.md: plan test pipeline + notes session |
| 09/03/2026 | suppliers | b56e371 |
Module fournisseurs complet: CRUD, validation INN, pagination, recherche |
| 09/03/2026 | products | 072b747 |
Module produits complet: CRUD, SKU unique, relation fournisseur, filtres |
| 09/03/2026 | docs | 74d62dd |
CLAUDE.md: marquer module products comme implemente |
10. PROCHAINS MODULES (ordre de dependance)
Phase 1: Orders (Commandes) - PRIORITE HAUTE
Dependances: User, Product (existants) Modeles: Order, OrderItem Endpoints prevus:
GET /api/v1/orders- Liste paginee avec filtres (status, client, date)GET /api/v1/orders/:id- Detail commande avec lignes et produitsPOST /api/v1/orders- Creer commande (brouillon)PUT /api/v1/orders/:id- Modifier commandePATCH /api/v1/orders/:id/status- Changer statut (workflow)DELETE /api/v1/orders/:id- Supprimer brouillon uniquementPOST /api/v1/orders/:id/items- Ajouter lignePUT /api/v1/orders/:id/items/:itemId- Modifier ligneDELETE /api/v1/orders/:id/items/:itemId- Supprimer ligne
Regles metier:
- Numero auto-genere:
MK-YYYYMMDD-NNN - Seules les commandes DRAFT peuvent etre modifiees/supprimees
- Le totalAmount est recalcule a chaque modification de ligne
- Workflow de statuts: DRAFT → CONFIRMED → IN_PRODUCTION → READY → SHIPPED → DELIVERED
- CANCELLED accessible depuis tout statut sauf DELIVERED
Phase 2: Inventory (Stock/Entrepot)
Dependances: Product (existant), Order (phase 1) Modeles: Inventory, InventoryMovement Endpoints prevus:
GET /api/v1/inventory- Etat du stock (avec alertes seuil)GET /api/v1/inventory/:productId- Stock d'un produit + historique mouvementsPOST /api/v1/inventory/movements- Enregistrer un mouvementGET /api/v1/inventory/alerts- Produits sous le seuil minimum
Regles metier:
- Mouvement IN: reception fournisseur → augmente stock
- Mouvement OUT: expedition commande → diminue stock (verifie disponibilite)
- Mouvement ADJUSTMENT: correction inventaire physique
- Mouvement RETURN: retour → augmente stock
- Alerte quand
quantity < minQuantity - Lien optionnel avec commande (orderId) pour tracabilite
Phase 3: Invoices (Facturation)
Dependances: Order (phase 1) Modeles: Invoice Endpoints prevus:
GET /api/v1/invoices- Liste paginee avec filtres (status, client, date)GET /api/v1/invoices/:id- Detail facturePOST /api/v1/invoices- Creer facture depuis une commandePATCH /api/v1/invoices/:id/status- Changer statutGET /api/v1/invoices/:id/pdf- Generer PDF (futur)
Regles metier:
- Une facture est liee a exactement une commande (1:1)
- Numero auto-genere:
МК-YYYY-NNN(prefixe cyrillique) - НДС calcule automatiquement a 20%
- Workflow: DRAFT → SENT → PAID / OVERDUE → CANCELLED
dueAt=issuedAt+ 30 jours par defaut
Phase 4: Reports (Rapports)
Dependances: Tous les modules precedents Endpoints prevus:
GET /api/v1/reports/sales- Rapport ventes (periode, totaux)GET /api/v1/reports/inventory- Rapport stock (valeur, rotation)GET /api/v1/reports/financial- Rapport financier (CA, marges, НДС)GET /api/v1/reports/suppliers- Rapport fournisseurs (volumes, delais)
Format: JSON structuree, export CSV/Excel prevu ulterieurement.
ANNEXE: Variables d'environnement requises
| Variable | Description | Exemple |
|---|---|---|
DATABASE_URL |
URL PostgreSQL | postgresql://metallkart:...@172.19.0.5:5432/erp_staging |
JWT_SECRET |
Cle secrete JWT access token | (genere aleatoirement) |
JWT_REFRESH_SECRET |
Cle secrete JWT refresh token | (genere aleatoirement) |
PORT |
Port du serveur | 3001 |
HOST |
Host du serveur | 0.0.0.0 |
Ce document est la source de verite pour l'agent ARCHITECT. Il doit etre mis a jour apres chaque modification significative du projet.