forgetCachedPermissions(); Role::create(['name' => 'Retailer']); Role::create(['name' => 'Manufacturer']); Role::create(['name' => 'Customer']); }); function makePartnerWithHub(): array { $hub = Hub::factory()->create(); $partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]); $user = User::factory()->create(['partner_id' => $partner->id]); return [$user, $partner, $hub]; } // --- Access Tests --- test('retailer can access standard product creation page', function () { [$user] = makePartnerWithHub(); $user->assignRole('Retailer'); $this->actingAs($user) ->get(route('products.create.standard')) ->assertSuccessful(); }); test('manufacturer can access standard product creation page', function () { [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $this->actingAs($user) ->get(route('products.create.standard')) ->assertSuccessful(); }); test('customer cannot access standard product creation page', function () { $customer = User::factory()->create(); $customer->assignRole('Customer'); $this->actingAs($customer) ->get(route('products.create.standard')) ->assertForbidden(); }); // --- Happy Path Tests --- test('manufacturer can create standard product with fixed price', function () { Storage::fake('public'); [$user, $partner, $hub] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Sofa ALBA 3-Sitzer') ->set('descriptionShort', 'Modernes Sofa mit Massivholzgestell.') ->set('descriptionLong', 'Das Sofa ALBA verbindet zeitloses Design mit regionaler Handwerkskunst.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('sku', 'SOFA-ALBA-3S') ->set('sellingPrice', 1250.00) ->set('mainImages', [UploadedFile::fake()->image('sofa.jpg', 800, 600)]) ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Sofa ALBA 3-Sitzer')->first(); expect($product)->not->toBeNull(); expect($product->product_type)->toBe(ProductType::SmartOrder); expect($product->price_type)->toBe(PriceType::Fixed); expect($product->status)->toBe(ProductStatus::Pending); expect($product->partner_id)->toBe($partner->id); expect($product->hub_id)->toBe($hub->id); expect($product->is_curated)->toBeFalse(); expect($product->description_long)->toBe('Das Sofa ALBA verbindet zeitloses Design mit regionaler Handwerkskunst.'); // Master-Variante prüfen $variant = $product->variants()->where('is_master_variant', true)->first(); expect($variant)->not->toBeNull(); expect($variant->sku)->toBe('SOFA-ALBA-3S'); expect($variant->selling_price)->toBe(125000); expect($variant->is_active)->toBeTrue(); // Bild prüfen expect($product->media)->toHaveCount(1); }); test('retailer can create standard product with from_price', function () { Storage::fake('public'); [$user, $partner] = makePartnerWithHub(); $user->assignRole('Retailer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Tisch Eiche massiv') ->set('descriptionShort', 'Massivholztisch auf Maß.') ->set('categoryId', $category->id) ->set('priceType', 'from_price') ->set('priceDisplayText', 'Ab 1.800 €') ->set('status', 'draft') ->set('sku', 'TISCH-EICHE-01') ->set('sellingPrice', 1800.00) ->set('mainImages', [UploadedFile::fake()->image('table.jpg', 800, 600)]) ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Tisch Eiche massiv')->first(); expect($product)->not->toBeNull(); expect($product->product_type)->toBe(ProductType::SmartOrder); expect($product->price_type)->toBe(PriceType::FromPrice); expect($product->price_display_text)->toBe('Ab 1.800 €'); expect($product->status)->toBe(ProductStatus::Draft); }); test('standard product saves physical dimensions and logistics', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Sideboard mit Logistik') ->set('descriptionShort', 'Ein Sideboard mit Maßen.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('sku', 'SB-LOG-001') ->set('sellingPrice', 990.00) ->set('widthCm', 180) ->set('heightCm', 85) ->set('depthCm', 45) ->set('weightG', 45000) ->set('assemblyStatus', 'partially') ->set('packageCount', 2) ->set('packageWeightG', 52000) ->set('packageWidthCm', 190) ->set('packageHeightCm', 50) ->set('packageDepthCm', 50) ->set('mainImages', [UploadedFile::fake()->image('sideboard.jpg', 800, 600)]) ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Sideboard mit Logistik')->first(); expect($product->width_cm)->toBe(180); expect($product->height_cm)->toBe(85); expect($product->depth_cm)->toBe(45); expect($product->assembly_status)->toBe('partially'); $variant = $product->variants()->where('is_master_variant', true)->first(); expect($variant->variant_weight_g)->toBe(45000); $logistics = $variant->logistics; expect($logistics)->not->toBeNull(); expect($logistics->package_count)->toBe(2); expect($logistics->package_weight_g)->toBe(52000); expect($logistics->package_width_cm)->toBe(190); }); test('standard product saves commercial variant data', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Produkt Kommerziell') ->set('descriptionShort', 'Kommerzielles Produkt.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('sku', 'KOMM-001') ->set('hanMpn', 'MPN-XYZ') ->set('eanGtin', '4012345678901') ->set('sellingPrice', 1250.00) ->set('purchasePrice', 680.00) ->set('msrp', 1499.00) ->set('availabilityStatus', 'on_order') ->set('deliveryTimeText', '4-6 Wochen') ->set('mainImages', [UploadedFile::fake()->image('product.jpg', 800, 600)]) ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Produkt Kommerziell')->first(); $variant = $product->variants()->where('is_master_variant', true)->first(); expect($variant->sku)->toBe('KOMM-001'); expect($variant->han_mpn)->toBe('MPN-XYZ'); expect($variant->ean_gtin)->toBe('4012345678901'); expect($variant->selling_price)->toBe(125000); expect($variant->purchase_price)->toBe(68000); expect($variant->msrp)->toBe(149900); expect($variant->availability_status)->toBe('on_order'); expect($variant->delivery_time_text)->toBe('4-6 Wochen'); }); test('standard product saves SEO metadata', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'SEO Produkt') ->set('descriptionShort', 'Ein Produkt mit SEO-Daten.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('sku', 'SEO-001') ->set('sellingPrice', 500.00) ->set('metaTitle', 'SEO Titel für Produkt') ->set('metaDescription', 'Eine SEO-Beschreibung für das Produkt.') ->set('mainImages', [UploadedFile::fake()->image('seo.jpg', 800, 600)]) ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'SEO Produkt')->first(); expect($product->meta_title)->toBe('SEO Titel für Produkt'); expect($product->meta_description)->toBe('Eine SEO-Beschreibung für das Produkt.'); }); // --- Validation Tests --- test('validation errors switch to the tab with the first error', function () { [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $this->actingAs($user); Volt::test('products.form-standard') ->set('activeTab', 'kommerziell') ->set('name', '') ->call('save') ->assertHasErrors(['name' => 'required']) ->assertSet('activeTab', 'basis'); }); test('standard product requires name', function () { [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', '') ->call('save') ->assertHasErrors(['name' => 'required']); }); test('standard product requires short description', function () { [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Test') ->set('descriptionShort', '') ->call('save') ->assertHasErrors(['descriptionShort' => 'required']); }); test('standard product requires category', function () { [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Test') ->set('descriptionShort', 'Beschreibung.') ->set('categoryId', null) ->call('save') ->assertHasErrors(['categoryId' => 'required']); }); test('standard product can be created without sku and selling price', function () { Storage::fake('public'); [$user, $partner] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Minimal-Produkt') ->set('descriptionShort', 'Produkt ohne SKU und Preis.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('sku', '') ->set('sellingPrice', null) ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Minimal-Produkt')->first(); expect($product)->not->toBeNull(); $variant = $product->variants()->where('is_master_variant', true)->first(); expect($variant->sku)->toBeNull(); expect($variant->selling_price)->toBeNull(); }); test('standard product requires unique sku', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); // Erstelle existierende Variante mit gleicher SKU $existingProduct = Product::factory()->create(['partner_id' => $user->partner_id]); $existingProduct->variants()->create([ 'sku' => 'DOPPEL-SKU', 'is_master_variant' => true, 'selling_price' => 10000, 'is_active' => true, ]); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Doppelte SKU') ->set('descriptionShort', 'Beschreibung.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('sku', 'DOPPEL-SKU') ->set('sellingPrice', 500.00) ->set('mainImages', [UploadedFile::fake()->image('test.jpg', 800, 600)]) ->call('save') ->assertHasErrors(['sku' => 'unique']); }); test('standard product allows all three price types', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); // Fixed Price Volt::test('products.form-standard') ->set('name', 'Fixed-Preis Produkt') ->set('descriptionShort', 'Ein Produkt mit Festpreis.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('sku', 'FIX-001') ->set('sellingPrice', 500.00) ->set('mainImages', [UploadedFile::fake()->image('fix.jpg', 800, 600)]) ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Fixed-Preis Produkt')->first(); expect($product->price_type)->toBe(PriceType::Fixed); // On Request Volt::test('products.form-standard') ->set('name', 'Anfrage-Produkt') ->set('descriptionShort', 'Ein Produkt auf Anfrage.') ->set('categoryId', $category->id) ->set('priceType', 'on_request') ->set('status', 'active') ->set('sku', 'REQ-001') ->set('sellingPrice', 500.00) ->set('mainImages', [UploadedFile::fake()->image('req.jpg', 800, 600)]) ->call('save') ->assertHasNoErrors(); $product2 = Product::where('name', 'Anfrage-Produkt')->first(); expect($product2->price_type)->toBe(PriceType::OnRequest); }); test('standard product requires price display text when from_price', function () { [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Ab-Preis ohne Text') ->set('descriptionShort', 'Beschreibung.') ->set('categoryId', $category->id) ->set('priceType', 'from_price') ->set('priceDisplayText', '') ->set('sku', 'ABP-001') ->set('sellingPrice', 500.00) ->call('save') ->assertHasErrors(['priceDisplayText']); }); test('standard product description short max 180 characters', function () { [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Test') ->set('descriptionShort', str_repeat('x', 181)) ->call('save') ->assertHasErrors(['descriptionShort' => 'max']); }); // --- CSV Extension Tests --- test('standard product saves material fields', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Material-Produkt') ->set('descriptionShort', 'Ein Produkt mit Materialdaten.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('sku', 'MAT-001') ->set('sellingPrice', 500.00) ->set('countryOfOrigin', 'DE') ->set('mainMaterial', 'Massivholz Buche') ->set('surfaceMaterial', 'Furnier Eiche geölt') ->set('coverMaterial', 'Stoff (Polyester)') ->set('colorFinish', 'Eiche natur / Anthrazit') ->set('certificates', ['FSC', 'OEKO-TEX']) ->set('assemblyTimeMin', 45) ->set('loadCapacityKg', 120) ->set('mainImages', [UploadedFile::fake()->image('mat.jpg', 800, 600)]) ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Material-Produkt')->first(); expect($product->country_of_origin)->toBe('DE'); expect($product->main_material)->toBe('Massivholz Buche'); expect($product->surface_material)->toBe('Furnier Eiche geölt'); expect($product->cover_material)->toBe('Stoff (Polyester)'); expect($product->color_finish)->toBe('Eiche natur / Anthrazit'); expect($product->certificates)->toBe(['FSC', 'OEKO-TEX']); expect($product->assembly_time_min)->toBe(45); expect($product->load_capacity_kg)->toBe(120); }); test('standard product saves logistics extension fields', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Logistik-Erweitert') ->set('descriptionShort', 'Produkt mit erweiterten Logistikdaten.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('sku', 'LOG-EXT-001') ->set('sellingPrice', 990.00) ->set('packageCount', 3) ->set('packageWeightG', 80000) ->set('packageWidthCm', 200) ->set('packageHeightCm', 60) ->set('packageDepthCm', 100) ->set('packagingType', 'karton_kantenschutz') ->set('packagingRecyclablePercent', 85) ->set('isPalletizable', true) ->set('hsCode', '94016100') ->set('deliveryType', 'spedition') ->set('mainImages', [UploadedFile::fake()->image('log.jpg', 800, 600)]) ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Logistik-Erweitert')->first(); $variant = $product->variants()->where('is_master_variant', true)->first(); $logistics = $variant->logistics; expect($product->delivery_type)->toBe('spedition'); expect($logistics)->not->toBeNull(); expect($logistics->packaging_type)->toBe('karton_kantenschutz'); expect($logistics->packaging_recyclable_percent)->toBe(85); expect($logistics->is_palletizable)->toBeTrue(); expect($logistics->hs_code)->toBe('94016100'); }); test('standard product saves service and warranty fields', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Service-Produkt') ->set('descriptionShort', 'Produkt mit Service.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('sku', 'SVC-001') ->set('sellingPrice', 1200.00) ->set('assemblyService', true) ->set('serviceRadiusKm', 50) ->set('warrantyMonths', 24) ->set('mainImages', [UploadedFile::fake()->image('svc.jpg', 800, 600)]) ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Service-Produkt')->first(); expect($product->assembly_service)->toBeTrue(); expect($product->service_radius_km)->toBe(50); expect($product->warranty_months)->toBe(24); }); test('standard product saves sustainability fields', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Nachhaltiges Produkt') ->set('descriptionShort', 'Nachhaltiges Möbelstück.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('sku', 'GREEN-001') ->set('sellingPrice', 800.00) ->set('co2FootprintKg', 35.50) ->set('recyclingPercentage', 40) ->set('isRegionalProduction', true) ->set('mainImages', [UploadedFile::fake()->image('green.jpg', 800, 600)]) ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Nachhaltiges Produkt')->first(); expect((float) $product->co2_footprint_kg)->toBe(35.50); expect($product->recycling_percentage)->toBe(40); expect($product->is_regional_production)->toBeTrue(); }); test('standard product saves EUDR wood origins', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'EUDR-Produkt') ->set('descriptionShort', 'Produkt mit Holzherkunft.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('sku', 'EUDR-001') ->set('sellingPrice', 1500.00) ->set('woodOrigins', [ [ 'wood_species' => 'Quercus robur', 'origin_country' => 'PL', 'origin_region' => 'Masowien', 'harvest_year' => 2024, 'forest_operator' => 'ForestPol Sp. z o.o.', 'sustainability_certificate' => 'FSC', 'eudr_reference_id' => 'EUDR-DD-2025-PL-03421', ], [ 'wood_species' => 'Fagus sylvatica', 'origin_country' => 'DE', 'origin_region' => '', 'harvest_year' => 2023, 'forest_operator' => '', 'sustainability_certificate' => 'PEFC', 'eudr_reference_id' => '', ], ]) ->set('mainImages', [UploadedFile::fake()->image('eudr.jpg', 800, 600)]) ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'EUDR-Produkt')->first(); expect($product->woodOrigins)->toHaveCount(2); $first = $product->woodOrigins->first(); expect($first->wood_species)->toBe('Quercus robur'); expect($first->origin_country)->toBe('PL'); expect($first->origin_region)->toBe('Masowien'); expect($first->harvest_year)->toBe(2024); expect($first->sustainability_certificate)->toBe('FSC'); $second = $product->woodOrigins->last(); expect($second->wood_species)->toBe('Fagus sylvatica'); expect($second->origin_country)->toBe('DE'); }); test('standard product saves visibility and scoring fields', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Scoring-Produkt') ->set('descriptionShort', 'Produkt mit Scoring.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('sku', 'SCORE-001') ->set('sellingPrice', 600.00) ->set('visibleIsAvailable', true) ->set('visibleFrom', '2026-03-01') ->set('visibleUntil', '2027-03-01') ->set('storageVolumeLiters', 280) ->set('assemblyEffortScore', 3) ->set('designScore', 5) ->set('mainImages', [UploadedFile::fake()->image('score.jpg', 800, 600)]) ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Scoring-Produkt')->first(); expect($product->visible_from->format('Y-m-d'))->toBe('2026-03-01'); expect($product->visible_until->format('Y-m-d'))->toBe('2027-03-01'); expect($product->storage_volume_liters)->toBe(280); expect($product->assembly_effort_score)->toBe(3); expect($product->design_score)->toBe(5); }); test('standard product saves currency on variant', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'CHF-Produkt') ->set('descriptionShort', 'Produkt in Schweizer Franken.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('sku', 'CHF-001') ->set('sellingPrice', 1100.00) ->set('currency', 'CHF') ->set('mainImages', [UploadedFile::fake()->image('chf.jpg', 800, 600)]) ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'CHF-Produkt')->first(); $variant = $product->variants()->where('is_master_variant', true)->first(); expect($variant->currency)->toBe('CHF'); }); test('standard product country of origin must be 2 characters', function () { [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Test') ->set('descriptionShort', 'Beschreibung.') ->set('categoryId', $category->id) ->set('sku', 'TEST-CO') ->set('sellingPrice', 100.00) ->set('countryOfOrigin', 'DEU') ->call('save') ->assertHasErrors(['countryOfOrigin' => 'size']); }); test('standard product visible_until must be after visible_from', function () { [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Test') ->set('descriptionShort', 'Beschreibung.') ->set('categoryId', $category->id) ->set('sku', 'TEST-VIS') ->set('sellingPrice', 100.00) ->set('visibleFrom', '2027-01-01') ->set('visibleUntil', '2026-01-01') ->call('save') ->assertHasErrors(['visibleUntil']); }); test('standard product assembly_effort_score must be between 1 and 5', function () { [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Test') ->set('descriptionShort', 'Beschreibung.') ->set('categoryId', $category->id) ->set('sku', 'TEST-AES') ->set('sellingPrice', 100.00) ->set('assemblyEffortScore', 6) ->call('save') ->assertHasErrors(['assemblyEffortScore' => 'max']); }); test('standard product recycling_percentage must be between 0 and 100', function () { [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Test') ->set('descriptionShort', 'Beschreibung.') ->set('categoryId', $category->id) ->set('sku', 'TEST-REC') ->set('sellingPrice', 100.00) ->set('recyclingPercentage', 101) ->call('save') ->assertHasErrors(['recyclingPercentage' => 'max']); }); test('wood origin add and remove works', function () { [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $this->actingAs($user); Volt::test('products.form-standard') ->call('addWoodOrigin') ->assertSet('woodOrigins', fn ($value) => count($value) === 1) ->call('addWoodOrigin') ->assertSet('woodOrigins', fn ($value) => count($value) === 2) ->call('removeWoodOrigin', 0) ->assertSet('woodOrigins', fn ($value) => count($value) === 1); }); // --- Product Number Tests --- test('partner product number is auto-generated on mount', function () { [$user, $partner] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $this->actingAs($user); $expected = sprintf('P%03d-%04d', $partner->id, 1); Volt::test('products.form-standard') ->assertSet('partnerProductNumber', $expected); }); test('partner product number can be overwritten', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Custom-Nr Produkt') ->set('descriptionShort', 'Produkt mit eigener Nummer.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('partnerProductNumber', 'MEINE-NR-42') ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Custom-Nr Produkt')->first(); expect($product->partner_product_number)->toBe('MEINE-NR-42'); }); test('b2in article number is auto-generated on save', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'B2in-Nr Produkt') ->set('descriptionShort', 'Produkt mit B2in-Nummer.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'B2in-Nr Produkt')->first(); expect($product->b2in_article_number)->toStartWith('B2IN-'); expect(strlen($product->b2in_article_number))->toBe(11); }); test('b2in article number increments sequentially', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); // Erstes Produkt Volt::test('products.form-standard') ->set('name', 'Sequenz-Produkt 1') ->set('descriptionShort', 'Erstes Sequenz-Produkt.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->call('save') ->assertHasNoErrors(); // Zweites Produkt Volt::test('products.form-standard') ->set('name', 'Sequenz-Produkt 2') ->set('descriptionShort', 'Zweites Sequenz-Produkt.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->call('save') ->assertHasNoErrors(); $first = Product::where('name', 'Sequenz-Produkt 1')->first(); $second = Product::where('name', 'Sequenz-Produkt 2')->first(); expect($first->b2in_article_number)->toBe('B2IN-000001'); expect($second->b2in_article_number)->toBe('B2IN-000002'); }); // --- Brand Autocomplete Tests --- test('selecting existing system brand links product to it', function () { Storage::fake('public'); [$user, $partner] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $systemBrand = Brand::factory()->create(['partner_id' => null, 'name' => 'Hülsta']); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Produkt mit System-Marke') ->set('descriptionShort', 'Bestehende Marke gewählt.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('brandName', 'Hülsta') ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Produkt mit System-Marke')->first(); expect($product->brand_id)->toBe($systemBrand->id); expect(Brand::count())->toBe(1); }); test('typing new brand name creates partner-specific brand', function () { Storage::fake('public'); [$user, $partner] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Produkt mit neuer Marke') ->set('descriptionShort', 'Eigene Marke eingegeben.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('brandName', 'Meine Tischlerei') ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Produkt mit neuer Marke')->first(); $newBrand = Brand::where('name', 'Meine Tischlerei')->first(); expect($newBrand)->not->toBeNull(); expect($newBrand->partner_id)->toBe($partner->id); expect($newBrand->is_active)->toBeTrue(); expect($product->brand_id)->toBe($newBrand->id); }); test('reusing custom brand name does not create duplicate', function () { Storage::fake('public'); [$user, $partner] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $existingBrand = Brand::factory()->create(['partner_id' => $partner->id, 'name' => 'Werkstatt Meier']); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Zweites Produkt gleiche Marke') ->set('descriptionShort', 'Vorhandene Partner-Marke.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('brandName', 'Werkstatt Meier') ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Zweites Produkt gleiche Marke')->first(); expect($product->brand_id)->toBe($existingBrand->id); expect(Brand::where('name', 'Werkstatt Meier')->count())->toBe(1); }); test('product can be created without brand', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Produkt ohne Marke') ->set('descriptionShort', 'Keine Marke angegeben.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->set('brandName', '') ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Produkt ohne Marke')->first(); expect($product->brand_id)->toBeNull(); }); // --- Curation Workflow Tests --- test('submitting product as active sets status to pending', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Freigabe-Produkt') ->set('descriptionShort', 'Produkt zur Freigabe.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'active') ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Freigabe-Produkt')->first(); expect($product->status)->toBe(ProductStatus::Pending); expect($product->is_curated)->toBeFalse(); expect($product->curated_at)->toBeNull(); expect($product->curated_by)->toBeNull(); }); test('saving product as draft keeps status as draft', function () { Storage::fake('public'); [$user] = makePartnerWithHub(); $user->assignRole('Manufacturer'); $category = Category::factory()->create(); $this->actingAs($user); Volt::test('products.form-standard') ->set('name', 'Entwurf-Produkt') ->set('descriptionShort', 'Noch nicht fertig.') ->set('categoryId', $category->id) ->set('priceType', 'fixed') ->set('status', 'draft') ->call('save') ->assertHasNoErrors(); $product = Product::where('name', 'Entwurf-Produkt')->first(); expect($product->status)->toBe(ProductStatus::Draft); expect($product->is_curated)->toBeFalse(); });