- Etikettgröße im Lieferschein korrekt parsen ("40#3,5" ergab 3 statt 3,5 cm)
- Produktkopie optional inkl. Kundenzuordnungen und Logo-Dateien (zweiter Kopier-Link)
- whitelabel:transfer-assignments: gezielte, wiederholbare Übertragung ohne Dubletten
- whitelabel:migrate-colors: Zuordnungen anhand der Bildfarbe auf die Farbprodukte übertragen (Dry-Run ohne --execute)
- whitelabel:report: rein lesende Bestandsprüfung (fehlende Zuordnungen, Farbwidersprüche, Dateien, Dubletten)
- Migration für attributes.value (Schema-Drift, auf Live No-op)
- Analyse/Entwicklungsplan um Gegenprüfung, systemischen Befund und neue Paketreihenfolge ergänzt
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
183 lines
7.3 KiB
PHP
183 lines
7.3 KiB
PHP
<?php
|
|
|
|
use App\Models\Attribute;
|
|
use App\Models\AttributeType;
|
|
use App\Models\Product;
|
|
use App\Models\ProductImage;
|
|
use App\Models\UserWhitelabelProduct;
|
|
use App\Repositories\InvoiceRepository;
|
|
use App\Repositories\ProductRepository;
|
|
use App\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Tests\TestCase;
|
|
|
|
uses(TestCase::class, RefreshDatabase::class);
|
|
|
|
function wlMakeUser(): User
|
|
{
|
|
return User::query()->create([
|
|
'email' => uniqid('wl_', true).'@test.example',
|
|
'password' => bcrypt('password'),
|
|
]);
|
|
}
|
|
|
|
function wlMakeProduct(string $name): Product
|
|
{
|
|
return Product::query()->create([
|
|
'name' => $name,
|
|
'title' => $name,
|
|
'active' => true,
|
|
'is_set' => false,
|
|
'whitelabel' => true,
|
|
]);
|
|
}
|
|
|
|
function wlMakeVariant(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,
|
|
]);
|
|
}
|
|
|
|
/** Kundenzuordnung mit einem Logo-Bild und Datei auf der Public-Disk anlegen. */
|
|
function wlMakeAssignment(User $user, Product $product, array $attributes): UserWhitelabelProduct
|
|
{
|
|
$assignment = UserWhitelabelProduct::create([
|
|
'user_id' => $user->id,
|
|
'product_id' => $product->id,
|
|
'active' => true,
|
|
]);
|
|
Storage::disk('public')->put('images/user_product/'.$assignment->id.'/logo.png', 'png-bytes');
|
|
ProductImage::create([
|
|
'user_wl_product_id' => $assignment->id,
|
|
'type' => 'uwllogo',
|
|
'filename' => 'logo.png',
|
|
'original_name' => 'logo.png',
|
|
'ext' => 'png',
|
|
'mine' => 'image/png',
|
|
'size' => 9,
|
|
'pos' => 0,
|
|
'active' => true,
|
|
'attributes' => $attributes,
|
|
]);
|
|
|
|
return $assignment->fresh();
|
|
}
|
|
|
|
test('kundenzuordnungen werden samt logo-datei auf das zielprodukt uebertragen und nicht doppelt angelegt', function () {
|
|
Storage::fake('public');
|
|
$user = wlMakeUser();
|
|
$source = wlMakeProduct('Creme 40 ml (Tiegel GOLD)');
|
|
$target = wlMakeProduct('Creme 40 ml (Tiegel SCHWARZ)');
|
|
wlMakeAssignment($user, $source, ['6']);
|
|
|
|
$repo = new ProductRepository($target);
|
|
$first = $repo->copyWhitelabelAssignments($source, $target);
|
|
$second = $repo->copyWhitelabelAssignments($source, $target);
|
|
|
|
expect($first['copied'])->toBe(1)->and($first['skipped'])->toBe(0)
|
|
->and($second['copied'])->toBe(0)->and($second['skipped'])->toBe(1);
|
|
|
|
$copies = UserWhitelabelProduct::query()->where('user_id', $user->id)->where('product_id', $target->id)->get();
|
|
expect($copies)->toHaveCount(1);
|
|
|
|
$image = $copies->first()->whitelabel_images->first();
|
|
expect($image)->not->toBeNull()
|
|
->and((array) $image->attributes)->toBe(['6'])
|
|
->and(Storage::disk('public')->exists('images/user_product/'.$copies->first()->id.'/'.$image->filename))->toBeTrue();
|
|
});
|
|
|
|
test('uebertragung kann auf einen partner eingeschraenkt werden', function () {
|
|
Storage::fake('public');
|
|
$a = wlMakeUser();
|
|
$b = wlMakeUser();
|
|
$source = wlMakeProduct('Creme (Tiegel GOLD)');
|
|
$target = wlMakeProduct('Creme (Tiegel WEISS)');
|
|
wlMakeAssignment($a, $source, ['5']);
|
|
wlMakeAssignment($b, $source, ['5']);
|
|
|
|
$result = (new ProductRepository($target))->copyWhitelabelAssignments($source, $target, $a->id);
|
|
|
|
expect($result['copied'])->toBe(1)
|
|
->and(UserWhitelabelProduct::query()->where('product_id', $target->id)->pluck('user_id')->all())->toBe([$a->id]);
|
|
});
|
|
|
|
test('produktkopie uebernimmt kundenzuordnungen nur auf ausdrueckliche anforderung', function () {
|
|
Storage::fake('public');
|
|
$user = wlMakeUser();
|
|
$source = wlMakeProduct('Creme (Tiegel GOLD)');
|
|
wlMakeAssignment($user, $source, ['6']);
|
|
|
|
$plain = (new ProductRepository(new Product))->copy($source);
|
|
$withAssignments = (new ProductRepository(new Product))->copy($source, true);
|
|
|
|
expect(UserWhitelabelProduct::query()->where('product_id', $plain->id)->count())->toBe(0)
|
|
->and(UserWhitelabelProduct::query()->where('product_id', $withAssignments->id)->count())->toBe(1);
|
|
});
|
|
|
|
test('artisan-befehl uebertraegt zuordnungen und meldet dry-run ohne aenderung', function () {
|
|
Storage::fake('public');
|
|
$user = wlMakeUser();
|
|
$source = wlMakeProduct('Creme (Tiegel GOLD)');
|
|
$target = wlMakeProduct('Creme (Tiegel SCHWARZ)');
|
|
wlMakeAssignment($user, $source, ['6']);
|
|
|
|
$this->artisan('whitelabel:transfer-assignments', ['source' => $source->id, 'target' => $target->id, '--dry-run' => true])
|
|
->expectsOutputToContain('Dry-run')
|
|
->assertSuccessful();
|
|
expect(UserWhitelabelProduct::query()->where('product_id', $target->id)->count())->toBe(0);
|
|
|
|
$this->artisan('whitelabel:transfer-assignments', ['source' => $source->id, 'target' => $target->id])
|
|
->expectsOutputToContain('Übernommen: 1')
|
|
->assertSuccessful();
|
|
expect(UserWhitelabelProduct::query()->where('product_id', $target->id)->count())->toBe(1);
|
|
});
|
|
|
|
test('farbmigration ordnet anhand der bildfarbe dem gleichfarbigen produkt zu', function () {
|
|
Storage::fake('public');
|
|
$schwarz = wlMakeVariant('Schwarz 40 ml', '40#3,5');
|
|
wlMakeVariant('Gold 40 ml', '40#3,5');
|
|
$user = wlMakeUser();
|
|
$gold = wlMakeProduct('Bio Creme 40 ml (Tiegel GOLD)');
|
|
$black = wlMakeProduct('Bio Creme 40 ml (Tiegel SCHWARZ)');
|
|
wlMakeProduct('Bio Creme 40 ml (Tiegel WEISS)');
|
|
wlMakeAssignment($user, $gold, [(string) $schwarz->id]);
|
|
|
|
$this->artisan('whitelabel:migrate-colors')->expectsOutputToContain('Dry-run')->assertSuccessful();
|
|
expect(UserWhitelabelProduct::query()->where('product_id', $black->id)->count())->toBe(0);
|
|
|
|
$this->artisan('whitelabel:migrate-colors', ['--execute' => true])->expectsOutputToContain('Übernommen: 1')->assertSuccessful();
|
|
expect(UserWhitelabelProduct::query()->where('product_id', $black->id)->where('user_id', $user->id)->count())->toBe(1)
|
|
->and(UserWhitelabelProduct::query()->where('product_id', $gold->id)->count())->toBe(1);
|
|
|
|
// wiederholt: keine Dublette
|
|
$this->artisan('whitelabel:migrate-colors', ['--execute' => true])->expectsOutputToContain('Übernommen: 0')->assertSuccessful();
|
|
expect(UserWhitelabelProduct::query()->where('product_id', $black->id)->count())->toBe(1);
|
|
});
|
|
|
|
test('bestandspruefung laeuft rein lesend durch', function () {
|
|
Storage::fake('public');
|
|
$user = wlMakeUser();
|
|
$gold = wlMakeProduct('Bio Creme 40 ml (Tiegel GOLD)');
|
|
$schwarz = wlMakeVariant('Schwarz 40 ml', '40#3,5');
|
|
wlMakeAssignment($user, $gold, [(string) $schwarz->id]);
|
|
$before = UserWhitelabelProduct::query()->count() + ProductImage::query()->count();
|
|
|
|
$this->artisan('whitelabel:report')->expectsOutputToContain('Farbwiderspruch')->assertSuccessful();
|
|
|
|
expect(UserWhitelabelProduct::query()->count() + ProductImage::query()->count())->toBe($before);
|
|
});
|
|
|
|
test('etikettgroesse mit komma wird korrekt geparst', function () {
|
|
expect(InvoiceRepository::parseVariantValue('40#3,5'))->toBe([40.0, 3.5])
|
|
->and(InvoiceRepository::parseVariantValue('15#2'))->toBe([15.0, 2.0])
|
|
->and(InvoiceRepository::parseVariantValue('30#5'))->toBe([30.0, 5.0]);
|
|
});
|