create([ 'email' => uniqid('ing_', true).'@test.example', 'password' => bcrypt('password'), ]); $user->forceFill([ 'admin' => $adminLevel, 'confirmed' => true, 'active' => true, 'wizard' => 100, 'blocked' => false, ])->save(); return $user->fresh(); } function ingredientOrderSupplier(string $name): Supplier { $supplier = Supplier::factory()->create([ 'name' => $name, 'active' => true, ]); // AP-49: Das INCI-Formular zeigt nur Rohstoff-Lieferanten (Kategorie „Kosmetikrohstoffe"). $category = SupplierCategory::query()->firstOrCreate(['name' => 'Kosmetikrohstoffe'], ['pos' => 0]); $supplier->supplierCategories()->attach($category->id); return $supplier; } test('ingredient form shows suppliers, tax rates and delivery time templates', function () { $supplier = ingredientOrderSupplier('Aktiv-Lieferant'); TaxRate::query()->create(['name' => 'Standard', 'percent' => 19, 'active' => true, 'pos' => 0]); DeliveryTime::query()->create(['label' => '3–5 Werktage', 'days' => 5, 'active' => true, 'pos' => 0]); DeliveryTime::query()->create(['label' => 'Inaktiv-Vorlage', 'active' => false, 'pos' => 1]); $this->actingAs(ingredientOrderUser(7), 'user'); $this->get(route('admin_product_ingredient_edit', 'new')) ->assertSuccessful() ->assertSee('supplier-repeater') ->assertSee('weiteren Lieferanten anlegen') ->assertSee('alt_name') ->assertSee('storage_room_id') ->assertSee('storage_shelf_id') ->assertSee('storage_slot_id') ->assertSee('tax_rate_id') ->assertSee('Aktiv-Lieferant') ->assertSee('3–5 Werktage') ->assertSee('data-days="5"', false) ->assertDontSee('Inaktiv-Vorlage'); }); test('ingredient can be stored with tax rate, delivery time and suppliers', function () { $taxRate = TaxRate::query()->create(['name' => 'Standard', 'percent' => 19, 'active' => true, 'pos' => 0]); $supplierA = ingredientOrderSupplier('Lieferant A'); $supplierB = ingredientOrderSupplier('Lieferant B'); $this->actingAs(ingredientOrderUser(7), 'user'); $this->post(route('admin_product_ingredient_store'), [ 'id' => 'new', 'name' => 'Sheabutter', 'alt_name' => 'Butyrospermum Parkii', 'active' => '1', 'tax_rate_id' => (string) $taxRate->id, 'delivery_time' => '3–5 Werktage', 'delivery_time_days' => '5', 'suppliers' => [ ['id' => (string) $supplierA->id, 'url' => 'https://shop.example/sheabutter?variant=25kg'], ['id' => (string) $supplierB->id, 'url' => ''], ], ])->assertRedirect(route('admin_product_ingredients')); $ingredient = Ingredient::query()->where('name', 'Sheabutter')->firstOrFail(); expect($ingredient->alt_name)->toBe('Butyrospermum Parkii') ->and($ingredient->tax_rate_id)->toBe($taxRate->id) ->and($ingredient->delivery_time)->toBe('3–5 Werktage') ->and($ingredient->delivery_time_days)->toBe(5) ->and($ingredient->suppliers()->pluck('suppliers.id')->all()) ->toEqualCanonicalizing([$supplierA->id, $supplierB->id]) // URL je Lieferant: gesetzt ⇒ Shop-Link, leer ⇒ Mailbestellung (null) ->and($ingredient->suppliers->firstWhere('id', $supplierA->id)->pivot->url) ->toBe('https://shop.example/sheabutter?variant=25kg') ->and($ingredient->suppliers->firstWhere('id', $supplierB->id)->pivot->url) ->toBeNull(); }); test('ingredient suppliers are synced on update', function () { $supplierA = ingredientOrderSupplier('Lieferant A'); $supplierB = ingredientOrderSupplier('Lieferant B'); $ingredient = Ingredient::query()->create([ 'name' => 'Lavendelöl', 'active' => true, ]); $ingredient->suppliers()->sync([$supplierA->id]); $this->actingAs(ingredientOrderUser(7), 'user'); $this->post(route('admin_product_ingredient_store'), [ 'id' => (string) $ingredient->id, 'name' => 'Lavendelöl', 'active' => '1', 'suppliers' => [ ['id' => (string) $supplierB->id, 'url' => 'https://shop.example/lavendel'], ], ])->assertRedirect(route('admin_product_ingredients')); expect($ingredient->fresh()->suppliers()->pluck('suppliers.id')->all()) ->toEqualCanonicalizing([$supplierB->id]); }); test('ingredient stores alt name and storage location', function () { $room = StorageRoom::factory()->create(['label' => 'Raum 1']); $shelf = StorageShelf::factory()->create(['label' => 'Regal 3']); $slot = StorageSlot::factory()->create(['label' => 'A']); $this->actingAs(ingredientOrderUser(7), 'user'); $this->post(route('admin_product_ingredient_store'), [ 'id' => 'new', 'name' => 'Lecithin', 'alt_name' => 'Phospholipon 80 H', 'active' => '1', 'storage_room_id' => (string) $room->id, 'storage_shelf_id' => (string) $shelf->id, 'storage_slot_id' => (string) $slot->id, ])->assertRedirect(route('admin_product_ingredients')); $ingredient = Ingredient::query()->where('name', 'Lecithin')->firstOrFail(); expect($ingredient->alt_name)->toBe('Phospholipon 80 H') ->and($ingredient->storage_room_id)->toBe($room->id) ->and($ingredient->storageLabel())->toBe('Raum 1 | Regal 3 | A'); }); test('ingredient storage location must exist', function () { $this->actingAs(ingredientOrderUser(7), 'user'); $this->post(route('admin_product_ingredient_store'), [ 'id' => 'new', 'name' => 'Falscher-Lagerort', 'active' => '1', 'storage_room_id' => '999999', ])->assertSessionHasErrors('storage_room_id'); }); test('ingredient supplier url must belong to existing supplier', function () { $this->actingAs(ingredientOrderUser(7), 'user'); $this->post(route('admin_product_ingredient_store'), [ 'id' => 'new', 'name' => 'Falscher-Lieferant', 'active' => '1', 'suppliers' => [ ['id' => '999999', 'url' => 'https://shop.example'], ], ])->assertSessionHasErrors('suppliers.0.id'); }); test('ingredient delivery time days must be an integer', function () { $this->actingAs(ingredientOrderUser(7), 'user'); $this->post(route('admin_product_ingredient_store'), [ 'id' => 'new', 'name' => 'Falsche-Tage', 'active' => '1', 'delivery_time_days' => 'viele', ])->assertSessionHasErrors('delivery_time_days'); }); test('ingredient tax rate must exist', function () { $this->actingAs(ingredientOrderUser(7), 'user'); $this->post(route('admin_product_ingredient_store'), [ 'id' => 'new', 'name' => 'Falscher-Steuersatz', 'active' => '1', 'tax_rate_id' => '999999', ])->assertSessionHasErrors('tax_rate_id'); }); test('ingredient name is required', function () { $this->actingAs(ingredientOrderUser(7), 'user'); $this->post(route('admin_product_ingredient_store'), [ 'id' => 'new', 'name' => '', 'active' => '1', ])->assertSessionHasErrors('name'); });