feat(purchase): add checkout expiration settings and reservation status to purchase items

- Introduced a new configuration file for purchase settings, including checkout and payment expiration times.
- Added a migration to include a `reservation_status` column in the `compra_items` table, defaulting to 'active' and updating existing records based on purchase status.
- Created a migration to add an `expires_at` timestamp to the `compras` table, updating it for existing records based on their status.
- Scoped unique indexes in the `carritos` table to only active carts, adding virtual columns for active user and guest tokens.
- Implemented a console command to expire overdue purchases and release stock reservations, scheduled to run every minute.
- Added tests for Google token exchange and login functionality, ensuring proper cart handling and user authentication.
- Updated purchase-related tests to reflect changes in item handling and customer data validation.
This commit is contained in:
2026-07-27 12:49:27 -03:00
parent c37b8894e4
commit 8b26a2b63e
30 changed files with 1738 additions and 396 deletions

View File

@@ -2,12 +2,10 @@
namespace App\Domains\Purchase\Resources;
use App\Domains\Cart\Models\CartItem;
use App\Domains\Purchase\Models\Purchase;
use App\Domains\Purchase\Models\PurchaseItem;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Collection;
/**
* @mixin Purchase
@@ -19,18 +17,20 @@ class PurchaseResource extends JsonResource
*/
public function toArray(Request $request): array
{
[$items, $itemsSource] = $this->resolveItems();
$items = $this->resource->relationLoaded('items')
? $this->resource->getRelation('items')
: collect();
$subtotal = $items->isNotEmpty()
? $items->reduce(
fn (float $carry, PurchaseItem|CartItem $item): float => $carry + $this->resolveItemSubtotal($item),
fn (float $carry, PurchaseItem $item): float => $carry + $this->resolveItemSubtotal($item),
0.0,
)
: (float) ($this->total ?? 0);
$total = $items->isNotEmpty()
? $items->reduce(
fn (float $carry, PurchaseItem|CartItem $item): float => $carry + $this->resolveItemTotal($item),
fn (float $carry, PurchaseItem $item): float => $carry + $this->resolveItemTotal($item),
0.0,
)
: (float) ($this->total ?? 0);
@@ -43,62 +43,27 @@ class PurchaseResource extends JsonResource
'created_at' => $this->created_at,
'status' => $this->status,
'payment_method' => $this->payment_method,
'expires_at' => $this->expires_at,
'dni' => $this->dni,
'transfer_payer_dni' => $this->transfer_payer_dni,
'telefono' => $this->telefono,
'nombre_apellido' => $this->nombre_apellido,
'email' => $this->email,
'items_source' => $itemsSource,
'items_source' => $items->isNotEmpty() ? 'purchase' : null,
'items' => PurchaseItemResource::collection($items),
'subtotal' => $this->formatMoney($subtotal),
'total' => $this->formatMoney($total),
];
}
/**
* @return array{0: Collection<int, PurchaseItem|CartItem>, 1: string|null}
*/
protected function resolveItems(): array
protected function resolveItemSubtotal(PurchaseItem $item): float
{
if (! $this->resource->relationLoaded('items')) {
return [collect(), null];
}
$purchaseItems = $this->resource->getRelation('items');
if ($purchaseItems->isNotEmpty()) {
return [$purchaseItems, 'purchase'];
}
if (! $this->resource->relationLoaded('cart')) {
return [collect(), null];
}
$cart = $this->resource->getRelation('cart');
if ($cart === null || ! $cart->relationLoaded('items')) {
return [collect(), null];
}
return [$cart->getRelation('items'), 'cart'];
return (float) $item->precio_unitario * $item->cantidad;
}
protected function resolveItemSubtotal(PurchaseItem|CartItem $item): float
protected function resolveItemTotal(PurchaseItem $item): float
{
if ($item instanceof PurchaseItem) {
return (float) $item->precio_unitario * $item->cantidad;
}
return (float) ($item->selectedItem()?->getPrice() ?? 0) * $item->cantidad;
}
protected function resolveItemTotal(PurchaseItem|CartItem $item): float
{
if ($item instanceof PurchaseItem) {
return (float) ($item->total ?? 0);
}
return $this->resolveItemSubtotal($item);
return (float) ($item->total ?? 0);
}
protected function formatMoney(float|int|string|null $amount): string