webmoney/backend/app/Services/LiabilityTemplateService.php
marcoitaloesp-ai 9c9d6443e7
v1.57.0: Redesign category modals + i18n updates + demo transactions fix
- 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
2025-12-18 19:06:07 +00:00

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');
}
}