b2in/tests/Feature/StandardProductCreateTest.php
2026-02-20 17:57:50 +01:00

1027 lines
35 KiB
PHP

<?php
declare(strict_types=1);
use App\Enums\PriceType;
use App\Enums\ProductStatus;
use App\Enums\ProductType;
use App\Models\Brand;
use App\Models\Category;
use App\Models\Hub;
use App\Models\Partner;
use App\Models\Product;
use App\Models\User;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Livewire\Volt\Volt;
use Spatie\Permission\Models\Role;
beforeEach(function () {
app()[\Spatie\Permission\PermissionRegistrar::class]->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();
});