refactor(variant): enhance selection values handling for multi-select attributes
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -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' => [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user