Warenwirtschaft: AP-28 Neue Produktions-Maske + Produktionsergebnis
- production_outputs (Migration inkl. idempotentem Backfill): eine Produktion kann mehrere Output-Produkte erzeugen (z. B. 40x50 ml + 50x5 ml) - ProductStockService bucht je Output-Produkt (idempotenter Diff, append-only); die geplante Stückzahl bucht nichts mehr, entfernte Outputs werden rückgebucht - ProductionService store/update mit outputs[] (leer = Fallback auf Planung inkl. suggestedBatchNumber — Backfill-Semantik, Alt-Aufrufer stabil) - Maske: INCI-Liste (Alternativname/Qualität/Anteil/Gramm/Regal), Chargen- Erfassung nach Rezeptur-Phasen mit Notiz + Status-Ampel, Produktionsergebnis- Repeater mit Chargen-Nr.-Vorbelegung (Präfix + ttmmjj), Planungs-Hinweis - Show: Produktionsergebnis-Karte; Index: Ergebnis-Spalte - Hinweise-Doku + Plan V6.0 §6 fortgeschrieben (nächster Schritt: AP-13) Tests: ProductionOutputTest (8 neu); Suite 201 grün, 731 Assertionen Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7a5b5e0f7b
commit
99a6c92e6c
17 changed files with 1009 additions and 74 deletions
|
|
@ -73,37 +73,58 @@ class ProductStockService
|
|||
}
|
||||
|
||||
/**
|
||||
* Verbucht den Produktbestand einer Produktion idempotent: gleicht die bereits gebuchte
|
||||
* Menge dieser Produktion mit der Soll-Stückzahl ab und bucht nur die Differenz.
|
||||
* Verbucht den Produktbestand einer Produktion idempotent, seit AP-28 **je Output-Produkt**
|
||||
* (Produktionsergebnis): Soll = Summe der production_outputs je Produkt; gebucht wird nur
|
||||
* die Differenz zur bereits gebuchten Menge (append-only). Die geplante Stückzahl der
|
||||
* Produktion bucht nichts mehr. Entfernte Output-Produkte werden per Gegenbuchung
|
||||
* auf null zurückgeführt.
|
||||
*/
|
||||
public function recordProductionStock(Production $production, ?int $userId = null): void
|
||||
{
|
||||
$production->loadMissing('product');
|
||||
$production->load('outputs');
|
||||
|
||||
$alreadyBooked = (int) ProductStockMovement::query()
|
||||
$target = $production->outputs
|
||||
->groupBy('product_id')
|
||||
->map(fn ($rows) => (int) $rows->sum('quantity'));
|
||||
|
||||
$booked = ProductStockMovement::query()
|
||||
->where('source', 'production')
|
||||
->where('reference_type', $production->getMorphClass())
|
||||
->where('reference_id', $production->getKey())
|
||||
->selectRaw("COALESCE(SUM(CASE WHEN direction = 'in' THEN quantity ELSE -quantity END), 0) as net")
|
||||
->value('net');
|
||||
->selectRaw("product_id, COALESCE(SUM(CASE WHEN direction = 'in' THEN quantity ELSE -quantity END), 0) as net")
|
||||
->groupBy('product_id')
|
||||
->pluck('net', 'product_id');
|
||||
|
||||
$target = (int) $production->quantity;
|
||||
$delta = $target - $alreadyBooked;
|
||||
$productIds = $target->keys()
|
||||
->merge($booked->keys())
|
||||
->map(fn ($id) => (int) $id)
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
if ($delta === 0) {
|
||||
if ($productIds->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->recordMovement(
|
||||
$production->product,
|
||||
$delta > 0 ? 'in' : 'out',
|
||||
abs($delta),
|
||||
$alreadyBooked === 0 ? __('Produktion') : __('Produktionskorrektur'),
|
||||
'production',
|
||||
null,
|
||||
$userId ?? $production->produced_by,
|
||||
$production,
|
||||
);
|
||||
$products = Product::query()->whereIn('id', $productIds)->get()->keyBy('id');
|
||||
|
||||
foreach ($productIds as $productId) {
|
||||
$alreadyBooked = (int) ($booked[$productId] ?? 0);
|
||||
$delta = (int) ($target[$productId] ?? 0) - $alreadyBooked;
|
||||
if ($delta === 0 || ! isset($products[$productId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->recordMovement(
|
||||
$products[$productId],
|
||||
$delta > 0 ? 'in' : 'out',
|
||||
abs($delta),
|
||||
$alreadyBooked === 0 ? __('Produktion') : __('Produktionskorrektur'),
|
||||
'production',
|
||||
null,
|
||||
$userId ?? $production->produced_by,
|
||||
$production,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue