refactor(variant): enhance selection values handling for multi-select attributes

This commit is contained in:
2026-08-07 12:07:07 -03:00
parent 21b70777f2
commit 6318a99328
6 changed files with 153 additions and 19 deletions

View File

@@ -109,10 +109,23 @@ class Variant extends Model
public function selectionValues(): Collection
{
$values = $this->definitions
->mapWithKeys(fn ($definition) => [
$definition->itemAttribute?->attribute?->codigo => $definition->value,
])
->filter(fn ($value, $key): bool => $key !== null);
->groupBy('item_attribute_id')
->mapWithKeys(function (Collection $definitions): array {
$itemAttribute = $definitions->first()?->itemAttribute;
$attributeCode = $itemAttribute?->attribute?->codigo;
if ($attributeCode === null) {
return [];
}
$definitionValues = $definitions->pluck('value')->values();
return [
$attributeCode => $itemAttribute->allow_multi_select
? $definitionValues->all()
: $definitionValues->first(),
];
});
$eventDateIds = $this->selectedEventDates()
->pluck('id')

View File

@@ -109,7 +109,8 @@ class StoreCatalogItemRequest extends FormRequest
'variants.*.reserved_stock' => ['prohibited'],
'variants.*.sold_units' => ['prohibited'],
'variants.*.values' => ['sometimes', 'array'],
'variants.*.values.*' => ['nullable', 'string'],
'variants.*.values.*' => ['nullable'],
'variants.*.values.*.*' => ['required', 'string'],
'variants.*.images' => ['sometimes', 'array'],
'variants.*.images.*' => ['required', new ImageOrBase64Rule],
'components' => [

View File

@@ -569,10 +569,16 @@ class CatalogService
]);
}
$variant->definitions()->create([
'item_attribute_id' => $itemAttribute->id,
'value' => $value,
]);
foreach ($this->validatedVariantValues(
$itemAttribute,
$value,
"variants.{$index}.values.{$attributeCode}",
) as $validatedValue) {
$variant->definitions()->create([
'item_attribute_id' => $itemAttribute->id,
'value' => $validatedValue,
]);
}
}
return $variant;
@@ -618,8 +624,14 @@ class CatalogService
$combination = [$eventDateIds];
foreach ($attributeCodes as $attributeCode) {
$value = trim((string) ($variant['values'][$attributeCode] ?? ''));
$combination[] = Str::ascii(mb_strtolower($value));
$values = $variant['values'][$attributeCode] ?? '';
$normalizedValues = collect(is_array($values) ? $values : [$values])
->map(fn ($value): string => $this->normalizeVariantValue((string) $value))
->unique()
->sort()
->values()
->implode(',');
$combination[] = $normalizedValues;
}
$key = implode('|', $combination);
@@ -633,6 +645,72 @@ class CatalogService
}
}
/** @return array<int, string> */
private function validatedVariantValues(
ItemAttribute $itemAttribute,
mixed $value,
string $validationKey,
): array {
$values = is_array($value) ? array_values($value) : [$value];
if ($values === [] || (! $itemAttribute->allow_multi_select && count($values) !== 1)) {
throw ValidationException::withMessages([
$validationKey => [
$itemAttribute->allow_multi_select
? 'At least one value must be selected.'
: 'Exactly one value must be selected.',
],
]);
}
if (collect($values)->contains(fn ($item): bool => ! is_string($item) || trim($item) === '')) {
throw ValidationException::withMessages([
$validationKey => ['Every selected value must be a non-empty string.'],
]);
}
$normalizedValues = collect($values)
->map(fn (string $item): string => $this->normalizeVariantValue($item));
if ($normalizedValues->unique()->count() !== count($values)) {
throw ValidationException::withMessages([
$validationKey => ['Selected values must be distinct.'],
]);
}
$attribute = $itemAttribute->attribute;
if ($attribute->type->supportsOptions() && ! $attribute->type->usesDynamicOptions()) {
$optionsByNormalizedValue = $attribute->options
->keyBy(fn ($option): string => $this->normalizeVariantValue($option->value));
$resolvedOptions = $normalizedValues->map(fn (string $normalizedValue) => $optionsByNormalizedValue->get($normalizedValue));
if ($resolvedOptions->contains(null)) {
throw ValidationException::withMessages([
$validationKey => ['One or more selected values are not valid attribute options.'],
]);
}
$validityTimeIds = $resolvedOptions
->pluck('validity_time_id')
->filter()
->unique();
if ($validityTimeIds->count() > 1) {
throw ValidationException::withMessages([
$validationKey => ['Selected values cannot have different validity windows.'],
]);
}
return $resolvedOptions->pluck('value')->all();
}
return collect($values)->map(fn (string $item): string => trim($item))->all();
}
private function normalizeVariantValue(string $value): string
{
return Str::ascii(mb_strtolower(trim($value)));
}
/**
* @param array<string, mixed> $data
* @param array<int, array<string, mixed>> $variants

View File

@@ -99,10 +99,18 @@ class PurchaseItemResource extends JsonResource
}
$attributes = $variant->definitions
->map(fn ($definition): array => [
'name' => (string) ($definition->itemAttribute?->attribute?->nombre ?? ''),
'value' => $definition->value,
])
->groupBy('item_attribute_id')
->map(function ($definitions): array {
$itemAttribute = $definitions->first()?->itemAttribute;
$values = $definitions->pluck('value')->values();
return [
'name' => (string) ($itemAttribute?->attribute?->nombre ?? ''),
'value' => $itemAttribute?->allow_multi_select
? $values->all()
: $values->first(),
];
})
->filter(fn (array $attribute): bool => $attribute['name'] !== '' || $attribute['value'] !== null)
->values();

View File

@@ -54,10 +54,18 @@ class PurchaseItemSnapshotFactory
private function snapshotAttributes(Variant $variant): array
{
$attributes = $variant->definitions
->map(fn ($definition): array => [
'name' => (string) ($definition->itemAttribute?->attribute?->nombre ?? ''),
'value' => $definition->value,
])
->groupBy('item_attribute_id')
->map(function ($definitions): array {
$itemAttribute = $definitions->first()?->itemAttribute;
$values = $definitions->pluck('value')->values();
return [
'name' => (string) ($itemAttribute?->attribute?->nombre ?? ''),
'value' => $itemAttribute?->allow_multi_select
? $values->all()
: $values->first(),
];
})
->filter(fn (array $attribute): bool => $attribute['name'] !== '' || $attribute['value'] !== null)
->values();