Add tests for ticket validity and event date formatting
- Create TicketValiditySchemaTest to verify database schema for ticket validity. - Update CatalogModelsTest to include tests for event date attributes and selection options. - Introduce EventDateTextFormatterTest for formatting event dates in Spanish. - Refactor EventModelsTest to include validity time relationships. - Add SaleDetailResourceTest to ensure correct serialization of purchase items. - Enhance TicketTest with validity time checks and status management. - Implement ValidityTimeResourceTest to validate resource output for different validity types. - Add ValidityTimeTest to verify casting and validity checks for validity time types.
This commit is contained in:
@@ -54,16 +54,26 @@ class CartController extends Controller
|
||||
Tenant $tenant,
|
||||
CartItem $cartItem,
|
||||
): CartResource {
|
||||
$updatesVariant = $request->exists('variant_id');
|
||||
|
||||
return CartResource::make(
|
||||
$this->cartService->updateItemQuantity(
|
||||
$this->cartService->updateItem(
|
||||
$tenant,
|
||||
$request,
|
||||
$cartItem->getKey(),
|
||||
(int) $request->validated('cantidad'),
|
||||
$updatesVariant
|
||||
? ($request->validated('variant_id') !== null
|
||||
? (int) $request->validated('variant_id')
|
||||
: null)
|
||||
: $cartItem->variant_id,
|
||||
$updatesVariant,
|
||||
)
|
||||
)->additional([
|
||||
'code' => 'cart.quantity_updated',
|
||||
'message' => __('api.cart.quantity_updated'),
|
||||
'code' => $updatesVariant ? 'cart.item_updated' : 'cart.quantity_updated',
|
||||
'message' => $updatesVariant
|
||||
? __('api.cart.item_updated')
|
||||
: __('api.cart.quantity_updated'),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\Inventory;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Catalog\Services\CatalogInventoryService;
|
||||
use App\Domains\Purchase\Services\UserPurchaseLimitService;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -86,6 +87,10 @@ class Cart extends Model
|
||||
return DB::transaction(function () use ($catalogItemId, $variantId, $quantity): CartItem {
|
||||
self::query()->whereKey($this->getKey())->lockForUpdate()->firstOrFail();
|
||||
$selectedItem = $this->resolveScopedItem($catalogItemId, $variantId, true);
|
||||
$cartQuantity = (int) $this->items()
|
||||
->where('catalog_item_id', $catalogItemId)
|
||||
->sum('cantidad');
|
||||
$this->assertUserPurchaseLimit($selectedItem, $cartQuantity + $quantity);
|
||||
$inventoryService = app(CatalogInventoryService::class);
|
||||
$availableQuantity = $inventoryService->availableQuantity($selectedItem);
|
||||
|
||||
@@ -119,29 +124,99 @@ class Cart extends Model
|
||||
});
|
||||
}
|
||||
|
||||
public function updateItem(int $cartItemId, int $quantity): CartItem
|
||||
{
|
||||
public function updateItem(
|
||||
int $cartItemId,
|
||||
int $quantity,
|
||||
?int $variantId = null,
|
||||
bool $updateVariant = false,
|
||||
): CartItem {
|
||||
if ($quantity <= 0) {
|
||||
throw ValidationException::withMessages([
|
||||
'cantidad' => __('api.cart.positive_quantity'),
|
||||
]);
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($cartItemId, $quantity): CartItem {
|
||||
return DB::transaction(function () use (
|
||||
$cartItemId,
|
||||
$quantity,
|
||||
$variantId,
|
||||
$updateVariant,
|
||||
): CartItem {
|
||||
/** @var CartItem $item */
|
||||
$item = $this->items()
|
||||
->where('id', $cartItemId)
|
||||
->lockForUpdate()
|
||||
->firstOrFail();
|
||||
|
||||
$selectedItem = $this->resolveScopedItem(
|
||||
$currentSelection = $this->resolveScopedItem(
|
||||
$item->catalog_item_id,
|
||||
$item->variant_id,
|
||||
true,
|
||||
);
|
||||
$inventoryService = app(CatalogInventoryService::class);
|
||||
|
||||
if ($updateVariant && $variantId !== $item->variant_id) {
|
||||
$nextSelection = $this->resolveScopedItem(
|
||||
$item->catalog_item_id,
|
||||
$variantId,
|
||||
true,
|
||||
);
|
||||
$otherVariantsQuantity = (int) $this->items()
|
||||
->where('catalog_item_id', $item->catalog_item_id)
|
||||
->whereKeyNot($item->getKey())
|
||||
->sum('cantidad');
|
||||
$this->assertUserPurchaseLimit(
|
||||
$nextSelection,
|
||||
$otherVariantsQuantity + $quantity,
|
||||
);
|
||||
|
||||
$inventoryService->release($currentSelection, $item->cantidad);
|
||||
$availableQuantity = $inventoryService->availableQuantity($nextSelection);
|
||||
|
||||
if ($availableQuantity !== null && $availableQuantity < $quantity) {
|
||||
throw ValidationException::withMessages([
|
||||
'variant_id' => __('api.cart.insufficient_stock', ['max' => $availableQuantity]),
|
||||
]);
|
||||
}
|
||||
|
||||
$targetItem = $this->items()
|
||||
->where('catalog_item_id', $item->catalog_item_id)
|
||||
->where('variant_id', $variantId)
|
||||
->whereKeyNot($item->getKey())
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
|
||||
$inventoryService->reserve($nextSelection, $quantity);
|
||||
|
||||
if ($targetItem !== null) {
|
||||
$targetItem->cantidad += $quantity;
|
||||
$targetItem->save();
|
||||
$item->delete();
|
||||
|
||||
return $targetItem->fresh();
|
||||
}
|
||||
|
||||
$item->variant_id = $variantId;
|
||||
$item->cantidad = $quantity;
|
||||
$item->save();
|
||||
|
||||
return $item->fresh();
|
||||
}
|
||||
|
||||
$delta = $quantity - $item->cantidad;
|
||||
$availableQuantity = $inventoryService->availableQuantity($selectedItem);
|
||||
|
||||
if ($delta > 0) {
|
||||
$otherVariantsQuantity = (int) $this->items()
|
||||
->where('catalog_item_id', $item->catalog_item_id)
|
||||
->whereKeyNot($item->getKey())
|
||||
->sum('cantidad');
|
||||
$this->assertUserPurchaseLimit(
|
||||
$currentSelection,
|
||||
$otherVariantsQuantity + $quantity,
|
||||
);
|
||||
}
|
||||
|
||||
$availableQuantity = $inventoryService->availableQuantity($currentSelection);
|
||||
|
||||
if ($delta > 0 && $availableQuantity !== null && $availableQuantity < $delta) {
|
||||
$maxAvailable = $availableQuantity + $item->cantidad;
|
||||
@@ -154,11 +229,11 @@ class Cart extends Model
|
||||
$item->save();
|
||||
|
||||
if ($delta > 0) {
|
||||
$inventoryService->reserve($selectedItem, $delta);
|
||||
$inventoryService->reserve($currentSelection, $delta);
|
||||
}
|
||||
|
||||
if ($delta < 0) {
|
||||
$inventoryService->release($selectedItem, abs($delta));
|
||||
$inventoryService->release($currentSelection, abs($delta));
|
||||
}
|
||||
|
||||
return $item->fresh();
|
||||
@@ -256,6 +331,26 @@ class Cart extends Model
|
||||
return $variant;
|
||||
}
|
||||
|
||||
private function assertUserPurchaseLimit(
|
||||
CatalogItem|Variant $selectedItem,
|
||||
int $cartQuantity,
|
||||
): void {
|
||||
if ($this->user_id === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$catalogItem = $selectedItem instanceof Variant
|
||||
? $selectedItem->catalogItem
|
||||
: $selectedItem;
|
||||
|
||||
app(UserPurchaseLimitService::class)->assertCanPurchase(
|
||||
$catalogItem,
|
||||
$this->user_id,
|
||||
$cartQuantity,
|
||||
field: 'cantidad',
|
||||
);
|
||||
}
|
||||
|
||||
protected function resolveInventory(int $inventoryId, bool $lockForUpdate): Inventory
|
||||
{
|
||||
$query = Inventory::query()->whereKey($inventoryId);
|
||||
|
||||
@@ -19,7 +19,7 @@ class UpdateCartItemQuantityRequest extends FormRequest
|
||||
return [
|
||||
'cantidad' => ['required', 'integer', 'min:1'],
|
||||
'catalog_item_id' => ['prohibited'],
|
||||
'variant_id' => ['prohibited'],
|
||||
'variant_id' => ['sometimes', 'nullable', 'integer'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Domains\Cart\Resources;
|
||||
|
||||
use App\Domains\Cart\Models\CartItem;
|
||||
use App\Domains\Catalog\Enums\InventoryPolicy;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
@@ -36,6 +38,16 @@ class CartItemResource extends JsonResource
|
||||
'product' => $selectedItem === null ? null : [
|
||||
'nombre' => $selectedItem->getName(),
|
||||
'imagen' => $imageUrl,
|
||||
'variants' => $this->catalogItem->visibleVariants($this->variant_id)
|
||||
->map(fn (Variant $variant): array => [
|
||||
'id' => $variant->id,
|
||||
'precio' => $this->formatMoney($variant->getPrice()),
|
||||
'stock_tecnico' => $this->catalogItem->inventory_policy === InventoryPolicy::Unlimited
|
||||
? null
|
||||
: $variant->inventory->availableStock(),
|
||||
'values' => $variant->selectionOptions($this->catalogItem->itemAttributes),
|
||||
])
|
||||
->values(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -51,11 +51,17 @@ class CartService
|
||||
];
|
||||
}
|
||||
|
||||
public function updateItemQuantity(Tenant $tenant, Request $request, int $cartItemId, int $quantity): Cart
|
||||
{
|
||||
public function updateItem(
|
||||
Tenant $tenant,
|
||||
Request $request,
|
||||
int $cartItemId,
|
||||
int $quantity,
|
||||
?int $variantId,
|
||||
bool $updateVariant,
|
||||
): Cart {
|
||||
$identity = $this->requireIdentity($request);
|
||||
$cart = $this->findCartOrFail($tenant, $identity);
|
||||
$cart->updateItem($cartItemId, $quantity);
|
||||
$cart->updateItem($cartItemId, $quantity, $variantId, $updateVariant);
|
||||
|
||||
return $this->loadCart($cart);
|
||||
}
|
||||
@@ -101,9 +107,16 @@ class CartService
|
||||
return $cart->fresh()->load([
|
||||
'items.catalogItem.attachments',
|
||||
'items.catalogItem.inventory',
|
||||
'items.catalogItem.itemAttributes.attribute',
|
||||
'items.catalogItem.variants.inventory',
|
||||
'items.catalogItem.variants.definitions.itemAttribute.attribute.options',
|
||||
'items.catalogItem.variants.eventDates',
|
||||
'items.catalogItem.variants.eventDate',
|
||||
'items.variant.attachments',
|
||||
'items.variant.inventory',
|
||||
'items.variant.definitions.itemAttribute.attribute',
|
||||
'items.variant.definitions.itemAttribute.attribute.options',
|
||||
'items.variant.eventDates',
|
||||
'items.variant.eventDate',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
32
app/Domains/Cart/documentacion/README.md
Normal file
32
app/Domains/Cart/documentacion/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Dominio Cart
|
||||
|
||||
## Propósito
|
||||
|
||||
Gestiona el carrito activo de un tenant tanto para visitantes como para usuarios autenticados.
|
||||
|
||||
## Modelo
|
||||
|
||||
- `Cart`: pertenece a un tenant y opcionalmente a un usuario; calcula el total y permite agregar, actualizar o quitar ítems.
|
||||
- `CartItem`: referencia un `CatalogItem` y, opcionalmente, una `Variant`; expone la selección efectiva.
|
||||
|
||||
## Servicios
|
||||
|
||||
- `CartService`: obtiene el carrito, modifica ítems y administra la cookie del token invitado.
|
||||
- `GuestCartMergeService`: incorpora el carrito invitado al usuario cuando este se autentica.
|
||||
|
||||
## Endpoints
|
||||
|
||||
Bajo `/tenants/{tenant:codigo}`:
|
||||
|
||||
- `GET /cart`.
|
||||
- `POST /cart/items`.
|
||||
- `PATCH /cart/items/{cartItem}`.
|
||||
- `DELETE /cart/items/{cartItem}`.
|
||||
|
||||
## Contratos
|
||||
|
||||
`AddCartItemRequest` y `UpdateCartItemQuantityRequest` validan selección y cantidad. `CartResource` y `CartItemResource` estabilizan la respuesta pública.
|
||||
|
||||
## Dependencias y reglas
|
||||
|
||||
Depende de `Catalog` para productos y variantes, de `Tenant` para aislar datos y de `Auth` cuando existe usuario. Toda operación debe comprobar que carrito e ítem pertenecen al tenant actual.
|
||||
Reference in New Issue
Block a user