pdv.backend/app/Http/Controllers/App/BillController.php

235 lines
8.6 KiB
PHP

<?php namespace App\Http\Controllers\App;
use App\Http\Requests\App\BillStoreRequest;
use App\Http\Requests\App\BillUpdateRequest;
use App\Models\Bill;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
use Notsoweb\ApiResponse\Enums\ApiResponse;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use Carbon\Carbon;
class BillController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$query = Bill::query()->with('supplier');
if (request()->filled('q')) {
$query->where('name', 'like', '%' . request()->q . '%');
}
$bills = $query->orderByDesc('created_at')
->paginate(config('app.pagination', 15));
return ApiResponse::OK->response(['bills' => $bills]);
}
/**
* Store a newly created resource in storage.
*/
public function store(BillStoreRequest $request)
{
$data = $request->validated();
if ($request->hasFile('file')) {
$data['file_path'] = $request->file('file')->store('bills', 'public');
}
unset($data['file']);
$bill = Bill::create($data);
return ApiResponse::CREATED->response(['bill' => $bill]);
}
/**
* Display the specified resource.
*/
public function show(Bill $bill)
{
return ApiResponse::OK->response(['bill' => $bill]);
}
/**
* Update the specified resource in storage.
*/
public function update(BillUpdateRequest $request, Bill $bill)
{
$data = $request->validated();
if ($request->hasFile('file')) {
if ($bill->file_path) {
Storage::disk('public')->delete($bill->file_path);
}
$data['file_path'] = $request->file('file')->store('bills', 'public');
}
unset($data['file']);
$bill->update($data);
return ApiResponse::OK->response(['bill' => $bill->fresh()]);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Bill $bill)
{
if ($bill->file_path) {
Storage::disk('public')->delete($bill->file_path);
}
$bill->delete();
return ApiResponse::OK->response();
}
/**
* Alterna el estado de pago de la factura.
*/
public function togglePaid(Bill $bill)
{
$bill->update(['paid' => !$bill->paid]);
return ApiResponse::OK->response(['bill' => $bill->fresh()]);
}
/**
* Exporta a Excel las facturas pendientes de pago.
*/
public function export()
{
$bills = Bill::with('supplier')->where('paid', false)
->orderBy('deadline')
->orderBy('created_at')
->get();
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setTitle('Facturas por Pagar');
$sheet->getParent()->getDefaultStyle()->getFont()->setName('Arial')->setSize(10);
$styleHeader = [
'font' => ['bold' => true, 'size' => 10, 'color' => ['rgb' => 'FFFFFF']],
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '4472C4']],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER],
'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]],
];
$styleData = [
'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'D0D0D0']]],
'alignment' => ['vertical' => Alignment::VERTICAL_CENTER],
];
// Título
$sheet->mergeCells('A1:F1');
$sheet->setCellValue('A1', 'FACTURAS PENDIENTES DE PAGO');
$sheet->getStyle('A1')->applyFromArray([
'font' => ['bold' => true, 'size' => 14],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
]);
$sheet->mergeCells('A2:F2');
$sheet->setCellValue('A2', 'Generado el ' . Carbon::now()->format('d/m/Y H:i'));
$sheet->getStyle('A2')->applyFromArray([
'font' => ['italic' => true, 'color' => ['rgb' => '666666']],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
]);
// Encabezados
$headers = ['A4' => '#', 'B4' => 'NOMBRE', 'C4' => 'PROVEEDOR', 'D4' => 'COSTO', 'E4' => 'FECHA LÍMITE', 'F4' => 'DÍAS RESTANTES'];
foreach ($headers as $cell => $text) {
$sheet->setCellValue($cell, $text);
}
$sheet->getStyle('A4:F4')->applyFromArray($styleHeader);
$sheet->getRowDimension(4)->setRowHeight(22);
// Datos
$row = 5;
$total = 0;
foreach ($bills as $i => $bill) {
$deadline = $bill->deadline ? Carbon::parse($bill->deadline) : null;
// Positivo = días que faltan, negativo = días vencida
$daysLeft = $deadline ? (int) Carbon::today()->diffInDays($deadline, false) : null;
if ($daysLeft === null) {
$daysLabel = '—';
} elseif ($daysLeft === 0) {
$daysLabel = 'Hoy';
} elseif ($daysLeft > 0) {
$daysLabel = "+{$daysLeft}";
} else {
$daysLabel = (string) $daysLeft; // ya incluye el signo negativo
}
$sheet->setCellValue('A' . $row, $i + 1);
$sheet->setCellValue('B' . $row, $bill->name);
$sheet->setCellValue('C' . $row, $bill->supplier?->business_name ?? '—');
$sheet->setCellValue('D' . $row, (float) $bill->cost);
$sheet->setCellValue('E' . $row, $deadline?->format('d/m/Y') ?? '—');
$sheet->setCellValue('F' . $row, $daysLabel);
$sheet->getStyle('D' . $row)->getNumberFormat()->setFormatCode('$#,##0.00');
$sheet->getStyle('A' . $row . ':F' . $row)->applyFromArray($styleData);
$sheet->getStyle('A' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getStyle('D' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
$sheet->getStyle('E' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getStyle('F' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
// Verde si quedan días, rojo si está vencida
if ($daysLeft !== null && $daysLeft > 0) {
$sheet->getStyle('F' . $row)->getFont()->getColor()->setRGB('1A7A1A');
} elseif ($daysLeft !== null && $daysLeft < 0) {
$sheet->getStyle('E' . $row . ':F' . $row)->getFont()->getColor()->setRGB('CC0000');
$sheet->getStyle('E' . $row . ':F' . $row)->getFont()->setBold(true);
} elseif ($daysLeft === 0) {
$sheet->getStyle('F' . $row)->getFont()->getColor()->setRGB('B45309');
$sheet->getStyle('F' . $row)->getFont()->setBold(true);
}
$total += (float) $bill->cost;
$row++;
}
// Total
$sheet->setCellValue('C' . $row, 'TOTAL PENDIENTE');
$sheet->setCellValue('D' . $row, $total);
$sheet->getStyle('D' . $row)->getNumberFormat()->setFormatCode('$#,##0.00');
$sheet->getStyle('C' . $row . ':D' . $row)->applyFromArray([
'font' => ['bold' => true, 'size' => 11],
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => 'FFF2CC']],
'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_RIGHT],
]);
// Anchos de columna
$sheet->getColumnDimension('A')->setWidth(6);
$sheet->getColumnDimension('B')->setWidth(36);
$sheet->getColumnDimension('C')->setWidth(30);
$sheet->getColumnDimension('D')->setWidth(18);
$sheet->getColumnDimension('E')->setWidth(16);
$sheet->getColumnDimension('F')->setWidth(16);
// Generar archivo
$fileName = 'Facturas_Pendientes_' . Carbon::now()->format('Ymd_His') . '.xlsx';
$filePath = storage_path('app/temp/' . $fileName);
if (!file_exists(dirname($filePath))) mkdir(dirname($filePath), 0755, true);
(new Xlsx($spreadsheet))->save($filePath);
return response()->download($filePath, $fileName, [
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
])->deleteFileAfterSend(true);
}
}