create(['email' => uniqid('wlr_', true).'@test.example', 'password' => bcrypt('password')]); $user->forceFill(['admin' => $admin, 'confirmed' => true, 'active' => true, 'wizard' => 100, 'blocked' => false])->save(); return $user->fresh(); } function wlrVariant(string $name, string $value): Attribute { $type = AttributeType::query()->firstOrCreate(['id' => 1], ['name' => 'Variante', 'slug' => 'variante']); return Attribute::query()->create(['attribute_type_id' => $type->id, 'name' => $name, 'value' => $value, 'slug' => uniqid('v_'), 'active' => true, 'pos' => 0]); } function wlrProduct(string $name, ?Attribute $variant, array $possibleVariants = []): Product { $product = Product::query()->create(['name' => $name, 'title' => $name, 'active' => true, 'is_set' => false, 'whitelabel' => true, 'whitelabel_variant_id' => $variant?->id]); foreach ($possibleVariants as $attribute) { ProductAttribute::create(['product_id' => $product->id, 'type_id' => 1, 'attribute_id' => $attribute->id]); } return $product; } function wlrStandardLabel(Product $product, array $attributes, ?float $sizeCm = null, string $position = 'deckel', bool $withFile = true): ProductImage { $name = uniqid('std_').'.png'; if ($withFile) { Storage::disk('public')->put('images/product/'.$product->id.'/'.$name, 'png'); } return ProductImage::create(['product_id' => $product->id, 'type' => 'wllogo', 'filename' => $name, 'original_name' => $name, 'ext' => 'png', 'mine' => 'image/png', 'size' => 3, 'pos' => 0, 'active' => true, 'attributes' => $attributes, 'position' => $position, 'size_cm' => $sizeCm]); } function wlrAssignment(User $user, Product $product, array $attributes, string $position = 'deckel', bool $withFile = true): UserWhitelabelProduct { $assignment = UserWhitelabelProduct::firstOrCreate(['user_id' => $user->id, 'product_id' => $product->id], ['active' => true]); $name = uniqid('logo_').'.png'; if ($withFile) { Storage::disk('public')->put('images/user_product/'.$assignment->id.'/'.$name, 'png'); } ProductImage::create(['user_wl_product_id' => $assignment->id, 'type' => 'uwllogo', 'filename' => $name, 'original_name' => $name, 'ext' => 'png', 'mine' => 'image/png', 'size' => 3, 'pos' => 0, 'active' => true, 'attributes' => $attributes, 'position' => $position]); return $assignment->fresh(); } function wlrOrder(User $partner, array $products): ShoppingOrder { $country = Country::query()->firstOrCreate(['code' => 'DE'], ['en' => 'Germany', 'de' => 'Deutschland', 'es' => 'Alemania', 'fr' => 'Allemagne', 'it' => 'Germania', 'ru' => 'Germanija']); $shoppingUser = ShoppingUser::query()->forceCreate(['billing_country_id' => $country->id, 'shipping_country_id' => $country->id, 'billing_firstname' => 'Test', 'billing_lastname' => 'Partner', 'billing_address' => 'Weg 1', 'billing_zipcode' => '11111', 'billing_city' => 'Stadt', 'same_as_billing' => true]); $shipping = Shipping::query()->firstOrCreate(['name' => 'Standard']); $shippingCountry = ShippingCountry::query()->firstOrCreate(['shipping_id' => $shipping->id, 'country_id' => $country->id]); $order = ShoppingOrder::query()->forceCreate(['shopping_user_id' => $shoppingUser->id, 'auth_user_id' => $partner->id, 'country_id' => $shippingCountry->id, 'shipped' => 0, 'txaction' => 'paid', 'total' => 10, 'user_white_label' => false]); foreach ($products as $product) { ShoppingOrderItem::query()->forceCreate(['shopping_order_id' => $order->id, 'product_id' => $product->id, 'qty' => 1, 'price' => 10]); } return $order->fresh(); } beforeEach(function () { Storage::fake('public'); $this->schwarz40 = wlrVariant('Schwarz 40 ml', '40#3,5'); $this->gold40 = wlrVariant('Gold 40 ml', '40#3,5'); $this->schwarz15 = wlrVariant('Schwarz 15 ml', '15#2'); }); test('kundenlogo mit vorhandener datei ergibt kundenlogo und produktfarbe', function () { $partner = wlrUser(); $black = wlrProduct('Creme 40 ml (Tiegel SCHWARZ)', $this->schwarz40); wlrStandardLabel($black, [(string) $this->schwarz40->id], 3.5); wlrAssignment($partner, $black, [(string) $this->schwarz40->id]); $result = (new WhiteLabelResolver)->resolveOrder(wlrOrder($partner, [$black]))->first(); expect($result)->toBeInstanceOf(ResolvedItem::class) ->and($result->isComplete())->toBeTrue() ->and($result->color)->toBe('Schwarz') ->and($result->colorSource)->toBe(ResolvedItem::COLOR_SOURCE_VARIANT) ->and($result->hasCustomerLogo())->toBeTrue() ->and($result->labels['deckel']->sizeCm)->toBe(3.5) ->and($result->labels['deckel']->volumeMl)->toBe(40.0) ->and($result->colorLine())->toBe('Tiegel: Schwarz') ->and($result->labelSourceLine())->toBe('Etikett: Kundenlogo'); }); test('standardetikett auf schwarzem produkt behaelt die produktfarbe und meldet fehlende zuordnung nur als hinweis', function () { $partner = wlrUser(); $black = wlrProduct('Creme 40 ml (Tiegel SCHWARZ)', $this->schwarz40); $other = wlrProduct('Creme 15 ml (Tiegel SCHWARZ)', $this->schwarz15); wlrStandardLabel($black, [(string) $this->schwarz40->id], 3.5); wlrAssignment($partner, $other, [(string) $this->schwarz15->id]); $result = (new WhiteLabelResolver)->resolveOrder(wlrOrder($partner, [$black]))->first(); expect($result->isComplete())->toBeTrue() ->and($result->color)->toBe('Schwarz') ->and($result->hasCustomerLogo())->toBeFalse() ->and($result->labelSourceLine())->toBe('Etikett: Standardetikett') ->and($result->warnings)->toHaveCount(1) ->and($result->warnings[0])->toContain('keine Kundenzuordnung'); }); test('kundenlogo ohne standardetikett ist zulaessig wenn die groesse aus der variante kommt', function () { $partner = wlrUser(); $black = wlrProduct('Creme 40 ml (Tiegel SCHWARZ)', $this->schwarz40); wlrAssignment($partner, $black, []); $result = (new WhiteLabelResolver)->resolveOrder(wlrOrder($partner, [$black]))->first(); expect($result->isComplete())->toBeTrue() ->and($result->hasCustomerLogo())->toBeTrue() ->and($result->labels['deckel']->sizeCm)->toBe(3.5); }); test('fehlende logodatei ist ein fehler ohne rueckfall auf das standardetikett', function () { $partner = wlrUser(); $black = wlrProduct('Creme 40 ml (Tiegel SCHWARZ)', $this->schwarz40); wlrStandardLabel($black, [(string) $this->schwarz40->id], 3.5); wlrAssignment($partner, $black, [(string) $this->schwarz40->id], 'deckel', false); $result = (new WhiteLabelResolver)->resolveOrder(wlrOrder($partner, [$black]))->first(); expect($result->isComplete())->toBeFalse() ->and($result->labels)->toBe([]) ->and($result->errors[0])->toContain('Datei fehlt'); }); test('farbwiderspruch zwischen kundenbild und produkt ist ein fehler', function () { $partner = wlrUser(); $gold = wlrProduct('Creme 40 ml (Tiegel GOLD)', $this->gold40); wlrStandardLabel($gold, [(string) $this->gold40->id], 3.5); wlrAssignment($partner, $gold, [(string) $this->schwarz40->id]); $result = (new WhiteLabelResolver)->resolveOrder(wlrOrder($partner, [$gold]))->first(); expect($result->isComplete())->toBeFalse() ->and($result->errors)->toHaveCount(1) ->and($result->errors[0])->toContain('für Schwarz hinterlegt, Produktfarbe ist Gold'); }); test('mehrere kundenlogos auf derselben position sind ein konflikt ohne auswahl nach reihenfolge', function () { $partner = wlrUser(); $black = wlrProduct('Creme 40 ml (Tiegel SCHWARZ)', $this->schwarz40); wlrStandardLabel($black, [(string) $this->schwarz40->id], 3.5); wlrAssignment($partner, $black, [(string) $this->schwarz40->id]); wlrAssignment($partner, $black, [(string) $this->schwarz40->id]); $result = (new WhiteLabelResolver)->resolveOrder(wlrOrder($partner, [$black]))->first(); expect($result->isComplete())->toBeFalse() ->and($result->errors[0])->toContain('mehrere Bilder für Position "deckel"'); }); test('positionen bleiben getrennt: kundenlogo am deckel ersetzt nicht das bodenetikett', function () { $partner = wlrUser(); $black = wlrProduct('Creme 40 ml (Tiegel SCHWARZ)', $this->schwarz40); wlrStandardLabel($black, [(string) $this->schwarz40->id], 3.5, 'deckel'); $boden = wlrStandardLabel($black, [], 4.7, 'boden'); wlrAssignment($partner, $black, [(string) $this->schwarz40->id], 'deckel'); $result = (new WhiteLabelResolver)->resolveOrder(wlrOrder($partner, [$black]))->first(); expect($result->isComplete())->toBeTrue() ->and($result->labels)->toHaveCount(2) ->and($result->labels['deckel']->isCustomer())->toBeTrue() ->and($result->labels['boden']->isCustomer())->toBeFalse() ->and($result->labels['boden']->image->id)->toBe($boden->id) ->and($result->labels['boden']->sizeCm)->toBe(4.7) ->and($result->maxLabelSize())->toBe(4.7); }); test('altlogik ohne verbindliche variante: kundenlogo greift nur bei attributueberschneidung', function () { $partner = wlrUser(); $legacy = wlrProduct('Tattoobalm 30 ml', null, [$this->schwarz40, $this->gold40]); wlrStandardLabel($legacy, [(string) $this->schwarz40->id, (string) $this->gold40->id]); $noOverlap = wlrAssignment($partner, $legacy, [(string) $this->schwarz15->id]); $result = (new WhiteLabelResolver)->resolveOrder(wlrOrder($partner, [$legacy]))->first(); expect($result->isComplete())->toBeTrue() ->and($result->hasCustomerLogo())->toBeFalse() ->and($result->color)->toBe('Standard') ->and($result->colorSource)->toBe(ResolvedItem::COLOR_SOURCE_LEGACY) ->and($result->labels['deckel']->sizeCm)->toBe(3.5) ->and(collect($result->warnings)->first(fn ($w) => str_contains($w, 'greift nicht')))->not->toBeNull(); $noOverlap->whitelabel_images()->update(['attributes' => json_encode([(string) $this->schwarz40->id])]); $result = (new WhiteLabelResolver)->resolveOrder(wlrOrder($partner, [$legacy]))->first(); expect($result->hasCustomerLogo())->toBeTrue() ->and($result->color)->toBe('Schwarz') ->and($result->volumeMl)->toBe(40.0); }); test('resolver schreibt weder bestellung noch kennzeichen', function () { $partner = wlrUser(); $black = wlrProduct('Creme 40 ml (Tiegel SCHWARZ)', $this->schwarz40); wlrStandardLabel($black, [(string) $this->schwarz40->id], 3.5); wlrAssignment($partner, $black, [(string) $this->schwarz40->id]); $order = wlrOrder($partner, [$black]); (new WhiteLabelResolver)->resolveOrder($order); expect($order->fresh()->user_white_label)->toBeFalse(); }); test('nicht-white-label-positionen werden nicht aufgeloest', function () { $partner = wlrUser(); $plain = Product::query()->create(['name' => 'Seife', 'title' => 'Seife', 'active' => true, 'is_set' => false, 'whitelabel' => false]); expect((new WhiteLabelResolver)->resolveOrder(wlrOrder($partner, [$plain])))->toHaveCount(0); }); test('produktformular speichert verbindliche variante sowie position und groesse je etikett', function () { $black = wlrProduct('Creme 40 ml (Tiegel SCHWARZ)', null); $label = wlrStandardLabel($black, [(string) $this->schwarz40->id]); (new ProductRepository($black))->update([ 'id' => (string) $black->id, 'name' => $black->name, 'title' => $black->title, 'active' => '1', 'whitelabel' => '1', 'whitelabel_variant_id' => (string) $this->schwarz40->id, 'whitelabel_variants' => [(string) $this->schwarz40->id], 'image_wl_attributes' => [$label->id => [(string) $this->schwarz40->id]], 'image_wl_position' => [$label->id => 'boden'], 'image_wl_size' => [$label->id => '4,7'], ]); $black->refresh(); $label->refresh(); expect($black->whitelabel_variant_id)->toBe($this->schwarz40->id) ->and($black->whitelabel_variant->colorName())->toBe('Schwarz') ->and($label->position)->toBe('boden') ->and($label->size_cm)->toBe(4.7); $this->actingAs(wlrUser(9), 'user'); $this->get(route('admin_product_edit', $black->id)) ->assertOk() ->assertSee('Tiegelfarbe / Größe (verbindlich)') ->assertSee('Etikettposition'); }); test('attribut-farbname trennt die groesse ab', function () { expect($this->schwarz40->colorName())->toBe('Schwarz') ->and(wlrVariant('Braunglasflasche 30 ml', '30#5')->colorName())->toBe('Braunglasflasche') ->and(wlrVariant('Bodenetikett', '40#3,5')->colorName())->toBe('Bodenetikett') ->and($this->schwarz40->variantSize())->toBe(3.5) ->and($this->schwarz40->variantVolume())->toBe(40.0); });