feat(desfile): refactor entry management to use row-based configuration and expand seat options
This commit is contained in:
@@ -26,6 +26,12 @@ class EntryService
|
||||
'seat' => 'asiento',
|
||||
];
|
||||
|
||||
private const ROW_ATTRIBUTE_MAP = [
|
||||
'type' => 'tipo',
|
||||
'sector' => 'sector',
|
||||
'row' => 'fila',
|
||||
];
|
||||
|
||||
public function __construct(private readonly AttachmentService $attachmentService) {}
|
||||
|
||||
public function current(Tenant $tenant): CatalogItem
|
||||
@@ -42,68 +48,70 @@ class EntryService
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $variants
|
||||
* @param array<int, array<string, mixed>> $rows
|
||||
*/
|
||||
public function syncVariants(Tenant $tenant, array $variants): CatalogItem
|
||||
public function syncRows(Tenant $tenant, array $rows): CatalogItem
|
||||
{
|
||||
DB::transaction(function () use ($tenant, $variants): void {
|
||||
DB::transaction(function () use ($tenant, $rows): void {
|
||||
$entry = $this->entryQuery($tenant)->lockForUpdate()->firstOrFail();
|
||||
$itemAttributes = $this->itemAttributes($entry);
|
||||
$existingVariants = $entry->variants()
|
||||
->with(['inventory', 'definitions.itemAttribute.attribute'])
|
||||
->lockForUpdate()
|
||||
->get();
|
||||
$incomingIds = collect($variants)
|
||||
->pluck('id')
|
||||
->filter()
|
||||
->map(fn ($id): int => (int) $id)
|
||||
->values();
|
||||
$existingBySelection = $existingVariants->keyBy(
|
||||
fn (Variant $variant): string => $this->selectionKey($variant->selectionValues()->all()),
|
||||
);
|
||||
$desiredSelections = collect();
|
||||
|
||||
foreach ($existingVariants->whereNotIn('id', $incomingIds) as $variant) {
|
||||
$this->assertVariantCanChangeIdentity($variant, 'variants');
|
||||
$variant->delete();
|
||||
foreach (array_values($rows) as $index => $data) {
|
||||
$rowValues = $this->resolveRowValues($itemAttributes, $data, $index);
|
||||
|
||||
foreach (range(1, (int) $data['max_seat']) as $seat) {
|
||||
$values = [
|
||||
...$rowValues,
|
||||
'asiento' => $this->resolveAttributeValue(
|
||||
$itemAttributes,
|
||||
'asiento',
|
||||
(string) $seat,
|
||||
"rows.{$index}.max_seat",
|
||||
),
|
||||
];
|
||||
$selectionKey = $this->selectionKey($values);
|
||||
$desiredSelections->put($selectionKey, true);
|
||||
$variant = $existingBySelection->get($selectionKey);
|
||||
|
||||
if ($variant === null) {
|
||||
$variant = $entry->variants()->create([
|
||||
'inventory_id' => Inventory::query()->create(['real_stock' => 1])->id,
|
||||
'descripcion' => $this->description($values),
|
||||
'precio' => $data['price'],
|
||||
]);
|
||||
$variant->definitions()->createMany(
|
||||
collect($values)->map(
|
||||
fn (string $value, string $code): array => [
|
||||
'item_attribute_id' => $itemAttributes[$code]->id,
|
||||
'value' => $value,
|
||||
],
|
||||
)->values()->all(),
|
||||
);
|
||||
} else {
|
||||
$variant->update([
|
||||
'descripcion' => $this->description($values),
|
||||
'precio' => $data['price'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_values($variants) as $index => $data) {
|
||||
$values = $this->resolveValues($itemAttributes, $data, $index);
|
||||
$variant = isset($data['id'])
|
||||
? $existingVariants->firstWhere('id', (int) $data['id'])
|
||||
: null;
|
||||
|
||||
if (isset($data['id']) && $variant === null) {
|
||||
throw ValidationException::withMessages([
|
||||
"variants.{$index}.id" => [
|
||||
'La variante no pertenece al producto Entrada del desfile.',
|
||||
],
|
||||
]);
|
||||
foreach ($existingVariants as $variant) {
|
||||
$selectionKey = $this->selectionKey($variant->selectionValues()->all());
|
||||
if ($desiredSelections->has($selectionKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($variant === null) {
|
||||
$variant = $entry->variants()->create([
|
||||
'inventory_id' => Inventory::query()->create(['real_stock' => 1])->id,
|
||||
'descripcion' => $this->description($values),
|
||||
'precio' => $data['price'],
|
||||
]);
|
||||
} else {
|
||||
if ($this->identityChanged($variant, $values)) {
|
||||
$this->assertVariantCanChangeIdentity($variant, "variants.{$index}");
|
||||
}
|
||||
|
||||
$variant->update([
|
||||
'descripcion' => $this->description($values),
|
||||
'precio' => $data['price'],
|
||||
]);
|
||||
$variant->definitions()->delete();
|
||||
}
|
||||
|
||||
$variant->definitions()->createMany(
|
||||
collect($values)->map(
|
||||
fn (string $value, string $code): array => [
|
||||
'item_attribute_id' => $itemAttributes[$code]->id,
|
||||
'value' => $value,
|
||||
],
|
||||
)->values()->all(),
|
||||
);
|
||||
$this->assertVariantCanChangeIdentity($variant, 'rows');
|
||||
$variant->delete();
|
||||
}
|
||||
|
||||
$minimumPrice = $entry->variants()->min('precio');
|
||||
@@ -183,7 +191,7 @@ class EntryService
|
||||
|
||||
if ($missing->isNotEmpty()) {
|
||||
throw ValidationException::withMessages([
|
||||
'variants' => [
|
||||
'rows' => [
|
||||
'Faltan atributos requeridos para las entradas del desfile: '.$missing->implode(', ').'.',
|
||||
],
|
||||
]);
|
||||
@@ -197,40 +205,49 @@ class EntryService
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function resolveValues(Collection $itemAttributes, array $data, int $index): array
|
||||
private function resolveRowValues(Collection $itemAttributes, array $data, int $index): array
|
||||
{
|
||||
$values = [];
|
||||
|
||||
foreach (self::ATTRIBUTE_MAP as $input => $code) {
|
||||
$requestedValue = trim((string) $data[$input]);
|
||||
$option = $itemAttributes[$code]->attribute->options->first(
|
||||
fn ($candidate): bool => $this->normalize($candidate->value) === $this->normalize($requestedValue),
|
||||
foreach (self::ROW_ATTRIBUTE_MAP as $input => $code) {
|
||||
$values[$code] = $this->resolveAttributeValue(
|
||||
$itemAttributes,
|
||||
$code,
|
||||
(string) $data[$input],
|
||||
"rows.{$index}.{$input}",
|
||||
);
|
||||
|
||||
if ($option === null) {
|
||||
throw ValidationException::withMessages([
|
||||
"variants.{$index}.{$input}" => ['La opción seleccionada no es válida.'],
|
||||
]);
|
||||
}
|
||||
|
||||
$values[$code] = $option->value;
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/** @param array<string, string> $values */
|
||||
private function identityChanged(Variant $variant, array $values): bool
|
||||
{
|
||||
$currentValues = $variant->definitions
|
||||
->mapWithKeys(fn ($definition): array => [
|
||||
$definition->itemAttribute?->attribute?->codigo => $definition->value,
|
||||
]);
|
||||
|
||||
return collect($values)->contains(
|
||||
fn (string $value, string $code): bool => $this->normalize((string) $currentValues->get($code))
|
||||
!== $this->normalize($value),
|
||||
/** @param Collection<string, ItemAttribute> $itemAttributes */
|
||||
private function resolveAttributeValue(
|
||||
Collection $itemAttributes,
|
||||
string $code,
|
||||
string $requestedValue,
|
||||
string $errorKey,
|
||||
): string {
|
||||
$requestedValue = trim($requestedValue);
|
||||
$option = $itemAttributes[$code]->attribute->options->first(
|
||||
fn ($candidate): bool => $this->normalize($candidate->value) === $this->normalize($requestedValue),
|
||||
);
|
||||
|
||||
if ($option === null) {
|
||||
throw ValidationException::withMessages([
|
||||
$errorKey => ['La opción seleccionada no es válida.'],
|
||||
]);
|
||||
}
|
||||
|
||||
return $option->value;
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $values */
|
||||
private function selectionKey(array $values): string
|
||||
{
|
||||
return collect(self::ATTRIBUTE_MAP)
|
||||
->map(fn (string $code): string => $this->normalize((string) ($values[$code] ?? '')))
|
||||
->implode('|');
|
||||
}
|
||||
|
||||
private function assertVariantCanChangeIdentity(Variant $variant, string $key): void
|
||||
|
||||
Reference in New Issue
Block a user