feat(variants): implement inventory cloning for historical stock updates and adjust related tests
This commit is contained in:
parent
ff43ba32f3
commit
dcf4383fbf
|
|
@ -3,6 +3,9 @@
|
|||
namespace App\Domains\Catalog\Services;
|
||||
|
||||
use App\Domains\Catalog\Models\BundleComponent;
|
||||
use App\Domains\Catalog\Models\Inventory;
|
||||
use App\Domains\Catalog\Models\StockReservation;
|
||||
use App\Domains\Catalog\Models\StockReservationLine;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Event\Models\EventDate;
|
||||
use Illuminate\Support\Collection;
|
||||
|
|
@ -92,11 +95,14 @@ class VariantReplacementService
|
|||
/** @param Collection<int, int> $eventDateIds */
|
||||
private function cloneWithDates(Variant $source, Collection $eventDateIds): Variant
|
||||
{
|
||||
$replacementInventory = $this->cloneInventory($source);
|
||||
$replacement = $source->replicate([
|
||||
'event_date_id',
|
||||
'inventory_id',
|
||||
'replaced_by_variant_id',
|
||||
'sales_disabled_at',
|
||||
]);
|
||||
$replacement->inventory_id = $replacementInventory->getKey();
|
||||
$replacement->event_date_id = $eventDateIds->count() === 1
|
||||
? $eventDateIds->first()
|
||||
: null;
|
||||
|
|
@ -125,6 +131,41 @@ class VariantReplacementService
|
|||
return $replacement->load(['eventDates', 'eventDate', 'definitions', 'allAttachments']);
|
||||
}
|
||||
|
||||
private function cloneInventory(Variant $source): Inventory
|
||||
{
|
||||
$activeLines = StockReservationLine::query()
|
||||
->where('inventory_id', $source->inventory_id)
|
||||
->whereHas('reservation', fn ($reservation) => $reservation
|
||||
->where('status', StockReservation::STATUS_ACTIVE))
|
||||
->orderBy('id')
|
||||
->lockForUpdate()
|
||||
->get();
|
||||
$sourceInventory = Inventory::query()
|
||||
->whereKey($source->inventory_id)
|
||||
->lockForUpdate()
|
||||
->firstOrFail();
|
||||
$reservedStock = (int) $activeLines->sum('quantity');
|
||||
|
||||
if ($sourceInventory->reserved_stock !== $reservedStock) {
|
||||
throw new \LogicException('El inventario reservado de la variante es inconsistente.');
|
||||
}
|
||||
|
||||
$replacementInventory = Inventory::query()->create([
|
||||
'sold_units' => $sourceInventory->sold_units,
|
||||
'reserved_stock' => $reservedStock,
|
||||
'real_stock' => $sourceInventory->real_stock,
|
||||
]);
|
||||
|
||||
if ($activeLines->isNotEmpty()) {
|
||||
StockReservationLine::query()
|
||||
->whereKey($activeLines->modelKeys())
|
||||
->update(['inventory_id' => $replacementInventory->getKey()]);
|
||||
}
|
||||
$sourceInventory->update(['reserved_stock' => 0]);
|
||||
|
||||
return $replacementInventory;
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
private function definitionSignature(Variant $variant): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
namespace App\Domains\FiestaFutbolInfantil\Controllers;
|
||||
|
||||
use App\Domains\FiestaFutbolInfantil\Requests\UpsertFoodVariantsRequest;
|
||||
use App\Domains\FiestaFutbolInfantil\Requests\UpdateHistoricalFoodStockRequest;
|
||||
use App\Domains\FiestaFutbolInfantil\Requests\UpsertFoodVariantsRequest;
|
||||
use App\Domains\FiestaFutbolInfantil\Resources\FoodResource;
|
||||
use App\Domains\FiestaFutbolInfantil\Services\FoodService;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
|
|
|||
|
|
@ -126,8 +126,6 @@ class FoodService
|
|||
$variantIds = collect($variants)->pluck('id')->map(fn ($id): int => (int) $id);
|
||||
$historicalVariants = $food->variants()
|
||||
->whereIn('id', $variantIds)
|
||||
->whereNull('sales_disabled_at')
|
||||
->whereNull('replaced_by_variant_id')
|
||||
->with(['inventory', 'eventDate', 'eventDates'])
|
||||
->lockForUpdate()
|
||||
->get()
|
||||
|
|
@ -145,10 +143,7 @@ class FoodService
|
|||
}
|
||||
|
||||
$stock = (int) $data['stock'];
|
||||
$inventory = Inventory::query()
|
||||
->whereKey($variant->inventory_id)
|
||||
->lockForUpdate()
|
||||
->firstOrFail();
|
||||
$inventory = $this->inventoryForHistoricalStockUpdate($variant);
|
||||
if ($stock < $inventory->reserved_stock) {
|
||||
throw ValidationException::withMessages([
|
||||
"variants.{$index}.stock" => [
|
||||
|
|
@ -164,6 +159,32 @@ class FoodService
|
|||
});
|
||||
}
|
||||
|
||||
private function inventoryForHistoricalStockUpdate(Variant $variant): Inventory
|
||||
{
|
||||
$inventory = Inventory::query()
|
||||
->whereKey($variant->inventory_id)
|
||||
->lockForUpdate()
|
||||
->firstOrFail();
|
||||
$variantsSharingInventory = Variant::query()
|
||||
->where('inventory_id', $inventory->getKey())
|
||||
->orderBy('id')
|
||||
->lockForUpdate()
|
||||
->get(['id']);
|
||||
|
||||
if ($variantsSharingInventory->count() === 1) {
|
||||
return $inventory;
|
||||
}
|
||||
|
||||
$historicalInventory = Inventory::query()->create([
|
||||
'sold_units' => $inventory->sold_units,
|
||||
'reserved_stock' => 0,
|
||||
'real_stock' => $inventory->real_stock,
|
||||
]);
|
||||
$variant->update(['inventory_id' => $historicalInventory->getKey()]);
|
||||
|
||||
return $historicalInventory;
|
||||
}
|
||||
|
||||
public function delete(Tenant $tenant, int $foodId): void
|
||||
{
|
||||
$variant = Variant::query()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ use App\Domains\Auth\Models\User;
|
|||
use App\Domains\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\Inventory;
|
||||
use App\Domains\Catalog\Models\StockReservation;
|
||||
use App\Domains\Catalog\Models\StockReservationLine;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Event\Events\EventDateRescheduled;
|
||||
use App\Domains\Event\Events\EventDateSuspended;
|
||||
|
|
@ -249,7 +251,17 @@ class AdminAppEventControllerTest extends TestCase
|
|||
'time_end' => '20:00',
|
||||
]);
|
||||
$variant = $this->createVariant($tenant, $original->id);
|
||||
$variant->inventory()->update(['real_stock' => 5]);
|
||||
$variant->inventory()->update(['real_stock' => 5, 'reserved_stock' => 2]);
|
||||
$reservation = StockReservation::query()->create([
|
||||
'status' => StockReservation::STATUS_ACTIVE,
|
||||
'expires_at' => now()->addHour(),
|
||||
]);
|
||||
$reservationLine = StockReservationLine::query()->create([
|
||||
'stock_reservation_id' => $reservation->id,
|
||||
'inventory_id' => $variant->inventory_id,
|
||||
'quantity' => 2,
|
||||
'tracks_inventory' => true,
|
||||
]);
|
||||
$ticket = $this->createTicket($tenant, $admin, $variant);
|
||||
Sanctum::actingAs($admin);
|
||||
|
||||
|
|
@ -283,7 +295,12 @@ class AdminAppEventControllerTest extends TestCase
|
|||
$replacement = $variant->replacement()->firstOrFail();
|
||||
$this->assertSame($original->id, $variant->event_date_id);
|
||||
$this->assertSame($destination->id, $replacement->event_date_id);
|
||||
$this->assertSame($variant->inventory_id, $replacement->inventory_id);
|
||||
$this->assertNotSame($variant->inventory_id, $replacement->inventory_id);
|
||||
$this->assertSame(5, $variant->inventory->fresh()->real_stock);
|
||||
$this->assertSame(0, $variant->inventory->fresh()->reserved_stock);
|
||||
$this->assertSame(5, $replacement->inventory->real_stock);
|
||||
$this->assertSame(2, $replacement->inventory->reserved_stock);
|
||||
$this->assertSame($replacement->inventory_id, $reservationLine->fresh()->inventory_id);
|
||||
$this->assertNotNull($variant->sales_disabled_at);
|
||||
$this->assertSame($replacement->id, $variant->replaced_by_variant_id);
|
||||
$this->assertSame(
|
||||
|
|
@ -350,7 +367,8 @@ class AdminAppEventControllerTest extends TestCase
|
|||
]);
|
||||
$replacement->refresh();
|
||||
$latestReplacement = $replacement->replacement()->firstOrFail();
|
||||
$this->assertSame($replacement->inventory_id, $latestReplacement->inventory_id);
|
||||
$this->assertNotSame($replacement->inventory_id, $latestReplacement->inventory_id);
|
||||
$this->assertSame(5, $latestReplacement->inventory->real_stock);
|
||||
$this->assertSame('2027-10-25', $latestReplacement->eventDate->date->format('Y-m-d'));
|
||||
$this->assertFalse($replacement->isSellable());
|
||||
$this->assertTrue($latestReplacement->isSellable());
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use App\Domains\Authorization\Enums\RoleCode;
|
|||
use App\Domains\Catalog\Models\Attribute;
|
||||
use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\Category;
|
||||
use App\Domains\Catalog\Models\Inventory;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Event\Services\EventService;
|
||||
use App\Domains\Menu\Models\Menu;
|
||||
|
|
@ -224,16 +225,33 @@ class FoodControllerTest extends TestCase
|
|||
$historicalVariantId = $created->json('data.variants.0.id');
|
||||
$activeVariantId = $created->json('data.variants.1.id');
|
||||
|
||||
app(EventService::class)->suspendDateForTenant($tenant, $historicalDate, $admin);
|
||||
app(EventService::class)->rescheduleDateForTenant(
|
||||
$tenant,
|
||||
$historicalDate,
|
||||
['date' => $activeDate->date->format('Y-m-d')],
|
||||
$admin,
|
||||
);
|
||||
$historicalVariant = Variant::query()->findOrFail($historicalVariantId);
|
||||
$replacementVariant = $historicalVariant->replacement()->firstOrFail();
|
||||
$replacementInventoryId = $replacementVariant->inventory_id;
|
||||
|
||||
$this->patchJson('/api/v1/adminapp/tenant/foods/history-stock', [
|
||||
// Simula variantes creadas antes de que la reprogramación separara sus inventarios.
|
||||
$replacementVariant->update(['inventory_id' => $historicalVariant->inventory_id]);
|
||||
Inventory::query()->whereKey($replacementInventoryId)->delete();
|
||||
|
||||
$updated = $this->patchJson('/api/v1/adminapp/tenant/foods/history-stock', [
|
||||
'variants' => [['id' => $historicalVariantId, 'stock' => 45]],
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.history.0.variants.0.id', $historicalVariantId)
|
||||
->assertJsonPath('data.history.0.variants.0.stock', 45);
|
||||
$replacement = collect($updated->json('data.variants'))
|
||||
->firstWhere('schedule', 'Almuerzo');
|
||||
$this->assertSame(100, $replacement['stock']);
|
||||
|
||||
$historicalInventoryId = Variant::query()->findOrFail($historicalVariantId)->inventory_id;
|
||||
$currentInventoryId = $replacementVariant->fresh()->inventory_id;
|
||||
$this->assertNotSame($historicalInventoryId, $currentInventoryId);
|
||||
$this->assertDatabaseHas('inventories', [
|
||||
'id' => $historicalInventoryId,
|
||||
'real_stock' => 45,
|
||||
|
|
|
|||
Loading…
Reference in New Issue