@mostajs/carte-restaurant
v0.2.0
Published
La carte d'un restaurant : catégories, articles, variantes, options, formules ; prix par CANAL et par devise, TVA résolue par règle (pays × nature × canal), allergènes codifiés, coût matière et temps de préparation. Calcule le prix d'une ligne. DB-agnosti
Maintainers
Readme
@mostajs/carte-restaurant
Auteur : Dr Hamid MADANI [email protected] Licence : AGPL-3.0-or-later · Couche : N2 (métier) · Dépendances de production : aucune
Ce que ce module décrit : ce que vend un restaurant — catégories, articles, variantes, options, formules — et le prix d'une ligne, par canal, par devise, TVA résolue par règle.
Ce qu'il ne fera jamais : les commandes (
@mostajs/restauration), les tables (@mostajs/booking), l'encaissement (@mostajs/payment), le stock réel (@mostajs/stocks, référencé par identifiant et jamais importé), les écrans (crud-ui/app-shell-ui).
⚠️ @mostajs/menu et @mostajs/menu-html sont des menus de NAVIGATION d'interface. Aucun
rapport avec une carte de restaurant. C'est le piège de nommage le plus coûteux du domaine.
État
Jalon 1 livré — le noyau du catalogue. La carte se charge, se liste, se traduit.
Le calcul de prix (prixLigne()), la TVA, les options, la marge et les formules arrivent aux
jalons suivants : voir docs/03-PLAN-DEV-CARTE-RESTAURANT.md §5.
| Jalon | Contenu | État |
|---|---|---|
| J1 | Établissement, catégories, articles, variantes, disponibilité, dépôts injectés | ✅ 8 tests verts |
| J2 | PrixCanal daté, RegleTva, prixLigne() | à venir |
| J3 | Options et groupes d'options | à venir |
| J4 | Fiche technique, coutMatiere(), marge() | à venir |
| J5 | Formules et ventilation du forfait | à venir |
Installation
npm i @mostajs/carte-restaurantExemple minimal
import { createCarte, creerReposMemoire } from '@mostajs/carte-restaurant';
const carte = createCarte({
repos: creerReposMemoire(), // ou vos propres dépôts (forme @mostajs/repository)
etablissement: {
id: 'khallil-fr',
nom: 'Khallil Sushi',
pays: 'FR', // entrée de la résolution de TVA
devise: 'EUR',
uniteMineure: 2,
arrondi: { rang: 1, sens: 'mathematique' }, // rang = PAS en unité mineure
langues: ['fr', 'en', 'ar'],
},
});
await carte.categories.save({ id: 'makis', ordre: 30, libelle: { fr: 'Makis', ar: 'ماكي' } });
await carte.articles.save({
id: 'california-saumon-avocat',
categorieId: 'makis',
libelle: { fr: 'California saumon avocat' },
nature: 'nourriture', // nourriture | boisson-sans-alcool | alcool
allergenes: ['poissons', 'sesame'], // énumération FERMÉE — hors liste = refus
tempsPreparation: 10, // minutes — entrée de @mostajs/ro-pla
});
// → une variante implicite « california-saumon-avocat::defaut » est créée d'office
await carte.categories.list({ langue: 'ar' });
await carte.articles.list({ canal: 'livraison', disponibleLe: new Date(), langue: 'fr' });Référence API — jalon 1
createCarte({ repos, etablissement }) → Carte
Valide et normalise l'établissement, vérifie la présence des dépôts categories, articles et
variantes. Un dépôt absent provoque un refus nommé (DEPOT_MANQUANT), jamais une liste vide.
carte.categories
| Méthode | Rend |
|---|---|
| save(categorie) | la catégorie validée ; refuse un second niveau d'imbrication |
| get(id) | Categorie \| null |
| list({ actives?, langue?, parentId? }) | CategorieVue[] — triées par ordre, texte résolu |
L'ordre ne dépend pas de la langue affichée : à ordre égal, le départage se fait sur la
langue de repli de l'établissement. Sans quoi la carte changerait d'ordre entre le français et
l'arabe, et le serveur ne verrait plus la même que le client.
carte.articles
| Méthode | Rend |
|---|---|
| save(article, { variantes? }) | ArticleVue ; crée la variante implicite s'il n'en existe aucune |
| get(id, { langue? }) | ArticleVue \| null |
| exiger(id) | ArticleVue ; lève en nommant l'identifiant introuvable |
| variantesDe(articleId) | Variante[] — jamais vide après save() |
| estDisponible(article, { date?, canal? }) | boolean |
| list({ categorieId?, canal?, disponibleLe?, langue? }) | ArticleVue[] |
Montants
import { montant, ajoute, multiplie, exact, arrondir } from '@mostajs/carte-restaurant';
montant(1250, 'EUR'); // 12,50 € — un ENTIER de la plus petite unité
montant(12.5, 'EUR'); // ✗ lève : MONTANT_NON_ENTIER
ajoute(montant(1000, 'EUR'), montant(1000, 'DZD')); // ✗ lève : DEVISE_ETRANGERE
arrondir(exact(1234), etablissement); // le SEUL point d'arrondi du moduleLes valeurs intermédiaires circulent en fraction d'entiers (Exact) et ne deviennent un montant
qu'à la toute fin. 0.1 + 0.2 !== 0.3 : un ticket faux d'un centime est un ticket refusé, et
l'écart n'apparaît jamais en développement — il apparaît chez le client, une fois par semaine.
Erreurs
Toute erreur est une ErreurCarte portant un code stable (ALLERGENE_INCONNU,
CANAL_MANQUANT, DEPOT_MANQUANT, MONTANT_NON_ENTIER, DEVISE_ETRANGERE…) et un message qui
nomme l'entité et le champ en cause.
Tests
npm test # typecheck + build + suite mjs-unit ; preuves dans test-scripts/.out/La suite tourne entièrement en mémoire : aucun serveur, aucun fichier, aucune variable d'environnement. Les tests des jalons à venir y figurent, déclarés ignorés avec leur jalon — une suite « tout au vert » ne doit jamais pouvoir se lire comme une suite complète.
Documentation
docs/00-PROPOSITION-MODULE.md · 01-ETUDE-ETAT-ART · 02-AUDIT-EXISTANT · 03-PLAN-DEV ·
04-PLAN-TESTS · DEVTEST-PLAN.carte-restaurant.json (jumeau qatrax) · llms.txt (fiche LLM).
