White Label: Sofortpaket WL-00 und Bestandsprüfung WL-01

- 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>
This commit is contained in:
Kevin Adametz 2026-09-17 11:05:42 +00:00
parent f7ab4c4519
commit d687a6c569
11 changed files with 1012 additions and 13 deletions

View file

@ -11,6 +11,7 @@ use App\Models\ProductCategory;
use App\Models\ProductImage;
use App\Models\ProductIngredient;
use App\Models\RecipePhase;
use App\Models\UserWhitelabelProduct;
use App\Services\Slim;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
@ -617,7 +618,7 @@ class ProductRepository extends BaseRepository
return true;
}
public function copy($model)
public function copy($model, bool $withWhitelabelAssignments = false)
{
$this->model = $model->replicate();
$this->model->name = 'Kopie: '.$this->model->name;
@ -717,9 +718,80 @@ class ProductRepository extends BaseRepository
$this->copyImages($model->images, 'product');
$this->copyImages($model->whitelabel_images, 'wllogo');
// Kundenzuordnungen (Partner-Logos) nur auf ausdrückliche Anforderung mitkopieren.
// Die stille Kopie ohne Zuordnungen war die Ursache für fehlende Logos auf Lieferscheinen (WL-00).
if ($withWhitelabelAssignments) {
$this->copyWhitelabelAssignments($model, $this->model);
}
return $this->model;
}
/**
* Kundenzuordnungen (user_whitelabel_products) samt Logo-Dateien von einem Produkt auf ein anderes übertragen.
* Vorhandene Kombinationen (user_id, product_id) am Zielprodukt werden übersprungen; wiederholbar ohne Dubletten.
*
* @return array{copied: int, skipped: int, users: array<int, int>}
*/
public function copyWhitelabelAssignments(Product $source, Product $target, ?int $userId = null): array
{
$result = ['copied' => 0, 'skipped' => 0, 'users' => []];
$query = UserWhitelabelProduct::query()->where('product_id', $source->id);
if ($userId !== null) {
$query->where('user_id', $userId);
}
foreach ($query->orderBy('id')->get() as $assignment) {
$exists = UserWhitelabelProduct::query()
->where('product_id', $target->id)
->where('user_id', $assignment->user_id)
->exists();
if ($exists) {
$result['skipped']++;
continue;
}
$copy = UserWhitelabelProduct::create([
'user_id' => $assignment->user_id,
'product_id' => $target->id,
'info' => $assignment->info,
'attributes' => $assignment->attributes,
'options' => $assignment->options,
'active' => $assignment->active,
]);
foreach ($assignment->whitelabel_images as $image) {
$name = uniqid().'_'.Slim::sanitizeFileName($image->original_name);
$sourcePath = 'images/user_product/'.$assignment->id.'/'.$image->filename;
$targetPath = 'images/user_product/'.$copy->id.'/'.$name;
if (\Storage::disk('public')->exists($sourcePath)) {
\Storage::disk('public')->copy($sourcePath, $targetPath);
}
ProductImage::create([
'user_wl_product_id' => $copy->id,
'type' => 'uwllogo',
'filename' => $name,
'original_name' => $image->original_name,
'ext' => $image->ext,
'mine' => $image->mine,
'size' => $image->size,
'pos' => $image->pos,
'active' => $image->active ?? true,
'attributes' => $image->attributes,
]);
}
$result['copied']++;
$result['users'][] = $assignment->user_id;
}
return $result;
}
/**
* @param Collection<int, ProductImage> $images
*/