create([ 'email' => uniqid('p51_', true).'@test.example', 'password' => bcrypt('password'), ]); $user->forceFill([ 'admin' => $adminLevel, 'confirmed' => true, 'active' => true, 'wizard' => 100, 'blocked' => false, ])->save(); return $user->fresh(); } function p51EnsureCountry(): Country { return Country::query()->firstOrCreate( ['code' => 'DE'], [ 'phone' => '00', 'en' => 'Germany', 'de' => 'Deutschland', 'es' => 'Germany', 'fr' => 'Germany', 'it' => 'Germany', 'ru' => 'Germany', 'active' => true, ] ); } /** * @return array{0: Ingredient, 1: Ingredient} */ function p51MakeIngredients(): array { $base = [ 'trans_name' => '', 'trans_inci' => '', 'effect' => '', 'trans_effect' => '', 'active' => true, 'pos' => 0, ]; $a = Ingredient::query()->create($base + ['name' => 'P51-Rohstoff-A', 'inci' => 'INCI-A', 'default_factor' => 1.10]); $b = Ingredient::query()->create($base + ['name' => 'P51-Rohstoff-B', 'inci' => 'INCI-B', 'default_factor' => 1.10]); return [$a, $b]; } test('ingredient speichert und liefert rohstoffqualitaet', function () { $quality = MaterialQuality::query()->create(['name' => 'bio kaltgepresst', 'pos' => 1]); $ing = Ingredient::query()->create([ 'name' => 'Sonnenblumenöl', 'trans_name' => '', 'inci' => 'Helianthus Annuus Seed Oil', 'trans_inci' => '', 'effect' => '', 'trans_effect' => '', 'active' => true, 'pos' => 0, 'material_quality_id' => $quality->id, ]); expect($ing->materialQuality)->not->toBeNull(); expect($ing->materialQuality->name)->toBe('bio kaltgepresst'); }); test('produkt speichert hersteller-rezeptur getrennt von produkt-rezeptur', function () { [$a, $b] = p51MakeIngredients(); $product = Product::query()->create([ 'name' => 'P51-Produkt', 'title' => 'P51-Produkt', 'active' => true, ]); $repo = new ProductRepository($product); $repo->update([ 'id' => (string) $product->id, 'name' => $product->name, 'title' => $product->title, 'active' => '1', // Produkt-Rezeptur 'product_inci_sync_sent' => '1', 'pi_ingredient_id' => [(string) $a->id], 'pi_gram' => ['80,000'], 'pi_factor' => ['1,10'], // Hersteller-Rezeptur 'manufacturer_inci_sync_sent' => '1', 'mfg_ingredient_id' => [(string) $b->id], 'mfg_gram' => ['20,000'], 'mfg_factor' => ['1,20'], ]); $product->refresh(); expect($product->p_ingredients)->toHaveCount(1); expect($product->p_ingredients->first()->id)->toBe($a->id); expect($product->manufacturer_ingredients)->toHaveCount(1); $mfg = $product->manufacturer_ingredients->first(); expect($mfg->id)->toBe($b->id); expect((float) $mfg->pivot->gram)->toBe(20.0); expect((float) $mfg->pivot->factor)->toBe(1.20); }); test('produkt-kopie uebernimmt beide rezepturen', function () { [$a, $b] = p51MakeIngredients(); $source = Product::query()->create([ 'name' => 'P51-Quelle', 'title' => 'P51-Quelle', 'active' => true, ]); ProductIngredient::query()->create([ 'product_id' => $source->id, 'ingredient_id' => $a->id, 'pos' => 0, 'gram' => 70, 'factor' => 1.10, 'recipe_type' => 'product', ]); ProductIngredient::query()->create([ 'product_id' => $source->id, 'ingredient_id' => $b->id, 'pos' => 0, 'gram' => 30, 'factor' => 1.15, 'recipe_type' => 'manufacturer', ]); $repo = new ProductRepository(new Product); $copy = $repo->copy($source->fresh()); expect($copy->p_ingredients()->count())->toBe(1); expect($copy->manufacturer_ingredients()->count())->toBe(1); $mfg = $copy->manufacturer_ingredients()->first(); expect($mfg->id)->toBe($b->id); expect((float) $mfg->pivot->gram)->toBe(30.0); expect((float) $mfg->pivot->factor)->toBe(1.15); }); test('produktions-formular zeigt nur aktive produkte', function () { p51EnsureCountry(); Location::factory()->create(['name' => 'Köln']); $user = p51MakeUser(1); Product::query()->create(['name' => 'P51-AKTIV-PROD', 'title' => 'P51-AKTIV-PROD', 'active' => true]); Product::query()->create(['name' => 'P51-INAKTIV-PROD', 'title' => 'P51-INAKTIV-PROD', 'active' => false]); $this->actingAs($user, 'user'); $response = $this->get(route('admin.inventory.productions.create')); $response->assertSuccessful(); $response->assertSee('P51-AKTIV-PROD'); $response->assertDontSee('P51-INAKTIV-PROD'); }); test('produktion kann bearbeitet und kopiert werden (views rendern)', function () { p51EnsureCountry(); $user = p51MakeUser(7); $location = Location::factory()->create(); $supplier = Supplier::factory()->create(); $ing = Ingredient::query()->create([ 'name' => 'P51-Prod-Roh', 'trans_name' => '', 'inci' => 'PR', 'trans_inci' => '', 'effect' => '', 'trans_effect' => '', 'active' => true, 'pos' => 0, ]); $product = Product::query()->create([ 'name' => 'P51-Prod', 'title' => 'P51-Prod', 'active' => true, ]); ProductIngredient::query()->create([ 'product_id' => $product->id, 'ingredient_id' => $ing->id, 'pos' => 0, 'gram' => 10, 'factor' => 1.0, 'recipe_type' => 'manufacturer', ]); $stock = StockEntry::query()->create([ 'entry_type' => 'ingredient', 'ingredient_id' => $ing->id, 'packaging_item_id' => null, 'supplier_id' => $supplier->id, 'location_id' => $location->id, 'unit' => 'gram', 'ordered_by' => $user->id, 'ordered_at' => '2026-01-01', 'ordered_quantity' => 10000, 'price_per_kg' => 1, 'status' => 'received', 'received_by' => $user->id, 'received_at' => '2026-01-15', 'received_quantity' => 10000, 'batch_number' => 'P51-B', 'best_before' => '2030-12-31', 'quality_id' => null, ]); $production = app(ProductionService::class)->store( [ 'product_id' => $product->id, 'location_id' => $location->id, 'produced_at' => '2026-03-01', 'quantity' => 5, 'notes' => null, ], [ ['ingredient_id' => $ing->id, 'stock_entry_id' => $stock->id, 'quantity_used' => '50'], ], $user->id ); $this->actingAs($user, 'user'); $this->get(route('admin.inventory.productions.edit', $production))->assertSuccessful(); $this->get(route('admin.inventory.productions.copy', $production))->assertSuccessful(); // Übersicht rendert mit Aktionsspalte (Ansicht, Bearbeiten, Kopieren) $index = $this->get(route('admin.inventory.productions.index')); $index->assertSuccessful(); $index->assertSee(route('admin.inventory.productions.show', $production), false); $index->assertSee(route('admin.inventory.productions.edit', $production), false); $index->assertSee(route('admin.inventory.productions.copy', $production), false); });