From 717f02ce16d0cefb54754350264413ad605a8d3d Mon Sep 17 00:00:00 2001 From: ncoronel Date: Fri, 25 Sep 2026 10:15:29 -0300 Subject: [PATCH] feat(admin-stock): apply idempotent availability deltas --- .../Controllers/AccommodationController.php | 1 + .../Controllers/EntryController.php | 1 + .../Controllers/FoodController.php | 2 + .../Controllers/MerchandiseController.php | 1 + .../UpdateHistoricalFoodStockRequest.php | 4 +- .../UpsertAccommodationVariantsRequest.php | 4 +- .../Requests/UpsertEntriesRequest.php | 4 +- .../Requests/UpsertFoodVariantsRequest.php | 4 +- .../Requests/UpsertMerchandiseRequest.php | 4 +- .../Resources/AccommodationResource.php | 2 +- .../Resources/EntryResource.php | 2 +- .../Resources/FoodResource.php | 2 +- .../Resources/MerchandiseResource.php | 2 +- .../Services/AccommodationService.php | 19 ++++------ .../Services/EntryService.php | 21 ++++------ .../Services/FoodService.php | 38 +++++++------------ .../Services/MerchandiseService.php | 20 ++++------ 17 files changed, 59 insertions(+), 72 deletions(-) diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/AccommodationController.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/AccommodationController.php index fb9ca451..e21425d3 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/AccommodationController.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/AccommodationController.php @@ -28,6 +28,7 @@ class AccommodationController extends Controller $this->accommodationService->upsertMany( $tenant, $request->validated('variants'), + $request->validated('stock_adjustment_id'), ) ); } diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/EntryController.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/EntryController.php index 68b11636..2fa4d1e1 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/EntryController.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/EntryController.php @@ -29,6 +29,7 @@ class EntryController extends Controller $entries = $this->entryService->upsertMany( $tenant, $request->validated('entries'), + $request->validated('stock_adjustment_id'), ); return EntryResource::collection($entries) diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/FoodController.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/FoodController.php index e580cc8d..86d5b5f4 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/FoodController.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/FoodController.php @@ -29,6 +29,7 @@ class FoodController extends Controller $this->foodService->upsertMany( $tenant, $request->validated('variants'), + $request->validated('stock_adjustment_id'), ) ); } @@ -39,6 +40,7 @@ class FoodController extends Controller $this->foodService->updateHistoricalStock( $request->user()->tenant()->firstOrFail(), $request->validated('variants'), + $request->validated('stock_adjustment_id'), ) ); } diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/MerchandiseController.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/MerchandiseController.php index 10b0307f..dd52f9a0 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/MerchandiseController.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Controllers/MerchandiseController.php @@ -28,6 +28,7 @@ class MerchandiseController extends Controller $items = $this->merchandiseService->upsertMany( $tenant, $request->validated('items'), + $request->validated('stock_adjustment_id'), ); return MerchandiseResource::collection($items) diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpdateHistoricalFoodStockRequest.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpdateHistoricalFoodStockRequest.php index aa784622..d137b168 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpdateHistoricalFoodStockRequest.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpdateHistoricalFoodStockRequest.php @@ -16,9 +16,11 @@ class UpdateHistoricalFoodStockRequest extends FormRequest { return [ 'variants' => ['required', 'array', 'min:1', 'max:500'], - 'variants.*' => ['required', 'array:id,stock'], + 'stock_adjustment_id' => ['nullable', 'uuid'], + 'variants.*' => ['required', 'array:id,stock,stock_difference'], 'variants.*.id' => ['required', 'integer', 'distinct'], 'variants.*.stock' => ['required', 'integer', 'min:0'], + 'variants.*.stock_difference' => ['nullable', 'integer'], ]; } } diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertAccommodationVariantsRequest.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertAccommodationVariantsRequest.php index d275c487..11ff365c 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertAccommodationVariantsRequest.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertAccommodationVariantsRequest.php @@ -16,11 +16,13 @@ class UpsertAccommodationVariantsRequest extends FormRequest { return [ 'variants' => ['required', 'array', 'min:1', 'max:500'], - 'variants.*' => ['required', 'array:id,title,description,stock,price'], + 'stock_adjustment_id' => ['nullable', 'uuid'], + 'variants.*' => ['required', 'array:id,title,description,stock,stock_difference,price'], 'variants.*.id' => ['sometimes', 'nullable', 'integer', 'distinct'], 'variants.*.title' => ['required', 'string', 'max:255'], 'variants.*.description' => ['sometimes', 'nullable', 'string'], 'variants.*.stock' => ['required', 'integer', 'min:0'], + 'variants.*.stock_difference' => ['nullable', 'integer'], 'variants.*.price' => ['required', 'numeric', 'min:0', 'max:99999999.99'], ]; } diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertEntriesRequest.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertEntriesRequest.php index 6827fc4b..b18cf1f3 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertEntriesRequest.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertEntriesRequest.php @@ -20,7 +20,8 @@ class UpsertEntriesRequest extends FormRequest return [ 'entries' => ['required', 'array', 'min:1', 'max:100'], - 'entries.*' => ['required', 'array:id,title,description,event_date_ids,stock,price'], + 'stock_adjustment_id' => ['nullable', 'uuid'], + 'entries.*' => ['required', 'array:id,title,description,event_date_ids,stock,stock_difference,price'], 'entries.*.id' => [ 'sometimes', 'nullable', @@ -47,6 +48,7 @@ class UpsertEntriesRequest extends FormRequest ), ], 'entries.*.stock' => ['required', 'integer', 'min:0'], + 'entries.*.stock_difference' => ['nullable', 'integer'], 'entries.*.price' => ['required', 'numeric', 'min:0', 'max:99999999.99'], ]; } diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertFoodVariantsRequest.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertFoodVariantsRequest.php index 164ca981..1f6872ec 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertFoodVariantsRequest.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertFoodVariantsRequest.php @@ -20,7 +20,8 @@ class UpsertFoodVariantsRequest extends FormRequest return [ 'variants' => ['required', 'array', 'min:1', 'max:500'], - 'variants.*' => ['required', 'array:id,event_date_id,schedule,service,description,stock,price'], + 'stock_adjustment_id' => ['nullable', 'uuid'], + 'variants.*' => ['required', 'array:id,event_date_id,schedule,service,description,stock,stock_difference,price'], 'variants.*.id' => ['sometimes', 'nullable', 'integer', 'distinct'], 'variants.*.event_date_id' => [ 'required', @@ -33,6 +34,7 @@ class UpsertFoodVariantsRequest extends FormRequest 'variants.*.service' => ['required', 'string', 'max:255'], 'variants.*.description' => ['sometimes', 'nullable', 'string'], 'variants.*.stock' => ['required', 'integer', 'min:0'], + 'variants.*.stock_difference' => ['nullable', 'integer'], 'variants.*.price' => ['required', 'numeric', 'min:0', 'max:99999999.99'], ]; } diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertMerchandiseRequest.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertMerchandiseRequest.php index 862a59a0..15b05967 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertMerchandiseRequest.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Requests/UpsertMerchandiseRequest.php @@ -39,11 +39,13 @@ class UpsertMerchandiseRequest extends FormRequest 'items.*.description' => ['sometimes', 'nullable', 'string'], 'items.*.max_units_per_user' => ['required', 'integer', 'min:1'], 'items.*.variants' => ['required', 'array', 'min:1', 'max:500'], - 'items.*.variants.*' => ['required', 'array:id,color,size,stock,price'], + 'stock_adjustment_id' => ['nullable', 'uuid'], + 'items.*.variants.*' => ['required', 'array:id,color,size,stock,stock_difference,price'], 'items.*.variants.*.id' => ['sometimes', 'nullable', 'integer', 'distinct'], 'items.*.variants.*.color' => ['required', 'string', 'max:255'], 'items.*.variants.*.size' => ['required', 'string', 'max:255'], 'items.*.variants.*.stock' => ['required', 'integer', 'min:0'], + 'items.*.variants.*.stock_difference' => ['nullable', 'integer'], 'items.*.variants.*.price' => ['required', 'numeric', 'min:0', 'max:99999999.99'], ]; } diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/AccommodationResource.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/AccommodationResource.php index b3cb3186..9b3a9d06 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/AccommodationResource.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/AccommodationResource.php @@ -37,7 +37,7 @@ class AccommodationResource extends JsonResource 'title' => $options->get($value)?->label ?? $value, 'value' => $value, 'description' => $variant->descripcion, - 'stock' => $variant->inventory->real_stock, + 'stock' => $variant->inventory->availableStock(), 'price' => number_format($variant->getPrice(), 2, '.', ''), ]; })->values(), diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/EntryResource.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/EntryResource.php index efce8a91..7da32839 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/EntryResource.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/EntryResource.php @@ -33,7 +33,7 @@ class EntryResource extends JsonResource 'title' => $this->nombre, 'description' => $this->descripcion, 'event_date_ids' => $variant->selectedEventDates()->pluck('id')->values(), - 'stock' => $variant->inventory->real_stock, + 'stock' => $variant->inventory->availableStock(), 'price' => $this->precio, ]; } diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/FoodResource.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/FoodResource.php index 436e62da..34d7abfb 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/FoodResource.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/FoodResource.php @@ -81,7 +81,7 @@ class FoodResource extends JsonResource 'schedule' => $values->get('horario'), 'service' => $values->get('servicio'), 'description' => $variant->descripcion, - 'stock' => $variant->inventory->real_stock, + 'stock' => $variant->inventory->availableStock(), 'price' => number_format($variant->getPrice(), 2, '.', ''), ]; } diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/MerchandiseResource.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/MerchandiseResource.php index 2b6f2a26..369e0783 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/MerchandiseResource.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Resources/MerchandiseResource.php @@ -44,7 +44,7 @@ class MerchandiseResource extends JsonResource 'color_value' => $colorValue, 'size' => $sizeOptions->get($sizeValue)?->label ?? $sizeValue, 'size_value' => $sizeValue, - 'stock' => $variant->inventory->real_stock, + 'stock' => $variant->inventory->availableStock(), 'price' => number_format($variant->getPrice(), 2, '.', ''), ]; })->values(), diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Services/AccommodationService.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Services/AccommodationService.php index 6e5baae2..20e44ee6 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Services/AccommodationService.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Services/AccommodationService.php @@ -39,9 +39,9 @@ class AccommodationService /** * @param array> $variants */ - public function upsertMany(Tenant $tenant, array $variants): CatalogItem + public function upsertMany(Tenant $tenant, array $variants, ?string $stockAdjustmentId = null): CatalogItem { - return DB::transaction(function () use ($tenant, $variants): CatalogItem { + return DB::transaction(function () use ($tenant, $variants, $stockAdjustmentId): CatalogItem { $attribute = $this->attribute($tenant); $accommodation = $this->accommodation($tenant, $variants); $itemAttribute = $accommodation->itemAttributes()->firstOrCreate( @@ -70,7 +70,7 @@ class AccommodationService if ($variant === null) { $this->createVariant($attribute, $accommodation, $itemAttribute, $data); } else { - $this->updateVariant($attribute, $variant, $itemAttribute, $data, $index); + $this->updateVariant($attribute, $variant, $itemAttribute, $data, $index, $stockAdjustmentId); } } @@ -212,6 +212,7 @@ class AccommodationService $this->createOption($attribute, $data['value'], $data['title']); $inventory = Inventory::query()->create(['real_stock' => $data['stock']]); + $inventory->recordInitialization(); $variant = $accommodation->variants()->create([ 'inventory_id' => $inventory->id, 'descripcion' => $data['description'], @@ -230,20 +231,13 @@ class AccommodationService ItemAttribute $itemAttribute, array $data, int $index, + ?string $stockAdjustmentId, ): void { $inventory = Inventory::query() ->whereKey($variant->inventory_id) ->lockForUpdate() ->firstOrFail(); - if ($data['stock'] < $inventory->reserved_stock) { - throw ValidationException::withMessages([ - "variants.{$index}.stock" => [ - 'El stock no puede ser menor que la cantidad actualmente reservada.', - ], - ]); - } - $definition = $variant->definitions ->firstWhere('item_attribute_id', $itemAttribute->id); $option = $definition === null @@ -263,7 +257,8 @@ class AccommodationService 'descripcion' => $data['description'], 'precio' => $data['price'], ]); - $inventory->update(['real_stock' => $data['stock']]); + $stockDifference = (int) ($data['stock_difference'] ?? ($data['stock'] - $inventory->availableStock())); + $inventory->adjustAvailableStock($stockDifference, idempotencyKey: $stockAdjustmentId); $variant->definitions()->updateOrCreate( ['item_attribute_id' => $itemAttribute->id], ['value' => $data['value']], diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Services/EntryService.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Services/EntryService.php index 5d028f5d..5c486029 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Services/EntryService.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Services/EntryService.php @@ -43,18 +43,18 @@ class EntryService * @param array> $entries * @return Collection */ - public function upsertMany(Tenant $tenant, array $entries): Collection + public function upsertMany(Tenant $tenant, array $entries, ?string $stockAdjustmentId = null): Collection { - return DB::transaction(function () use ($tenant, $entries): Collection { + return DB::transaction(function () use ($tenant, $entries, $stockAdjustmentId): Collection { $reservedSlugs = []; $category = Category::query()->firstOrCreate([ 'tenant_code' => $tenant->codigo, 'nombre' => 'Entradas', ]); - return collect($entries)->map(function (array $entry, int $index) use ($tenant, $category, &$reservedSlugs): CatalogItem { + return collect($entries)->map(function (array $entry, int $index) use ($tenant, $category, $stockAdjustmentId, &$reservedSlugs): CatalogItem { if (isset($entry['id'])) { - return $this->update($tenant, $category, $entry, $index); + return $this->update($tenant, $category, $entry, $index, $stockAdjustmentId); } $slug = $this->uniqueSlug($tenant, $entry['title'], $reservedSlugs); @@ -92,7 +92,7 @@ class EntryService } /** @param array $entry */ - private function update(Tenant $tenant, Category $category, array $entry, int $index): CatalogItem + private function update(Tenant $tenant, Category $category, array $entry, int $index, ?string $stockAdjustmentId): CatalogItem { $catalogItem = CatalogItem::query() ->whereKey($entry['id']) @@ -121,14 +121,6 @@ class EntryService ->lockForUpdate() ->firstOrFail(); - if ((int) $entry['stock'] < $inventory->reserved_stock) { - throw ValidationException::withMessages([ - "entries.{$index}.stock" => [ - 'El stock no puede ser menor que la cantidad actualmente reservada.', - ], - ]); - } - $eventDateIds = collect($entry['event_date_ids']) ->map(fn ($id): int => (int) $id) ->unique() @@ -149,7 +141,8 @@ class EntryService $catalogItem->itemAttributes() ->whereHas('attribute', fn ($query) => $query->where('codigo', 'event_date')) ->update(['allow_multi_select' => true]); - $inventory->update(['real_stock' => $entry['stock']]); + $stockDifference = (int) ($entry['stock_difference'] ?? ((int) $entry['stock'] - $inventory->availableStock())); + $inventory->adjustAvailableStock($stockDifference, idempotencyKey: $stockAdjustmentId); return $catalogItem->load([ 'variants.inventory', diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Services/FoodService.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Services/FoodService.php index 88488541..8b9f4c05 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Services/FoodService.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Services/FoodService.php @@ -11,9 +11,9 @@ use App\Domains\Commerce\Catalog\Models\Inventory; use App\Domains\Commerce\Catalog\Models\ItemAttribute; use App\Domains\Commerce\Catalog\Models\Variant; use App\Domains\Commerce\Catalog\Services\CatalogService; +use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Ticketing\Event\Enums\EventDateStatus; use App\Domains\Ticketing\Event\Models\EventDate; -use App\Domains\Core\Tenant\Models\Tenant; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Validation\ValidationException; @@ -50,9 +50,9 @@ class FoodService /** * @param array> $variants */ - public function upsertMany(Tenant $tenant, array $variants): CatalogItem + public function upsertMany(Tenant $tenant, array $variants, ?string $stockAdjustmentId = null): CatalogItem { - return DB::transaction(function () use ($tenant, $variants): CatalogItem { + return DB::transaction(function () use ($tenant, $variants, $stockAdjustmentId): CatalogItem { $attributes = $this->attributes($tenant); $food = $this->food($tenant, $variants); $itemAttributes = $this->itemAttributes($food, $attributes); @@ -85,7 +85,7 @@ class FoodService if ($variant === null) { $this->createVariant($food, $itemAttributes, $data); } else { - $this->updateVariant($variant, $itemAttributes, $data, $index); + $this->updateVariant($variant, $itemAttributes, $data, $index, $stockAdjustmentId); } } @@ -115,9 +115,9 @@ class FoodService /** * @param array $variants */ - public function updateHistoricalStock(Tenant $tenant, array $variants): CatalogItem + public function updateHistoricalStock(Tenant $tenant, array $variants, ?string $stockAdjustmentId = null): CatalogItem { - return DB::transaction(function () use ($tenant, $variants): CatalogItem { + return DB::transaction(function () use ($tenant, $variants, $stockAdjustmentId): CatalogItem { $food = CatalogItem::query() ->forTenantCatalog($tenant) ->where('slug', 'comida') @@ -142,17 +142,9 @@ class FoodService ]); } - $stock = (int) $data['stock']; $inventory = $this->inventoryForHistoricalStockUpdate($variant); - if ($stock < $inventory->reserved_stock) { - throw ValidationException::withMessages([ - "variants.{$index}.stock" => [ - 'El stock no puede ser menor que la cantidad actualmente reservada.', - ], - ]); - } - - $inventory->update(['real_stock' => $stock]); + $stockDifference = (int) ($data['stock_difference'] ?? ((int) $data['stock'] - $inventory->availableStock())); + $inventory->adjustAvailableStock($stockDifference, idempotencyKey: $stockAdjustmentId); } return $this->current($tenant) ?? $food; @@ -181,6 +173,7 @@ class FoodService 'reserved_stock' => 0, 'real_stock' => $inventory->real_stock, ]); + $historicalInventory->recordTransferInitialization(); $variant->update(['inventory_id' => $historicalInventory->getKey()]); return $historicalInventory; @@ -384,6 +377,7 @@ class FoodService private function createVariant(CatalogItem $food, Collection $itemAttributes, array $data): void { $inventory = Inventory::query()->create(['real_stock' => $data['stock']]); + $inventory->recordInitialization(); $variant = $food->variants()->create([ 'event_date_id' => $data['event_date_id'], 'inventory_id' => $inventory->id, @@ -400,27 +394,21 @@ class FoodService Collection $itemAttributes, array $data, int $index, + ?string $stockAdjustmentId, ): void { $inventory = Inventory::query() ->whereKey($variant->inventory_id) ->lockForUpdate() ->firstOrFail(); - if ($data['stock'] < $inventory->reserved_stock) { - throw ValidationException::withMessages([ - "variants.{$index}.stock" => [ - 'El stock no puede ser menor que la cantidad actualmente reservada.', - ], - ]); - } - $variant->update([ 'event_date_id' => $data['event_date_id'], 'descripcion' => $data['description'], 'precio' => $data['price'], ]); $variant->eventDates()->sync([$data['event_date_id']]); - $inventory->update(['real_stock' => $data['stock']]); + $stockDifference = (int) ($data['stock_difference'] ?? ($data['stock'] - $inventory->availableStock())); + $inventory->adjustAvailableStock($stockDifference, idempotencyKey: $stockAdjustmentId); $this->syncDefinitions($variant, $itemAttributes, $data); } diff --git a/app/Domains/Ticketing/FiestaFutbolInfantil/Services/MerchandiseService.php b/app/Domains/Ticketing/FiestaFutbolInfantil/Services/MerchandiseService.php index 3d3183ca..735406df 100644 --- a/app/Domains/Ticketing/FiestaFutbolInfantil/Services/MerchandiseService.php +++ b/app/Domains/Ticketing/FiestaFutbolInfantil/Services/MerchandiseService.php @@ -43,9 +43,9 @@ class MerchandiseService * @param array> $items * @return Collection */ - public function upsertMany(Tenant $tenant, array $items): Collection + public function upsertMany(Tenant $tenant, array $items, ?string $stockAdjustmentId = null): Collection { - return DB::transaction(function () use ($tenant, $items): Collection { + return DB::transaction(function () use ($tenant, $items, $stockAdjustmentId): Collection { $attributes = $this->attributes($tenant); $category = Category::query()->firstOrCreate([ 'tenant_code' => $tenant->codigo, @@ -57,6 +57,7 @@ class MerchandiseService $tenant, $attributes, $category, + $stockAdjustmentId, &$reservedSlugs, ): CatalogItem { $item = isset($data['id']) @@ -101,7 +102,7 @@ class MerchandiseService if ($variant === null) { $this->createVariant($item, $itemAttributes, $variantData); } else { - $this->updateVariant($variant, $itemAttributes, $variantData, $index, $variantIndex); + $this->updateVariant($variant, $itemAttributes, $variantData, $index, $variantIndex, $stockAdjustmentId); } } @@ -342,6 +343,7 @@ class MerchandiseService array $data, ): void { $inventory = Inventory::query()->create(['real_stock' => $data['stock']]); + $inventory->recordInitialization(); $variant = $item->variants()->create([ 'inventory_id' => $inventory->id, 'precio' => $data['price'], @@ -359,22 +361,16 @@ class MerchandiseService array $data, int $itemIndex, int $variantIndex, + ?string $stockAdjustmentId, ): void { $inventory = Inventory::query() ->whereKey($variant->inventory_id) ->lockForUpdate() ->firstOrFail(); - if ($data['stock'] < $inventory->reserved_stock) { - throw ValidationException::withMessages([ - "items.{$itemIndex}.variants.{$variantIndex}.stock" => [ - 'El stock no puede ser menor que la cantidad actualmente reservada.', - ], - ]); - } - $variant->update(['precio' => $data['price']]); - $inventory->update(['real_stock' => $data['stock']]); + $stockDifference = (int) ($data['stock_difference'] ?? ($data['stock'] - $inventory->availableStock())); + $inventory->adjustAvailableStock($stockDifference, idempotencyKey: $stockAdjustmentId); $this->syncDefinitions($variant, $itemAttributes, $data); }