- Redesigned category create/edit modal with elegant wizard-style UI - Redesigned batch categorization modal with visual cards and better preview - Added missing i18n translations (common.continue, creating, remove) - Added budgets.general and wizard translations for ES, PT-BR, EN - Fixed 3 demo user transactions that were missing categories
292 lines
12 KiB
PHP
292 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
|
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
|
use PhpOffice\PhpSpreadsheet\Style\Border;
|
|
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
|
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
|
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
|
|
|
|
class LiabilityTemplateService
|
|
{
|
|
/**
|
|
* Gerar template Excel para importação de passivos
|
|
*/
|
|
public function generateTemplate(): Spreadsheet
|
|
{
|
|
$spreadsheet = new Spreadsheet();
|
|
|
|
// Criar aba de Parcelas (principal)
|
|
$installmentsSheet = $spreadsheet->getActiveSheet();
|
|
$installmentsSheet->setTitle('Parcelas');
|
|
$this->createInstallmentsSheet($installmentsSheet);
|
|
|
|
// Criar aba de Instruções
|
|
$instructionsSheet = $spreadsheet->createSheet();
|
|
$instructionsSheet->setTitle('Instrucciones');
|
|
$this->createInstructionsSheet($instructionsSheet);
|
|
|
|
// Criar aba de Exemplo
|
|
$exampleSheet = $spreadsheet->createSheet();
|
|
$exampleSheet->setTitle('Ejemplo');
|
|
$this->createExampleSheet($exampleSheet);
|
|
|
|
// Voltar para primeira aba
|
|
$spreadsheet->setActiveSheetIndex(0);
|
|
|
|
return $spreadsheet;
|
|
}
|
|
|
|
/**
|
|
* Criar aba de parcelas com cabeçalho e validações
|
|
*/
|
|
private function createInstallmentsSheet($sheet): void
|
|
{
|
|
// Definir largura das colunas
|
|
$sheet->getColumnDimension('A')->setWidth(12); // Nº
|
|
$sheet->getColumnDimension('B')->setWidth(15); // Fecha
|
|
$sheet->getColumnDimension('C')->setWidth(15); // Cuota
|
|
$sheet->getColumnDimension('D')->setWidth(15); // Intereses
|
|
$sheet->getColumnDimension('E')->setWidth(15); // Capital
|
|
$sheet->getColumnDimension('F')->setWidth(15); // Tasas/Seguros
|
|
$sheet->getColumnDimension('G')->setWidth(15); // Estado
|
|
$sheet->getColumnDimension('H')->setWidth(30); // Observaciones
|
|
|
|
// Cabeçalho principal - título
|
|
$sheet->setCellValue('A1', 'PLANTILLA DE IMPORTACIÓN - CUENTA PASIVO');
|
|
$sheet->mergeCells('A1:H1');
|
|
$sheet->getStyle('A1')->applyFromArray([
|
|
'font' => ['bold' => true, 'size' => 16, 'color' => ['rgb' => 'FFFFFF']],
|
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => '1E40AF']],
|
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
|
|
]);
|
|
$sheet->getRowDimension(1)->setRowHeight(30);
|
|
|
|
// Subtítulo
|
|
$sheet->setCellValue('A2', 'Rellene las columnas con los datos de sus cuotas. Las columnas marcadas con * son obligatorias.');
|
|
$sheet->mergeCells('A2:H2');
|
|
$sheet->getStyle('A2')->applyFromArray([
|
|
'font' => ['italic' => true, 'size' => 10, 'color' => ['rgb' => '6B7280']],
|
|
]);
|
|
|
|
// Cabeçalhos das colunas
|
|
$headers = [
|
|
'A3' => 'Nº Cuota *',
|
|
'B3' => 'Fecha Venc. *',
|
|
'C3' => 'Valor Cuota *',
|
|
'D3' => 'Intereses',
|
|
'E3' => 'Capital',
|
|
'F3' => 'Tasas/Seguros',
|
|
'G3' => 'Estado',
|
|
'H3' => 'Observaciones',
|
|
];
|
|
|
|
foreach ($headers as $cell => $value) {
|
|
$sheet->setCellValue($cell, $value);
|
|
}
|
|
|
|
// Estilo do cabeçalho
|
|
$sheet->getStyle('A3:H3')->applyFromArray([
|
|
'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
|
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => '3B82F6']],
|
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
|
|
'borders' => [
|
|
'allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => '1E40AF']],
|
|
],
|
|
]);
|
|
|
|
// Dicas sob o cabeçalho
|
|
$tips = [
|
|
'A4' => '1, 2, 3...',
|
|
'B4' => 'DD/MM/AAAA',
|
|
'C4' => '0.00',
|
|
'D4' => '0.00',
|
|
'E4' => '0.00',
|
|
'F4' => '0.00',
|
|
'G4' => 'Pendiente/Pagado',
|
|
'H4' => 'Texto libre',
|
|
];
|
|
|
|
foreach ($tips as $cell => $value) {
|
|
$sheet->setCellValue($cell, $value);
|
|
}
|
|
|
|
$sheet->getStyle('A4:H4')->applyFromArray([
|
|
'font' => ['italic' => true, 'size' => 9, 'color' => ['rgb' => '9CA3AF']],
|
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => 'F3F4F6']],
|
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
|
|
]);
|
|
|
|
// Área de dados (linhas 5-64 para até 60 parcelas)
|
|
for ($row = 5; $row <= 64; $row++) {
|
|
// Número da parcela
|
|
$sheet->setCellValue("A{$row}", $row - 4);
|
|
|
|
// Aplicar formato de número nas colunas de valores
|
|
$sheet->getStyle("C{$row}:F{$row}")->getNumberFormat()
|
|
->setFormatCode(NumberFormat::FORMAT_NUMBER_COMMA_SEPARATED1);
|
|
|
|
// Aplicar formato de data na coluna B
|
|
$sheet->getStyle("B{$row}")->getNumberFormat()
|
|
->setFormatCode('DD/MM/YYYY');
|
|
|
|
// Adicionar validação de lista para Estado
|
|
$validation = $sheet->getCell("G{$row}")->getDataValidation();
|
|
$validation->setType(DataValidation::TYPE_LIST);
|
|
$validation->setErrorStyle(DataValidation::STYLE_INFORMATION);
|
|
$validation->setAllowBlank(true);
|
|
$validation->setShowDropDown(true);
|
|
$validation->setFormula1('"Pendiente,Pagado,Vencido"');
|
|
|
|
// Bordas leves
|
|
$sheet->getStyle("A{$row}:H{$row}")->applyFromArray([
|
|
'borders' => [
|
|
'allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'E5E7EB']],
|
|
],
|
|
]);
|
|
|
|
// Alternar cores das linhas
|
|
if (($row - 5) % 2 == 1) {
|
|
$sheet->getStyle("A{$row}:H{$row}")->applyFromArray([
|
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => 'F9FAFB']],
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Congelar painel no cabeçalho
|
|
$sheet->freezePane('A5');
|
|
}
|
|
|
|
/**
|
|
* Criar aba de instruções
|
|
*/
|
|
private function createInstructionsSheet($sheet): void
|
|
{
|
|
$sheet->getColumnDimension('A')->setWidth(80);
|
|
|
|
$instructions = [
|
|
['INSTRUCCIONES DE USO', true, '1E40AF'],
|
|
['', false, 'FFFFFF'],
|
|
['1. DATOS OBLIGATORIOS', true, '059669'],
|
|
[' • Nº Cuota: Número secuencial de la cuota (1, 2, 3...)', false, '000000'],
|
|
[' • Fecha Venc.: Fecha de vencimiento en formato DD/MM/AAAA', false, '000000'],
|
|
[' • Valor Cuota: Valor total de la cuota a pagar', false, '000000'],
|
|
['', false, 'FFFFFF'],
|
|
['2. DATOS OPCIONALES (recomendados)', true, '059669'],
|
|
[' • Intereses: Parte de la cuota correspondiente a intereses', false, '000000'],
|
|
[' • Capital: Parte de la cuota correspondiente a amortización', false, '000000'],
|
|
[' • Tasas/Seguros: Otros cargos incluidos en la cuota', false, '000000'],
|
|
[' • Estado: Pendiente, Pagado o Vencido', false, '000000'],
|
|
[' • Observaciones: Notas adicionales', false, '000000'],
|
|
['', false, 'FFFFFF'],
|
|
['3. TIPOS DE CONTRATOS SOPORTADOS', true, '059669'],
|
|
[' • Préstamo Personal (Sistema PRICE - cuota fija)', false, '000000'],
|
|
[' • Financiación de Vehículo', false, '000000'],
|
|
[' • Financiación Inmobiliaria (Sistema SAC o PRICE)', false, '000000'],
|
|
[' • Consorcio (cuotas variables)', false, '000000'],
|
|
[' • Leasing', false, '000000'],
|
|
['', false, 'FFFFFF'],
|
|
['4. SISTEMA PRICE (Cuota Fija)', true, '059669'],
|
|
[' En este sistema:', false, '000000'],
|
|
[' • La cuota es CONSTANTE todos los meses', false, '000000'],
|
|
[' • Los intereses DISMINUYEN cada mes', false, '000000'],
|
|
[' • La amortización AUMENTA cada mes', false, '000000'],
|
|
[' • Cuota = Intereses + Capital + Tasas', false, '000000'],
|
|
['', false, 'FFFFFF'],
|
|
['5. SISTEMA SAC (Amortización Constante)', true, '059669'],
|
|
[' En este sistema:', false, '000000'],
|
|
[' • La amortización es CONSTANTE todos los meses', false, '000000'],
|
|
[' • Los intereses DISMINUYEN cada mes', false, '000000'],
|
|
[' • La cuota DISMINUYE cada mes', false, '000000'],
|
|
['', false, 'FFFFFF'],
|
|
['6. CUOTA DE CARENCIA', true, '059669'],
|
|
[' Algunos contratos tienen una primera cuota de carencia:', false, '000000'],
|
|
[' • Solo se pagan intereses (sin amortización de capital)', false, '000000'],
|
|
[' • El capital amortizado en esta cuota debe ser 0', false, '000000'],
|
|
['', false, 'FFFFFF'],
|
|
['7. ESTADOS VÁLIDOS', true, '059669'],
|
|
[' • Pendiente: Cuota aún no pagada', false, '000000'],
|
|
[' • Pagado: Cuota ya abonada', false, '000000'],
|
|
[' • Vencido: Cuota no pagada y pasada la fecha de vencimiento', false, '000000'],
|
|
];
|
|
|
|
$row = 1;
|
|
foreach ($instructions as $item) {
|
|
$sheet->setCellValue("A{$row}", $item[0]);
|
|
|
|
$style = ['font' => ['color' => ['rgb' => $item[2]]]];
|
|
if ($item[1]) {
|
|
$style['font']['bold'] = true;
|
|
$style['font']['size'] = 12;
|
|
}
|
|
$sheet->getStyle("A{$row}")->applyFromArray($style);
|
|
|
|
$row++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Criar aba de exemplo preenchida
|
|
*/
|
|
private function createExampleSheet($sheet): void
|
|
{
|
|
// Copiar estrutura da aba de parcelas
|
|
$this->createInstallmentsSheet($sheet);
|
|
|
|
// Dados de exemplo (Empréstimo PRICE típico)
|
|
$exampleData = [
|
|
[1, '05/06/2025', 20.85, 20.85, 0.00, 0.00, 'Pagado', 'Cuota de carencia (solo intereses)'],
|
|
[2, '05/07/2025', 122.00, 48.33, 73.67, 0.00, 'Pagado', ''],
|
|
[3, '05/08/2025', 122.00, 47.68, 74.32, 0.00, 'Pagado', ''],
|
|
[4, '05/09/2025', 122.00, 47.01, 74.99, 0.00, 'Pagado', ''],
|
|
[5, '05/10/2025', 122.00, 46.35, 75.65, 0.00, 'Pagado', ''],
|
|
[6, '05/11/2025', 122.00, 45.68, 76.32, 0.00, 'Pagado', ''],
|
|
[7, '05/12/2025', 122.00, 45.00, 77.00, 0.00, 'Pendiente', ''],
|
|
[8, '05/01/2026', 122.00, 44.31, 77.69, 0.00, 'Pendiente', ''],
|
|
[9, '05/02/2026', 122.00, 43.62, 78.38, 0.00, 'Pendiente', ''],
|
|
[10, '05/03/2026', 122.00, 42.93, 79.07, 0.00, 'Pendiente', ''],
|
|
];
|
|
|
|
$row = 5;
|
|
foreach ($exampleData as $data) {
|
|
$sheet->setCellValue("A{$row}", $data[0]);
|
|
$sheet->setCellValue("B{$row}", $data[1]);
|
|
$sheet->setCellValue("C{$row}", $data[2]);
|
|
$sheet->setCellValue("D{$row}", $data[3]);
|
|
$sheet->setCellValue("E{$row}", $data[4]);
|
|
$sheet->setCellValue("F{$row}", $data[5]);
|
|
$sheet->setCellValue("G{$row}", $data[6]);
|
|
$sheet->setCellValue("H{$row}", $data[7]);
|
|
$row++;
|
|
}
|
|
|
|
// Destacar que é exemplo
|
|
$sheet->setCellValue('A1', 'EJEMPLO - PRÉSTAMO PERSONAL (Sistema PRICE)');
|
|
$sheet->getStyle('A1')->applyFromArray([
|
|
'font' => ['bold' => true, 'size' => 16, 'color' => ['rgb' => 'FFFFFF']],
|
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => '059669']],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Salvar template em arquivo
|
|
*/
|
|
public function saveTemplate(string $path): void
|
|
{
|
|
$spreadsheet = $this->generateTemplate();
|
|
$writer = new Xlsx($spreadsheet);
|
|
$writer->save($path);
|
|
}
|
|
|
|
/**
|
|
* Obter caminho do template
|
|
*/
|
|
public static function getTemplatePath(): string
|
|
{
|
|
return storage_path('app/templates/passivo_template.xlsx');
|
|
}
|
|
}
|