feat(inventory): track administrative entry reservations
This commit is contained in:
@@ -237,7 +237,7 @@ class CatalogItem extends Model
|
||||
->whereHas(
|
||||
'inventory',
|
||||
fn (Builder $inventoryQuery): Builder => $inventoryQuery
|
||||
->whereColumn('inventories.real_stock', '>', 'inventories.reserved_stock')
|
||||
->whereRaw('inventories.real_stock > inventories.reserved_stock + inventories.entry_reserved_stock')
|
||||
)
|
||||
)
|
||||
->orWhere(function (Builder $directItemQuery): void {
|
||||
@@ -249,7 +249,7 @@ class CatalogItem extends Model
|
||||
->orWhereHas(
|
||||
'inventory',
|
||||
fn (Builder $availableInventoryQuery): Builder => $availableInventoryQuery
|
||||
->whereColumn('inventories.real_stock', '>', 'inventories.reserved_stock')
|
||||
->whereRaw('inventories.real_stock > inventories.reserved_stock + inventories.entry_reserved_stock')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
'sold_units',
|
||||
'refunded_units',
|
||||
'reserved_stock',
|
||||
'entry_reserved_stock',
|
||||
'real_stock',
|
||||
])]
|
||||
class Inventory extends Model
|
||||
@@ -35,6 +36,7 @@ class Inventory extends Model
|
||||
'sold_units' => 'integer',
|
||||
'refunded_units' => 'integer',
|
||||
'reserved_stock' => 'integer',
|
||||
'entry_reserved_stock' => 'integer',
|
||||
'real_stock' => 'integer',
|
||||
];
|
||||
}
|
||||
@@ -59,7 +61,16 @@ class Inventory extends Model
|
||||
|
||||
public function availableStock(): int
|
||||
{
|
||||
return max(0, $this->real_stock - $this->reserved_stock);
|
||||
return max(0, $this->real_stock - $this->reserved_stock - $this->entry_reserved_stock);
|
||||
}
|
||||
|
||||
public function reserveEntry(int $amount, bool $tracksInventory): void
|
||||
{
|
||||
if ($amount < 1 || ($tracksInventory && $this->availableStock() < $amount)) {
|
||||
throw new \InvalidArgumentException('No hay stock disponible para la reserva de entradas.');
|
||||
}
|
||||
$this->entry_reserved_stock += $amount;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function reserve(int $amount, bool $tracksInventory): void
|
||||
@@ -92,7 +103,7 @@ class Inventory extends Model
|
||||
throw new \InvalidArgumentException('La cantidad reservada no alcanza para confirmar la compra.');
|
||||
}
|
||||
|
||||
if ($tracksInventory && $this->real_stock < $amount) {
|
||||
if ($tracksInventory && $this->real_stock - $this->entry_reserved_stock < $amount) {
|
||||
throw new \InvalidArgumentException('No hay suficiente stock real para confirmar la compra.');
|
||||
}
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ class CatalogInventoryService
|
||||
|
||||
if ($operation === 'commit'
|
||||
&& $requirement['tracks_inventory']
|
||||
&& $inventory->real_stock < $requiredQuantity) {
|
||||
&& $inventory->real_stock - $inventory->entry_reserved_stock < $requiredQuantity) {
|
||||
throw new \InvalidArgumentException('No hay suficiente stock real para confirmar la compra.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ class StockReservationService
|
||||
$inventory = $inventories->get($line->inventory_id)
|
||||
?? throw new \InvalidArgumentException('No se encontró el inventario reservado.');
|
||||
if ($inventory->reserved_stock < $line->quantity
|
||||
|| ($line->tracks_inventory && $inventory->real_stock < $line->quantity)) {
|
||||
|| ($line->tracks_inventory && $inventory->real_stock - $inventory->entry_reserved_stock < $line->quantity)) {
|
||||
throw new \InvalidArgumentException('La reserva de stock no alcanza para confirmar la compra.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Domains\Commerce\Catalog\Models\Inventory;
|
||||
use App\Domains\Commerce\Catalog\Models\StockReservation;
|
||||
use App\Domains\Commerce\Catalog\Models\StockReservationLine;
|
||||
use App\Domains\Commerce\Catalog\Models\Variant;
|
||||
use App\Domains\Ticketing\Desfile\Models\EntryReservation;
|
||||
use App\Domains\Ticketing\Event\Models\EventDate;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
@@ -190,6 +191,7 @@ class VariantReplacementService
|
||||
'sold_units' => $sourceInventory->sold_units,
|
||||
'refunded_units' => $sourceInventory->refunded_units,
|
||||
'reserved_stock' => $reservedStock,
|
||||
'entry_reserved_stock' => $sourceInventory->entry_reserved_stock,
|
||||
'real_stock' => $sourceInventory->real_stock,
|
||||
]);
|
||||
|
||||
@@ -198,7 +200,9 @@ class VariantReplacementService
|
||||
->whereKey($activeLines->modelKeys())
|
||||
->update(['inventory_id' => $replacementInventory->getKey()]);
|
||||
}
|
||||
$sourceInventory->update(['reserved_stock' => 0]);
|
||||
EntryReservation::query()->where('inventory_id', $sourceInventory->id)
|
||||
->update(['inventory_id' => $replacementInventory->id]);
|
||||
$sourceInventory->update(['reserved_stock' => 0, 'entry_reserved_stock' => 0]);
|
||||
|
||||
return $replacementInventory;
|
||||
}
|
||||
@@ -235,6 +239,7 @@ class VariantReplacementService
|
||||
$destinationInventory->update([
|
||||
'real_stock' => $destinationInventory->real_stock + $sourceInventory->real_stock,
|
||||
'reserved_stock' => $destinationInventory->reserved_stock + $sourceInventory->reserved_stock,
|
||||
'entry_reserved_stock' => $destinationInventory->entry_reserved_stock + $sourceInventory->entry_reserved_stock,
|
||||
'sold_units' => $destinationInventory->sold_units + $sourceInventory->sold_units,
|
||||
'refunded_units' => $destinationInventory->refunded_units + $sourceInventory->refunded_units,
|
||||
]);
|
||||
@@ -243,9 +248,12 @@ class VariantReplacementService
|
||||
->whereKey($activeLines->modelKeys())
|
||||
->update(['inventory_id' => $destinationInventory->getKey()]);
|
||||
}
|
||||
EntryReservation::query()->where('inventory_id', $sourceInventory->id)
|
||||
->update(['inventory_id' => $destinationInventory->id]);
|
||||
$sourceInventory->update([
|
||||
'real_stock' => 0,
|
||||
'reserved_stock' => 0,
|
||||
'entry_reserved_stock' => 0,
|
||||
'sold_units' => 0,
|
||||
'refunded_units' => 0,
|
||||
]);
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
namespace App\Domains\Ticketing\Desfile\Services;
|
||||
|
||||
use App\Shared\Attachable\Models\Attachment;
|
||||
use App\Shared\Attachable\Services\AttachmentService;
|
||||
use App\Domains\Commerce\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Commerce\Catalog\Models\Inventory;
|
||||
use App\Domains\Commerce\Catalog\Models\ItemAttribute;
|
||||
use App\Domains\Commerce\Catalog\Models\Variant;
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use App\Shared\Attachable\Models\Attachment;
|
||||
use App\Shared\Attachable\Services\AttachmentService;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -254,7 +254,7 @@ class EntryService
|
||||
{
|
||||
$inventory = $variant->inventory;
|
||||
|
||||
if (($inventory?->reserved_stock ?? 0) > 0 || ($inventory?->sold_units ?? 0) > 0) {
|
||||
if (($inventory?->reserved_stock ?? 0) > 0 || ($inventory?->entry_reserved_stock ?? 0) > 0 || ($inventory?->sold_units ?? 0) > 0) {
|
||||
throw ValidationException::withMessages([
|
||||
$key => [
|
||||
'No se puede modificar ni eliminar un asiento con ventas o reservas.',
|
||||
|
||||
@@ -393,7 +393,7 @@ class InvitationPurchaseProvisioner
|
||||
|
||||
$inventory = DB::table('inventories')->where('id', $variant->inventory_id)->lockForUpdate()->first();
|
||||
|
||||
if ($inventory === null || $inventory->real_stock < 1 || $inventory->reserved_stock > 0) {
|
||||
if ($inventory === null || $inventory->real_stock < 1 || $inventory->reserved_stock > 0 || ($inventory->entry_reserved_stock ?? 0) > 0) {
|
||||
throw new RuntimeException("El asiento {$variant->descripcion} ya no está disponible.");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user