metallkart-erp/MASTER_CONTEXT.md
Louis-Andre 1f7cab10de docs: add MASTER_CONTEXT.md - architect persistent memory
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:31:19 +00:00

29 KiB
Raw Blame History

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:

  • Supplier 1:N Product (un fournisseur a plusieurs produits)
  • User 1:N Order (un utilisateur cree plusieurs commandes)
  • Order 1:N OrderItem (une commande a plusieurs lignes)
  • Product 1:N OrderItem (un produit dans plusieurs lignes)
  • Order 1:1 Invoice (une commande = une facture)
  • Product 1:1 Inventory (un produit = une ligne stock)
  • Product 1:N InventoryMovement (un produit a plusieurs mouvements)
  • Order 1:N InventoryMovement (une commande genere des mouvements)
  • User 1:N InventoryMovement (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 name et inn, filtre active. Tri par createdAt 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 name et sku, filtres category, active, supplierId. Inclut relation supplier { id, name }. Tri par createdAt 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:

  1. <nom>.schemas.ts - Schemas Zod + types exportes (Create*Input, Update*Input, *Query)
  2. <nom>.service.ts - Classe service avec methodes CRUD, interactions Prisma
  3. <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 clientInn dans 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:

  1. ARCHITECT - Lit MASTER_CONTEXT, planifie, produit les specs detaillees
  2. CODER - Execute les specs, ecrit le code, fait les migrations
  3. 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 produits
  • POST /api/v1/orders - Creer commande (brouillon)
  • PUT /api/v1/orders/:id - Modifier commande
  • PATCH /api/v1/orders/:id/status - Changer statut (workflow)
  • DELETE /api/v1/orders/:id - Supprimer brouillon uniquement
  • POST /api/v1/orders/:id/items - Ajouter ligne
  • PUT /api/v1/orders/:id/items/:itemId - Modifier ligne
  • DELETE /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 mouvements
  • POST /api/v1/inventory/movements - Enregistrer un mouvement
  • GET /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 facture
  • POST /api/v1/invoices - Creer facture depuis une commande
  • PATCH /api/v1/invoices/:id/status - Changer statut
  • GET /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.