gruene-seele/app/Repositories/StockEntryRepository.php
Kevin Adametz 78679e0c55 Warenwirtschaft: AP-00 bis AP-08 + aktualisierter Entwicklungsplan
Umsetzung der Warenwirtschafts-/Produktmanagement-Erweiterung gemaess
Entwicklungsplan V4.0:

- AP-00: Regressionsbasis fuer 5.1-Features (ProductPhase51Test)
- AP-01: URL-Bugfixes B1/B2 (suppliers/packaging-items, breitere url-Spalten)
- AP-04/04.1: iPad-taugliche, vereinheitlichte Tabellen-Aktionen
- AP-05: Einstellungen "Allgemein" mit UST-Saetzen (tax_rates) und
  Lieferzeit-Vorlagen (delivery_times, inkl. Tage-Feld)
- AP-06: Lieferanten um Bestellweg, Bestell-Mail/-URL und Lieferzeit erweitert
- AP-07/07.1: INCI um Lieferanten-Mehrfachwahl, UST und Lieferzeit erweitert;
  Lieferanten-Detailansicht im Modal mit pflegbaren INCI-/Verpackungslisten
- AP-08: Einkauf um UST-Snapshot, Netto/Brutto-Automatik und Duplizieren erweitert

Entwicklungsplan aktualisiert: alle Klaerungspunkte (§5) vom Kunden beantwortet
und in die jeweiligen APs eingearbeitet (AP-02/03/09/13/15), neues AP-18
(Hinweise-Doku unter Einstellungen) ergaenzt. Naechster Schritt eindeutig
markiert: AP-09 (Produktion auf Hersteller-Rezeptur, kein Fallback, Warnung).
2026-06-02 16:30:42 +00:00

145 lines
4.2 KiB
PHP

<?php
namespace App\Repositories;
use App\Models\StockEntry;
use App\Models\TaxRate;
use Illuminate\Database\Eloquent\Collection;
class StockEntryRepository
{
/**
* @param array<string, mixed> $data
*/
public function create(array $data): StockEntry
{
$data['unit'] = ($data['entry_type'] ?? '') === 'ingredient' ? 'gram' : 'piece';
$data = $this->resolvePrices($data);
return StockEntry::query()->create($data);
}
/**
* @param array<string, mixed> $data
*/
public function update(StockEntry $stockEntry, array $data): StockEntry
{
if (array_key_exists('entry_type', $data)) {
$data['unit'] = ($data['entry_type'] ?? '') === 'ingredient' ? 'gram' : 'piece';
}
$data = $this->resolvePrices($data);
$stockEntry->update($data);
return $stockEntry->fresh();
}
/**
* Ergänzt Netto-/Brutto-Preis pro kg und den UST-Snapshot.
* Es genügt, Netto oder Brutto anzugeben; der jeweils fehlende Wert wird
* aus dem gewählten Steuersatz berechnet.
*
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
protected function resolvePrices(array $data): array
{
if (($data['entry_type'] ?? '') !== 'ingredient') {
$data['price_per_kg'] = null;
$data['price_per_kg_gross'] = null;
$data['tax_rate_id'] = null;
$data['tax_rate_percent'] = null;
return $data;
}
$percent = null;
if (! empty($data['tax_rate_id'])) {
$percent = TaxRate::query()->whereKey($data['tax_rate_id'])->value('percent');
}
$data['tax_rate_percent'] = $percent;
$factor = 1 + ((float) ($percent ?? 0)) / 100;
$net = isset($data['price_per_kg']) && $data['price_per_kg'] !== null && $data['price_per_kg'] !== ''
? (float) $data['price_per_kg']
: null;
$gross = isset($data['price_per_kg_gross']) && $data['price_per_kg_gross'] !== null && $data['price_per_kg_gross'] !== ''
? (float) $data['price_per_kg_gross']
: null;
if ($net !== null && $gross === null) {
$gross = round($net * $factor, 4);
} elseif ($gross !== null && $net === null) {
$net = $factor > 0 ? round($gross / $factor, 4) : $gross;
} elseif ($net !== null && $gross !== null) {
$gross = round($net * $factor, 4);
}
$data['price_per_kg'] = $net;
$data['price_per_kg_gross'] = $gross;
$data['price_total'] = null;
return $data;
}
/**
* @param array<string, mixed> $data
*/
public function receive(StockEntry $stockEntry, array $data): StockEntry
{
$data['status'] = 'received';
$data['received_by'] = auth()->id();
$stockEntry->update($data);
return $stockEntry->fresh();
}
/**
* @return Collection<int, StockEntry>
*/
public function getByStatus(string $status): Collection
{
return StockEntry::query()
->where('status', $status)
->orderByDesc('ordered_at')
->get();
}
/**
* @return Collection<int, StockEntry>
*/
public function getForIngredient(int $ingredientId): Collection
{
return StockEntry::query()
->where('ingredient_id', $ingredientId)
->where('status', 'received')
->orderByDesc('received_at')
->get();
}
/**
* Liste: Pending zuerst (neuestes Bestelldatum), dann Received (neuester Eingang).
*
* @return Collection<int, StockEntry>
*/
public function listForIndex(): Collection
{
$with = [
'ingredient',
'packagingItem',
'supplier',
'location',
'quality',
'orderedByUser',
'receivedByUser',
];
$pending = StockEntry::query()->with($with)->where('status', 'pending')->orderByDesc('ordered_at')->get();
$received = StockEntry::query()->with($with)->where('status', 'received')->orderByDesc('received_at')->get();
return $pending->concat($received)->values();
}
}