feat: agregar relación de proveedor a facturas y actualizar solicitudes y migraciones

This commit is contained in:
Juan Felipe Zapata Moreno 2026-03-21 12:39:10 -06:00
parent 8eedb89172
commit da49a9a75a
5 changed files with 76 additions and 35 deletions

View File

@ -20,7 +20,7 @@ class BillController extends Controller
*/ */
public function index() public function index()
{ {
$query = Bill::query(); $query = Bill::query()->with('supplier');
if (request()->filled('q')) { if (request()->filled('q')) {
$query->where('name', 'like', '%' . request()->q . '%'); $query->where('name', 'like', '%' . request()->q . '%');
@ -108,7 +108,7 @@ public function togglePaid(Bill $bill)
*/ */
public function export() public function export()
{ {
$bills = Bill::where('paid', false) $bills = Bill::with('supplier')->where('paid', false)
->orderBy('deadline') ->orderBy('deadline')
->orderBy('created_at') ->orderBy('created_at')
->get(); ->get();
@ -132,14 +132,14 @@ public function export()
]; ];
// Título // Título
$sheet->mergeCells('A1:E1'); $sheet->mergeCells('A1:F1');
$sheet->setCellValue('A1', 'FACTURAS PENDIENTES DE PAGO'); $sheet->setCellValue('A1', 'FACTURAS PENDIENTES DE PAGO');
$sheet->getStyle('A1')->applyFromArray([ $sheet->getStyle('A1')->applyFromArray([
'font' => ['bold' => true, 'size' => 14], 'font' => ['bold' => true, 'size' => 14],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
]); ]);
$sheet->mergeCells('A2:E2'); $sheet->mergeCells('A2:F2');
$sheet->setCellValue('A2', 'Generado el ' . Carbon::now()->format('d/m/Y H:i')); $sheet->setCellValue('A2', 'Generado el ' . Carbon::now()->format('d/m/Y H:i'));
$sheet->getStyle('A2')->applyFromArray([ $sheet->getStyle('A2')->applyFromArray([
'font' => ['italic' => true, 'color' => ['rgb' => '666666']], 'font' => ['italic' => true, 'color' => ['rgb' => '666666']],
@ -147,11 +147,11 @@ public function export()
]); ]);
// Encabezados // Encabezados
$headers = ['A4' => '#', 'B4' => 'NOMBRE', 'C4' => 'COSTO', 'D4' => 'FECHA LÍMITE', 'E4' => 'DÍAS RESTANTES']; $headers = ['A4' => '#', 'B4' => 'NOMBRE', 'C4' => 'PROVEEDOR', 'D4' => 'COSTO', 'E4' => 'FECHA LÍMITE', 'F4' => 'DÍAS RESTANTES'];
foreach ($headers as $cell => $text) { foreach ($headers as $cell => $text) {
$sheet->setCellValue($cell, $text); $sheet->setCellValue($cell, $text);
} }
$sheet->getStyle('A4:E4')->applyFromArray($styleHeader); $sheet->getStyle('A4:F4')->applyFromArray($styleHeader);
$sheet->getRowDimension(4)->setRowHeight(22); $sheet->getRowDimension(4)->setRowHeight(22);
// Datos // Datos
@ -174,26 +174,27 @@ public function export()
$sheet->setCellValue('A' . $row, $i + 1); $sheet->setCellValue('A' . $row, $i + 1);
$sheet->setCellValue('B' . $row, $bill->name); $sheet->setCellValue('B' . $row, $bill->name);
$sheet->setCellValue('C' . $row, (float) $bill->cost); $sheet->setCellValue('C' . $row, $bill->supplier?->business_name ?? '—');
$sheet->setCellValue('D' . $row, $deadline?->format('d/m/Y') ?? '—'); $sheet->setCellValue('D' . $row, (float) $bill->cost);
$sheet->setCellValue('E' . $row, $daysLabel); $sheet->setCellValue('E' . $row, $deadline?->format('d/m/Y') ?? '—');
$sheet->setCellValue('F' . $row, $daysLabel);
$sheet->getStyle('C' . $row)->getNumberFormat()->setFormatCode('$#,##0.00'); $sheet->getStyle('D' . $row)->getNumberFormat()->setFormatCode('$#,##0.00');
$sheet->getStyle('A' . $row . ':E' . $row)->applyFromArray($styleData); $sheet->getStyle('A' . $row . ':F' . $row)->applyFromArray($styleData);
$sheet->getStyle('A' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $sheet->getStyle('A' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getStyle('C' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT); $sheet->getStyle('D' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
$sheet->getStyle('D' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getStyle('E' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $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 // Verde si quedan días, rojo si está vencida
if ($daysLeft !== null && $daysLeft > 0) { if ($daysLeft !== null && $daysLeft > 0) {
$sheet->getStyle('E' . $row)->getFont()->getColor()->setRGB('1A7A1A'); $sheet->getStyle('F' . $row)->getFont()->getColor()->setRGB('1A7A1A');
} elseif ($daysLeft !== null && $daysLeft < 0) { } elseif ($daysLeft !== null && $daysLeft < 0) {
$sheet->getStyle('D' . $row . ':E' . $row)->getFont()->getColor()->setRGB('CC0000'); $sheet->getStyle('E' . $row . ':F' . $row)->getFont()->getColor()->setRGB('CC0000');
$sheet->getStyle('D' . $row . ':E' . $row)->getFont()->setBold(true); $sheet->getStyle('E' . $row . ':F' . $row)->getFont()->setBold(true);
} elseif ($daysLeft === 0) { } elseif ($daysLeft === 0) {
$sheet->getStyle('E' . $row)->getFont()->getColor()->setRGB('B45309'); $sheet->getStyle('F' . $row)->getFont()->getColor()->setRGB('B45309');
$sheet->getStyle('E' . $row)->getFont()->setBold(true); $sheet->getStyle('F' . $row)->getFont()->setBold(true);
} }
$total += (float) $bill->cost; $total += (float) $bill->cost;
@ -201,10 +202,10 @@ public function export()
} }
// Total // Total
$sheet->setCellValue('B' . $row, 'TOTAL PENDIENTE'); $sheet->setCellValue('C' . $row, 'TOTAL PENDIENTE');
$sheet->setCellValue('C' . $row, $total); $sheet->setCellValue('D' . $row, $total);
$sheet->getStyle('C' . $row)->getNumberFormat()->setFormatCode('$#,##0.00'); $sheet->getStyle('D' . $row)->getNumberFormat()->setFormatCode('$#,##0.00');
$sheet->getStyle('B' . $row . ':C' . $row)->applyFromArray([ $sheet->getStyle('C' . $row . ':D' . $row)->applyFromArray([
'font' => ['bold' => true, 'size' => 11], 'font' => ['bold' => true, 'size' => 11],
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => 'FFF2CC']], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => 'FFF2CC']],
'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]], 'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]],
@ -213,10 +214,11 @@ public function export()
// Anchos de columna // Anchos de columna
$sheet->getColumnDimension('A')->setWidth(6); $sheet->getColumnDimension('A')->setWidth(6);
$sheet->getColumnDimension('B')->setWidth(40); $sheet->getColumnDimension('B')->setWidth(36);
$sheet->getColumnDimension('C')->setWidth(18); $sheet->getColumnDimension('C')->setWidth(30);
$sheet->getColumnDimension('D')->setWidth(16); $sheet->getColumnDimension('D')->setWidth(18);
$sheet->getColumnDimension('E')->setWidth(16); $sheet->getColumnDimension('E')->setWidth(16);
$sheet->getColumnDimension('F')->setWidth(16);
// Generar archivo // Generar archivo
$fileName = 'Facturas_Pendientes_' . Carbon::now()->format('Ymd_His') . '.xlsx'; $fileName = 'Facturas_Pendientes_' . Carbon::now()->format('Ymd_His') . '.xlsx';

View File

@ -20,11 +20,12 @@ public function authorize(): bool
public function rules(): array public function rules(): array
{ {
return [ return [
'name' => ['required', 'string', 'max:255'], 'name' => ['required', 'string', 'max:255'],
'cost' => ['required', 'numeric', 'min:0'], 'supplier_id' => ['nullable', 'exists:suppliers,id'],
'deadline' => ['nullable', 'date'], 'cost' => ['required', 'numeric', 'min:0'],
'paid' => ['boolean'], 'deadline' => ['nullable', 'date'],
'file' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:10240'], 'paid' => ['boolean'],
'file' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:10240'],
]; ];
} }
@ -35,6 +36,7 @@ public function messages(): array
{ {
return [ return [
'name.required' => 'El nombre de la factura es obligatorio.', 'name.required' => 'El nombre de la factura es obligatorio.',
'supplier_id.exists' => 'El proveedor seleccionado no es válido.',
'cost.required' => 'El costo es obligatorio.', 'cost.required' => 'El costo es obligatorio.',
'cost.numeric' => 'El costo debe ser un número válido.', 'cost.numeric' => 'El costo debe ser un número válido.',
'cost.min' => 'El costo debe ser un valor positivo.', 'cost.min' => 'El costo debe ser un valor positivo.',

View File

@ -20,11 +20,12 @@ public function authorize(): bool
public function rules(): array public function rules(): array
{ {
return [ return [
'name' => ['required', 'string', 'max:255'], 'name' => ['required', 'string', 'max:255'],
'cost' => ['required', 'numeric', 'min:0'], 'supplier_id' => ['nullable', 'exists:suppliers,id'],
'deadline' => ['nullable', 'date'], 'cost' => ['required', 'numeric', 'min:0'],
'paid' => ['boolean'], 'deadline' => ['nullable', 'date'],
'file' => ['sometimes', 'nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:10240'], 'paid' => ['boolean'],
'file' => ['sometimes', 'nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:10240'],
]; ];
} }
@ -35,6 +36,7 @@ public function messages(): array
{ {
return [ return [
'name.required' => 'El nombre de la factura es obligatorio.', 'name.required' => 'El nombre de la factura es obligatorio.',
'supplier_id.exists' => 'El proveedor seleccionado no es válido.',
'cost.required' => 'El costo es obligatorio.', 'cost.required' => 'El costo es obligatorio.',
'cost.numeric' => 'El costo debe ser un número válido.', 'cost.numeric' => 'El costo debe ser un número válido.',
'cost.min' => 'El costo debe ser un valor positivo.', 'cost.min' => 'El costo debe ser un valor positivo.',

View File

@ -10,6 +10,7 @@ class Bill extends Model
{ {
protected $fillable = [ protected $fillable = [
'name', 'name',
'supplier_id',
'cost', 'cost',
'file_path', 'file_path',
'deadline', 'deadline',
@ -23,6 +24,11 @@ class Bill extends Model
protected $appends = ['file_url']; protected $appends = ['file_url'];
public function supplier()
{
return $this->belongsTo(Supplier::class);
}
public function getFileUrlAttribute(): ?string public function getFileUrlAttribute(): ?string
{ {
if (!$this->file_path) return null; if (!$this->file_path) return null;

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('bills', function (Blueprint $table) {
$table->foreignId('supplier_id')->after('name')->nullable()->constrained('suppliers')->nullOnDelete();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('bills', function (Blueprint $table) {
$table->dropForeign(['supplier_id']);
$table->dropColumn('supplier_id');
});
}
};