feat(planning): sépare portions demandées et portions préparées #48

Merged
kyuno merged 1 commit from feat/leftovers-portions-foundations into main 2026-09-05 23:45:19 +02:00
Owner

Chantier « gestion des restes » (issue #34) — PR 1/4 : les fondations. Aucune UI de panier ni glisser-déposer encore ; juste le découpage portions demandées / préparées et le calcul par lot.

Comportement

À l'ajout d'une recette au planning, on ne saisit plus qu'un seul nombre : portionsRequested (« on est 3 », « il en faut 10 »). Le serveur calcule combien de portions sont réellement préparées en arrondissant au multiple supérieur du rendement de la recette (on cuisine des lots entiers d'un plat, jamais une fraction) :

portions = ceil(portionsRequested / recipe.portions) × recipe.portions
demandées recette 4 p. → préparées reste
3 4 1
10 12 2
8 8 0

Modèle (PlanningItem, purement additif)

  • portionsRequested NOT NULL — migration nullable → backfill (= portions) → verrou, même discipline que 20260819064721_planning_item_portions / ingredient_unit_catalog. Toute ligne existante précède la fonctionnalité, donc demandé == préparé.
  • sourcePlanningItemId (auto-FK nullable, onDelete: Cascade) + discardedPortions (DEFAULT 0) — posés maintenant, consommés par les PRs suivantes.

Logique pure partagée

packages/shared/src/logic/planning-portions.ts : computeBatchedPortions(recipePortions, requested) et computeAvailableLeftover(item) — une seule formule appelée des deux côtés (API : dérivation à l'écriture + future validation ; web : indice picker + futur panier).

API

addPlanningItemSchema prend portionsRequested (le client n'envoie plus portions). addPlanningItem calcule et stocke les deux valeurs ; PlanningItemView / getPlanningForDate exposent portionsRequested / discardedPortions / sourceItem sans include Prisma supplémentaire.

Front

  • RecipePickerDialog — libellé « Portions demandées » + indice dérivé en direct via computeBatchedPortions (« → 12 portions préparées, 2 en restes »), affiché seulement si leftover > 0 ; envoie portionsRequested.
  • RecipeImportForm — envoie portionsRequested (un import atterrit à son propre rendement → reste nul).

Tests

  • apps/api/test/planning-portions.test.ts — les deux exemples de #34 + bornes (lot exact, plusieurs lots, une portion, recette 1 portion ; pool disponible, service, jet, jamais négatif). Vérifié localement, 10/10.
  • apps/api/test/planning.test.ts — nouveau bloc POST /planning/items (addPlanningItem n'était pas testé du tout) : arrondi au lot, plusieurs lots, borne exacte, 400 sur portionsRequested non positif ; l'assertion GET /planning vérifie aussi les 3 nouveaux champs.
  • Fixtures prisma.planningItem.create/createMany des suites cooking-session / recipe / shopping-list mises à jour.
  • Cypress planning.feature / planning.ts — le corps POST /planning/items porte portionsRequested.

Docs

batch-cooking-modele.md (entité + diagramme), backend-architecture.md (règle de lot), frontend-architecture.md (étape portions).

🤖 Generated with Claude Code

**Chantier « gestion des restes » ([issue #34](https://git.dev.kyuno.fr/kyuno/batchCooking/issues/34)) — PR 1/4 : les fondations.** Aucune UI de panier ni glisser-déposer encore ; juste le découpage portions demandées / préparées et le calcul par lot. ## Comportement À l'ajout d'une recette au planning, on ne saisit plus qu'**un seul nombre** : `portionsRequested` (« on est 3 », « il en faut 10 »). Le serveur calcule combien de portions sont **réellement préparées** en arrondissant au multiple supérieur du rendement de la recette (on cuisine des lots entiers d'un plat, jamais une fraction) : ``` portions = ceil(portionsRequested / recipe.portions) × recipe.portions ``` | demandées | recette 4 p. → préparées | reste | |---|---|---| | 3 | 4 | 1 | | 10 | 12 | 2 | | 8 | 8 | 0 | ## Modèle (`PlanningItem`, purement additif) - `portionsRequested` `NOT NULL` — migration **nullable → backfill (`= portions`) → verrou**, même discipline que `20260819064721_planning_item_portions` / `ingredient_unit_catalog`. Toute ligne existante précède la fonctionnalité, donc `demandé == préparé`. - `sourcePlanningItemId` (auto-FK nullable, `onDelete: Cascade`) + `discardedPortions` (`DEFAULT 0`) — posés maintenant, consommés par les PRs suivantes. ## Logique pure partagée `packages/shared/src/logic/planning-portions.ts` : `computeBatchedPortions(recipePortions, requested)` et `computeAvailableLeftover(item)` — une seule formule appelée des deux côtés (API : dérivation à l'écriture + future validation ; web : indice picker + futur panier). ## API `addPlanningItemSchema` prend `portionsRequested` (le client n'envoie plus `portions`). `addPlanningItem` calcule et stocke les deux valeurs ; `PlanningItemView` / `getPlanningForDate` exposent `portionsRequested` / `discardedPortions` / `sourceItem` sans `include` Prisma supplémentaire. ## Front - `RecipePickerDialog` — libellé « Portions demandées » + indice dérivé en direct via `computeBatchedPortions` (« → 12 portions préparées, 2 en restes »), affiché seulement si `leftover > 0` ; envoie `portionsRequested`. - `RecipeImportForm` — envoie `portionsRequested` (un import atterrit à son propre rendement → reste nul). ## Tests - `apps/api/test/planning-portions.test.ts` — les deux exemples de #34 + bornes (lot exact, plusieurs lots, une portion, recette 1 portion ; pool disponible, service, jet, jamais négatif). **Vérifié localement, 10/10.** - `apps/api/test/planning.test.ts` — nouveau bloc `POST /planning/items` (`addPlanningItem` n'était pas testé du tout) : arrondi au lot, plusieurs lots, borne exacte, 400 sur `portionsRequested` non positif ; l'assertion `GET /planning` vérifie aussi les 3 nouveaux champs. - Fixtures `prisma.planningItem.create`/`createMany` des suites `cooking-session` / `recipe` / `shopping-list` mises à jour. - Cypress `planning.feature` / `planning.ts` — le corps `POST /planning/items` porte `portionsRequested`. ## Docs `batch-cooking-modele.md` (entité + diagramme), `backend-architecture.md` (règle de lot), `frontend-architecture.md` (étape portions). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kyuno added 1 commit 2026-09-05 23:30:24 +02:00
feat(planning): sépare portions demandées et portions préparées
All checks were successful
CI / lint (push) Successful in 2m37s
CI / intent-service-test (push) Successful in 4m26s
CI / test (push) Successful in 15m8s
CI / build (push) Successful in 2m45s
CI / e2e (push) Successful in 8m6s
eba0ea70bd
Première PR du chantier « gestion des restes » (issue #34) — les
fondations, sans encore d'UI de panier.

À l'ajout d'une recette au planning on ne saisit plus qu'un seul nombre,
`portionsRequested` (« combien ce créneau doit nourrir »). Le serveur en
dérive `portions` (ce qui est réellement préparé) via
`computeBatchedPortions` : arrondi au multiple supérieur du rendement de
la recette (on cuisine des lots entiers, jamais une fraction de plat).
L'écart est le futur pool de restes de l'item.

Modèle (`PlanningItem`, purement additif) :
- `portionsRequested` NOT NULL — migration nullable → backfill (`= portions`
  pour les lignes existantes, sans restes à l'époque) → verrou, même
  discipline que `ingredient_unit_catalog`.
- `sourcePlanningItemId` (auto-FK nullable, `onDelete: Cascade`) et
  `discardedPortions` (défaut 0) — posés maintenant, utilisés plus tard.

Partagé (`@batch-cooking/shared/logic/planning-portions.ts`) :
- `computeBatchedPortions(recipePortions, requested)` → `{ portions, leftover }`
- `computeAvailableLeftover(item)` → pool encore disponible (posé pour les
  PRs suivantes : validation d'écriture côté API, affichage du panier
  côté web — une seule formule, deux appelants)

API : `addPlanningItemSchema` prend `portionsRequested` ; `addPlanningItem`
calcule et stocke les deux valeurs + l'évènement analytics ; `PlanningItemView`
et `getPlanningForDate` exposent `portionsRequested`/`discardedPortions`/
`sourceItem`.

Front : `RecipePickerDialog` — champ « portions demandées » + indice en
direct « → N préparées, M en restes » quand l'arrondi crée un reste ;
envoie `portionsRequested`. `RecipeImportForm` — envoie `portionsRequested`
(un import atterrit à son propre rendement, donc reste nul).

Tests : `planning-portions.test.ts` (les deux exemples de #34 + bornes) ;
nouveau bloc `POST /planning/items` dans `planning.test.ts` (arrondi au
lot, plusieurs lots, borne exacte, 400 sur non-positif) ; fixtures
`planningItem.create` des suites existantes mises à jour ; scénario
Cypress planning ajusté (`portionsRequested` dans le corps).

Docs : `batch-cooking-modele.md`, `backend-architecture.md`,
`frontend-architecture.md`.

Réf #34

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno merged commit 5e34a250df into main 2026-09-05 23:45:19 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: kyuno/batchCooking#48
No description provided.