fix(sales): render PDFs in client timezone
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
namespace App\Domains\Sale\Controllers\AdminApp;
|
||||
|
||||
use App\Domains\Sale\Requests\AdminAppSaleIndexRequest;
|
||||
use App\Domains\Sale\Requests\AdminAppSaleModificationPdfRequest;
|
||||
use App\Domains\Sale\Requests\AdminAppSalePdfRequest;
|
||||
use App\Domains\Sale\Resources\AdminApp\SaleDetailResource;
|
||||
use App\Domains\Sale\Resources\AdminApp\SaleModificationResource;
|
||||
use App\Domains\Sale\Resources\AdminApp\SaleResource;
|
||||
@@ -71,23 +73,25 @@ class SaleController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
public function downloadPdf(AdminAppSaleIndexRequest $request): Response
|
||||
public function downloadPdf(AdminAppSalePdfRequest $request): Response
|
||||
{
|
||||
$tenant = $request->user()->tenant()->firstOrFail();
|
||||
|
||||
return $this->salePdfService->downloadSales(
|
||||
$tenant,
|
||||
$this->saleService->salesForExport($tenant, $request->validated()),
|
||||
$request->validated('timezone'),
|
||||
);
|
||||
}
|
||||
|
||||
public function downloadModificationsPdf(Request $request): Response
|
||||
public function downloadModificationsPdf(AdminAppSaleModificationPdfRequest $request): Response
|
||||
{
|
||||
$tenant = $request->user()->tenant()->firstOrFail();
|
||||
|
||||
return $this->salePdfService->downloadModifications(
|
||||
$tenant,
|
||||
$this->saleService->modificationsForExport($tenant),
|
||||
$request->validated('timezone'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,14 @@ use Illuminate\Support\Collection;
|
||||
class AdminAppSalePdfService
|
||||
{
|
||||
/** @param Collection<int, Purchase> $sales */
|
||||
public function downloadSales(Tenant $tenant, Collection $sales): Response
|
||||
public function downloadSales(Tenant $tenant, Collection $sales, string $timeZone): Response
|
||||
{
|
||||
$generatedAt = now();
|
||||
$pdf = Pdf::loadView('pdf.adminapp.sales', [
|
||||
'tenant' => $tenant,
|
||||
'sales' => $sales,
|
||||
'generatedAt' => now(),
|
||||
'generatedAt' => $generatedAt,
|
||||
'timeZone' => $timeZone,
|
||||
'confirmedSalesTotal' => number_format(
|
||||
(float) $sales->where('status', Purchase::STATUS_PAID)->sum('total'),
|
||||
2,
|
||||
@@ -30,23 +32,30 @@ class AdminAppSalePdfService
|
||||
$this->addPageNumbers($pdf);
|
||||
|
||||
return $pdf->download(
|
||||
'ventas_'.$tenant->codigo.'_'.now()->format('Ymd_His').'.pdf'
|
||||
'ventas_'.$tenant->codigo.'_'
|
||||
.$generatedAt->copy()->timezone($timeZone)->format('Ymd_His').'.pdf'
|
||||
);
|
||||
}
|
||||
|
||||
/** @param Collection<int, ValueChange> $modifications */
|
||||
public function downloadModifications(Tenant $tenant, Collection $modifications): Response
|
||||
{
|
||||
public function downloadModifications(
|
||||
Tenant $tenant,
|
||||
Collection $modifications,
|
||||
string $timeZone,
|
||||
): Response {
|
||||
$generatedAt = now();
|
||||
$pdf = Pdf::loadView('pdf.adminapp.sale-modifications', [
|
||||
'tenant' => $tenant,
|
||||
'modifications' => $modifications,
|
||||
'generatedAt' => now(),
|
||||
'generatedAt' => $generatedAt,
|
||||
'timeZone' => $timeZone,
|
||||
])->setPaper('a4', 'landscape');
|
||||
|
||||
$this->addPageNumbers($pdf);
|
||||
|
||||
return $pdf->download(
|
||||
'historial_modificaciones_'.$tenant->codigo.'_'.now()->format('Ymd_His').'.pdf'
|
||||
'historial_modificaciones_'.$tenant->codigo.'_'
|
||||
.$generatedAt->copy()->timezone($timeZone)->format('Ymd_His').'.pdf'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<h1>Historial de modificaciones de ventas</h1>
|
||||
<p class="subtitle">{{ $tenant->nombre }} · Generado el {{ $generatedAt->format('d/m/Y H:i') }}</p>
|
||||
<p class="subtitle">{{ $tenant->nombre }} · Generado el {{ $generatedAt->copy()->timezone($timeZone)->format('d/m/Y H:i') }}</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
@@ -44,8 +44,8 @@
|
||||
@forelse ($modifications as $modification)
|
||||
@php($sale = $modification->trackable)
|
||||
<tr>
|
||||
<td>{{ $modification->changed_at->format('d/m/Y') }}</td>
|
||||
<td>{{ $modification->changed_at->format('H:i:s') }}</td>
|
||||
<td>{{ $modification->changed_at->copy()->timezone($timeZone)->format('d/m/Y') }}</td>
|
||||
<td>{{ $modification->changed_at->copy()->timezone($timeZone)->format('H:i:s') }}</td>
|
||||
<td>#{{ $modification->trackable_id }}</td>
|
||||
<td>{{ $sale?->nombre_apellido ?: 'Sin nombre' }}</td>
|
||||
<td>{{ $modification->attribute }}</td>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<h1>Historial de ventas</h1>
|
||||
<p class="subtitle">{{ $tenant->nombre }} · Generado el {{ $generatedAt->format('d/m/Y H:i') }}</p>
|
||||
<p class="subtitle">{{ $tenant->nombre }} · Generado el {{ $generatedAt->copy()->timezone($timeZone)->format('d/m/Y H:i') }}</p>
|
||||
|
||||
<div class="summary">
|
||||
Total de ventas confirmadas en este reporte: <strong>${{ number_format((float) $confirmedSalesTotal, 2, ',', '.') }}</strong>
|
||||
@@ -44,7 +44,7 @@
|
||||
@forelse ($sales as $sale)
|
||||
<tr>
|
||||
<td>#{{ $sale->id }}</td>
|
||||
<td>{{ $sale->created_at?->format('d/m/Y H:i') ?? '-' }}</td>
|
||||
<td>{{ $sale->created_at?->copy()->timezone($timeZone)->format('d/m/Y H:i') ?? '-' }}</td>
|
||||
<td>{{ $sale->nombre_apellido ?: 'Sin nombre' }}</td>
|
||||
<td class="center">{{ (int) ($sale->quantity ?? 0) }}</td>
|
||||
<td>{{ match ($sale->status) {
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Domains\Purchase\Models\Purchase;
|
||||
use App\Domains\Sale\Services\AdminAppSalePdfService;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Barryvdh\DomPDF\ServiceProvider;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AdminAppSalePdfServiceTest extends TestCase
|
||||
@@ -17,6 +18,14 @@ class AdminAppSalePdfServiceTest extends TestCase
|
||||
parent::setUp();
|
||||
|
||||
$this->app->register(ServiceProvider::class);
|
||||
Carbon::setTestNow(Carbon::parse('2026-08-24 17:53:00', 'UTC'));
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Carbon::setTestNow();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_it_downloads_the_sales_report_as_a_pdf(): void
|
||||
@@ -24,11 +33,12 @@ class AdminAppSalePdfServiceTest extends TestCase
|
||||
$response = app(AdminAppSalePdfService::class)->downloadSales(
|
||||
$this->tenant(),
|
||||
collect([$this->sale()]),
|
||||
'America/La_Paz',
|
||||
);
|
||||
|
||||
$this->assertSame('application/pdf', $response->headers->get('content-type'));
|
||||
$this->assertStringContainsString(
|
||||
'attachment; filename=ventas_acme_',
|
||||
'attachment; filename=ventas_acme_20260824_135300.pdf',
|
||||
(string) $response->headers->get('content-disposition'),
|
||||
);
|
||||
$this->assertStringStartsWith('%PDF', $response->getContent());
|
||||
@@ -57,16 +67,52 @@ class AdminAppSalePdfServiceTest extends TestCase
|
||||
$response = app(AdminAppSalePdfService::class)->downloadModifications(
|
||||
$this->tenant(),
|
||||
collect([$modification]),
|
||||
'America/La_Paz',
|
||||
);
|
||||
|
||||
$this->assertSame('application/pdf', $response->headers->get('content-type'));
|
||||
$this->assertStringContainsString(
|
||||
'attachment; filename=historial_modificaciones_acme_',
|
||||
'attachment; filename=historial_modificaciones_acme_20260824_135300.pdf',
|
||||
(string) $response->headers->get('content-disposition'),
|
||||
);
|
||||
$this->assertStringStartsWith('%PDF', $response->getContent());
|
||||
}
|
||||
|
||||
public function test_it_renders_pdf_dates_in_the_requested_timezone(): void
|
||||
{
|
||||
$sale = $this->sale();
|
||||
$modification = (new ValueChange)->forceFill([
|
||||
'id' => 1,
|
||||
'trackable_id' => $sale->id,
|
||||
'attribute' => 'status',
|
||||
'old_value' => Purchase::STATUS_PENDING_PAYMENT,
|
||||
'new_value' => Purchase::STATUS_PAID,
|
||||
'changed_at' => now(),
|
||||
'actor_type' => 'system',
|
||||
]);
|
||||
$modification->setRelation('trackable', $sale);
|
||||
$modification->setRelation('user', null);
|
||||
|
||||
$salesHtml = view('pdf.adminapp.sales', [
|
||||
'tenant' => $this->tenant(),
|
||||
'sales' => collect([$sale]),
|
||||
'generatedAt' => now(),
|
||||
'timeZone' => 'America/La_Paz',
|
||||
'confirmedSalesTotal' => '25000.00',
|
||||
])->render();
|
||||
$modificationsHtml = view('pdf.adminapp.sale-modifications', [
|
||||
'tenant' => $this->tenant(),
|
||||
'modifications' => collect([$modification]),
|
||||
'generatedAt' => now(),
|
||||
'timeZone' => 'America/La_Paz',
|
||||
])->render();
|
||||
|
||||
$this->assertStringContainsString('Generado el 24/08/2026 13:53', $salesHtml);
|
||||
$this->assertStringContainsString('24/08/2026 13:53', $salesHtml);
|
||||
$this->assertStringContainsString('Generado el 24/08/2026 13:53', $modificationsHtml);
|
||||
$this->assertStringContainsString('13:53:00', $modificationsHtml);
|
||||
}
|
||||
|
||||
private function tenant(): Tenant
|
||||
{
|
||||
return (new Tenant)->forceFill([
|
||||
|
||||
Reference in New Issue
Block a user