April 2026 waren Wirtschaft Feedback
This commit is contained in:
parent
02f2a4c23e
commit
9ce711d6b2
167 changed files with 25278 additions and 8518 deletions
22
database/factories/LocationFactory.php
Normal file
22
database/factories/LocationFactory.php
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Location;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<Location>
|
||||
*/
|
||||
class LocationFactory extends Factory
|
||||
{
|
||||
protected $model = Location::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->city(),
|
||||
'active' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
22
database/factories/MaterialQualityFactory.php
Normal file
22
database/factories/MaterialQualityFactory.php
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\MaterialQuality;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<MaterialQuality>
|
||||
*/
|
||||
class MaterialQualityFactory extends Factory
|
||||
{
|
||||
protected $model = MaterialQuality::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->unique()->words(2, true),
|
||||
'pos' => $this->faker->numberBetween(0, 50),
|
||||
];
|
||||
}
|
||||
}
|
||||
29
database/factories/PackagingItemFactory.php
Normal file
29
database/factories/PackagingItemFactory.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\PackagingItem;
|
||||
use App\Models\PackagingMaterial;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<PackagingItem>
|
||||
*/
|
||||
class PackagingItemFactory extends Factory
|
||||
{
|
||||
protected $model = PackagingItem::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'packaging_material_id' => PackagingMaterial::factory(),
|
||||
'supplier_id' => null,
|
||||
'name' => $this->faker->words(3, true),
|
||||
'category' => 'packaging',
|
||||
'weight_grams' => 0,
|
||||
'min_stock_alert' => null,
|
||||
'product_id' => null,
|
||||
'active' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
22
database/factories/PackagingMaterialFactory.php
Normal file
22
database/factories/PackagingMaterialFactory.php
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\PackagingMaterial;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<PackagingMaterial>
|
||||
*/
|
||||
class PackagingMaterialFactory extends Factory
|
||||
{
|
||||
protected $model = PackagingMaterial::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->unique()->word(),
|
||||
'pos' => $this->faker->numberBetween(0, 50),
|
||||
];
|
||||
}
|
||||
}
|
||||
208
database/factories/StockEntryFactory.php
Normal file
208
database/factories/StockEntryFactory.php
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Ingredient;
|
||||
use App\Models\Location;
|
||||
use App\Models\MaterialQuality;
|
||||
use App\Models\PackagingItem;
|
||||
use App\Models\StockEntry;
|
||||
use App\Models\Supplier;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<StockEntry>
|
||||
*/
|
||||
class StockEntryFactory extends Factory
|
||||
{
|
||||
protected $model = StockEntry::class;
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
$orderedAt = Carbon::instance($this->faker->dateTimeBetween('-3 months', 'now'));
|
||||
|
||||
return [
|
||||
'entry_type' => 'ingredient',
|
||||
'ingredient_id' => fn () => $this->makeIngredient()->id,
|
||||
'packaging_item_id' => null,
|
||||
'supplier_id' => Supplier::factory(),
|
||||
'location_id' => Location::factory(),
|
||||
'unit' => 'gram',
|
||||
'ordered_by' => fn () => $this->makeOrderUser()->id,
|
||||
'ordered_at' => $orderedAt->format('Y-m-d'),
|
||||
'ordered_quantity' => $this->faker->randomFloat(2, 250, 50_000),
|
||||
'price_per_kg' => $this->faker->randomFloat(4, 2, 120),
|
||||
'price_total' => null,
|
||||
'received_by' => null,
|
||||
'received_at' => null,
|
||||
'received_quantity' => null,
|
||||
'batch_number' => null,
|
||||
'best_before' => null,
|
||||
'quality_id' => null,
|
||||
'status' => 'pending',
|
||||
];
|
||||
}
|
||||
|
||||
public function ingredient(): static
|
||||
{
|
||||
return $this->state(fn () => [
|
||||
'entry_type' => 'ingredient',
|
||||
'ingredient_id' => fn () => $this->makeIngredient()->id,
|
||||
'packaging_item_id' => null,
|
||||
'unit' => 'gram',
|
||||
'price_per_kg' => $this->faker->randomFloat(4, 2, 120),
|
||||
'price_total' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function packaging(): static
|
||||
{
|
||||
return $this->state(function () {
|
||||
$supplier = Supplier::factory()->create();
|
||||
$item = PackagingItem::factory()->create([
|
||||
'supplier_id' => $supplier->id,
|
||||
'category' => 'packaging',
|
||||
]);
|
||||
|
||||
return [
|
||||
'entry_type' => 'packaging',
|
||||
'ingredient_id' => null,
|
||||
'packaging_item_id' => $item->id,
|
||||
'supplier_id' => $supplier->id,
|
||||
'unit' => 'piece',
|
||||
'price_per_kg' => null,
|
||||
'price_total' => $this->faker->randomFloat(4, 15, 2500),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function label(): static
|
||||
{
|
||||
return $this->state(function () {
|
||||
$supplier = Supplier::factory()->create();
|
||||
$item = PackagingItem::factory()->create([
|
||||
'supplier_id' => $supplier->id,
|
||||
'category' => 'label',
|
||||
]);
|
||||
|
||||
return [
|
||||
'entry_type' => 'label',
|
||||
'ingredient_id' => null,
|
||||
'packaging_item_id' => $item->id,
|
||||
'supplier_id' => $supplier->id,
|
||||
'unit' => 'piece',
|
||||
'price_per_kg' => null,
|
||||
'price_total' => $this->faker->randomFloat(4, 5, 800),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function shippingOffice(): static
|
||||
{
|
||||
return $this->state(function () {
|
||||
$supplier = Supplier::factory()->create();
|
||||
$item = PackagingItem::factory()->create([
|
||||
'supplier_id' => $supplier->id,
|
||||
'category' => 'shipping_office',
|
||||
]);
|
||||
|
||||
return [
|
||||
'entry_type' => 'shipping_office',
|
||||
'ingredient_id' => null,
|
||||
'packaging_item_id' => $item->id,
|
||||
'supplier_id' => $supplier->id,
|
||||
'unit' => 'piece',
|
||||
'price_per_kg' => null,
|
||||
'price_total' => $this->faker->randomFloat(4, 8, 1500),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function pending(): static
|
||||
{
|
||||
return $this->state(fn () => [
|
||||
'status' => 'pending',
|
||||
'received_by' => null,
|
||||
'received_at' => null,
|
||||
'received_quantity' => null,
|
||||
'batch_number' => null,
|
||||
'best_before' => null,
|
||||
'quality_id' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function received(): static
|
||||
{
|
||||
return $this->state(function (array $attributes) {
|
||||
$receiver = $this->makeOrderUser();
|
||||
$orderedAt = $attributes['ordered_at'] ?? now()->format('Y-m-d');
|
||||
$orderedCarbon = Carbon::parse($orderedAt);
|
||||
$receivedCarbon = (clone $orderedCarbon)->addDays($this->faker->numberBetween(1, 21));
|
||||
if ($receivedCarbon->isFuture()) {
|
||||
$receivedCarbon = Carbon::now();
|
||||
}
|
||||
|
||||
$qty = $attributes['ordered_quantity'] ?? 1000;
|
||||
$entryType = $attributes['entry_type'] ?? 'ingredient';
|
||||
|
||||
$extra = [
|
||||
'status' => 'received',
|
||||
'received_by' => $receiver->id,
|
||||
'received_at' => $receivedCarbon->format('Y-m-d'),
|
||||
'received_quantity' => round((float) $qty, 2),
|
||||
];
|
||||
|
||||
if ($entryType === 'ingredient') {
|
||||
$extra['batch_number'] = $this->faker->bothify('CH-####-??');
|
||||
$extra['best_before'] = $this->faker->dateTimeBetween('+2 months', '+24 months')->format('Y-m-d');
|
||||
$extra['quality_id'] = MaterialQuality::query()->inRandomOrder()->value('id');
|
||||
} else {
|
||||
$extra['batch_number'] = null;
|
||||
$extra['best_before'] = null;
|
||||
$extra['quality_id'] = null;
|
||||
}
|
||||
|
||||
return $extra;
|
||||
});
|
||||
}
|
||||
|
||||
private function makeIngredient(): Ingredient
|
||||
{
|
||||
$name = $this->faker->randomElement([
|
||||
'Shea Butter',
|
||||
'Kokosöl raffiniert',
|
||||
'Mandelöl süß',
|
||||
'Jojobaöl',
|
||||
'Distillat Rosenhydrolat',
|
||||
]).' '.$this->faker->unique()->numerify('###');
|
||||
|
||||
return Ingredient::query()->create([
|
||||
'name' => $name,
|
||||
'trans_name' => '',
|
||||
'inci' => $this->faker->optional(0.7)->words(3, true) ?? '',
|
||||
'trans_inci' => '',
|
||||
'effect' => '',
|
||||
'trans_effect' => '',
|
||||
'active' => true,
|
||||
'pos' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
private function makeOrderUser(): User
|
||||
{
|
||||
return User::query()->create([
|
||||
'email' => 'sf_u_'.str_replace('.', '', uniqid('', true)).'@factory.test',
|
||||
'password' => bcrypt('password'),
|
||||
'admin' => 7,
|
||||
'confirmed' => true,
|
||||
'active' => true,
|
||||
'wizard' => 100,
|
||||
'blocked' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
22
database/factories/SupplierCategoryFactory.php
Normal file
22
database/factories/SupplierCategoryFactory.php
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\SupplierCategory;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<SupplierCategory>
|
||||
*/
|
||||
class SupplierCategoryFactory extends Factory
|
||||
{
|
||||
protected $model = SupplierCategory::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->unique()->words(2, true),
|
||||
'pos' => $this->faker->numberBetween(0, 50),
|
||||
];
|
||||
}
|
||||
}
|
||||
48
database/factories/SupplierFactory.php
Normal file
48
database/factories/SupplierFactory.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\Supplier;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<Supplier>
|
||||
*/
|
||||
class SupplierFactory extends Factory
|
||||
{
|
||||
protected $model = Supplier::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->company(),
|
||||
'url' => $this->faker->optional()->url(),
|
||||
'contact_person' => $this->faker->optional()->name(),
|
||||
'email' => $this->faker->optional()->companyEmail(),
|
||||
'phone' => $this->faker->optional()->phoneNumber(),
|
||||
'country_id' => Country::query()->value('id') ?? $this->resolveCountryId(),
|
||||
'notes' => null,
|
||||
'active' => true,
|
||||
];
|
||||
}
|
||||
|
||||
protected function resolveCountryId(): int
|
||||
{
|
||||
$country = Country::query()->firstOrCreate(
|
||||
['code' => 'TE'],
|
||||
[
|
||||
'phone' => '00',
|
||||
'en' => 'Testland',
|
||||
'de' => 'Testland',
|
||||
'es' => 'Testland',
|
||||
'fr' => 'Testland',
|
||||
'it' => 'Testland',
|
||||
'ru' => 'Testland',
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
|
||||
return (int) $country->id;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateCountriesTable extends Migration
|
||||
{
|
||||
|
|
@ -35,14 +35,11 @@ class CreateCountriesTable extends Migration
|
|||
$table->boolean('currency_calc')->default(false);
|
||||
$table->decimal('currency_faktor', 4, 2)->nullable();
|
||||
|
||||
$table->boolean('active')->default(true);
|
||||
$table->text('trans_name')->nullable();
|
||||
$table->text('attr')->nullable();
|
||||
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateAttributesTable extends Migration
|
||||
{
|
||||
|
|
@ -25,16 +25,13 @@ class CreateAttributesTable extends Migration
|
|||
|
||||
$table->string('slug')->unique()->index();
|
||||
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('parent_id')
|
||||
->references('id')
|
||||
->on('attributes');
|
||||
|
||||
$table->foreign('attribute_type_id')
|
||||
->references('id')
|
||||
->on('attribute_types');
|
||||
// FK zu attribute_types wird in create_attribute_types_table Migration gesetzt (Reihenfolge-Problem)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateProductAttributesTable extends Migration
|
||||
{
|
||||
|
|
@ -32,10 +32,7 @@ class CreateProductAttributesTable extends Migration
|
|||
->on('attributes')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign('type_id')
|
||||
->references('id')
|
||||
->on('attribute_types')
|
||||
->onDelete('cascade');
|
||||
// FK zu attribute_types wird in create_attribute_types_table Migration gesetzt (Reihenfolge-Problem)
|
||||
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateProductImagesTable extends Migration
|
||||
{
|
||||
|
|
@ -29,7 +29,7 @@ class CreateProductImagesTable extends Migration
|
|||
$table->string('slug')->unique()->index();
|
||||
$table->unsignedTinyInteger('pos')->nullable()->default(0);
|
||||
$table->text('attributes')->nullable();
|
||||
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('product_id')
|
||||
|
|
@ -37,10 +37,7 @@ class CreateProductImagesTable extends Migration
|
|||
->on('products')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign('user_wl_product_id')
|
||||
->references('id')
|
||||
->on('user_whitelabel_products')
|
||||
->onDelete('cascade');
|
||||
// FK zu user_whitelabel_products wird in create_user_whitelabel_products_table Migration gesetzt (Reihenfolge-Problem)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ class CreateProductBuysTable extends Migration
|
|||
$table->timestamps();
|
||||
|
||||
$table->foreign('product_id')
|
||||
->references('id')
|
||||
->on('products');
|
||||
->references('id')
|
||||
->on('products');
|
||||
|
||||
$table->foreign('user_id')
|
||||
->references('id')
|
||||
->on('users');
|
||||
$table->foreign('auth_user_id')
|
||||
->references('id')
|
||||
->on('users');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,14 +25,24 @@ class CreateAttributeTypesTable extends Migration
|
|||
|
||||
$table->string('slug')->unique()->index();
|
||||
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('parent_id')
|
||||
->references('id')
|
||||
->on('attributes');
|
||||
});
|
||||
|
||||
|
||||
Schema::table('attributes', function (Blueprint $table) {
|
||||
$table->foreign('attribute_type_id')
|
||||
->references('id')
|
||||
->on('attribute_types');
|
||||
});
|
||||
|
||||
Schema::table('product_attributes', function (Blueprint $table) {
|
||||
$table->foreign('type_id')
|
||||
->references('id')
|
||||
->on('attribute_types')
|
||||
->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -43,6 +53,14 @@ class CreateAttributeTypesTable extends Migration
|
|||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('product_attributes', function (Blueprint $table) {
|
||||
$table->dropForeign(['type_id']);
|
||||
});
|
||||
|
||||
Schema::table('attributes', function (Blueprint $table) {
|
||||
$table->dropForeign(['attribute_type_id']);
|
||||
});
|
||||
|
||||
Schema::dropIfExists('attribute_types');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,13 @@ class CreateUserWhitelabelProductsTable extends Migration
|
|||
->on('products')
|
||||
->onDelete('cascade');
|
||||
});
|
||||
|
||||
Schema::table('product_images', function (Blueprint $table) {
|
||||
$table->foreign('user_wl_product_id')
|
||||
->references('id')
|
||||
->on('user_whitelabel_products')
|
||||
->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -45,6 +52,10 @@ class CreateUserWhitelabelProductsTable extends Migration
|
|||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('product_images', function (Blueprint $table) {
|
||||
$table->dropForeign(['user_wl_product_id']);
|
||||
});
|
||||
|
||||
Schema::dropIfExists('user_whitelabel_products');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?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::table('shopping_orders', function (Blueprint $table) {
|
||||
$table->text('cancellation_invoice')->nullable()->after('invoice');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('shopping_orders', function (Blueprint $table) {
|
||||
$table->dropColumn('cancellation_invoice');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -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::table('product_ingredients', function (Blueprint $table) {
|
||||
$table->unsignedSmallInteger('pos')->default(0)->after('ingredient_id');
|
||||
$table->decimal('gram', 12, 2)->nullable()->after('pos');
|
||||
$table->decimal('factor', 4, 2)->default(1.10)->after('gram');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('product_ingredients', function (Blueprint $table) {
|
||||
$table->dropColumn(['pos', 'gram', 'factor']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?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::table('products', function (Blueprint $table) {
|
||||
$table->enum('shelf_life_type', ['pao', 'fixed'])->nullable()->after('max_buy_num');
|
||||
$table->unsignedTinyInteger('shelf_life_months')->nullable()->after('shelf_life_type');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
$table->dropColumn(['shelf_life_type', 'shelf_life_months']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?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::table('ingredients', function (Blueprint $table) {
|
||||
$table->decimal('default_factor', 4, 2)->default(1.10)->after('pos');
|
||||
$table->decimal('min_stock_alert', 12, 2)->nullable()->after('default_factor');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('ingredients', function (Blueprint $table) {
|
||||
$table->dropColumn(['default_factor', 'min_stock_alert']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?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('locations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->boolean('active')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('locations');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?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('supplier_categories', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->unsignedTinyInteger('pos')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('supplier_categories');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?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('suppliers', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('url')->nullable();
|
||||
$table->string('contact_person')->nullable();
|
||||
$table->string('email')->nullable();
|
||||
$table->string('phone', 100)->nullable();
|
||||
$table->unsignedInteger('country_id');
|
||||
$table->text('notes')->nullable();
|
||||
$table->boolean('active')->default(true);
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->foreign('country_id')->references('id')->on('countries');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('suppliers');
|
||||
}
|
||||
};
|
||||
|
|
@ -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::create('supplier_supplier_category', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('supplier_id')->constrained('suppliers')->cascadeOnDelete();
|
||||
$table->foreignId('supplier_category_id')->constrained('supplier_categories')->cascadeOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['supplier_id', 'supplier_category_id'], 'supplier_supplier_cat_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('supplier_supplier_category');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?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('material_qualities', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->unsignedTinyInteger('pos')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('material_qualities');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?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('packaging_materials', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->unsignedTinyInteger('pos')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('packaging_materials');
|
||||
}
|
||||
};
|
||||
|
|
@ -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
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('packaging_items', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('packaging_material_id')->constrained('packaging_materials');
|
||||
$table->foreignId('supplier_id')->nullable()->constrained('suppliers')->nullOnDelete();
|
||||
$table->string('name');
|
||||
$table->enum('category', ['packaging', 'label', 'shipping_office']);
|
||||
$table->decimal('weight_grams', 10, 2)->default(0);
|
||||
$table->unsignedInteger('min_stock_alert')->nullable();
|
||||
$table->unsignedInteger('product_id')->nullable();
|
||||
$table->foreign('product_id')->references('id')->on('products')->nullOnDelete();
|
||||
$table->boolean('active')->default(true);
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('packaging_items');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?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('stock_entries', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->enum('entry_type', ['ingredient', 'packaging', 'label', 'shipping_office']);
|
||||
$table->unsignedInteger('ingredient_id')->nullable();
|
||||
$table->foreign('ingredient_id')->references('id')->on('ingredients')->nullOnDelete();
|
||||
$table->foreignId('packaging_item_id')->nullable()->constrained('packaging_items')->nullOnDelete();
|
||||
$table->foreignId('supplier_id')->constrained('suppliers');
|
||||
$table->foreignId('location_id')->constrained('locations');
|
||||
$table->enum('unit', ['gram', 'piece']);
|
||||
|
||||
$table->unsignedInteger('ordered_by');
|
||||
$table->foreign('ordered_by')->references('id')->on('users');
|
||||
$table->date('ordered_at');
|
||||
$table->decimal('ordered_quantity', 12, 2);
|
||||
$table->decimal('price_per_kg', 10, 4)->nullable();
|
||||
$table->decimal('price_total', 10, 4)->nullable();
|
||||
|
||||
$table->unsignedInteger('received_by')->nullable();
|
||||
$table->foreign('received_by')->references('id')->on('users')->nullOnDelete();
|
||||
$table->date('received_at')->nullable();
|
||||
$table->decimal('received_quantity', 12, 2)->nullable();
|
||||
$table->string('batch_number', 100)->nullable();
|
||||
$table->date('best_before')->nullable();
|
||||
$table->foreignId('quality_id')->nullable()->constrained('material_qualities')->nullOnDelete();
|
||||
|
||||
$table->enum('status', ['pending', 'received'])->default('pending');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['status', 'ordered_at']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('stock_entries');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('product_packagings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('product_id');
|
||||
$table->foreign('product_id')->references('id')->on('products')->cascadeOnDelete();
|
||||
$table->foreignId('packaging_item_id')->constrained('packaging_items')->cascadeOnDelete();
|
||||
$table->decimal('quantity', 12, 2)->default(1);
|
||||
$table->unsignedSmallInteger('pos')->default(0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['product_id', 'packaging_item_id']);
|
||||
});
|
||||
|
||||
if (Schema::hasTable('packaging_items')) {
|
||||
$rows = DB::table('packaging_items')
|
||||
->whereNotNull('product_id')
|
||||
->orderBy('id')
|
||||
->get(['id', 'product_id']);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
DB::table('product_packagings')->insert([
|
||||
'product_id' => $row->product_id,
|
||||
'packaging_item_id' => $row->id,
|
||||
'quantity' => 1,
|
||||
'pos' => 0,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('product_packagings');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?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('productions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('product_id');
|
||||
$table->foreign('product_id')->references('id')->on('products')->cascadeOnDelete();
|
||||
$table->foreignId('location_id')->constrained('locations');
|
||||
$table->unsignedInteger('produced_by');
|
||||
$table->foreign('produced_by')->references('id')->on('users');
|
||||
$table->date('produced_at');
|
||||
$table->unsignedInteger('quantity');
|
||||
$table->text('notes')->nullable();
|
||||
$table->boolean('mhd_warning')->default(false);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['produced_at', 'location_id']);
|
||||
});
|
||||
|
||||
Schema::create('production_ingredients', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('production_id')->constrained('productions')->cascadeOnDelete();
|
||||
$table->unsignedInteger('ingredient_id');
|
||||
$table->foreign('ingredient_id')->references('id')->on('ingredients');
|
||||
$table->foreignId('stock_entry_id')->constrained('stock_entries');
|
||||
$table->decimal('quantity_used', 12, 2);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['production_id', 'ingredient_id']);
|
||||
});
|
||||
|
||||
Schema::create('production_packagings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('production_id')->constrained('productions')->cascadeOnDelete();
|
||||
$table->foreignId('packaging_item_id')->constrained('packaging_items');
|
||||
$table->unsignedInteger('quantity_used');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('production_id');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('production_packagings');
|
||||
Schema::dropIfExists('production_ingredients');
|
||||
Schema::dropIfExists('productions');
|
||||
}
|
||||
};
|
||||
|
|
@ -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('packaging_items', function (Blueprint $table) {
|
||||
$table->string('url', 500)->nullable()->after('min_stock_alert');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('packaging_items', function (Blueprint $table) {
|
||||
$table->dropColumn('url');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?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('material_quality_id')->nullable()->after('min_stock_alert');
|
||||
$table->foreign('material_quality_id')->references('id')->on('material_qualities')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('ingredients', function (Blueprint $table) {
|
||||
$table->dropForeign(['material_quality_id']);
|
||||
$table->dropColumn('material_quality_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -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('product_ingredients', function (Blueprint $table) {
|
||||
$table->string('recipe_type', 20)->default('product')->after('factor');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('product_ingredients', function (Blueprint $table) {
|
||||
$table->dropColumn('recipe_type');
|
||||
});
|
||||
}
|
||||
};
|
||||
49
database/seeders/InventoryStammdatenSeeder.php
Normal file
49
database/seeders/InventoryStammdatenSeeder.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Location;
|
||||
use App\Models\MaterialQuality;
|
||||
use App\Models\PackagingMaterial;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class InventoryStammdatenSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$locations = ['Köln', 'Waldböl'];
|
||||
foreach ($locations as $name) {
|
||||
Location::query()->firstOrCreate(
|
||||
['name' => $name],
|
||||
['active' => true]
|
||||
);
|
||||
}
|
||||
|
||||
$qualities = [
|
||||
'konventionell',
|
||||
'bio kaltgepresst',
|
||||
'bio raffiniert',
|
||||
'konventionell kaltgepresst',
|
||||
'konventionell raffiniert',
|
||||
];
|
||||
foreach ($qualities as $pos => $name) {
|
||||
MaterialQuality::query()->firstOrCreate(
|
||||
['name' => $name],
|
||||
['pos' => $pos]
|
||||
);
|
||||
}
|
||||
|
||||
$materials = [
|
||||
'Glas',
|
||||
'Holz/Bambus',
|
||||
'Pappe/Papier',
|
||||
'Kunststoff',
|
||||
];
|
||||
foreach ($materials as $pos => $name) {
|
||||
PackagingMaterial::query()->firstOrCreate(
|
||||
['name' => $name],
|
||||
['pos' => $pos]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
315
database/seeders/InventoryStammdatenTestSeeder.php
Normal file
315
database/seeders/InventoryStammdatenTestSeeder.php
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\Ingredient;
|
||||
use App\Models\Location;
|
||||
use App\Models\PackagingItem;
|
||||
use App\Models\PackagingMaterial;
|
||||
use App\Models\StockEntry;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\SupplierCategory;
|
||||
use App\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Befüllt Stammdaten der Warenwirtschaft mit erkennbaren Demo-/Testinhalten.
|
||||
* Ruft zuerst {@see InventoryStammdatenSeeder} auf (Lagerorte, Qualitäten, Verpackungsmaterialien).
|
||||
*
|
||||
* Aufruf: php artisan db:seed --class=InventoryStammdatenTestSeeder
|
||||
*/
|
||||
class InventoryStammdatenTestSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$this->call(InventoryStammdatenSeeder::class);
|
||||
|
||||
$countryDe = Country::query()->firstOrCreate(
|
||||
['code' => 'DE'],
|
||||
[
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Германия',
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$locationExtra = Location::query()->firstOrCreate(
|
||||
['name' => 'Demo Test-Zentrallager'],
|
||||
['active' => true]
|
||||
);
|
||||
|
||||
$catRohstoffe = SupplierCategory::query()->firstOrCreate(
|
||||
['name' => 'Demo Rohstoffe'],
|
||||
['pos' => 1]
|
||||
);
|
||||
$catVerpackung = SupplierCategory::query()->firstOrCreate(
|
||||
['name' => 'Demo Verpackung & Zubehör'],
|
||||
['pos' => 2]
|
||||
);
|
||||
$catDienst = SupplierCategory::query()->firstOrCreate(
|
||||
['name' => 'Demo Dienstleistung'],
|
||||
['pos' => 3]
|
||||
);
|
||||
|
||||
$supplierOele = Supplier::query()->firstOrCreate(
|
||||
['name' => 'Demo-Lieferant Naturöle GmbH'],
|
||||
[
|
||||
'url' => 'https://demo-naturoele.example.test',
|
||||
'contact_person' => 'Erika Beispiel',
|
||||
'email' => 'einkauf@demo-naturoele.example.test',
|
||||
'phone' => '+49 221 5550100',
|
||||
'country_id' => $countryDe->id,
|
||||
'notes' => 'Seeder-Testdaten – Rohstoffe (Öle, Butter).',
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
$supplierOele->supplierCategories()->sync([
|
||||
$catRohstoffe->id,
|
||||
$catVerpackung->id,
|
||||
]);
|
||||
|
||||
$supplierGlas = Supplier::query()->firstOrCreate(
|
||||
['name' => 'Demo Verpackung Glas & Co.'],
|
||||
[
|
||||
'url' => 'https://demo-glas.example.test',
|
||||
'contact_person' => 'Tom Test',
|
||||
'email' => 'vertrieb@demo-glas.example.test',
|
||||
'phone' => '+49 211 5550200',
|
||||
'country_id' => $countryDe->id,
|
||||
'notes' => 'Seeder-Testdaten – Flaschen und Gläser.',
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
$supplierGlas->supplierCategories()->sync([
|
||||
$catVerpackung->id,
|
||||
]);
|
||||
|
||||
$supplierLogistik = Supplier::query()->firstOrCreate(
|
||||
['name' => 'Demo Logistik Partner'],
|
||||
[
|
||||
'url' => null,
|
||||
'contact_person' => 'Lisa Versand',
|
||||
'email' => 'buero@demo-logistik.example.test',
|
||||
'phone' => '+49 40 5550300',
|
||||
'country_id' => $countryDe->id,
|
||||
'notes' => 'Seeder-Testdaten – Kartons, Versandmaterial.',
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
$supplierLogistik->supplierCategories()->sync([
|
||||
$catVerpackung->id,
|
||||
$catDienst->id,
|
||||
]);
|
||||
|
||||
$materialGlas = PackagingMaterial::query()->where('name', 'Glas')->first();
|
||||
$materialKunststoff = PackagingMaterial::query()->where('name', 'Kunststoff')->first();
|
||||
$materialPappe = PackagingMaterial::query()->where('name', 'Pappe/Papier')->first();
|
||||
|
||||
if ($materialGlas) {
|
||||
PackagingItem::query()->firstOrCreate(
|
||||
['name' => 'Demo Braunglasflasche 30 ml'],
|
||||
[
|
||||
'packaging_material_id' => $materialGlas->id,
|
||||
'supplier_id' => $supplierGlas->id,
|
||||
'category' => 'packaging',
|
||||
'weight_grams' => 45.5,
|
||||
'min_stock_alert' => 200,
|
||||
'product_id' => null,
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
PackagingItem::query()->firstOrCreate(
|
||||
['name' => 'Demo Tropferflasche Klarglas 10 ml'],
|
||||
[
|
||||
'packaging_material_id' => $materialGlas->id,
|
||||
'supplier_id' => $supplierGlas->id,
|
||||
'category' => 'packaging',
|
||||
'weight_grams' => 22,
|
||||
'min_stock_alert' => 500,
|
||||
'product_id' => null,
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ($materialKunststoff) {
|
||||
PackagingItem::query()->firstOrCreate(
|
||||
['name' => 'Demo Pumpspender 150 ml (PET)'],
|
||||
[
|
||||
'packaging_material_id' => $materialKunststoff->id,
|
||||
'supplier_id' => $supplierGlas->id,
|
||||
'category' => 'packaging',
|
||||
'weight_grams' => 28.75,
|
||||
'min_stock_alert' => 150,
|
||||
'product_id' => null,
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ($materialPappe) {
|
||||
PackagingItem::query()->firstOrCreate(
|
||||
['name' => 'Demo Faltschachtel S (bedruckt)'],
|
||||
[
|
||||
'packaging_material_id' => $materialPappe->id,
|
||||
'supplier_id' => $supplierLogistik->id,
|
||||
'category' => 'packaging',
|
||||
'weight_grams' => 12,
|
||||
'min_stock_alert' => 1000,
|
||||
'product_id' => null,
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
PackagingItem::query()->firstOrCreate(
|
||||
['name' => 'Demo Versandkarton S'],
|
||||
[
|
||||
'packaging_material_id' => $materialPappe->id,
|
||||
'supplier_id' => $supplierLogistik->id,
|
||||
'category' => 'shipping_office',
|
||||
'weight_grams' => 95,
|
||||
'min_stock_alert' => 300,
|
||||
'product_id' => null,
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
PackagingItem::query()->firstOrCreate(
|
||||
['name' => 'Demo Etikett Front 50×80 mm'],
|
||||
[
|
||||
'packaging_material_id' => $materialPappe->id,
|
||||
'supplier_id' => $supplierOele->id,
|
||||
'category' => 'label',
|
||||
'weight_grams' => 1.2,
|
||||
'min_stock_alert' => 5000,
|
||||
'product_id' => null,
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$locationKoln = Location::query()->where('name', 'Köln')->first() ?? $locationExtra;
|
||||
|
||||
$orderUser = User::query()->firstOrCreate(
|
||||
['email' => 'demo-wareneingang@example.test'],
|
||||
[
|
||||
'password' => bcrypt('password'),
|
||||
'admin' => 7,
|
||||
'confirmed' => true,
|
||||
'active' => true,
|
||||
'wizard' => 100,
|
||||
'blocked' => false,
|
||||
]
|
||||
);
|
||||
|
||||
$ingShea = Ingredient::query()->firstOrCreate(
|
||||
['name' => 'Demo Wareneingang Shea Butter'],
|
||||
[
|
||||
'trans_name' => '',
|
||||
'inci' => 'Butyrospermum Parkii Butter',
|
||||
'trans_inci' => '',
|
||||
'effect' => '',
|
||||
'trans_effect' => '',
|
||||
'active' => true,
|
||||
'pos' => 0,
|
||||
]
|
||||
);
|
||||
|
||||
$ingMandel = Ingredient::query()->firstOrCreate(
|
||||
['name' => 'Demo Wareneingang Mandelöl'],
|
||||
[
|
||||
'trans_name' => '',
|
||||
'inci' => 'Prunus Amygdalus Dulcis Oil',
|
||||
'trans_inci' => '',
|
||||
'effect' => '',
|
||||
'trans_effect' => '',
|
||||
'active' => true,
|
||||
'pos' => 0,
|
||||
]
|
||||
);
|
||||
|
||||
StockEntry::factory()->create([
|
||||
'ingredient_id' => $ingShea->id,
|
||||
'supplier_id' => $supplierOele->id,
|
||||
'location_id' => $locationKoln->id,
|
||||
'ordered_by' => $orderUser->id,
|
||||
'ordered_at' => now()->subDays(5)->format('Y-m-d'),
|
||||
'ordered_quantity' => 10000,
|
||||
'price_per_kg' => 8.5,
|
||||
'entry_type' => 'ingredient',
|
||||
'unit' => 'gram',
|
||||
'packaging_item_id' => null,
|
||||
'status' => 'pending',
|
||||
]);
|
||||
|
||||
StockEntry::factory()->create([
|
||||
'ingredient_id' => $ingMandel->id,
|
||||
'supplier_id' => $supplierOele->id,
|
||||
'location_id' => $locationKoln->id,
|
||||
'ordered_by' => $orderUser->id,
|
||||
'ordered_at' => now()->subDays(12)->format('Y-m-d'),
|
||||
'ordered_quantity' => 5000,
|
||||
'price_per_kg' => 9.95,
|
||||
'entry_type' => 'ingredient',
|
||||
'unit' => 'gram',
|
||||
'packaging_item_id' => null,
|
||||
'status' => 'pending',
|
||||
]);
|
||||
|
||||
$demoPackagingItem = PackagingItem::query()
|
||||
->where('name', 'like', 'Demo %')
|
||||
->where('category', 'packaging')
|
||||
->first();
|
||||
|
||||
if ($demoPackagingItem !== null) {
|
||||
StockEntry::query()->create([
|
||||
'entry_type' => 'packaging',
|
||||
'ingredient_id' => null,
|
||||
'packaging_item_id' => $demoPackagingItem->id,
|
||||
'supplier_id' => $supplierGlas->id,
|
||||
'location_id' => $locationKoln->id,
|
||||
'unit' => 'piece',
|
||||
'ordered_by' => $orderUser->id,
|
||||
'ordered_at' => now()->subDays(2)->format('Y-m-d'),
|
||||
'ordered_quantity' => 480,
|
||||
'price_per_kg' => null,
|
||||
'price_total' => 612.0,
|
||||
'received_by' => null,
|
||||
'received_at' => null,
|
||||
'received_quantity' => null,
|
||||
'batch_number' => null,
|
||||
'best_before' => null,
|
||||
'quality_id' => null,
|
||||
'status' => 'pending',
|
||||
]);
|
||||
}
|
||||
|
||||
StockEntry::factory()->received()->create([
|
||||
'ingredient_id' => $ingMandel->id,
|
||||
'supplier_id' => $supplierOele->id,
|
||||
'location_id' => $locationKoln->id,
|
||||
'ordered_by' => $orderUser->id,
|
||||
'ordered_at' => now()->subMonths(2)->format('Y-m-d'),
|
||||
'ordered_quantity' => 3000,
|
||||
'price_per_kg' => 9.5,
|
||||
'entry_type' => 'ingredient',
|
||||
'unit' => 'gram',
|
||||
'packaging_item_id' => null,
|
||||
]);
|
||||
|
||||
$this->command?->info(sprintf(
|
||||
'Inventory-Stammdaten-Test: DE (countries.id=%d), Lagerort „%s“ (id=%d), Lieferanten-IDs %d / %d / %d; Verpackungsartikel mit Präfix „Demo …“; Demo-Wareneingänge (stock_entries) mit User %s.',
|
||||
$countryDe->id,
|
||||
$locationExtra->name,
|
||||
$locationExtra->id,
|
||||
$supplierOele->id,
|
||||
$supplierGlas->id,
|
||||
$supplierLogistik->id,
|
||||
$orderUser->email
|
||||
));
|
||||
}
|
||||
}
|
||||
234
database/seeders/ProductionDemoSeeder.php
Normal file
234
database/seeders/ProductionDemoSeeder.php
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Ingredient;
|
||||
use App\Models\Location;
|
||||
use App\Models\PackagingItem;
|
||||
use App\Models\Product;
|
||||
use App\Models\Production;
|
||||
use App\Models\StockEntry;
|
||||
use App\Models\Supplier;
|
||||
use App\Services\ProductionService;
|
||||
use App\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Demo-Daten für Phase 5 (Produktion): Rezeptur, eingegangene Chargen, Beispiel-Produktionsläufe.
|
||||
* Baut auf {@see InventoryStammdatenTestSeeder} auf (Demo-Rohstoffe, Lagerort Köln, Verpackung).
|
||||
*
|
||||
* Aufruf: php artisan db:seed --class=ProductionDemoSeeder
|
||||
*/
|
||||
class ProductionDemoSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$this->call(InventoryStammdatenTestSeeder::class);
|
||||
|
||||
$location = Location::query()->where('name', 'Köln')->first()
|
||||
?? Location::query()->where('name', 'Demo Test-Zentrallager')->first()
|
||||
?? Location::query()->first();
|
||||
|
||||
if ($location === null) {
|
||||
$this->command?->error('ProductionDemoSeeder: Kein Lagerort gefunden.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ingShea = Ingredient::query()->where('name', 'Demo Wareneingang Shea Butter')->first();
|
||||
$ingMandel = Ingredient::query()->where('name', 'Demo Wareneingang Mandelöl')->first();
|
||||
|
||||
if ($ingShea === null || $ingMandel === null) {
|
||||
$this->command?->error('ProductionDemoSeeder: Demo-Inhaltsstoffe fehlen (Shea/Mandel).');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$supplierOele = Supplier::query()->where('name', 'Demo-Lieferant Naturöle GmbH')->first();
|
||||
if ($supplierOele === null) {
|
||||
$this->command?->error('ProductionDemoSeeder: Demo-Lieferant fehlt.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$orderUser = User::query()->where('email', 'demo-wareneingang@example.test')->first();
|
||||
if ($orderUser === null) {
|
||||
$this->command?->error('ProductionDemoSeeder: User demo-wareneingang@example.test fehlt.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sheaStock = StockEntry::query()
|
||||
->where('ingredient_id', $ingShea->id)
|
||||
->where('location_id', $location->id)
|
||||
->where('status', 'received')
|
||||
->orderByDesc('id')
|
||||
->first();
|
||||
|
||||
if ($sheaStock === null) {
|
||||
$sheaStock = StockEntry::factory()->received()->create([
|
||||
'ingredient_id' => $ingShea->id,
|
||||
'supplier_id' => $supplierOele->id,
|
||||
'location_id' => $location->id,
|
||||
'ordered_by' => $orderUser->id,
|
||||
'ordered_at' => now()->subMonths(3)->format('Y-m-d'),
|
||||
'ordered_quantity' => 8000,
|
||||
'price_per_kg' => 8.5,
|
||||
'entry_type' => 'ingredient',
|
||||
'unit' => 'gram',
|
||||
'packaging_item_id' => null,
|
||||
'batch_number' => 'DEMO-SHEA-PROD',
|
||||
'best_before' => now()->addMonths(18)->format('Y-m-d'),
|
||||
]);
|
||||
}
|
||||
|
||||
$mandelStock = StockEntry::query()
|
||||
->where('ingredient_id', $ingMandel->id)
|
||||
->where('location_id', $location->id)
|
||||
->where('status', 'received')
|
||||
->orderByDesc('received_at')
|
||||
->orderByDesc('id')
|
||||
->first();
|
||||
|
||||
if ($mandelStock === null) {
|
||||
$mandelStock = StockEntry::factory()->received()->create([
|
||||
'ingredient_id' => $ingMandel->id,
|
||||
'supplier_id' => $supplierOele->id,
|
||||
'location_id' => $location->id,
|
||||
'ordered_by' => $orderUser->id,
|
||||
'ordered_at' => now()->subMonths(2)->format('Y-m-d'),
|
||||
'ordered_quantity' => 5000,
|
||||
'price_per_kg' => 9.5,
|
||||
'entry_type' => 'ingredient',
|
||||
'unit' => 'gram',
|
||||
'packaging_item_id' => null,
|
||||
'batch_number' => 'DEMO-MANDEL-PROD',
|
||||
'best_before' => now()->addMonths(20)->format('Y-m-d'),
|
||||
]);
|
||||
}
|
||||
|
||||
$bottle = PackagingItem::query()->where('name', 'Demo Braunglasflasche 30 ml')->first();
|
||||
|
||||
$product = Product::query()->firstOrCreate(
|
||||
['name' => 'Demo Produktion Handcreme 30 ml'],
|
||||
[
|
||||
'title' => 'Demo Produktion Handcreme 30 ml',
|
||||
'active' => true,
|
||||
'shelf_life_type' => null,
|
||||
'shelf_life_months' => null,
|
||||
]
|
||||
);
|
||||
|
||||
if ($product->p_ingredients()->count() === 0) {
|
||||
$product->p_ingredients()->sync([
|
||||
$ingShea->id => ['pos' => 0, 'gram' => 20, 'factor' => 1],
|
||||
$ingMandel->id => ['pos' => 1, 'gram' => 15, 'factor' => 1],
|
||||
]);
|
||||
}
|
||||
|
||||
if ($bottle !== null && $product->packagings()->count() === 0) {
|
||||
$product->packagings()->sync([
|
||||
$bottle->id => ['quantity' => 1, 'pos' => 0],
|
||||
]);
|
||||
}
|
||||
|
||||
$service = app(ProductionService::class);
|
||||
|
||||
if (! Production::query()->where('product_id', $product->id)->exists()) {
|
||||
$qty = 10;
|
||||
$service->store(
|
||||
[
|
||||
'product_id' => $product->id,
|
||||
'location_id' => $location->id,
|
||||
'produced_at' => now()->subDays(3)->format('Y-m-d'),
|
||||
'quantity' => $qty,
|
||||
'notes' => 'Seeder: Demo-Produktionslauf (Shea + Mandel, Braunglas).',
|
||||
],
|
||||
[
|
||||
[
|
||||
'ingredient_id' => $ingShea->id,
|
||||
'stock_entry_id' => $sheaStock->id,
|
||||
'quantity_used' => (string) (20 * 1 * $qty),
|
||||
],
|
||||
[
|
||||
'ingredient_id' => $ingMandel->id,
|
||||
'stock_entry_id' => $mandelStock->id,
|
||||
'quantity_used' => (string) (15 * 1 * $qty),
|
||||
],
|
||||
],
|
||||
$orderUser->id
|
||||
);
|
||||
}
|
||||
|
||||
$mhdStock = StockEntry::query()
|
||||
->where('ingredient_id', $ingMandel->id)
|
||||
->where('location_id', $location->id)
|
||||
->where('status', 'received')
|
||||
->where('batch_number', 'DEMO-MHD-WARNUNG')
|
||||
->first();
|
||||
|
||||
if ($mhdStock === null) {
|
||||
$mhdStock = StockEntry::factory()->received()->create([
|
||||
'ingredient_id' => $ingMandel->id,
|
||||
'supplier_id' => $supplierOele->id,
|
||||
'location_id' => $location->id,
|
||||
'ordered_by' => $orderUser->id,
|
||||
'ordered_at' => now()->subYear()->format('Y-m-d'),
|
||||
'ordered_quantity' => 500,
|
||||
'price_per_kg' => 9.5,
|
||||
'entry_type' => 'ingredient',
|
||||
'unit' => 'gram',
|
||||
'packaging_item_id' => null,
|
||||
'batch_number' => 'DEMO-MHD-WARNUNG',
|
||||
'best_before' => '2027-06-01',
|
||||
'received_at' => now()->subMonths(6)->format('Y-m-d'),
|
||||
]);
|
||||
}
|
||||
|
||||
$productMhd = Product::query()->firstOrCreate(
|
||||
['name' => 'Demo Produktion MHD-Warnung (Test)'],
|
||||
[
|
||||
'title' => 'Demo MHD-Warnung',
|
||||
'active' => true,
|
||||
'shelf_life_type' => 'fixed',
|
||||
'shelf_life_months' => 24,
|
||||
]
|
||||
);
|
||||
|
||||
if ($productMhd->p_ingredients()->count() === 0) {
|
||||
$productMhd->p_ingredients()->sync([
|
||||
$ingMandel->id => ['pos' => 0, 'gram' => 10, 'factor' => 1],
|
||||
]);
|
||||
}
|
||||
|
||||
if (! Production::query()->where('product_id', $productMhd->id)->exists()) {
|
||||
$service->store(
|
||||
[
|
||||
'product_id' => $productMhd->id,
|
||||
'location_id' => $location->id,
|
||||
'produced_at' => '2026-03-01',
|
||||
'quantity' => 1,
|
||||
'notes' => 'Seeder: MHD-Warnung (Rohstoff-MHD vor Produkt-Ende).',
|
||||
],
|
||||
[
|
||||
[
|
||||
'ingredient_id' => $ingMandel->id,
|
||||
'stock_entry_id' => $mhdStock->id,
|
||||
'quantity_used' => '10',
|
||||
],
|
||||
],
|
||||
$orderUser->id
|
||||
);
|
||||
}
|
||||
|
||||
$this->command?->info(sprintf(
|
||||
'ProductionDemoSeeder: Produkt „%s“ (id=%d), MHD-Demo „%s“ (id=%d), Lagerort %s (id=%d).',
|
||||
$product->name,
|
||||
$product->id,
|
||||
$productMhd->name,
|
||||
$productMhd->id,
|
||||
$location->name,
|
||||
$location->id
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -12,5 +12,8 @@ class DatabaseSeeder extends Seeder
|
|||
public function run()
|
||||
{
|
||||
// $this->call(UsersTableSeeder::class);
|
||||
// Stammdaten Warenwirtschaft (PSR-4: database/seeders/)
|
||||
// $this->call(\Database\Seeders\InventoryStammdatenSeeder::class);
|
||||
// $this->call(\Database\Seeders\InventoryStammdatenTestSeeder::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue