diff --git a/Controller/Modelo130.php b/Controller/Modelo130.php index 9fd40d0..6583645 100644 --- a/Controller/Modelo130.php +++ b/Controller/Modelo130.php @@ -21,12 +21,15 @@ use FacturaScripts\Core\Base\Controller; use FacturaScripts\Core\DataSrc\Ejercicios; +use FacturaScripts\Core\Response; use FacturaScripts\Core\Tools; use FacturaScripts\Core\Where; use FacturaScripts\Dinamic\Model\Ejercicio; +use FacturaScripts\Dinamic\Model\Empresa; use FacturaScripts\Dinamic\Model\FormaPago; use FacturaScripts\Dinamic\Model\Subcuenta130; use FacturaScripts\Dinamic\Lib\Modelo130 as DinModelo130; +use FacturaScripts\Dinamic\Lib\Modelo130Export as DinModelo130Export; /** * Description of Modelo130 @@ -171,6 +174,56 @@ public function privateCore(&$response, $user, $permissions) $this->gastosJustificacionPct = (float)$this->request->request->get('gastosJustificacionPct', 7.0); $this->result = DinModelo130::generate($this->codejercicio, $this->period, $this->applyGastosJustificacion, $this->todeduct, $this->gastosJustificacionPct); + + if ($action === 'download') { + $this->downloadFile($response); + } + } + + protected function downloadFile(Response $response): void + { + if (empty($this->result)) { + Tools::log()->warning('no-data'); + return; + } + + $empresa = new Empresa(); + if (false === $empresa->load($this->result['exercise']->idempresa)) { + Tools::log()->error('company-not-found'); + return; + } + + // validamos los datos mínimos para no generar un fichero corrupto + $nif = DinModelo130Export::formatNif($empresa->cifnif ?? ''); + if (strlen($nif) !== 9) { + Tools::log()->error('aeat-file-invalid-nif'); + return; + } + + if (empty(trim($empresa->nombre ?? ''))) { + Tools::log()->error('aeat-file-missing-company-name'); + return; + } + + $year = date('Y', strtotime($this->result['exercise']->fechainicio)); + if (strlen($year) !== 4) { + Tools::log()->error('aeat-file-invalid-exercise'); + return; + } + + $content = DinModelo130Export::generate($this->result, $empresa, $this->period, $year); + if (strlen($content) !== DinModelo130Export::FILE_LENGTH) { + Tools::log()->error('aeat-file-invalid-length'); + return; + } + + $filename = 'modelo130_' . $year . '_' . DinModelo130Export::getPeriodNumber($this->period) . '.txt'; + + $response->headers->set('Content-Type', 'text/plain; charset=ISO-8859-1'); + $response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"'); + $response->setContent(mb_convert_encoding($content, 'ISO-8859-1', 'UTF-8')); + $response->send(); + exit; } protected function addDeductibleSubaccount(): void diff --git a/Lib/Modelo130Export.php b/Lib/Modelo130Export.php new file mode 100644 index 0000000..25ff419 --- /dev/null +++ b/Lib/Modelo130Export.php @@ -0,0 +1,211 @@ + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +namespace FacturaScripts\Plugins\Modelo130\Lib; + +use FacturaScripts\Dinamic\Model\Empresa; + +/** + * Genera el fichero de presentación del Modelo 130 según el diseño de + * registro oficial de la AEAT (DR130e15v12, Orden HAP/258/2015, ejercicios + * 2019 y siguientes): sobre + zona (328 posiciones), + * página 1 de 600 posiciones y cierre . + * + * @author Abderrahim Darghal Belkacemi + */ +class Modelo130Export +{ + /** Longitud total del fichero: 328 de sobre + 600 de página + 18 de cierre. */ + const FILE_LENGTH = 946; + + /** + * Genera el contenido completo del fichero a partir del resultado + * calculado por Modelo130::generate(). + */ + public static function generate(array $result, Empresa $empresa, string $period, string $year): string + { + $periodo = static::getPeriodNumber($period); + $nif = static::formatNif($empresa->cifnif ?? ''); + + // registro inicial: + $content = ''; + + // zona : 70 blancos reservados + versión del programa (4) + 4 blancos + // + NIF de la empresa desarrolladora (9) + 213 blancos reservados + $content .= '' . str_repeat(' ', 300) . ''; + + // contenido de la página 1 (600 posiciones) + $content .= static::generatePage1($result, $empresa, $nif, $year, $periodo); + + // registro de cierre + $content .= ''; + + return $content; + } + + /** + * Tipo de declaración: I (ingreso), N (negativa) o B (resultado a deducir). + */ + public static function getDeclarationType(float $result): string + { + if ($result > 0) { + return 'I'; + } + + return $result < 0 ? 'B' : 'N'; + } + + public static function getPeriodNumber(string $period): string + { + return match ($period) { + 'T2' => '2T', + 'T3' => '3T', + 'T4' => '4T', + default => '1T', + }; + } + + public static function formatAlphanumeric(string $value, int $length): string + { + // solo se admiten letras, números y blancos, alineados a la izquierda + $value = strtoupper(static::removeAccents($value)); + $value = preg_replace('/[^A-Z0-9 ]/', '', $value); + + return str_pad(substr($value, 0, $length), $length, ' ', STR_PAD_RIGHT); + } + + public static function formatNif(string $value): string + { + $value = strtoupper(static::removeAccents($value)); + + return preg_replace('/[^A-Z0-9]/', '', $value); + } + + public static function formatNumeric(float $value, bool $signed = false): string + { + // 17 posiciones: 15 enteros + 2 decimales, sin separador, con ceros a la izquierda; + // los importes negativos llevan una N en la primera posición + $cents = (int)round(abs($value) * 100); + if ($signed && $value < 0) { + return 'N' . str_pad((string)$cents, 16, '0', STR_PAD_LEFT); + } + + return str_pad((string)$cents, 17, '0', STR_PAD_LEFT); + } + + public static function splitDeclarantName(string $fullName): array + { + // el diseño de registro separa apellidos (60) y nombre (20); si el nombre de la + // empresa tiene varias palabras, la primera se toma como nombre y el resto como apellidos + $parts = preg_split('/\s+/', trim($fullName), -1, PREG_SPLIT_NO_EMPTY); + if (count($parts) < 2) { + return [trim($fullName), '']; + } + + $nombre = array_shift($parts); + return [implode(' ', $parts), $nombre]; + } + + protected static function generatePage1(array $result, Empresa $empresa, string $nif, string $year, string $periodo): string + { + // I. Actividades económicas en estimación directa + $box01 = (float)$result['taxbaseIngresos']; + $box02 = round((float)$result['taxbaseGastos'] + (float)$result['gastosJustificacion'], 2); + $box03 = round($box01 - $box02, 2); + $box04 = (float)$result['afterdeduct']; + $box05 = max(0.0, (float)$result['positivosTrimestres']); + $box06 = (float)$result['taxbaseRetenciones']; + $box07 = round($box04 - $box05 - $box06, 2); + + // II. Actividades agrícolas, ganaderas, forestales y pesqueras (no soportadas) + $box08 = $box09 = $box10 = $box11 = 0.0; + + // III. Total liquidación: si la suma de [07] y [11] es negativa, se consigna cero + $box12 = max(0.0, round($box07 + $box11, 2)); + $box13 = 0.0; + $box14 = round($box12 - $box13, 2); + $box15 = $box16 = 0.0; + $box17 = round($box14 - $box15 - $box16, 2); + $box18 = 0.0; + $box19 = round($box17 - $box18, 2); + + [$apellidos, $nombre] = static::splitDeclarantName($empresa->nombre ?? ''); + + $page = ''; + + // indicador de página complementaria (en blanco) + $page .= ' '; + + // tipo de declaración: I (ingreso), N (negativa) o B (resultado a deducir) + $page .= static::getDeclarationType($box19); + + // declarante + $page .= static::formatAlphanumeric($nif, 9); + $page .= static::formatAlphanumeric($apellidos, 60); + $page .= static::formatAlphanumeric($nombre, 20); + + // devengo + $page .= $year; + $page .= $periodo; + + // liquidación: casillas [01] a [19], 17 posiciones cada una + $page .= static::formatNumeric($box01); + $page .= static::formatNumeric($box02); + $page .= static::formatNumeric($box03, true); + $page .= static::formatNumeric($box04); + $page .= static::formatNumeric($box05); + $page .= static::formatNumeric($box06); + $page .= static::formatNumeric($box07, true); + $page .= static::formatNumeric($box08); + $page .= static::formatNumeric($box09); + $page .= static::formatNumeric($box10); + $page .= static::formatNumeric($box11, true); + $page .= static::formatNumeric($box12); + $page .= static::formatNumeric($box13); + $page .= static::formatNumeric($box14, true); + $page .= static::formatNumeric($box15); + $page .= static::formatNumeric($box16); + $page .= static::formatNumeric($box17, true); + $page .= static::formatNumeric($box18); + $page .= static::formatNumeric($box19, true); + + // declaración complementaria (blanco), justificante anterior (13) e IBAN (34) + $page .= ' '; + $page .= str_repeat(' ', 13); + $page .= str_repeat(' ', 34); + + // reservado AEAT (96) y sello electrónico (13) + $page .= str_repeat(' ', 109); + + // indicador de fin de registro + $page .= ''; + + return $page; + } + + protected static function removeAccents(string $text): string + { + $unwanted = [ + 'á' => 'a', 'é' => 'e', 'í' => 'i', 'ó' => 'o', 'ú' => 'u', + 'Á' => 'A', 'É' => 'E', 'Í' => 'I', 'Ó' => 'O', 'Ú' => 'U', + 'à' => 'a', 'è' => 'e', 'ì' => 'i', 'ò' => 'o', 'ù' => 'u', + 'ñ' => 'N', 'Ñ' => 'N', 'ü' => 'u', 'Ü' => 'U', 'ç' => 'C', 'Ç' => 'C' + ]; + return strtr($text, $unwanted); + } +} diff --git a/Test/main/Modelo130FileTest.php b/Test/main/Modelo130FileTest.php new file mode 100644 index 0000000..35eceaa --- /dev/null +++ b/Test/main/Modelo130FileTest.php @@ -0,0 +1,313 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +namespace FacturaScripts\Test\Plugins; + +use FacturaScripts\Dinamic\Model\Empresa; +use FacturaScripts\Plugins\Modelo130\Lib\Modelo130Export; +use PHPUnit\Framework\TestCase; +use ReflectionClass; + +/** + * Tests del fichero de presentación AEAT del Modelo 130. + * + * Verifican que el fichero generado por Modelo130Export respeta el diseño + * de registro oficial publicado por la AEAT (DR130e15v12, Orden HAP/258/2015, + * ejercicios 2019 y siguientes): sobre + zona + * (328 posiciones), página 1 de 600 posiciones y cierre + * . + * + * @author Abderrahim Darghal Belkacemi + */ +final class Modelo130FileTest extends TestCase +{ + /** Posición (base 0) donde empieza la página 1 dentro del fichero. */ + const PAGE_OFFSET = 328; + + /** Posición (base 0) de la primera casilla dentro de la página. */ + const BOXES_OFFSET = 108; + + public function testCompleteFilePositiveResult(): void + { + $expected = '' + . '' . str_repeat(' ', 300) . '' + . '' + . ' ' // página complementaria: en blanco + . 'I' // tipo de declaración: ingreso + . '12345678Z' // NIF + . 'PEREZ GARCIA' . str_repeat(' ', 48) // apellidos (60) + . 'MARIA' . str_repeat(' ', 15) // nombre (20) + . '2026' // ejercicio + . '2T' // período + . '00000000002500050' // [01] ingresos 25.000,50 + . '00000000001105027' // [02] gastos 10.000,25 + 1.050,02 + . '00000000001395023' // [03] rendimiento neto 13.950,23 + . '00000000000279005' // [04] 20% de [03] = 2.790,05 + . '00000000000050000' // [05] trimestres anteriores 500,00 + . '00000000000120075' // [06] retenciones 1.200,75 + . '00000000000108930' // [07] = [04]-[05]-[06] = 1.089,30 + . '00000000000000000' // [08] agrícolas: no soportado + . '00000000000000000' // [09] + . '00000000000000000' // [10] + . '00000000000000000' // [11] + . '00000000000108930' // [12] = [07]+[11] + . '00000000000000000' // [13] + . '00000000000108930' // [14] = [12]-[13] + . '00000000000000000' // [15] + . '00000000000000000' // [16] + . '00000000000108930' // [17] = [14]-[15]-[16] + . '00000000000000000' // [18] + . '00000000000108930' // [19] resultado + . ' ' // declaración complementaria + . str_repeat(' ', 13) // justificante anterior + . str_repeat(' ', 34) // IBAN + . str_repeat(' ', 96) // reservado AEAT + . str_repeat(' ', 13) // sello electrónico + . '' + . ''; + + $content = Modelo130Export::generate($this->sampleResult(), $this->sampleCompany(), 'T2', '2026'); + $this->assertSame($expected, $content); + } + + public function testCompleteFileNegativeResult(): void + { + $result = [ + 'taxbaseIngresos' => 1000.00, + 'taxbaseGastos' => 5000.00, + 'gastosJustificacion' => 0.0, + 'afterdeduct' => 0.0, + 'positivosTrimestres' => 0.0, + 'taxbaseRetenciones' => 100.00, + ]; + + $expected = '' + . '' . str_repeat(' ', 300) . '' + . '' + . ' ' // página complementaria: en blanco + . 'N' // tipo de declaración: negativa + . '12345678Z' // NIF + . 'PEREZ GARCIA' . str_repeat(' ', 48) // apellidos (60) + . 'MARIA' . str_repeat(' ', 15) // nombre (20) + . '2026' // ejercicio + . '3T' // período + . '00000000000100000' // [01] ingresos 1.000,00 + . '00000000000500000' // [02] gastos 5.000,00 + . 'N0000000000400000' // [03] rendimiento neto -4.000,00 + . '00000000000000000' // [04] no puede ser negativa + . '00000000000000000' // [05] + . '00000000000010000' // [06] retenciones 100,00 + . 'N0000000000010000' // [07] = -100,00 + . '00000000000000000' // [08] + . '00000000000000000' // [09] + . '00000000000000000' // [10] + . '00000000000000000' // [11] + . '00000000000000000' // [12] si [07]+[11] es negativa: cero + . '00000000000000000' // [13] + . '00000000000000000' // [14] + . '00000000000000000' // [15] + . '00000000000000000' // [16] + . '00000000000000000' // [17] + . '00000000000000000' // [18] + . '00000000000000000' // [19] resultado cero + . ' ' // declaración complementaria + . str_repeat(' ', 13) // justificante anterior + . str_repeat(' ', 34) // IBAN + . str_repeat(' ', 96) // reservado AEAT + . str_repeat(' ', 13) // sello electrónico + . '' + . ''; + + $content = Modelo130Export::generate($result, $this->sampleCompany(), 'T3', '2026'); + $this->assertSame($expected, $content); + } + + public function testFileStructure(): void + { + $content = Modelo130Export::generate($this->sampleResult(), $this->sampleCompany(), 'T2', '2026'); + + // longitud total exacta y sin saltos de línea + $this->assertSame(Modelo130Export::FILE_LENGTH, strlen($content)); + $this->assertStringNotContainsString("\n", $content); + $this->assertStringNotContainsString("\r", $content); + + // sobre: apertura, zona reservada en blanco y cierre + $this->assertSame('', substr($content, 0, 17)); + $this->assertSame('', substr($content, 17, 5)); + $this->assertSame(str_repeat(' ', 300), substr($content, 22, 300)); + $this->assertSame('', substr($content, 322, 6)); + $this->assertSame('', substr($content, -18)); + + // página 1: 600 posiciones con sus constantes de inicio y fin + $page = substr($content, self::PAGE_OFFSET, 600); + $this->assertSame(600, strlen($page)); + $this->assertSame('', substr($page, 0, 11)); + $this->assertSame('', substr($page, 588, 12)); + + // posiciones 432-588 de la página (complementaria, justificante, + // IBAN, reservado AEAT y sello electrónico) en blanco + $this->assertSame(str_repeat(' ', 157), substr($page, 431, 157)); + } + + public function testFileDeclarantAndAccrual(): void + { + $content = Modelo130Export::generate($this->sampleResult(), $this->sampleCompany(), 'T2', '2026'); + $page = substr($content, self::PAGE_OFFSET, 600); + + // indicador de página complementaria en blanco + $this->assertSame(' ', $page[11]); + + // tipo de declaración I: el resultado de la muestra es positivo + $this->assertSame('I', $page[12]); + + // declarante: NIF (9), apellidos (60) y nombre (20), en mayúsculas + // sin acentos; la primera palabra del nombre de la empresa se toma + // como nombre y el resto como apellidos + $this->assertSame('12345678Z', substr($page, 13, 9)); + $this->assertSame(str_pad('PEREZ GARCIA', 60), substr($page, 22, 60)); + $this->assertSame(str_pad('MARIA', 20), substr($page, 82, 20)); + + // devengo: ejercicio (4) y período (2) + $this->assertSame('2026', substr($page, 102, 4)); + $this->assertSame('2T', substr($page, 106, 2)); + } + + public function testFileBoxesNegativeResult(): void + { + $result = [ + 'taxbaseIngresos' => 1000.00, + 'taxbaseGastos' => 5000.00, + 'gastosJustificacion' => 0.0, + 'afterdeduct' => 0.0, + 'positivosTrimestres' => 0.0, + 'taxbaseRetenciones' => 100.00, + ]; + + $content = Modelo130Export::generate($result, $this->sampleCompany(), 'T3', '2026'); + $page = substr($content, self::PAGE_OFFSET, 600); + + // los importes negativos llevan una N en la primera posición del campo + $this->assertSame('N0000000000400000', $this->box($page, 3)); + $this->assertSame('N0000000000010000', $this->box($page, 7)); + + // [04] no puede ser negativa + $this->assertSame(str_repeat('0', 17), $this->box($page, 4)); + + // si la suma de [07] y [11] es negativa, en [12] se consigna cero + $this->assertSame(str_repeat('0', 17), $this->box($page, 12)); + $this->assertSame(str_repeat('0', 17), $this->box($page, 19)); + + // resultado cero: tipo de declaración N (negativa) + $this->assertSame('N', $page[12]); + } + + public function testDeclarationType(): void + { + // I (ingreso), N (negativa) y B (a deducir) + $this->assertSame('I', Modelo130Export::getDeclarationType(100.0)); + $this->assertSame('N', Modelo130Export::getDeclarationType(0.0)); + $this->assertSame('B', Modelo130Export::getDeclarationType(-100.0)); + } + + public function testFormatNumeric(): void + { + // 17 posiciones: 15 enteros + 2 decimales, ceros a la izquierda + $this->assertSame('00000000000000000', Modelo130Export::formatNumeric(0.0)); + $this->assertSame('00000000000012345', Modelo130Export::formatNumeric(123.45)); + $this->assertSame('00000000000000001', Modelo130Export::formatNumeric(0.01)); + + // redondeo a céntimos + $this->assertSame('00000000000010000', Modelo130Export::formatNumeric(99.999)); + + // los campos sin signo nunca llevan N aunque el valor sea negativo + $this->assertSame('00000000000012345', Modelo130Export::formatNumeric(-123.45)); + + // los campos con signo llevan N en la primera posición si son negativos + $this->assertSame('N0000000000012345', Modelo130Export::formatNumeric(-123.45, true)); + $this->assertSame('00000000000012345', Modelo130Export::formatNumeric(123.45, true)); + $this->assertSame(17, strlen(Modelo130Export::formatNumeric(-123.45, true))); + } + + public function testFormatAlphanumeric(): void + { + // mayúsculas, sin acentos, alineado a la izquierda con blancos + $this->assertSame('MARIA PEREZ ', Modelo130Export::formatAlphanumeric('María Pérez', 20)); + + // solo se admiten letras, números y blancos + $this->assertSame('ACME SL ', Modelo130Export::formatAlphanumeric('Acme, S.L.', 10)); + + // truncado a la longitud del campo + $this->assertSame('ABCDE', Modelo130Export::formatAlphanumeric('abcdefghij', 5)); + } + + public function testFormatNif(): void + { + $this->assertSame('12345678Z', Modelo130Export::formatNif('12345678-z')); + $this->assertSame('B12345678', Modelo130Export::formatNif(' b 12.345.678 ')); + $this->assertSame('', Modelo130Export::formatNif('')); + } + + public function testSplitDeclarantName(): void + { + // la primera palabra es el nombre y el resto los apellidos + $this->assertSame(['Pérez García', 'María'], Modelo130Export::splitDeclarantName('María Pérez García')); + $this->assertSame(['Pérez', 'María'], Modelo130Export::splitDeclarantName('María Pérez')); + + // con una sola palabra, todo va a apellidos + $this->assertSame(['ACME', ''], Modelo130Export::splitDeclarantName('ACME')); + $this->assertSame(['', ''], Modelo130Export::splitDeclarantName(' ')); + } + + public function testGetPeriodNumber(): void + { + foreach (['T1' => '1T', 'T2' => '2T', 'T3' => '3T', 'T4' => '4T'] as $period => $expected) { + $this->assertSame($expected, Modelo130Export::getPeriodNumber($period)); + } + } + + /** + * Devuelve la casilla [n] de la página: 17 posiciones desde la 109. + */ + private function box(string $page, int $num): string + { + return substr($page, self::BOXES_OFFSET + ($num - 1) * 17, 17); + } + + private function sampleCompany(): Empresa + { + // se instancia sin constructor para no requerir conexión a base de datos + $empresa = (new ReflectionClass(Empresa::class))->newInstanceWithoutConstructor(); + $empresa->cifnif = '12345678Z'; + $empresa->nombre = 'María Pérez García'; + + return $empresa; + } + + private function sampleResult(): array + { + return [ + 'taxbaseIngresos' => 25000.50, + 'taxbaseGastos' => 10000.25, + 'gastosJustificacion' => 1050.02, + 'afterdeduct' => 2790.05, + 'positivosTrimestres' => 500.00, + 'taxbaseRetenciones' => 1200.75, + ]; + } +} diff --git a/Translation/en_EN.json b/Translation/en_EN.json index 3fa78a2..965e778 100644 --- a/Translation/en_EN.json +++ b/Translation/en_EN.json @@ -1,5 +1,11 @@ { "acc-concept-irpf-130": "IRPF regularization %period%", + "aeat-file-invalid-exercise": "Cannot generate the AEAT file: the selected fiscal year is not valid.", + "aeat-file-invalid-length": "Cannot generate the AEAT file: the generated file does not have the expected length.", + "aeat-file-invalid-nif": "Cannot generate the AEAT file: the company tax ID is empty or not valid.", + "aeat-file-missing-company-name": "Cannot generate the AEAT file: the company name is empty.", + "company-not-found": "Company not found", + "download-aeat-file": "Download AEAT file", "accounting-created-130": "An accounting entry has been created in fiscal year %codejercicio% with the concept: %concepto%", "after-deduct": "about box 04", "deductible-subaccounts": "Deductible Accounts", diff --git a/Translation/es_ES.json b/Translation/es_ES.json index 31ba53e..d076616 100644 --- a/Translation/es_ES.json +++ b/Translation/es_ES.json @@ -1,5 +1,11 @@ { "acc-concept-irpf-130": "Regularización de IRPF %period%", + "aeat-file-invalid-exercise": "No se puede generar el fichero AEAT: el ejercicio seleccionado no es válido.", + "aeat-file-invalid-length": "No se puede generar el fichero AEAT: el fichero generado no tiene la longitud esperada.", + "aeat-file-invalid-nif": "No se puede generar el fichero AEAT: el NIF de la empresa está vacío o no es válido.", + "aeat-file-missing-company-name": "No se puede generar el fichero AEAT: el nombre de la empresa está vacío.", + "company-not-found": "Empresa no encontrada", + "download-aeat-file": "Descargar fichero AEAT", "accounting-created-130": "Se ha creado el asiento en el ejercicio %codejercicio% con el concepto: %concepto%", "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", diff --git a/View/Modelo130.html.twig b/View/Modelo130.html.twig index 7291687..a239e16 100644 --- a/View/Modelo130.html.twig +++ b/View/Modelo130.html.twig @@ -146,6 +146,12 @@ return false; {{ trans('preview') }} + {% if fsc.result is not empty %} + + {% endif %}