217 lines
7.5 KiB
PHP
217 lines
7.5 KiB
PHP
<?php
|
|
|
|
namespace App\Domains\Sale\Services;
|
|
|
|
use App\Domains\Logging\Models\ValueChange;
|
|
use App\Domains\Purchase\Models\Purchase;
|
|
use App\Domains\Purchase\Services\CheckoutService;
|
|
use App\Domains\Tenant\Models\Tenant;
|
|
use App\Domains\Ticket\Models\Ticket;
|
|
use App\Domains\Ticket\Services\TicketPresentationResolver;
|
|
use App\Domains\Ticket\Services\TicketValidityResolver;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Support\Collection;
|
|
|
|
class AdminAppSaleService
|
|
{
|
|
public function __construct(
|
|
protected CheckoutService $checkoutService,
|
|
) {}
|
|
|
|
public function confirmedSalesTotal(Tenant $tenant): string
|
|
{
|
|
$total = Purchase::query()
|
|
->where('tenant_codigo', $tenant->codigo)
|
|
->where('status', Purchase::STATUS_PAID)
|
|
->sum('total');
|
|
|
|
return number_format((float) $total, 2, '.', '');
|
|
}
|
|
|
|
/**
|
|
* @param array{
|
|
* q?: string|null,
|
|
* id?: int|null,
|
|
* sale_date?: string|null,
|
|
* status?: string|null,
|
|
* sort_by?: string,
|
|
* sort_direction?: string
|
|
* } $filters
|
|
* @return LengthAwarePaginator<Purchase>
|
|
*/
|
|
public function sales(Tenant $tenant, array $filters = []): LengthAwarePaginator
|
|
{
|
|
return $this->salesQuery($tenant, $filters)
|
|
->paginateFromRequest()
|
|
->withQueryString();
|
|
}
|
|
|
|
public function detail(Tenant $tenant, int $saleId): Purchase
|
|
{
|
|
return Purchase::query()
|
|
->where('tenant_codigo', $tenant->codigo)
|
|
->with([
|
|
'items',
|
|
'cart' => fn ($query) => $query->withTrashed(),
|
|
'cart.items.catalogItem',
|
|
'cart.items.variant.catalogItem',
|
|
'cart.items.variant.eventDates',
|
|
'cart.items.variant.eventDate',
|
|
])
|
|
->findOrFail($saleId);
|
|
}
|
|
|
|
/** @return Collection<int, Ticket> */
|
|
public function tickets(Tenant $tenant, int $saleId): Collection
|
|
{
|
|
return $this->findForTenant($tenant, $saleId)
|
|
->tickets()
|
|
->with([...TicketValidityResolver::RELATIONS, ...TicketPresentationResolver::RELATIONS])
|
|
->orderBy('id')
|
|
->get();
|
|
}
|
|
|
|
public function confirm(Tenant $tenant, int $saleId): Purchase
|
|
{
|
|
$sale = $this->findForTenant($tenant, $saleId);
|
|
|
|
return $this->saleForResponse(
|
|
$this->checkoutService->confirmPaidPurchase($sale)
|
|
);
|
|
}
|
|
|
|
public function cancel(Tenant $tenant, int $saleId): Purchase
|
|
{
|
|
$sale = $this->findForTenant($tenant, $saleId);
|
|
|
|
return $this->saleForResponse(
|
|
$this->checkoutService->cancelPurchaseWithoutRestoringCart($sale)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $filters
|
|
* @return Collection<int, Purchase>
|
|
*/
|
|
public function salesForExport(Tenant $tenant, array $filters = []): Collection
|
|
{
|
|
return $this->salesQuery($tenant, $filters)->get();
|
|
}
|
|
|
|
/** @return LengthAwarePaginator<ValueChange> */
|
|
public function modifications(Tenant $tenant): LengthAwarePaginator
|
|
{
|
|
return $this->modificationsQuery($tenant)
|
|
->paginateFromRequest()
|
|
->withQueryString();
|
|
}
|
|
|
|
/** @return Collection<int, ValueChange> */
|
|
public function modificationsForExport(Tenant $tenant): Collection
|
|
{
|
|
return $this->modificationsQuery($tenant)->get();
|
|
}
|
|
|
|
/** @param array<string, mixed> $filters */
|
|
protected function salesQuery(Tenant $tenant, array $filters): Builder
|
|
{
|
|
$sortColumns = [
|
|
'id' => 'id',
|
|
'date' => 'created_at',
|
|
'customer_name' => 'nombre_apellido',
|
|
'quantity' => 'quantity',
|
|
'status' => 'status',
|
|
'total' => 'total',
|
|
];
|
|
$requestedSort = $filters['sort_by'] ?? 'date';
|
|
$sortBy = array_key_exists($requestedSort, $sortColumns) ? $requestedSort : 'date';
|
|
$requestedDirection = $filters['sort_direction'] ?? 'desc';
|
|
$sortDirection = in_array($requestedDirection, ['asc', 'desc'], true)
|
|
? $requestedDirection
|
|
: 'desc';
|
|
|
|
return Purchase::query()
|
|
->where('tenant_codigo', $tenant->codigo)
|
|
->when($filters['q'] ?? null, function (Builder $query, string $search): void {
|
|
$term = trim($search);
|
|
|
|
$query->where(function (Builder $query) use ($term): void {
|
|
$query
|
|
->where('id', 'like', "%{$term}%")
|
|
->orWhere('nombre_apellido', 'like', "%{$term}%")
|
|
->orWhere('created_at', 'like', "%{$term}%");
|
|
});
|
|
})
|
|
->when($filters['id'] ?? null, fn (Builder $query, int $id): Builder => $query->whereKey($id))
|
|
->when(
|
|
$filters['sale_date'] ?? null,
|
|
fn (Builder $query, string $date): Builder => $query->whereDate('created_at', $date)
|
|
)
|
|
->when(
|
|
$filters['status'] ?? null,
|
|
fn (Builder $query, string $status): Builder => $query->where('status', $status)
|
|
)
|
|
->select('compras.*')
|
|
->selectRaw(
|
|
'CASE WHEN compras.status IN (?, ?) '
|
|
.'THEN (SELECT COALESCE(SUM(cart_items.cantidad), 0) FROM carrito_items AS cart_items '
|
|
.'WHERE cart_items.cart_id = compras.cart_id) '
|
|
.'ELSE (SELECT COALESCE(SUM(purchase_items.cantidad), 0) FROM compra_items AS purchase_items '
|
|
.'WHERE purchase_items.compra_id = compras.id) END AS quantity',
|
|
[
|
|
Purchase::STATUS_CREATED,
|
|
Purchase::STATUS_PENDING_PAYMENT,
|
|
],
|
|
)
|
|
->withCount('tickets')
|
|
->orderBy($sortColumns[$sortBy], $sortDirection)
|
|
->when($sortBy !== 'id', fn (Builder $query): Builder => $query->orderByDesc('id'));
|
|
}
|
|
|
|
/** @return Builder<ValueChange> */
|
|
protected function modificationsQuery(Tenant $tenant): Builder
|
|
{
|
|
return ValueChange::query()
|
|
->where('tenant_code', $tenant->codigo)
|
|
->where('trackable_type', (new Purchase)->getMorphClass())
|
|
->with(['trackable', 'user'])
|
|
->orderByDesc('changed_at')
|
|
->orderByDesc('id');
|
|
}
|
|
|
|
protected function findForTenant(Tenant $tenant, int $saleId): Purchase
|
|
{
|
|
return Purchase::query()
|
|
->where('tenant_codigo', $tenant->codigo)
|
|
->findOrFail($saleId);
|
|
}
|
|
|
|
protected function saleForResponse(Purchase $sale): Purchase
|
|
{
|
|
/** @var Purchase */
|
|
return Purchase::query()
|
|
->select('compras.*')
|
|
->selectRaw($this->quantityExpression())
|
|
->withCount('tickets')
|
|
->findOrFail($sale->getKey());
|
|
}
|
|
|
|
protected function quantityExpression(): string
|
|
{
|
|
// El fallback se resuelve en SQL para poder ordenar por cantidad antes de paginar;
|
|
// los ítems consolidados de la compra tienen prioridad sobre los del carrito de origen.
|
|
return <<<'SQL'
|
|
COALESCE(
|
|
(SELECT SUM(compra_items.cantidad)
|
|
FROM compra_items
|
|
WHERE compra_items.compra_id = compras.id),
|
|
(SELECT SUM(carrito_items.cantidad)
|
|
FROM carrito_items
|
|
WHERE carrito_items.cart_id = compras.cart_id),
|
|
0
|
|
) AS quantity
|
|
SQL;
|
|
}
|
|
}
|