refactor(backend): reorganize domains into Core, Commerce, Ticketing and Shared
This commit is contained in:
@@ -1,206 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Sale\Services;
|
||||
|
||||
use App\Domains\Logging\Models\ValueChange;
|
||||
use App\Domains\Purchase\Models\Purchase;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Support\Collection;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class AdminAppSaleExcelService
|
||||
{
|
||||
/** @param Collection<int, Purchase> $sales */
|
||||
public function downloadSales(Tenant $tenant, Collection $sales, string $timeZone): StreamedResponse
|
||||
{
|
||||
$generatedAt = now();
|
||||
$spreadsheet = $this->spreadsheet($tenant, 'Historial de ventas');
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$sheet->setTitle('Ventas');
|
||||
$sheet->fromArray([
|
||||
'ID',
|
||||
'Fecha',
|
||||
'Cliente',
|
||||
'Cantidad',
|
||||
'Estado',
|
||||
'Importe',
|
||||
'Tickets',
|
||||
], null, 'A1');
|
||||
|
||||
foreach ($sales->values() as $index => $sale) {
|
||||
$row = $index + 2;
|
||||
$sheet->setCellValueExplicit("A{$row}", '#'.$sale->id, DataType::TYPE_STRING);
|
||||
if ($sale->created_at) {
|
||||
$sheet->setCellValue(
|
||||
"B{$row}",
|
||||
Date::dateTimeToExcel($sale->created_at->copy()->timezone($timeZone)),
|
||||
);
|
||||
}
|
||||
$sheet->setCellValueExplicit(
|
||||
"C{$row}",
|
||||
$sale->nombre_apellido ?: 'Sin nombre',
|
||||
DataType::TYPE_STRING,
|
||||
);
|
||||
$sheet->setCellValue("D{$row}", (int) ($sale->quantity ?? 0));
|
||||
$sheet->setCellValue("E{$row}", $this->saleStatus($sale->status));
|
||||
$sheet->setCellValue("F{$row}", (float) $sale->total);
|
||||
$sheet->setCellValue("G{$row}", (int) ($sale->tickets_count ?? 0));
|
||||
}
|
||||
|
||||
$lastRow = max(2, $sales->count() + 1);
|
||||
$sheet->getStyle("B2:B{$lastRow}")->getNumberFormat()->setFormatCode('dd/mm/yyyy hh:mm');
|
||||
$sheet->getStyle("F2:F{$lastRow}")->getNumberFormat()->setFormatCode('$ #,##0.00');
|
||||
$this->formatSheet($spreadsheet, 'A1:G1', "A1:G{$lastRow}", [
|
||||
'A' => 13,
|
||||
'B' => 20,
|
||||
'C' => 32,
|
||||
'D' => 12,
|
||||
'E' => 22,
|
||||
'F' => 16,
|
||||
'G' => 12,
|
||||
]);
|
||||
|
||||
return $this->download(
|
||||
$spreadsheet,
|
||||
'ventas_'.$tenant->codigo.'_'
|
||||
.$generatedAt->copy()->timezone($timeZone)->format('Ymd_His').'.xlsx',
|
||||
);
|
||||
}
|
||||
|
||||
/** @param Collection<int, ValueChange> $modifications */
|
||||
public function downloadModifications(
|
||||
Tenant $tenant,
|
||||
Collection $modifications,
|
||||
string $timeZone,
|
||||
): StreamedResponse {
|
||||
$generatedAt = now();
|
||||
$spreadsheet = $this->spreadsheet($tenant, 'Historial de modificaciones de ventas');
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$sheet->setTitle('Modificaciones');
|
||||
$sheet->fromArray([
|
||||
'Fecha',
|
||||
'Hora',
|
||||
'Venta',
|
||||
'Cliente',
|
||||
'Campo',
|
||||
'Valor anterior',
|
||||
'Valor nuevo',
|
||||
'Modificado por',
|
||||
], null, 'A1');
|
||||
|
||||
foreach ($modifications->values() as $index => $modification) {
|
||||
$row = $index + 2;
|
||||
$changedAt = $modification->changed_at->copy()->timezone($timeZone);
|
||||
$sale = $modification->trackable;
|
||||
$sheet->setCellValue("A{$row}", Date::dateTimeToExcel($changedAt));
|
||||
$sheet->setCellValue("B{$row}", Date::dateTimeToExcel($changedAt));
|
||||
$sheet->setCellValueExplicit(
|
||||
"C{$row}",
|
||||
'#'.$modification->trackable_id,
|
||||
DataType::TYPE_STRING,
|
||||
);
|
||||
$sheet->setCellValueExplicit(
|
||||
"D{$row}",
|
||||
$sale?->nombre_apellido ?: 'Sin nombre',
|
||||
DataType::TYPE_STRING,
|
||||
);
|
||||
$sheet->setCellValueExplicit(
|
||||
"E{$row}",
|
||||
$modification->attribute,
|
||||
DataType::TYPE_STRING,
|
||||
);
|
||||
$sheet->setCellValueExplicit(
|
||||
"F{$row}",
|
||||
$modification->old_value ?? '-',
|
||||
DataType::TYPE_STRING,
|
||||
);
|
||||
$sheet->setCellValueExplicit(
|
||||
"G{$row}",
|
||||
$modification->new_value ?? '-',
|
||||
DataType::TYPE_STRING,
|
||||
);
|
||||
$sheet->setCellValueExplicit(
|
||||
"H{$row}",
|
||||
$modification->user?->nombre_apellido ?? 'Sistema',
|
||||
DataType::TYPE_STRING,
|
||||
);
|
||||
}
|
||||
|
||||
$lastRow = max(2, $modifications->count() + 1);
|
||||
$sheet->getStyle("A2:A{$lastRow}")->getNumberFormat()->setFormatCode('dd/mm/yyyy');
|
||||
$sheet->getStyle("B2:B{$lastRow}")->getNumberFormat()->setFormatCode('hh:mm:ss');
|
||||
$this->formatSheet($spreadsheet, 'A1:H1', "A1:H{$lastRow}", [
|
||||
'A' => 14,
|
||||
'B' => 12,
|
||||
'C' => 13,
|
||||
'D' => 32,
|
||||
'E' => 20,
|
||||
'F' => 24,
|
||||
'G' => 24,
|
||||
'H' => 28,
|
||||
]);
|
||||
|
||||
return $this->download(
|
||||
$spreadsheet,
|
||||
'historial_modificaciones_'.$tenant->codigo.'_'
|
||||
.$generatedAt->copy()->timezone($timeZone)->format('Ymd_His').'.xlsx',
|
||||
);
|
||||
}
|
||||
|
||||
private function spreadsheet(Tenant $tenant, string $title): Spreadsheet
|
||||
{
|
||||
$spreadsheet = new Spreadsheet;
|
||||
$spreadsheet->getProperties()
|
||||
->setCreator('Shopit')
|
||||
->setTitle($title)
|
||||
->setSubject($tenant->nombre);
|
||||
|
||||
return $spreadsheet;
|
||||
}
|
||||
|
||||
/** @param array<string, int> $widths */
|
||||
private function formatSheet(
|
||||
Spreadsheet $spreadsheet,
|
||||
string $headerRange,
|
||||
string $filterRange,
|
||||
array $widths,
|
||||
): void {
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$sheet->getStyle($headerRange)->applyFromArray([
|
||||
'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
|
||||
'fill' => [
|
||||
'fillType' => Fill::FILL_SOLID,
|
||||
'startColor' => ['rgb' => '26382E'],
|
||||
],
|
||||
'alignment' => ['vertical' => Alignment::VERTICAL_CENTER],
|
||||
]);
|
||||
$sheet->getRowDimension(1)->setRowHeight(24);
|
||||
$sheet->freezePane('A2');
|
||||
$sheet->setAutoFilter($filterRange);
|
||||
|
||||
foreach ($widths as $column => $width) {
|
||||
$sheet->getColumnDimension($column)->setWidth($width);
|
||||
}
|
||||
}
|
||||
|
||||
private function download(Spreadsheet $spreadsheet, string $filename): StreamedResponse
|
||||
{
|
||||
return response()->streamDownload(function () use ($spreadsheet): void {
|
||||
(new Xlsx($spreadsheet))->save('php://output');
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
}, $filename, [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}
|
||||
|
||||
private function saleStatus(string $status): string
|
||||
{
|
||||
return Purchase::adminStatusNameFor($status) ?? $status;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Sale\Services;
|
||||
|
||||
use App\Domains\Logging\Models\ValueChange;
|
||||
use App\Domains\Purchase\Models\Purchase;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Barryvdh\DomPDF\PDF as DomPdf;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class AdminAppSalePdfService
|
||||
{
|
||||
/** @param Collection<int, Purchase> $sales */
|
||||
public function downloadSales(Tenant $tenant, Collection $sales, string $timeZone): Response
|
||||
{
|
||||
$generatedAt = now();
|
||||
$pdf = Pdf::loadView('pdf.adminapp.sales', [
|
||||
'tenant' => $tenant,
|
||||
'sales' => $sales,
|
||||
'generatedAt' => $generatedAt,
|
||||
'timeZone' => $timeZone,
|
||||
'confirmedSalesTotal' => number_format(
|
||||
(float) $sales->where('status', Purchase::STATUS_PAID)->sum('total'),
|
||||
2,
|
||||
'.',
|
||||
'',
|
||||
),
|
||||
])->setPaper('a4', 'landscape');
|
||||
|
||||
$this->addPageNumbers($pdf);
|
||||
|
||||
return $pdf->download(
|
||||
'ventas_'.$tenant->codigo.'_'
|
||||
.$generatedAt->copy()->timezone($timeZone)->format('Ymd_His').'.pdf'
|
||||
);
|
||||
}
|
||||
|
||||
/** @param Collection<int, ValueChange> $modifications */
|
||||
public function downloadModifications(
|
||||
Tenant $tenant,
|
||||
Collection $modifications,
|
||||
string $timeZone,
|
||||
): Response {
|
||||
$generatedAt = now();
|
||||
$pdf = Pdf::loadView('pdf.adminapp.sale-modifications', [
|
||||
'tenant' => $tenant,
|
||||
'modifications' => $modifications,
|
||||
'generatedAt' => $generatedAt,
|
||||
'timeZone' => $timeZone,
|
||||
])->setPaper('a4', 'landscape');
|
||||
|
||||
$this->addPageNumbers($pdf);
|
||||
|
||||
return $pdf->download(
|
||||
'historial_modificaciones_'.$tenant->codigo.'_'
|
||||
.$generatedAt->copy()->timezone($timeZone)->format('Ymd_His').'.pdf'
|
||||
);
|
||||
}
|
||||
|
||||
private function addPageNumbers(DomPdf $pdf): void
|
||||
{
|
||||
$pdf->render();
|
||||
$domPdf = $pdf->getDomPDF();
|
||||
$font = $domPdf->getFontMetrics()->getFont('DejaVu Sans');
|
||||
|
||||
$domPdf->getCanvas()->page_text(
|
||||
385,
|
||||
575,
|
||||
'Página {PAGE_NUM} de {PAGE_COUNT}',
|
||||
$font,
|
||||
7,
|
||||
[0.48, 0.52, 0.49],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
<?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\Purchase\Services\PurchaseRefundSummaryService;
|
||||
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,
|
||||
protected PurchaseRefundSummaryService $refundSummaryService,
|
||||
) {}
|
||||
|
||||
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, '.', '');
|
||||
}
|
||||
|
||||
public function refundedTotal(Tenant $tenant): string
|
||||
{
|
||||
return $this->refundSummaryService->totalForTenant($tenant);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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')
|
||||
->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, 'refund'])
|
||||
->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->cancelPurchaseFromAdmin($sale)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $filters
|
||||
* @return Collection<int, Purchase>
|
||||
*/
|
||||
public function salesForExport(Tenant $tenant, array $filters = []): Collection
|
||||
{
|
||||
return $this->salesQuery($tenant, $filters)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $filters
|
||||
* @return LengthAwarePaginator<ValueChange>
|
||||
*/
|
||||
public function modifications(Tenant $tenant, array $filters = []): LengthAwarePaginator
|
||||
{
|
||||
return $this->modificationsQuery($tenant, $filters)
|
||||
->paginateFromRequest()
|
||||
->withQueryString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $filters
|
||||
* @return Collection<int, ValueChange>
|
||||
*/
|
||||
public function modificationsForExport(Tenant $tenant, array $filters = []): Collection
|
||||
{
|
||||
return $this->modificationsQuery($tenant, $filters)->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->whereIn(
|
||||
'status',
|
||||
Purchase::realStatusesForAdminStatus($status),
|
||||
)
|
||||
)
|
||||
->select('compras.*')
|
||||
->selectRaw(
|
||||
'(SELECT COALESCE(SUM(purchase_items.cantidad), 0) '
|
||||
.'FROM compra_items AS purchase_items '
|
||||
.'WHERE purchase_items.compra_id = compras.id) AS quantity',
|
||||
)
|
||||
->withCount('tickets')
|
||||
->orderBy($sortColumns[$sortBy], $sortDirection)
|
||||
->when($sortBy !== 'id', fn (Builder $query): Builder => $query->orderByDesc('id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $filters
|
||||
* @return Builder<ValueChange>
|
||||
*/
|
||||
protected function modificationsQuery(Tenant $tenant, array $filters): Builder
|
||||
{
|
||||
return ValueChange::query()
|
||||
->where('tenant_code', $tenant->codigo)
|
||||
->where('trackable_type', (new Purchase)->getMorphClass())
|
||||
->when($filters['q'] ?? null, function (Builder $query, string $search): void {
|
||||
$term = trim($search);
|
||||
|
||||
$query->where(function (Builder $query) use ($term): void {
|
||||
$query
|
||||
->where('trackable_id', 'like', "%{$term}%")
|
||||
->orWhereHasMorph(
|
||||
'trackable',
|
||||
[Purchase::class],
|
||||
function (Builder $sales) use ($term): void {
|
||||
$sales
|
||||
->where('nombre_apellido', 'like', "%{$term}%")
|
||||
->orWhere('created_at', 'like', "%{$term}%");
|
||||
},
|
||||
);
|
||||
});
|
||||
})
|
||||
->when(
|
||||
$filters['id'] ?? null,
|
||||
fn (Builder $query, int $id): Builder => $query->where('trackable_id', $id)
|
||||
)
|
||||
->when(
|
||||
$filters['sale_date'] ?? null,
|
||||
fn (Builder $query, string $date): Builder => $query->whereHasMorph(
|
||||
'trackable',
|
||||
[Purchase::class],
|
||||
fn (Builder $sales): Builder => $sales->whereDate('created_at', $date),
|
||||
)
|
||||
)
|
||||
->when(
|
||||
$filters['status'] ?? null,
|
||||
fn (Builder $query, string $status): Builder => $query
|
||||
->where('attribute', 'status')
|
||||
->whereIn('new_value', Purchase::realStatusesForAdminStatus($status))
|
||||
)
|
||||
->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
|
||||
{
|
||||
return <<<'SQL'
|
||||
COALESCE(
|
||||
(SELECT SUM(compra_items.cantidad)
|
||||
FROM compra_items
|
||||
WHERE compra_items.compra_id = compras.id),
|
||||
0
|
||||
) AS quantity
|
||||
SQL;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user