Warenwirtschaft: AP-21 INCI-Erweiterungen
- Lagerort-Stammdaten Raum/Regal/Fach (storage_rooms/shelves/slots) mit CRUD auf Einstellungen -> Allgemein (SettingCrudController-Kinder, ein gemeinsamer StoreStorageSegmentRequest, generische Partials setting-list-card/-segment-form) - Ingredient: alt_name (Handelsname), Lagerort-FKs + storageLabel(), scopeUsedInActiveRecipes() - INCI-Formular: Lieferanten-Repeater mit Shop-URL je Lieferant (leer = Mail), Alternativname, drei Lagerort-Dropdowns - INCI-Liste: Filter "nur aktive (in Rezeptur aktiver Produkte)" + Spalte Alternativer Name - Rohstoffbestand-Detail: Bestellweg pro INCI-Lieferant (Pivot-URL statt order_method-Fallback), Kopf zeigt Alternativname + Lagerort - Hinweise-Doku fortgeschrieben inkl. Chargen-Format-Entscheid ttmmjj (AP-24) Tests: StorageSettingsTest, IngredientActiveRecipeFilterTest, IngredientOrderFieldsTest (Repeater-Format), RawMaterialStockTest erweitert (volle Suite 170 gruen, 613 Assertions) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
8c90d95c69
commit
d05df498d3
34 changed files with 1091 additions and 45 deletions
|
|
@ -7,6 +7,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
|
@ -63,6 +64,7 @@ class Ingredient extends Model
|
|||
protected $fillable = [
|
||||
'name',
|
||||
'trans_name',
|
||||
'alt_name',
|
||||
'inci',
|
||||
'trans_inci',
|
||||
'effect',
|
||||
|
|
@ -75,6 +77,9 @@ class Ingredient extends Model
|
|||
'tax_rate_id',
|
||||
'delivery_time',
|
||||
'delivery_time_days',
|
||||
'storage_room_id',
|
||||
'storage_shelf_id',
|
||||
'storage_slot_id',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -115,4 +120,57 @@ class Ingredient extends Model
|
|||
{
|
||||
return $this->hasMany(ProductIngredient::class, 'ingredient_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<StorageRoom, $this>
|
||||
*/
|
||||
public function storageRoom(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(StorageRoom::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<StorageShelf, $this>
|
||||
*/
|
||||
public function storageShelf(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(StorageShelf::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<StorageSlot, $this>
|
||||
*/
|
||||
public function storageSlot(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(StorageSlot::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lagerort-Anzeige im Schema „Raum 1 | Regal 3 | A" (nur gepflegte Teile).
|
||||
*/
|
||||
public function storageLabel(): ?string
|
||||
{
|
||||
$parts = array_filter([
|
||||
$this->storageRoom?->label,
|
||||
$this->storageShelf?->label,
|
||||
$this->storageSlot?->label,
|
||||
]);
|
||||
|
||||
return $parts === [] ? null : implode(' | ', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nur INCI, die in der Hersteller-Rezeptur mindestens eines aktiven Produkts vorkommen
|
||||
* (fachlich „aktiv" laut Kunde — nicht zu verwechseln mit `ingredients.active`).
|
||||
*
|
||||
* @param Builder<Ingredient> $query
|
||||
* @return Builder<Ingredient>
|
||||
*/
|
||||
public function scopeUsedInActiveRecipes($query)
|
||||
{
|
||||
return $query->whereHas('product_ingredients', function ($q) {
|
||||
$q->where('recipe_type', 'manufacturer')
|
||||
->whereHas('product', fn ($p) => $p->where('active', true));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
app/Models/StorageRoom.php
Normal file
14
app/Models/StorageRoom.php
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Database\Factories\StorageRoomFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class StorageRoom extends StorageSegment
|
||||
{
|
||||
/** @use HasFactory<StorageRoomFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'storage_rooms';
|
||||
}
|
||||
38
app/Models/StorageSegment.php
Normal file
38
app/Models/StorageSegment.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Gemeinsame Basis der Lagerort-Bausteine Raum/Regal/Fach (AP-21).
|
||||
*/
|
||||
abstract class StorageSegment extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'label',
|
||||
'active',
|
||||
'pos',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'active' => 'boolean',
|
||||
'pos' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder<static> $query
|
||||
* @return Builder<static>
|
||||
*/
|
||||
public function scopeActive(Builder $query): Builder
|
||||
{
|
||||
return $query->where('active', true);
|
||||
}
|
||||
}
|
||||
14
app/Models/StorageShelf.php
Normal file
14
app/Models/StorageShelf.php
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Database\Factories\StorageShelfFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class StorageShelf extends StorageSegment
|
||||
{
|
||||
/** @use HasFactory<StorageShelfFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'storage_shelves';
|
||||
}
|
||||
14
app/Models/StorageSlot.php
Normal file
14
app/Models/StorageSlot.php
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Database\Factories\StorageSlotFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class StorageSlot extends StorageSegment
|
||||
{
|
||||
/** @use HasFactory<StorageSlotFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'storage_slots';
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue