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).
This commit is contained in:
Kevin Adametz 2026-06-02 16:30:42 +00:00
parent ca3eb663fe
commit 78679e0c55
67 changed files with 3523 additions and 101 deletions

View file

@ -0,0 +1,27 @@
<?php
namespace Database\Factories;
use App\Models\DeliveryTime;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<DeliveryTime>
*/
class DeliveryTimeFactory extends Factory
{
protected $model = DeliveryTime::class;
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'label' => $this->faker->randomElement(['13 Werktage', '35 Werktage', '12 Wochen']),
'days' => $this->faker->randomElement([3, 5, 14]),
'active' => true,
'pos' => 0,
];
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Database\Factories;
use App\Models\TaxRate;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<TaxRate>
*/
class TaxRateFactory extends Factory
{
protected $model = TaxRate::class;
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => $this->faker->randomElement(['Standard', 'Ermäßigt', 'Steuerfrei']),
'percent' => $this->faker->randomElement([19.00, 7.00, 0.00]),
'active' => true,
'pos' => 0,
];
}
}

View file

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('suppliers', function (Blueprint $table) {
$table->string('url', 2048)->nullable()->change();
});
Schema::table('packaging_items', function (Blueprint $table) {
$table->string('url', 2048)->nullable()->change();
});
}
public function down(): void
{
Schema::table('suppliers', function (Blueprint $table) {
$table->string('url')->nullable()->change();
});
Schema::table('packaging_items', function (Blueprint $table) {
$table->string('url', 500)->nullable()->change();
});
}
};

View file

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('tax_rates', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->decimal('percent', 5, 2);
$table->boolean('active')->default(true);
$table->unsignedTinyInteger('pos')->default(0);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('tax_rates');
}
};

View file

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('delivery_times', function (Blueprint $table) {
$table->id();
$table->string('label');
$table->boolean('active')->default(true);
$table->unsignedTinyInteger('pos')->default(0);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('delivery_times');
}
};

View file

@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('suppliers', function (Blueprint $table) {
$table->enum('order_method', ['email', 'online_shop'])->nullable()->after('url');
$table->string('order_email')->nullable()->after('order_method');
$table->string('order_url', 2048)->nullable()->after('order_email');
$table->string('delivery_time')->nullable()->after('order_url');
});
}
public function down(): void
{
Schema::table('suppliers', function (Blueprint $table) {
$table->dropColumn(['order_method', 'order_email', 'order_url', 'delivery_time']);
});
}
};

View file

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('delivery_times', function (Blueprint $table) {
$table->unsignedSmallInteger('days')->nullable()->after('label');
});
}
public function down(): void
{
Schema::table('delivery_times', function (Blueprint $table) {
$table->dropColumn('days');
});
}
};

View file

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('suppliers', function (Blueprint $table) {
$table->unsignedSmallInteger('delivery_time_days')->nullable()->after('delivery_time');
});
}
public function down(): void
{
Schema::table('suppliers', function (Blueprint $table) {
$table->dropColumn('delivery_time_days');
});
}
};

View file

@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('ingredients', function (Blueprint $table) {
$table->unsignedBigInteger('tax_rate_id')->nullable()->after('material_quality_id');
$table->string('delivery_time')->nullable()->after('tax_rate_id');
$table->unsignedSmallInteger('delivery_time_days')->nullable()->after('delivery_time');
$table->foreign('tax_rate_id')->references('id')->on('tax_rates')->nullOnDelete();
});
}
public function down(): void
{
Schema::table('ingredients', function (Blueprint $table) {
$table->dropForeign(['tax_rate_id']);
$table->dropColumn(['tax_rate_id', 'delivery_time', 'delivery_time_days']);
});
}
};

View file

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('ingredient_supplier', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('ingredient_id');
$table->unsignedBigInteger('supplier_id');
$table->boolean('preferred')->default(false);
$table->string('supplier_sku')->nullable();
$table->string('url', 2048)->nullable();
$table->timestamps();
$table->unique(['ingredient_id', 'supplier_id']);
$table->foreign('ingredient_id')->references('id')->on('ingredients')->cascadeOnDelete();
$table->foreign('supplier_id')->references('id')->on('suppliers')->cascadeOnDelete();
});
}
public function down(): void
{
Schema::dropIfExists('ingredient_supplier');
}
};

View file

@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('stock_entries', function (Blueprint $table) {
$table->decimal('price_per_kg_gross', 10, 4)->nullable()->after('price_per_kg');
$table->unsignedBigInteger('tax_rate_id')->nullable()->after('price_total');
$table->decimal('tax_rate_percent', 5, 2)->nullable()->after('tax_rate_id');
$table->foreign('tax_rate_id')->references('id')->on('tax_rates')->nullOnDelete();
});
}
public function down(): void
{
Schema::table('stock_entries', function (Blueprint $table) {
$table->dropForeign(['tax_rate_id']);
$table->dropColumn(['price_per_kg_gross', 'tax_rate_id', 'tax_rate_percent']);
});
}
};

View file

@ -2,9 +2,11 @@
namespace Database\Seeders;
use App\Models\DeliveryTime;
use App\Models\Location;
use App\Models\MaterialQuality;
use App\Models\PackagingMaterial;
use App\Models\TaxRate;
use Illuminate\Database\Seeder;
class InventoryStammdatenSeeder extends Seeder
@ -45,5 +47,29 @@ class InventoryStammdatenSeeder extends Seeder
['pos' => $pos]
);
}
$taxRates = [
['name' => 'Standard', 'percent' => 19.00],
['name' => 'Ermäßigt', 'percent' => 7.00],
['name' => 'Steuerfrei', 'percent' => 0.00],
];
foreach ($taxRates as $pos => $taxRate) {
TaxRate::query()->firstOrCreate(
['percent' => $taxRate['percent']],
['name' => $taxRate['name'], 'active' => true, 'pos' => $pos]
);
}
$deliveryTimes = [
['label' => '13 Werktage', 'days' => 3],
['label' => '35 Werktage', 'days' => 5],
['label' => '12 Wochen', 'days' => 14],
];
foreach ($deliveryTimes as $pos => $deliveryTime) {
DeliveryTime::query()->firstOrCreate(
['label' => $deliveryTime['label']],
['days' => $deliveryTime['days'], 'active' => true, 'pos' => $pos]
);
}
}
}