create([ 'email' => uniqid('set_', true).'@test.example', 'password' => bcrypt('password'), ]); $user->forceFill([ 'admin' => $adminLevel, 'confirmed' => true, 'active' => true, 'wizard' => 100, 'blocked' => false, ])->save(); return $user->fresh(); } function setMakeProduct(string $name, bool $active = true): Product { return Product::query()->create([ 'name' => $name, 'title' => $name, 'active' => $active, ]); } test('repository speichert ein set mit bestandteilen und mengen', function () { $set = setMakeProduct('SET-Bundle'); $a = setMakeProduct('SET-Einzel-A'); $b = setMakeProduct('SET-Einzel-B'); $repo = new ProductRepository($set); $repo->update([ 'id' => (string) $set->id, 'name' => $set->name, 'title' => $set->title, 'active' => '1', 'is_set' => '1', 'set_component_id' => [(string) $a->id, (string) $b->id], 'set_quantity' => ['2', '3'], ]); $set->refresh(); expect($set->is_set)->toBeTrue(); expect($set->setItems)->toHaveCount(2); expect((int) $set->setItems->firstWhere('id', $a->id)->pivot->quantity)->toBe(2); expect((int) $set->setItems->firstWhere('id', $b->id)->pivot->quantity)->toBe(3); }); test('set leert rezeptur und verpackung', function () { $ing = Ingredient::query()->create([ 'name' => 'SET-Roh', 'trans_name' => '', 'inci' => 'SR', 'trans_inci' => '', 'effect' => '', 'trans_effect' => '', 'active' => true, 'pos' => 0, ]); $packaging = PackagingItem::factory()->create(); $component = setMakeProduct('SET-Komp'); $product = setMakeProduct('SET-Wandelbar'); ProductIngredient::query()->create([ 'product_id' => $product->id, 'ingredient_id' => $ing->id, 'pos' => 0, 'gram' => 50, 'factor' => 1.1, 'recipe_type' => 'manufacturer', ]); $product->packagings()->attach($packaging->id, ['quantity' => 1, 'pos' => 0]); $repo = new ProductRepository($product); $repo->update([ 'id' => (string) $product->id, 'name' => $product->name, 'title' => $product->title, 'active' => '1', 'is_set' => '1', 'set_component_id' => [(string) $component->id], 'set_quantity' => ['1'], ]); $product->refresh(); expect($product->is_set)->toBeTrue(); expect($product->manufacturer_ingredients)->toHaveCount(0); expect($product->packagings)->toHaveCount(0); expect($product->setItems)->toHaveCount(1); }); test('hauptprodukt-zuordnung wird gespeichert und bei set genullt', function () { $main = setMakeProduct('SET-Haupt'); $variant = setMakeProduct('SET-Variante'); $repo = new ProductRepository($variant); $repo->update([ 'id' => (string) $variant->id, 'name' => $variant->name, 'title' => $variant->title, 'active' => '1', 'main_product_id' => (string) $main->id, 'main_product_quantity' => '50', ]); $variant->refresh(); expect($variant->main_product_id)->toBe($main->id); expect($variant->main_product_quantity)->toBe(50); // Wird das Produkt zum Set, ist es keine Variante mehr. $repo->update([ 'id' => (string) $variant->id, 'name' => $variant->name, 'title' => $variant->title, 'active' => '1', 'is_set' => '1', 'main_product_id' => (string) $main->id, 'set_component_id' => [(string) $main->id], 'set_quantity' => ['1'], ]); $variant->refresh(); expect($variant->main_product_id)->toBeNull(); }); test('scopes trennen einzelprodukte, sets und hauptprodukte', function () { $single = setMakeProduct('SCOPE-Einzel'); $set = setMakeProduct('SCOPE-Set'); $set->update(['is_set' => true]); $variant = setMakeProduct('SCOPE-Variante'); $variant->update(['main_product_id' => $single->id]); expect(Product::query()->singleProducts()->pluck('id')->all()) ->toContain($single->id, $variant->id) ->not->toContain($set->id); expect(Product::query()->sets()->pluck('id')->all()) ->toContain($set->id) ->not->toContain($single->id); expect(Product::query()->mainProducts()->pluck('id')->all()) ->toContain($single->id, $set->id) ->not->toContain($variant->id); }); test('set kann nicht produziert werden', function () { $location = Location::factory()->create(); $set = setMakeProduct('SET-NichtProduzierbar'); $set->update(['is_set' => true]); $service = app(ProductionService::class); expect(fn () => $service->store( [ 'product_id' => $set->id, 'location_id' => $location->id, 'produced_at' => '2026-03-01', 'quantity' => 1, 'notes' => null, ], [], setMakeUser()->id ))->toThrow(ValidationException::class); }); test('produktions-formular zeigt keine sets', function () { p51EnsureCountryForSet(); Location::factory()->create(['name' => 'Köln']); $user = setMakeUser(1); setMakeProduct('SET-FORM-EINZEL'); $set = setMakeProduct('SET-FORM-SET'); $set->update(['is_set' => true]); $this->actingAs($user, 'user'); $response = $this->get(route('admin.inventory.productions.create')); $response->assertSuccessful(); $response->assertSee('SET-FORM-EINZEL'); $response->assertDontSee('SET-FORM-SET'); }); test('set ohne bestandteile wird abgelehnt', function () { $user = setMakeUser(1); $this->actingAs($user, 'user'); $response = $this->post(route('admin_product_store'), [ 'id' => 'new', 'name' => 'SET-LEER', 'is_set' => '1', ]); $response->assertSuccessful(); $response->assertSee('mindestens ein Einzelprodukt', false); expect(Product::query()->where('name', 'SET-LEER')->exists())->toBeFalse(); }); test('set mit set-bestandteil wird abgelehnt', function () { $user = setMakeUser(1); $this->actingAs($user, 'user'); $innerSet = setMakeProduct('SET-INNEN'); $innerSet->update(['is_set' => true]); $response = $this->post(route('admin_product_store'), [ 'id' => 'new', 'name' => 'SET-AUSSEN', 'is_set' => '1', 'set_component_id' => [(string) $innerSet->id], 'set_quantity' => ['1'], ]); $response->assertSuccessful(); $response->assertSee('dürfen selbst keine Sets sein', false); }); test('gueltiges set wird per http gespeichert', function () { $user = setMakeUser(1); $this->actingAs($user, 'user'); $component = setMakeProduct('SET-HTTP-KOMP'); $response = $this->post(route('admin_product_store'), [ 'id' => 'new', 'name' => 'SET-HTTP', 'is_set' => '1', 'set_component_id' => [(string) $component->id], 'set_quantity' => ['4'], ]); $response->assertRedirect(); $set = Product::query()->where('name', 'SET-HTTP')->firstOrFail(); expect($set->is_set)->toBeTrue(); expect($set->setItems)->toHaveCount(1); expect((int) $set->setItems->first()->pivot->quantity)->toBe(4); }); test('produkt-kopie uebernimmt set-bestandteile', function () { $component = setMakeProduct('COPY-KOMP'); $set = setMakeProduct('COPY-SET'); $set->update(['is_set' => true]); $set->setItems()->attach($component->id, ['quantity' => 5, 'pos' => 0]); $repo = new ProductRepository(new Product); $copy = $repo->copy($set->fresh()); expect($copy->is_set)->toBeTrue(); expect($copy->setItems()->count())->toBe(1); expect((int) $copy->setItems()->first()->pivot->quantity)->toBe(5); }); function p51EnsureCountryForSet(): Country { return Country::query()->firstOrCreate( ['code' => 'DE'], [ 'phone' => '00', 'en' => 'Germany', 'de' => 'Deutschland', 'es' => 'Germany', 'fr' => 'Germany', 'it' => 'Germany', 'ru' => 'Germany', 'active' => true, ] ); }