$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 $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 $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 match ($status) { Purchase::STATUS_PAID => 'Confirmado', Purchase::STATUS_CREATED => 'Por completar datos', Purchase::STATUS_PENDING_PAYMENT, Purchase::STATUS_IN_REVIEW => 'Esperando pago', default => 'Anulado', }; } }