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()
{
$query = Bill::query();
$query = Bill::query()->with('supplier');
if (request()->filled('q')) {
$query->where('name', 'like', '%' . request()->q . '%');
@ -108,7 +108,7 @@ public function togglePaid(Bill $bill)
*/
public function export()
{
$bills = Bill::where('paid', false)
$bills = Bill::with('supplier')->where('paid', false)
->orderBy('deadline')
->orderBy('created_at')
->get();
@ -132,14 +132,14 @@ public function export()
];
// Título
$sheet->mergeCells('A1:E1');
$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:E2');
$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']],
@ -147,11 +147,11 @@ public function export()
]);
// 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) {
$sheet->setCellValue($cell, $text);
}
$sheet->getStyle('A4:E4')->applyFromArray($styleHeader);
$sheet->getStyle('A4:F4')->applyFromArray($styleHeader);
$sheet->getRowDimension(4)->setRowHeight(22);
// Datos
@ -174,26 +174,27 @@ public function export()
$sheet->setCellValue('A' . $row, $i + 1);
$sheet->setCellValue('B' . $row, $bill->name);
$sheet->setCellValue('C' . $row, (float) $bill->cost);
$sheet->setCellValue('D' . $row, $deadline?->format('d/m/Y') ?? '—');
$sheet->setCellValue('E' . $row, $daysLabel);
$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('C' . $row)->getNumberFormat()->setFormatCode('$#,##0.00');
$sheet->getStyle('A' . $row . ':E' . $row)->applyFromArray($styleData);
$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('C' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
$sheet->getStyle('D' . $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('E' . $row)->getFont()->getColor()->setRGB('1A7A1A');
$sheet->getStyle('F' . $row)->getFont()->getColor()->setRGB('1A7A1A');
} elseif ($daysLeft !== null && $daysLeft < 0) {
$sheet->getStyle('D' . $row . ':E' . $row)->getFont()->getColor()->setRGB('CC0000');
$sheet->getStyle('D' . $row . ':E' . $row)->getFont()->setBold(true);
$sheet->getStyle('E' . $row . ':F' . $row)->getFont()->getColor()->setRGB('CC0000');
$sheet->getStyle('E' . $row . ':F' . $row)->getFont()->setBold(true);
} elseif ($daysLeft === 0) {
$sheet->getStyle('E' . $row)->getFont()->getColor()->setRGB('B45309');
$sheet->getStyle('E' . $row)->getFont()->setBold(true);
$sheet->getStyle('F' . $row)->getFont()->getColor()->setRGB('B45309');
$sheet->getStyle('F' . $row)->getFont()->setBold(true);
}
$total += (float) $bill->cost;
@ -201,10 +202,10 @@ public function export()
}
// Total
$sheet->setCellValue('B' . $row, 'TOTAL PENDIENTE');
$sheet->setCellValue('C' . $row, $total);
$sheet->getStyle('C' . $row)->getNumberFormat()->setFormatCode('$#,##0.00');
$sheet->getStyle('B' . $row . ':C' . $row)->applyFromArray([
$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]],
@ -213,10 +214,11 @@ public function export()
// Anchos de columna
$sheet->getColumnDimension('A')->setWidth(6);
$sheet->getColumnDimension('B')->setWidth(40);
$sheet->getColumnDimension('C')->setWidth(18);
$sheet->getColumnDimension('D')->setWidth(16);
$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';

View File

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

View File

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

View File

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