Files
Inventory/docs/superpowers/specs/2026-03-12-piece-quantity-design.md
Matthieu b8edf1ea95 docs : update piece quantity spec after review
Address review findings: drop Groups attribute, add clone logic,
specify PATCH payload format, list frontend functions to update,
add validation and test cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 11:48:01 +01:00

141 lines
5.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Piece Quantity — Design Spec
## Context
L'application gère des machines composées de composants et de pièces. Une même pièce (catalogue) peut apparaître dans plusieurs contextes avec des quantités différentes. La quantité doit être portée par la **relation**, pas par l'entité catalogue.
## Scope
- Quantité sur les **pièces directement liées à une machine** (`MachinePieceLink`)
- Quantité sur les **pièces d'un composant** (JSON `structure.pieces` du `Composant`)
- **Hors scope** : quantité sur `MachineComponentLink`, `MachineProductLink`, override de quantité composant au niveau machine, audit logging
## Règles métier
| Contexte | Stockage | Éditable depuis | Visible sur machine |
|----------|----------|-----------------|---------------------|
| Pièce directement sur machine | `MachinePieceLink.quantity` | Page machine | Oui, éditable |
| Pièce d'un composant | `Composant.structure.pieces[].quantity` | Page composant (création + édition) | Oui, lecture seule |
- Type : entier, valeur par défaut = 1, minimum = 1
- Affichage : "×N" après le nom de la pièce, masqué si N = 1
- Quantité = 0 n'est pas valide (utiliser la suppression du lien à la place)
## Backend
### 1. MachinePieceLink — Nouvelle colonne
Ajout d'un champ `quantity` sur l'entité `MachinePieceLink` :
```php
#[ORM\Column(type: Types::INTEGER, options: ['default' => 1])]
private int $quantity = 1;
```
Getter/setter standard. **Pas de `#[Groups]`** — cohérent avec les autres champs de l'entité qui n'en déclarent pas (l'entité n'a pas de `normalizationContext`).
Validation : `#[Assert\GreaterThanOrEqual(1)]`
### 2. Migration SQL
```sql
ALTER TABLE machine_piece_link ADD COLUMN IF NOT EXISTS quantity INTEGER NOT NULL DEFAULT 1;
```
Idempotente avec `IF NOT EXISTS`.
### 3. Composant.structure JSON
Le tableau `pieces` dans le JSON `structure` du Composant accepte une nouvelle clé `quantity`. Pas de migration DB nécessaire — c'est un champ JSON libre.
Avant :
```json
{ "typePieceId": "...", "role": "Filtration" }
```
Après :
```json
{ "typePieceId": "...", "role": "Filtration", "quantity": 4 }
```
Les entrées existantes sans `quantity` sont traitées comme `quantity = 1` (défaut côté frontend et backend).
### 4. MachineStructureController
#### Normalisation (GET)
`normalizePieceLinks()` : inclure `quantity` dans la réponse JSON :
- **Pièce machine directe** (parentLink = null) : `quantity` depuis `MachinePieceLink.quantity`
- **Pièce sous composant** : `quantity` depuis le `structure.pieces` du composant source. Résolution :
1. Naviguer `MachinePieceLink``parentLink` (MachineComponentLink) → `composant``structure['pieces']`
2. Matcher par index de position dans le tableau `pieces` (l'ordre des pièces dans la structure correspond à l'ordre de création des liens)
3. Fallback : `quantity = 1` si non trouvé
#### PATCH structure
Dans `applyPieceLinks()`, accepter `quantity` au même niveau que `pieceId` dans le payload :
```json
{
"pieceLinks": [
{ "pieceId": "cl...", "quantity": 4, "overrides": { "nameOverride": "..." } }
]
}
```
- `quantity` est appliqué uniquement pour les pièces directement sur la machine (pas de `parentComponentLinkId`)
- Si `parentComponentLinkId` est présent, `quantity` est **ignoré silencieusement** (la valeur vient du composant)
#### Clone
`clonePieceLinks()` doit copier `quantity` depuis le lien source :
```php
$newLink->setQuantity($link->getQuantity());
```
Sans cela, les machines clonées perdraient les quantités (reset à 1).
### 5. Tests
Ajouter dans `MachinePieceLinkTest.php` :
- POST avec `quantity` explicite → vérifier la valeur
- POST sans `quantity` → vérifier défaut = 1
- PATCH `quantity` sur pièce directe → vérifier mise à jour
- GET structure → vérifier `quantity` dans la réponse normalisée
- Clone → vérifier que `quantity` est préservé
## Frontend
### 1. Types TypeScript
Mise à jour de `ComponentModelPiece` dans `shared/types/inventory.ts` — ajout du champ `quantity` :
```typescript
quantity?: number // défaut 1
```
### 2. Fonctions de sanitization/normalisation à mettre à jour
Ces fonctions énumèrent explicitement les champs à conserver et doivent inclure `quantity` :
- `normalizeStructureForSave()` dans `shared/model/componentStructure.ts` — inclure `quantity` dans le payload backend des pièces
- `sanitizePieceDefinition()` dans `shared/utils/structureAssignmentHelpers.ts` — préserver `quantity`
- `sanitizePieces()` dans `shared/model/componentStructureSanitize.ts` — préserver `quantity` dans la sortie
- `hydratePieces()` / `mapComponentPieces()` — préserver `quantity` lors de l'hydratation
### 3. Pages composant (création + édition)
Dans l'éditeur de structure, chaque pièce du tableau `structure.pieces` affiche un champ input :
- Type : `number`, min = 1, step = 1
- Valeur par défaut : 1
- Style : `input input-bordered input-sm md:input-md` (DaisyUI)
- Position : à côté des champs existants (reference, role)
### 4. Page machine (détail/structure)
- **Pièce directe** (parentLink = null) : affiche "×N" à côté du nom, quantité éditable (input entier)
- **Pièce de composant** : affiche "×N" à côté du nom, lecture seule (pas d'input)
- Si quantité = 1 : rien n'est affiché (pas de bruit visuel)
- Style du label : texte secondaire (`text-base-content/60` ou classe équivalente)