From 6c104b7e279444dd7102f5a128edce2f23a0bbcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mart=C3=ADn=20Gonz=C3=A1lez?= Date: Wed, 22 Jul 2026 16:52:53 +0200 Subject: [PATCH 01/15] =?UTF-8?q?Cambiada=20la=20l=C3=B3gica=20de=20c?= =?UTF-8?q?=C3=A1lculo=20para=20basarse=20en=20los=20asientos=20de=20conta?= =?UTF-8?q?bilidad=20y=20no=20en=20facturas=20con=20el=20fin=20de=20soport?= =?UTF-8?q?ar=20amortizaciones=20y=20otros=20tipos=20de=20ingresos/gastos?= =?UTF-8?q?=20no=20asociados=20a=20facturas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Controller/Modelo130.php | 211 ++---- Data/Lang/ES/subcuentas_130.csv | 3 - Init.php | 28 - Lib/Modelo130.php | 771 +++++++++++++++------ Lib/Modelo130Accounts.php | 171 +++++ Model/Subcuenta130.php | 119 ---- Table/subcuentas_130.xml | 55 -- Test/Modelo130Test.php | 190 +++++ Test/main/Subcuenta130Test.php | 163 ----- Translation/ca_ES.json | 5 +- Translation/de_DE.json | 5 +- Translation/en_EN.json | 5 +- Translation/es_ES.json | 5 +- Translation/eu_ES.json | 5 +- Translation/fr_FR.json | 5 +- Translation/gl_ES.json | 5 +- Translation/it_IT.json | 5 +- Translation/pt_BR.json | 5 +- Translation/pt_PT.json | 5 +- View/Modelo130.html.twig | 1143 ++++++++++++------------------- 20 files changed, 1461 insertions(+), 1443 deletions(-) delete mode 100644 Data/Lang/ES/subcuentas_130.csv create mode 100644 Lib/Modelo130Accounts.php delete mode 100644 Model/Subcuenta130.php delete mode 100644 Table/subcuentas_130.xml create mode 100644 Test/Modelo130Test.php delete mode 100644 Test/main/Subcuenta130Test.php diff --git a/Controller/Modelo130.php b/Controller/Modelo130.php index 76e3752..1fefb04 100644 --- a/Controller/Modelo130.php +++ b/Controller/Modelo130.php @@ -23,11 +23,9 @@ 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; @@ -41,9 +39,6 @@ */ class Modelo130 extends Controller { - /** @var string */ - public $activeTab = ''; - /** bool */ public $applyGastosJustificacion = false; @@ -53,12 +48,6 @@ class Modelo130 extends Controller /** @var string */ public $codejercicio; - /** @var Subcuenta130 */ - public $deductibleSubaccount; - - /** @var Subcuenta130 */ - public $incomeSubaccount; - /** @var string */ public $period = 'T1'; @@ -84,13 +73,8 @@ public function getAllExercises(?int $idempresa): array $list[] = $exercise; } } - return $list; - } - public function getDeductibleSubaccounts(): array - { - $where = [Where::eq('tipo', Subcuenta130::TIPO_DEDUCIBLE)]; - return (new Subcuenta130())->all($where, ['codsubcuenta' => 'ASC'], 0, 0); + return $list; } /** @@ -104,12 +88,6 @@ public function getExercise(?string $codejercicio): Ejercicio return $exercise; } - public function getIncomeSubaccounts(): array - { - $where = [Where::eq('tipo', Subcuenta130::TIPO_INGRESO)]; - return (new Subcuenta130())->all($where, ['codsubcuenta' => 'ASC'], 0, 0); - } - public function getPageData(): array { $data = parent::getPageData(); @@ -138,31 +116,9 @@ public function getPeriodsForComboBoxHtml(): array public function privateCore(&$response, $user, $permissions) { parent::privateCore($response, $user, $permissions); - $this->deductibleSubaccount = new Subcuenta130(); - $this->incomeSubaccount = new Subcuenta130(); $action = $this->request->request->get('action', $this->request->input('action')); switch ($action) { - case 'autocomplete-subaccount': - $this->autocompleteSubaccount(); - return; - - case 'add-deductible-subaccount': - $this->addDeductibleSubaccount(); - return; - - case 'delete-deductible-subaccount': - $this->deleteDeductibleSubaccount(); - return; - - case 'add-income-subaccount': - $this->addIncomeSubaccount(); - return; - - case 'delete-income-subaccount': - $this->deleteIncomeSubaccount(); - return; - case 'gen-accounting': $this->createAccountingEntry(); return; @@ -170,11 +126,23 @@ public function privateCore(&$response, $user, $permissions) $this->codejercicio = $this->request->request->get('codejercicio', ''); $this->period = $this->request->request->get('period', $this->period); - $this->applyGastosJustificacion = (bool)$this->request->request->get('applyGastosJustificacion', false); + $this->applyGastosJustificacion = (bool)$this->request->request->get( + 'applyGastosJustificacion', + false + ); $this->todeduct = (float)$this->request->request->get('todeduct', 20.0); - $this->gastosJustificacionPct = (float)$this->request->request->get('gastosJustificacionPct', 7.0); - - $this->result = DinModelo130::generate($this->codejercicio, $this->period, $this->applyGastosJustificacion, $this->todeduct, $this->gastosJustificacionPct); + $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); @@ -212,78 +180,40 @@ protected function downloadFile(Response $response): void return; } - $content = DinModelo130Export::generate($this->result, $empresa, $this->period, $year); + $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'; + $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; - } + $response->headers->set( + 'Content-Type', + 'text/plain; charset=ISO-8859-1' + ); - protected function addDeductibleSubaccount(): void - { - $this->activeTab = 'deductible-subaccount'; + $response->headers->set( + 'Content-Disposition', + 'attachment; filename="' . $filename . '"' + ); - if (false === $this->validateFormToken()) { - return; - } + $response->setContent( + mb_convert_encoding($content, 'ISO-8859-1', 'UTF-8') + ); - $subaccount130 = new Subcuenta130(); - $subaccount130->codsubcuenta = $this->request->request->get('codsubcuenta'); - if (false === $subaccount130->save()) { - Tools::log()->error('record-save-error'); - return; - } - - Tools::log()->notice('record-updated-correctly'); - } - - protected function addIncomeSubaccount(): void - { - $this->activeTab = 'income-subaccount'; - - if (false === $this->validateFormToken()) { - return; - } - - $subaccount = new Subcuenta130(); - $subaccount->codsubcuenta = $this->request->request->get('codsubcuenta'); - $subaccount->tipo = Subcuenta130::TIPO_INGRESO; - if (false === $subaccount->save()) { - Tools::log()->error('record-save-error'); - return; - } - - Tools::log()->notice('record-updated-correctly'); - } - - protected function autocompleteSubaccount(): void - { - $this->setTemplate(false); - - $list = []; - $term = $this->request->get('term'); - $sql = 'SELECT DISTINCT codsubcuenta, descripcion FROM subcuentas WHERE codsubcuenta LIKE "' . $term . '%";'; - - foreach ($this->dataBase->select($sql) as $value) { - $list[] = [ - 'key' => Tools::fixHtml($value['codsubcuenta']), - 'value' => Tools::fixHtml($value['descripcion']) - ]; - } - - if (empty($list)) { - $list[] = ['key' => null, 'value' => Tools::lang()->trans('no-data')]; - } - - $this->response->setContent(json_encode($list)); + $response->send(); + exit; } protected function createAccountingEntry(): void @@ -299,52 +229,17 @@ protected function createAccountingEntry(): void $amount = (float)$this->request->request->get('amount'); $paymentMethodId = (int)$this->request->request->get('paymentMethod'); - if (DinModelo130::generateEntries($idempresa, $codejercicio, $period, $date, $amount, $paymentMethodId)) { + if ( + DinModelo130::generateEntries( + $idempresa, + $codejercicio, + $period, + $date, + $amount, + $paymentMethodId + ) + ) { Tools::log()->notice('record-updated-correctly'); } } - - protected function deleteDeductibleSubaccount(): void - { - $this->activeTab = 'deductible-subaccount'; - - if (false === $this->validateFormToken()) { - return; - } - - $subaccount130 = new Subcuenta130(); - if (false === $subaccount130->load($this->request->request->get('id'))) { - Tools::log()->error('record-not-found'); - return; - } - - if (false === $subaccount130->delete()) { - Tools::log()->error('record-deleted-error'); - return; - } - - Tools::log()->notice('record-deleted-correctly'); - } - - protected function deleteIncomeSubaccount(): void - { - $this->activeTab = 'income-subaccount'; - - if (false === $this->validateFormToken()) { - return; - } - - $subaccount = new Subcuenta130(); - if (false === $subaccount->load($this->request->request->get('id'))) { - Tools::log()->error('record-not-found'); - return; - } - - if (false === $subaccount->delete()) { - Tools::log()->error('record-deleted-error'); - return; - } - - Tools::log()->notice('record-deleted-correctly'); - } -} +} \ No newline at end of file diff --git a/Data/Lang/ES/subcuentas_130.csv b/Data/Lang/ES/subcuentas_130.csv deleted file mode 100644 index 8ba0807..0000000 --- a/Data/Lang/ES/subcuentas_130.csv +++ /dev/null @@ -1,3 +0,0 @@ -"codsubcuenta" -"6420000000" -"4730000000" \ No newline at end of file diff --git a/Init.php b/Init.php index 98da1ff..db8eed9 100644 --- a/Init.php +++ b/Init.php @@ -33,33 +33,5 @@ public function uninstall(): void public function update(): void { - $this->cleanInvalidUsers(); - } - - private function cleanInvalidUsers(): void - { - // ver si existe la tabla subcuentas o usuario - $db = new DataBase(); - if (false === $db->tableExists('subcuentas_130') || - false === $db->tableExists('users')) { - return; - } - - // consulta compatible con mysql y postgresql - // elimina el registro foráneo si no existe en la tabla original - $templateSql = "UPDATE subcuentas_130 - SET REPLACE_COLUMN = NULL - WHERE REPLACE_COLUMN IS NOT NULL - AND NOT EXISTS ( - SELECT 1 - FROM users - WHERE users.nick = subcuentas_130.REPLACE_COLUMN - );"; - - foreach (['nick', 'last_nick'] as $column) { - // reemplazar la columna de user en la tabla subcuentas_130 y ejecutar - $sql = str_replace("REPLACE_COLUMN", $column, $templateSql); - $db->exec($sql); - } } } diff --git a/Lib/Modelo130.php b/Lib/Modelo130.php index b84b556..4a55ae2 100644 --- a/Lib/Modelo130.php +++ b/Lib/Modelo130.php @@ -27,7 +27,6 @@ use FacturaScripts\Dinamic\Model\FacturaProveedor; use FacturaScripts\Dinamic\Model\FormaPago; use FacturaScripts\Dinamic\Model\Partida; -use FacturaScripts\Dinamic\Model\Subcuenta130; /** * @author Carlos Garcia Gomez @@ -35,15 +34,27 @@ */ class Modelo130 { - /** Límite anual deducible por gastos de difícil justificación (art. 30.2.4ª LIRPF). */ + /** + * Límite anual deducible por gastos de difícil justificación + * (art. 30.2.4ª LIRPF). + */ const LIMITE_GASTOS_JUSTIFICACION = 2000.0; - /** @var Partida[] */ + /** + * Partidas contables que intervienen en el cálculo y que no están + * asociadas a ninguna factura. + * + * Cada elemento contiene: + * + * - entry: asiento contable. + * - partida: partida contable. + * - type: income, expense o previous-payment. + * - amount: importe computado. + * + * @var array[] + */ protected static $accountingEntries = []; - /** @var FacturaCliente[] */ - protected static $customerInvoices = []; - /** @var DataBase */ protected static $dataBase; @@ -59,14 +70,37 @@ class Modelo130 /** @var int|null */ protected static $idempresa; - /** @var Partida[] */ - protected static $incomeEntries = []; - /** @var string */ protected static $period = ''; - /** @var FacturaProveedor[] */ - protected static $supplierInvoices = []; + + /** + * Asientos asociados a facturas de proveedor que contienen partidas + * computables como gasto. + * + * @var array[] + */ + protected static $purchases = []; + + /** + * Asientos asociados a facturas de cliente que contienen partidas + * computables como ingreso o retenciones en la cuenta 473. + * + * @var array[] + */ + protected static $sales = []; + + /** @var float */ + protected static $taxbaseExpenses = 0.0; + + /** @var float */ + protected static $taxbaseIncomes = 0.0; + + /** @var float */ + protected static $taxbaseRetentions = 0.0; + + /** @var float */ + protected static $previousPayments = 0.0; public static function generate( string $codejercicio, @@ -82,45 +116,62 @@ public static function generate( static::$dataBase = new DataBase(); static::$period = strtoupper($period); + static::$accountingEntries = []; - static::$customerInvoices = []; - static::$incomeEntries = []; - static::$supplierInvoices = []; + static::$purchases = []; + static::$sales = []; + + static::$taxbaseExpenses = 0.0; + static::$taxbaseIncomes = 0.0; + static::$taxbaseRetentions = 0.0; + static::$previousPayments = 0.0; static::loadDates(); - static::loadInvoices(); - static::loadAsientos(); - static::loadIncomeAsientos(); + static::loadAccountingData(); - $results = static::loadResults($applyGastosJustificacion, $todeduct, $gastosJustificacionPct); + $results = static::loadResults( + $applyGastosJustificacion, + $todeduct, + $gastosJustificacionPct + ); return array_merge([ 'exercise' => static::$exercise, 'period' => static::$period, 'idempresa' => static::$idempresa, - 'customerInvoices' => static::$customerInvoices, - 'supplierInvoices' => static::$supplierInvoices, + 'sales' => static::$sales, + 'purchases' => static::$purchases, 'accountingEntries' => static::$accountingEntries, - 'incomeEntries' => static::$incomeEntries, 'applyGastosJustificacion' => $applyGastosJustificacion, 'todeduct' => $todeduct, 'gastosJustificacionPct' => $gastosJustificacionPct, ], $results); } - public static function generateEntries(int $idempresa, string $codejercicio, string $period, string $date, float $amount, ?string $paymentMethodId): bool - { + public static function generateEntries( + int $idempresa, + string $codejercicio, + string $period, + string $date, + float $amount, + ?string $paymentMethodId + ): bool { $asiento = new Asiento(); - $concepto = Tools::trans('acc-concept-irpf-130', ['%period%' => $period]); + $concepto = Tools::trans( + 'acc-concept-irpf-130', + ['%period%' => $period] + ); // si ya existe un asiento igual, no lo creamos - if ($asiento->loadWhere( - [ - Where::eq('codejercicio', $codejercicio), - Where::eq('concepto', $concepto), - ] - )) { - Tools::log()->warning('exists-accounting-130', ['%codejercicio%' => $codejercicio, '%concepto%' => $concepto]); + if ($asiento->loadWhere([ + Where::eq('codejercicio', $codejercicio), + Where::eq('concepto', $concepto), + ])) { + Tools::log()->warning('exists-accounting-130', [ + '%codejercicio%' => $codejercicio, + '%concepto%' => $concepto, + ]); + return false; } @@ -129,6 +180,7 @@ public static function generateEntries(int $idempresa, string $codejercicio, str $asiento->concepto = $concepto; $asiento->fecha = $date; $asiento->importe = $amount; + if (false === $asiento->save()) { return false; } @@ -138,12 +190,15 @@ public static function generateEntries(int $idempresa, string $codejercicio, str $partida1->concepto = $asiento->concepto; $partida1->debe = $amount; $partida1->codsubcuenta = '4730000000'; + if (false === $partida1->save()) { $asiento->delete(); return false; } + $bankAccount = null; $paymentMethod = new FormaPago(); + if ($paymentMethod->load($paymentMethodId)) { $bankAccount = $paymentMethod->getBankAccount(); } @@ -152,29 +207,95 @@ public static function generateEntries(int $idempresa, string $codejercicio, str $partida2->idasiento = $asiento->idasiento; $partida2->concepto = $asiento->concepto; $partida2->haber = $amount; - $partida2->codsubcuenta = !empty($bankAccount->codsubcuenta) ? $bankAccount->codsubcuenta : '5720000000'; + $partida2->codsubcuenta = !empty($bankAccount->codsubcuenta) + ? $bankAccount->codsubcuenta + : '5720000000'; + if (false === $partida2->save()) { $asiento->delete(); return false; } - Tools::log()->notice('accounting-created-130', ['%codejercicio%' => $codejercicio, '%concepto%' => $concepto]); + Tools::log()->notice('accounting-created-130', [ + '%codejercicio%' => $codejercicio, + '%concepto%' => $concepto, + ]); + return true; } - protected static function getAccountingEntrySubaccounts(): array - { - $codsubs = []; - $where = [Where::eq('tipo', Subcuenta130::TIPO_DEDUCIBLE)]; - foreach ((new Subcuenta130())->all($where, [], 0, 0) as $subaccount) { - $codsubs[] = $subaccount->codsubcuenta; + /** + * Calcula la deducción por gastos de difícil justificación aplicando el + * porcentaje indicado sobre la base y topándola al límite anual (2.000 €). + */ + public static function calcGastosJustificacion( + float $taxbase, + bool $apply, + float $gastosJustificacionPct = 7.0 + ): float { + if (false === $apply || $taxbase <= 0) { + return 0.0; } - return static::sanitizeSubaccountCodes($codsubs); + $importe = round( + $taxbase * ($gastosJustificacionPct / 100), + 2 + ); + + // la deducción no puede superar el límite anual (2.000 €) + return min( + $importe, + static::LIMITE_GASTOS_JUSTIFICACION + ); } - protected static function getSqlValueCondition(string $field, $value): string - { + /** + * Calcula la casilla 04 (20% del rendimiento neto tras deducir los gastos + * de difícil justificación). Si el rendimiento neto acumulado es negativo + * (pérdidas de trimestres anteriores del mismo ejercicio), la casilla no + * puede ser negativa. + */ + public static function calcAfterDeduct( + float $taxbase, + float $gastosJustificacion, + float $todeduct + ): float { + $baseDeducible = max( + 0.0, + $taxbase - $gastosJustificacion + ); + + return round( + ($baseDeducible * $todeduct) / 100, + 2 + ); + } + + /** + * Calcula el resultado final del modelo (casilla 07) restando retenciones e + * ingresos de trimestres anteriores. Si el resultado es negativo, la + * casilla no puede ser negativa. + */ + public static function calcResult( + float $afterdeduct, + float $taxbaseRetenciones, + float $positivosTrimestres + ): float { + return max( + 0.0, + round( + $afterdeduct + - $taxbaseRetenciones + - $positivosTrimestres, + 2 + ) + ); + } + + protected static function getSqlValueCondition( + string $field, + $value + ): string { if (null === $value) { return $field . ' IS NULL'; } @@ -182,229 +303,459 @@ protected static function getSqlValueCondition(string $field, $value): string return $field . ' = ' . static::$dataBase->var2str($value); } - protected static function getSqlValueList(array $values): string + /** + * Carga los datos utilizados por el modelo directamente desde las + * partidas contables. + * + * Las facturas únicamente se utilizan para clasificar visualmente el + * asiento en las pestañas Ventas o Compras y para distinguir si un + * movimiento de la cuenta 473 corresponde a una retención. + * + * Los importes de ingresos, gastos e IRPF se obtienen siempre de las + * partidas del asiento, no de los totales almacenados en la factura. + */ + protected static function loadAccountingData(): void { - $list = []; - foreach ($values as $value) { - $list[] = static::$dataBase->var2str($value); + $conditions = []; + + foreach (Modelo130Accounts::queryPrefixes() as $prefix) { + $conditions[] = 'p.codsubcuenta LIKE ' + . static::$dataBase->var2str($prefix . '%'); } - return implode(',', $list); - } + if (empty($conditions)) { + return; + } - protected static function loadAsientos(): void - { - $codsubs = static::getAccountingEntrySubaccounts(); + $sql = 'SELECT p.*' + . ' FROM ' . Partida::tableName() . ' p' + . ' INNER JOIN ' . Asiento::tableName() . ' a' + . ' ON p.idasiento = a.idasiento' + . ' WHERE ' + . static::getSqlValueCondition( + 'a.idempresa', + static::$idempresa + ) + . ' AND a.fecha BETWEEN ' + . static::$dataBase->var2str( + date('Y-m-d', strtotime(static::$dateStart)) + ) + . ' AND ' + . static::$dataBase->var2str( + date('Y-m-d', strtotime(static::$dateEnd)) + ) + . ' AND a.operacion IS ' + . static::$dataBase->var2str( + Asiento::OPERATION_GENERAL + ) + . ' AND (' . implode(' OR ', $conditions) . ')' + . ' ORDER BY a.fecha ASC, a.numero ASC, p.orden ASC'; + + /** + * Agrupamos las partidas por asiento. + * + * De esta forma una factura con varias partidas de gasto o ingreso + * aparece una única vez en la pestaña correspondiente. + */ + $groups = []; - if (empty($codsubs)) { + foreach (static::$dataBase->select($sql) as $row) { + $partida = new Partida($row); + $idasiento = (int)$partida->idasiento; + + if (!isset($groups[$idasiento])) { + $groups[$idasiento] = [ + 'entries' => [], + 'income' => 0.0, + 'expense' => 0.0, + 'retention' => 0.0, + ]; + } + + $code = (string)$partida->codsubcuenta; + + /** + * En ingresos sumamos haber - debe. + * + * Así una rectificación o un asiento inverso reduce el ingreso + * computable en lugar de incrementarlo. + */ + $income = Modelo130Accounts::isIncome($code) + ? round( + (float)$partida->haber + - (float)$partida->debe, + 2 + ) + : 0.0; + + /** + * En gastos sumamos debe - haber. + * + * Así una devolución o regularización inversa reduce el gasto. + */ + $expense = Modelo130Accounts::isExpense($code) + ? round( + (float)$partida->debe + - (float)$partida->haber, + 2 + ) + : 0.0; + + /** + * En la cuenta 473 el movimiento habitual está en el debe. + * + * Puede ser una retención de factura o un pago fraccionado del + * Modelo 130. La diferencia se determina después comprobando si + * el asiento está asociado a una factura de cliente. + */ + $retention = Modelo130Accounts::isWithholding($code) + ? round( + (float)$partida->debe + - (float)$partida->haber, + 2 + ) + : 0.0; + + $groups[$idasiento]['income'] += $income; + $groups[$idasiento]['expense'] += $expense; + $groups[$idasiento]['retention'] += $retention; + + $groups[$idasiento]['entries'][] = [ + 'partida' => $partida, + 'income' => $income, + 'expense' => $expense, + 'retention' => $retention, + ]; + } + + if (empty($groups)) { return; } - $sql = 'SELECT * FROM ' . Partida::tableName() . ' as p' - . ' LEFT JOIN ' . Asiento::tableName() . ' as a ON p.idasiento = a.idasiento' - . ' WHERE ' . static::getSqlValueCondition('a.idempresa', static::$idempresa) - . ' AND a.fecha BETWEEN ' . static::$dataBase->var2str(date('Y-m-d', strtotime(static::$dateStart))) - . ' AND ' . static::$dataBase->var2str(date('Y-m-d', strtotime(static::$dateEnd))) - . ' AND p.codsubcuenta IN (' . static::getSqlValueList($codsubs) . ')' - . ' AND a.operacion IS ' . static::$dataBase->var2str(Asiento::OPERATION_GENERAL) - . ' ORDER BY numero DESC'; + $entryIds = array_keys($groups); - foreach (static::$dataBase->select($sql) as $row) { - static::$accountingEntries[] = new Partida($row); + /** + * Cargamos en bloque los asientos y las facturas relacionadas para no + * ejecutar una consulta adicional por cada partida. + */ + $accountingEntries = static::loadEntriesByIds($entryIds); + + $customerInvoices = static::loadInvoicesByEntries( + new FacturaCliente(), + $entryIds + ); + + $supplierInvoices = static::loadInvoicesByEntries( + new FacturaProveedor(), + $entryIds + ); + + foreach ($groups as $idasiento => $group) { + $entry = $accountingEntries[$idasiento] ?? null; + + if (null === $entry) { + continue; + } + + $customerInvoice = $customerInvoices[$idasiento] ?? null; + $supplierInvoice = $supplierInvoices[$idasiento] ?? null; + + static::$taxbaseIncomes += $group['income']; + static::$taxbaseExpenses += $group['expense']; + + /** + * Factura de cliente. + * + * El ingreso y el IRPF se obtienen desde las partidas contables, + * pero se muestran agrupados utilizando los datos identificativos + * de la factura. + * + */ + if ($customerInvoice) { + static::$taxbaseRetentions += $group['retention']; + + if ( + $group['income'] != 0.0 + || $group['retention'] != 0.0 + ) { + static::$sales[] = [ + 'invoice' => $customerInvoice, + 'entry' => $entry, + 'serie' => $customerInvoice->codserie ?? '', + 'factura' => $customerInvoice->numero ?? '', + 'documento' => $customerInvoice->codigo ?? '', + 'fecha' => $entry->fecha, + 'concepto' => $entry->concepto, + 'baseimponible' => round( + $group['income'], + 2 + ), + 'irpf' => round( + $group['retention'], + 2 + ), + ]; + } + + continue; + } + + /** + * Factura de proveedor. + * + * Únicamente aparece si el asiento contiene alguna partida + * considerada gasto. + * + * Una factura contabilizada íntegramente contra una cuenta 21X no + * aparecerá ni se deducirá. En una factura mixta únicamente se + * computará la parte contabilizada en cuentas de gasto. + */ + if ($supplierInvoice) { + if ($group['expense'] != 0.0) { + static::$purchases[] = [ + 'invoice' => $supplierInvoice, + 'entry' => $entry, + 'serie' => $supplierInvoice->codserie ?? '', + 'factura' => $supplierInvoice->numero ?? '', + 'documento' => $supplierInvoice->codigo ?? '', + 'fecha' => $entry->fecha, + 'concepto' => $entry->concepto, + 'baseimponible' => round( + $group['expense'], + 2 + ), + 'irpf' => 0.0, + ]; + } + + continue; + } + + /** + * El asiento no está asociado a ninguna factura. + * + * Sus partidas se muestran en la pestaña Asientos: + * + * - amortizaciones; + * - Seguridad Social; + * - gastos manuales; + * - ingresos manuales; + * - pagos fraccionados de trimestres anteriores. + */ + foreach ($group['entries'] as $item) { + $type = ''; + $amount = 0.0; + + if ($item['income'] != 0.0) { + $type = 'income'; + $amount = $item['income']; + } elseif ($item['expense'] != 0.0) { + $type = 'expense'; + $amount = $item['expense']; + } elseif ($item['retention'] != 0.0) { + /** + * Toda partida de la cuenta 473 sin factura asociada se considera + * un pago fraccionado del Modelo 130. + * + * También se incluye el pago correspondiente al propio trimestre. + * El asiento se genera con fecha del último día del período, por lo + * que al volver a calcular el modelo ese importe aparece en la + * casilla 05 y evita crear el mismo pago de nuevo. + */ + $type = 'previous-payment'; + $amount = $item['retention']; + + static::$previousPayments += $amount; + } + + if ($type === '') { + continue; + } + + static::$accountingEntries[] = [ + 'entry' => $entry, + 'partida' => $item['partida'], + 'type' => $type, + 'amount' => round($amount, 2), + ]; + } } + + static::$taxbaseIncomes = round( + static::$taxbaseIncomes, + 2 + ); + + static::$taxbaseExpenses = round( + static::$taxbaseExpenses, + 2 + ); + + static::$taxbaseRetentions = round( + static::$taxbaseRetentions, + 2 + ); + + static::$previousPayments = round( + static::$previousPayments, + 2 + ); } protected static function loadDates(): void { - if (!in_array(static::$period, ['T1', 'T2', 'T3', 'T4'])) { + if (!in_array( + static::$period, + ['T1', 'T2', 'T3', 'T4'] + )) { static::$period = 'T1'; } + $year = date( + 'Y', + strtotime(static::$exercise->fechainicio) + ); + + /** + * El Modelo 130 es acumulativo. + * + * Todos los períodos comienzan el 1 de enero y terminan el último día + * del trimestre seleccionado. + */ + static::$dateStart = '01-01-' . $year; + switch (static::$period) { case 'T1': - static::$dateStart = date('01-01-Y', strtotime(static::$exercise->fechainicio)); - static::$dateEnd = date('31-03-Y', strtotime(static::$exercise->fechainicio)); + static::$dateEnd = '31-03-' . $year; break; - + case 'T2': - static::$dateStart = date('01-01-Y', strtotime(static::$exercise->fechainicio)); - static::$dateEnd = date('30-06-Y', strtotime(static::$exercise->fechainicio)); + static::$dateEnd = '30-06-' . $year; break; - + case 'T3': - static::$dateStart = date('01-01-Y', strtotime(static::$exercise->fechainicio)); - static::$dateEnd = date('30-09-Y', strtotime(static::$exercise->fechainicio)); + static::$dateEnd = '30-09-' . $year; break; - + default: - static::$dateStart = date('01-01-Y', strtotime(static::$exercise->fechainicio)); - static::$dateEnd = date('31-12-Y', strtotime(static::$exercise->fechainicio)); + static::$dateEnd = '31-12-' . $year; break; } static::$idempresa = static::$exercise->idempresa; } - protected static function loadIncomeAsientos(): void - { - $codsubs = []; - $where = [Where::eq('tipo', Subcuenta130::TIPO_INGRESO)]; - foreach ((new Subcuenta130())->all($where, [], 0, 0) as $subaccount) { - $codsubs[] = $subaccount->codsubcuenta; - } - $codsubs = static::sanitizeSubaccountCodes($codsubs); - - if (empty($codsubs)) { - return; - } - - $sql = 'SELECT * FROM ' . Partida::tableName() . ' as p' - . ' LEFT JOIN ' . Asiento::tableName() . ' as a ON p.idasiento = a.idasiento' - . ' WHERE ' . static::getSqlValueCondition('a.idempresa', static::$idempresa) - . ' AND a.fecha BETWEEN ' . static::$dataBase->var2str(date('Y-m-d', strtotime(static::$dateStart))) - . ' AND ' . static::$dataBase->var2str(date('Y-m-d', strtotime(static::$dateEnd))) - . ' AND p.codsubcuenta IN (' . static::getSqlValueList($codsubs) . ')' - . ' AND a.operacion IS ' . static::$dataBase->var2str(Asiento::OPERATION_GENERAL) - . ' ORDER BY numero DESC'; - - foreach (static::$dataBase->select($sql) as $row) { - static::$incomeEntries[] = new Partida($row); - } - } - - protected static function loadInvoices(): void - { - $whereFtrasProveedores = [ - Where::gte('fecha', date('Y-m-d', strtotime(static::$dateStart))), - Where::lte('fecha', date('Y-m-d', strtotime(static::$dateEnd))), - Where::eq('idempresa', static::$idempresa), - ]; - - $whereFtrasClientes = [ - Where::gte('fecha', date('Y-m-d', strtotime(static::$dateStart))), - Where::lte('fecha', date('Y-m-d', strtotime(static::$dateEnd))), - Where::eq('idempresa', static::$idempresa), - ]; - - $order = ['fecha' => 'DESC', 'numero' => 'DESC']; - - static::$supplierInvoices = (new FacturaProveedor())->all($whereFtrasProveedores, $order, 0, 0); - static::$customerInvoices = (new FacturaCliente())->all($whereFtrasClientes, $order, 0, 0); - } - /** - * Calcula la deducción por gastos de difícil justificación aplicando el - * porcentaje indicado sobre la base y topándola al límite anual (2.000 €). + * Carga los asientos indicados y devuelve un mapa indexado por idasiento. + * + * @param int[] $entryIds + * @return Asiento[] */ - public static function calcGastosJustificacion(float $taxbase, bool $apply, float $gastosJustificacionPct = 7.0): float - { - if (false === $apply || $taxbase <= 0) { - return 0.0; + protected static function loadEntriesByIds( + array $entryIds + ): array { + if (empty($entryIds)) { + return []; } - $importe = round($taxbase * ($gastosJustificacionPct / 100), 2); + $result = []; + $ids = implode( + ',', + array_map('intval', $entryIds) + ); - // la deducción no puede superar el límite anual (2.000 €) - return min($importe, static::LIMITE_GASTOS_JUSTIFICACION); - } + $where = [ + Where::in('idasiento', $ids), + ]; - /** - * Calcula la casilla 04 (20% del rendimiento neto tras deducir los gastos - * de difícil justificación). Si el rendimiento neto acumulado es negativo - * (pérdidas de trimestres anteriores del mismo ejercicio), la casilla no - * puede ser negativa. - */ - public static function calcAfterDeduct(float $taxbase, float $gastosJustificacion, float $todeduct): float - { - $baseDeducible = max(0.0, $taxbase - $gastosJustificacion); + foreach ( + (new Asiento())->all($where, [], 0, 0) + as $entry + ) { + $result[(int)$entry->idasiento] = $entry; + } - return round(($baseDeducible * $todeduct) / 100, 2); + return $result; } /** - * Calcula el resultado final del modelo (casilla 07) restando retenciones e - * ingresos de trimestres anteriores. Si el resultado es negativo, la casilla - * no puede ser negativa + * Carga en una sola consulta las facturas asociadas a los asientos. + * + * @param FacturaCliente|FacturaProveedor $model + * @param int[] $entryIds + * + * @return array */ - public static function calcResult(float $afterdeduct, float $taxbaseRetenciones, float $positivosTrimestres): float - { - return max(0.0, round($afterdeduct - $taxbaseRetenciones - $positivosTrimestres, 2)); - } - - protected static function loadResults(bool $applyGastosJustificacion, float $todeduct, float $gastosJustificacionPct = 7.0): array - { - $taxbaseIngresos = 0.0; - $taxbaseRetenciones = 0.0; - $taxbaseGastos = 0.0; - $segSocial = 0.0; - $otrasDeducciones = 0.0; - $positivosTrimestres = 0.0; - - foreach (static::$customerInvoices as $invoice) { - $taxbaseIngresos += $invoice->neto; - $taxbaseRetenciones += $invoice->totalirpf; + protected static function loadInvoicesByEntries( + $model, + array $entryIds + ): array { + if (empty($entryIds)) { + return []; } - foreach (static::$incomeEntries as $partida) { - $taxbaseIngresos += $partida->haber; - } + $result = []; + $ids = implode( + ',', + array_map('intval', $entryIds) + ); - foreach (static::$supplierInvoices as $invoice) { - $taxbaseGastos += $invoice->neto; - } + $where = [ + Where::in('idasiento', $ids), + ]; - foreach (static::$accountingEntries as $asiento) { - switch ($asiento->codsubcuenta) { - case '6420000000': - $segSocial += $asiento->debe; - break; - case '4730000000': - $positivosTrimestres += $asiento->debe; - break; - default: - $otrasDeducciones += $asiento->debe; - break; + foreach ( + $model->all($where, [], 0, 0) + as $invoice + ) { + if (empty($invoice->idasiento)) { + continue; } - } - - // la seguridad social y el resto de deducciones se cuentan como gasto deducible - $taxbaseGastos += ($segSocial + $otrasDeducciones); - - // la cuenta 473 incluye trimestres anteriores y retenciones de facturas - $positivosTrimestres = round($positivosTrimestres - $taxbaseRetenciones, 2); - $taxbase = round($taxbaseIngresos - $taxbaseGastos, 2); - - $gastosJustificacion = static::calcGastosJustificacion($taxbase, $applyGastosJustificacion, $gastosJustificacionPct); + $result[(int)$invoice->idasiento] = $invoice; + } - $afterdeduct = static::calcAfterDeduct($taxbase, $gastosJustificacion, $todeduct); + return $result; + } - $result = static::calcResult($afterdeduct, $taxbaseRetenciones, $positivosTrimestres); + protected static function loadResults( + bool $applyGastosJustificacion, + float $todeduct, + float $gastosJustificacionPct = 7.0 + ): array { + $taxbase = round( + static::$taxbaseIncomes + - static::$taxbaseExpenses, + 2 + ); + + $gastosJustificacion = static::calcGastosJustificacion( + $taxbase, + $applyGastosJustificacion, + $gastosJustificacionPct + ); + + $afterdeduct = static::calcAfterDeduct( + $taxbase, + $gastosJustificacion, + $todeduct + ); + + $result = static::calcResult( + $afterdeduct, + static::$taxbaseRetentions, + static::$previousPayments + ); return [ - 'taxbaseIngresos' => $taxbaseIngresos, - 'taxbaseRetenciones' => $taxbaseRetenciones, - 'taxbaseGastos' => $taxbaseGastos, + 'taxbaseIngresos' => static::$taxbaseIncomes, + 'taxbaseRetenciones' => static::$taxbaseRetentions, + 'taxbaseGastos' => static::$taxbaseExpenses, 'taxbase' => $taxbase, 'gastosJustificacion' => $gastosJustificacion, 'afterdeduct' => $afterdeduct, - 'positivosTrimestres' => $positivosTrimestres, + 'positivosTrimestres' => static::$previousPayments, 'result' => $result, ]; } - - protected static function sanitizeSubaccountCodes(array $codes): array - { - $result = []; - foreach ($codes as $code) { - $code = trim((string)$code); - if ($code === '') { - continue; - } - - $result[] = $code; - } - - return array_values(array_unique($result)); - } -} +} \ No newline at end of file diff --git a/Lib/Modelo130Accounts.php b/Lib/Modelo130Accounts.php new file mode 100644 index 0000000..bd6f6ad --- /dev/null +++ b/Lib/Modelo130Accounts.php @@ -0,0 +1,171 @@ + + * + * 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; + +/** + * Centraliza las cuentas contables utilizadas para calcular el Modelo 130. + * + * Los códigos indicados son prefijos. Por ejemplo, el código 64 incluye + * cualquier subcuenta que comience por 64. + */ +final class Modelo130Accounts +{ + /** + * Hacienda pública, retenciones y pagos a cuenta. + * + * La cuenta 473 puede contener: + * - retenciones practicadas en facturas; + * - pagos fraccionados de trimestres anteriores. + * + * La diferencia se determina comprobando si el asiento tiene una factura + * asociada. + */ + public const WITHHOLDING_ACCOUNT = '473'; + + /** + * Cuentas de gastos computables. + * + * @return array + */ + public static function expenses(): array + { + return [ + '60' => 'Compras', + '61' => 'Variación de existencias', + '62' => 'Servicios exteriores', + '63' => 'Tributos', + '64' => 'Gastos de personal', + '65' => 'Otros gastos de gestión', + '66' => 'Gastos financieros', + '67' => 'Pérdidas procedentes de activos y gastos excepcionales', + '68' => 'Dotaciones para amortizaciones', + '69' => 'Pérdidas por deterioro y otras dotaciones', + ]; + } + + /** + * Cuentas excluidas aunque pertenezcan a uno de los grupos de gastos. + * + * La cuenta 678 suele utilizarse para multas, sanciones y otros gastos + * excepcionales no deducibles. + * + * @return array + */ + public static function excludedExpenses(): array + { + return [ + '678' => 'Gastos excepcionales (subcuenta habitual para no deducibles', + ]; + } + + /** + * Cuentas de ingresos computables. + * + * @return array + */ + public static function incomes(): array + { + return [ + '70' => 'Ventas de mercaderías, producción propia y prestación de servicios', + '71' => 'Variación de existencias', + '72' => 'Trabajos realizados para la empresa', + '73' => 'Subvenciones, donaciones y legados', + '74' => 'Subvenciones, donaciones y legados a la explotación', + '75' => 'Otros ingresos de gestión', + '76' => 'Ingresos financieros', + '77' => 'Beneficios procedentes de activos e ingresos excepcionales', + '78' => 'Excesos y aplicaciones de provisiones', + '79' => 'Excesos y aplicaciones de provisiones y deterioros', + ]; + } + + /** + * Comprueba si una subcuenta debe considerarse gasto. + */ + public static function isExpense(string $code): bool + { + $code = trim($code); + + if ($code === '') { + return false; + } + + if (static::matches($code, array_keys(static::excludedExpenses()))) { + return false; + } + + return static::matches($code, array_keys(static::expenses())); + } + + /** + * Comprueba si una subcuenta debe considerarse ingreso. + */ + public static function isIncome(string $code): bool + { + return static::matches( + trim($code), + array_keys(static::incomes()) + ); + } + + /** + * Comprueba si una subcuenta pertenece a la cuenta 473. + */ + public static function isWithholding(string $code): bool + { + return str_starts_with( + trim($code), + static::WITHHOLDING_ACCOUNT + ); + } + + /** + * Devuelve todos los prefijos que deben buscarse en contabilidad. + * + * Se usa para construir la consulta SQL inicial y evitar cargar partidas + * que nunca van a intervenir en el Modelo 130. + * + * @return string[] + */ + public static function queryPrefixes(): array + { + return array_values(array_unique(array_merge( + array_keys(static::expenses()), + array_keys(static::incomes()), + [static::WITHHOLDING_ACCOUNT] + ))); + } + + /** + * Comprueba si un código comienza por alguno de los prefijos indicados. + * + * @param string[] $prefixes + */ + private static function matches(string $code, array $prefixes): bool + { + foreach ($prefixes as $prefix) { + if (str_starts_with($code, $prefix)) { + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/Model/Subcuenta130.php b/Model/Subcuenta130.php deleted file mode 100644 index 8d0f7e7..0000000 --- a/Model/Subcuenta130.php +++ /dev/null @@ -1,119 +0,0 @@ - - * - * 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\Model; - -use FacturaScripts\Core\Session; -use FacturaScripts\Core\Template\ModelClass; -use FacturaScripts\Core\Template\ModelTrait; -use FacturaScripts\Core\Tools; -use FacturaScripts\Core\Where; -use FacturaScripts\Dinamic\Model\Subcuenta; -use FacturaScripts\Dinamic\Model\User; - -class Subcuenta130 extends ModelClass -{ - use ModelTrait; - - const TIPO_DEDUCIBLE = 'deducible'; - const TIPO_INGRESO = 'ingreso'; - - /** @var string */ - public $codsubcuenta; - - /** @var string */ - public $creation_date; - - /** @var int */ - public $id; - - /** @var string */ - public $last_nick; - - /** @var string */ - public $last_update; - - /** @var string */ - public $name; - - /** @var string */ - public $nick; - - /** @var string */ - public $tipo = self::TIPO_DEDUCIBLE; - - public function clear(): void - { - parent::clear(); - $this->tipo = self::TIPO_DEDUCIBLE; - } - - public function getSubcuenta(): Subcuenta - { - $subcuenta = new Subcuenta(); - $where = [Where::eq('codsubcuenta', $this->codsubcuenta)]; - $subcuenta->loadWhere($where); - return $subcuenta; - } - - public function install(): string - { - new User(); - new Subcuenta(); - - return parent::install(); - } - - public static function tableName(): string - { - return "subcuentas_130"; - } - - public function test(): bool - { - if (empty($this->id())) { - $this->creation_date = Tools::dateTime(); - $this->last_nick = null; - $this->last_update = null; - $this->nick = Session::user()->nick; - } else { - $this->creation_date = $this->creationdate ?? Tools::dateTime(); - $this->last_nick = Session::user()->nick; - $this->last_update = Tools::dateTime(); - $this->nick = $this->nick ?? Session::user()->nick; - } - - $this->codsubcuenta = trim(Tools::noHtml((string)$this->codsubcuenta)); - $this->name = Tools::noHtml($this->name); - $this->tipo = in_array($this->tipo, [self::TIPO_DEDUCIBLE, self::TIPO_INGRESO], true) - ? $this->tipo - : self::TIPO_DEDUCIBLE; - - if (strlen($this->codsubcuenta) < 1 || strlen($this->codsubcuenta) > 15) { - Tools::log()->warning('invalid-column-lenght', [ - '%column%' => 'codsubcuenta', - '%min%' => '1', - '%max%' => '15' - ]); - return false; - } - - return parent::test(); - } -} diff --git a/Table/subcuentas_130.xml b/Table/subcuentas_130.xml deleted file mode 100644 index 0bd5775..0000000 --- a/Table/subcuentas_130.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - codsubcuenta - character varying(15) - NO - - - creation_date - timestamp - - - id - serial - NO - - - last_nick - character varying(50) - - - last_update - timestamp - - - name - character varying(100) - - - nick - character varying(50) - - - tipo - character varying(20) - 'deducible' - NO - - - subcuentas_130_pkey - PRIMARY KEY (id) - - - ca_subcuentas_130_users_last_nick - FOREIGN KEY (last_nick) REFERENCES users (nick) ON DELETE SET NULL ON UPDATE CASCADE - - - ca_subcuentas_130_users_nick - FOREIGN KEY (nick) REFERENCES users (nick) ON DELETE SET NULL ON UPDATE CASCADE - - - uniq_subcuentas_130 - UNIQUE (codsubcuenta) - -
\ No newline at end of file diff --git a/Test/Modelo130Test.php b/Test/Modelo130Test.php new file mode 100644 index 0000000..ce60b49 --- /dev/null +++ b/Test/Modelo130Test.php @@ -0,0 +1,190 @@ + + * + * 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\Core\Tools; +use FacturaScripts\Core\Where; +use FacturaScripts\Dinamic\Model\Asiento; +use FacturaScripts\Dinamic\Model\Ejercicio; +use FacturaScripts\Dinamic\Model\FormaPago; +use FacturaScripts\Dinamic\Model\Partida; +use FacturaScripts\Plugins\Modelo130\Lib\Modelo130; +use FacturaScripts\Test\Traits\DefaultSettingsTrait; +use FacturaScripts\Test\Traits\LogErrorsTrait; +use PHPUnit\Framework\TestCase; + +/** + * Tests para la clase estática Modelo130\Lib\Modelo130. + * + * @author Daniel Fernández Giménez + */ +final class Modelo130Test extends TestCase +{ + use DefaultSettingsTrait; + use LogErrorsTrait; + + public static function setUpBeforeClass(): void + { + self::setDefaultSettings(); + self::installAccountingPlan(); + } + + public function testGenerate(): void + { + $ejercicios = (new Ejercicio())->all([], ['codejercicio' => 'DESC'], 0, 1); + $this->assertNotEmpty($ejercicios, 'No hay ningún ejercicio en la base de datos'); + + $ejercicio = $ejercicios[0]; + $codejercicio = $ejercicio->codejercicio; + $resultado = Modelo130::generate($codejercicio, 'T1'); + + $this->assertIsArray($resultado, 'generate() debe devolver un array'); + $this->assertNotEmpty($resultado, 'generate() no debe devolver un array vacío para un ejercicio válido'); + + $clavesEsperadas = [ + 'exercise', + 'period', + 'idempresa', + 'sales', + 'purchases', + 'accountingEntries', + 'applyGastosJustificacion', + 'todeduct', + 'gastosJustificacionPct', + 'taxbaseIngresos', + 'taxbaseRetenciones', + 'taxbaseGastos', + 'taxbase', + 'gastosJustificacion', + 'afterdeduct', + 'positivosTrimestres', + 'result', + ]; + + foreach ($clavesEsperadas as $clave) { + $this->assertArrayHasKey($clave, $resultado, "El resultado debe contener la clave '$clave'"); + } + + $this->assertInstanceOf(Ejercicio::class, $resultado['exercise']); + $this->assertSame('T1', $resultado['period']); + $this->assertSame($codejercicio, $resultado['exercise']->codejercicio); + $this->assertIsArray($resultado['sales']); + $this->assertIsArray($resultado['purchases']); + $this->assertIsArray($resultado['accountingEntries']); + + $resultadoVacio = Modelo130::generate('EJERCICIO_INEXISTENTE_XYZ', 'T1'); + $this->assertIsArray($resultadoVacio); + $this->assertEmpty($resultadoVacio); + } + + public function testGenerateEntries(): void + { + $ejercicios = (new Ejercicio())->all([], ['codejercicio' => 'DESC'], 0, 1); + $this->assertNotEmpty($ejercicios, 'No hay ningún ejercicio en la base de datos'); + + $ejercicio = $ejercicios[0]; + $codejercicio = $ejercicio->codejercicio; + $idempresa = $ejercicio->idempresa; + + $formasPago = (new FormaPago())->all([], [], 0, 1); + $paymentMethodId = empty($formasPago) ? null : $formasPago[0]->id(); + + $periodo = 'T1'; + $importe = 100.0; + $fecha = date('Y-m-d'); + + $creado = Modelo130::generateEntries( + $idempresa, + $codejercicio, + $periodo, + $fecha, + $importe, + $paymentMethodId + ); + $this->assertTrue($creado, 'generateEntries() debe devolver true al crear el asiento por primera vez'); + + $concepto = Tools::trans('acc-concept-irpf-130', ['%period%' => $periodo]); + $asiento = new Asiento(); + $encontrado = $asiento->loadWhere([ + Where::eq('codejercicio', $codejercicio), + Where::eq('concepto', $concepto), + ]); + + $this->assertTrue($encontrado, 'El asiento debe existir en la base de datos tras generateEntries()'); + $this->assertEquals($importe, $asiento->importe, 'El importe del asiento debe ser 100.0'); + + $partidas = (new Partida())->all([ + Where::eq('idasiento', $asiento->idasiento), + ], ['orden' => 'ASC']); + + $this->assertCount(2, $partidas, 'El asiento debe contener dos partidas'); + $this->assertSame('4730000000', $partidas[0]->codsubcuenta); + $this->assertEquals($importe, $partidas[0]->debe); + $this->assertEquals(0.0, (float)$partidas[0]->haber); + $this->assertEquals($importe, $partidas[1]->haber); + + $duplicado = Modelo130::generateEntries( + $idempresa, + $codejercicio, + $periodo, + $fecha, + $importe, + $paymentMethodId + ); + $this->assertFalse($duplicado, 'generateEntries() debe devolver false si ya existe el asiento'); + + $this->assertTrue($asiento->delete(), 'No se pudo eliminar el asiento de prueba'); + } + + public function testCalcGastosJustificacion(): void + { + $this->assertSame(0.0, Modelo130::calcGastosJustificacion(10000.0, false, 7.0)); + $this->assertSame(0.0, Modelo130::calcGastosJustificacion(0.0, true, 7.0)); + $this->assertSame(0.0, Modelo130::calcGastosJustificacion(-500.0, true, 7.0)); + $this->assertSame(700.0, Modelo130::calcGastosJustificacion(10000.0, true, 7.0)); + $this->assertSame(500.0, Modelo130::calcGastosJustificacion(10000.0, true, 5.0)); + $this->assertSame(70.35, Modelo130::calcGastosJustificacion(1005.0, true, 7.0)); + $this->assertSame(2000.0, Modelo130::calcGastosJustificacion(40000.0, true, 7.0)); + $this->assertSame(2000.0, Modelo130::calcGastosJustificacion(100000.0, true, 7.0)); + $this->assertSame(1995.0, Modelo130::calcGastosJustificacion(28500.0, true, 7.0)); + $this->assertSame(2000.0, Modelo130::LIMITE_GASTOS_JUSTIFICACION); + } + + public function testCalcAfterDeduct(): void + { + $this->assertSame(2000.0, Modelo130::calcAfterDeduct(10000.0, 0.0, 20.0)); + $this->assertSame(0.0, Modelo130::calcAfterDeduct(-500.0, 0.0, 20.0)); + $this->assertSame(0.0, Modelo130::calcAfterDeduct(0.0, 0.0, 20.0)); + $this->assertSame(1860.0, Modelo130::calcAfterDeduct(10000.0, 700.0, 20.0)); + } + + public function testCalcResult(): void + { + $this->assertSame(1200.0, Modelo130::calcResult(2000.0, 500.0, 300.0)); + $this->assertSame(0.0, Modelo130::calcResult(500.0, 400.0, 300.0)); + $this->assertSame(0.0, Modelo130::calcResult(100.0, 50.0, 50.0)); + $this->assertSame(33.33, Modelo130::calcResult(100.005, 33.33, 33.345)); + } + + protected function tearDown(): void + { + $this->logErrors(); + } +} diff --git a/Test/main/Subcuenta130Test.php b/Test/main/Subcuenta130Test.php deleted file mode 100644 index c19f4d7..0000000 --- a/Test/main/Subcuenta130Test.php +++ /dev/null @@ -1,163 +0,0 @@ - - * - * 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\Cuenta; -use FacturaScripts\Dinamic\Model\Ejercicio; -use FacturaScripts\Dinamic\Model\Subcuenta; -use FacturaScripts\Dinamic\Model\Subcuenta130; -use FacturaScripts\Test\Traits\DefaultSettingsTrait; -use FacturaScripts\Test\Traits\LogErrorsTrait; -use PHPUnit\Framework\TestCase; - -/** - * @author Abderrahim Darghal Belkacemi - */ -final class ModTest extends TestCase -{ - use DefaultSettingsTrait; - use LogErrorsTrait; - - public static function setUpBeforeClass(): void - { - self::setDefaultSettings(); - self::installAccountingPlan(); - } - - public function testCreateSubcuenta130(): void - { - // crear un ejercicio - $exercise = $this->getRandomExercise(); - - // crear una cuenta - $account = new Cuenta(); - $account->codcuenta = '9999'; - $account->codejercicio = $exercise->codejercicio; - $account->descripcion = 'Test'; - $this->assertTrue($account->save(), 'cant-save-account'); - - // crear una subcuenta - $subaccount = new Subcuenta(); - $subaccount->codcuenta = $account->codcuenta; - $subaccount->codejercicio = $exercise->codejercicio; - $subaccount->codsubcuenta = $account->codcuenta . '.1'; - $subaccount->descripcion = 'Test'; - $this->assertTrue($subaccount->save(), 'cant-save-subaccount'); - $this->assertTrue($subaccount->exists(), 'subaccount-cant-persist'); - - - // crear subcuenta130 - $subcuenta130 = new Subcuenta130(); - $subcuenta130->codsubcuenta = $subaccount->codsubcuenta; - $this->assertTrue($subcuenta130->save(), 'subcuenta130-cant-save'); - - // borrar subcuenta y subcuenta130 - $this->assertTrue($subcuenta130->delete(), 'subcuenta130-cant-delete'); - $this->assertTrue($subaccount->delete(), 'subaccount-cant-delete'); - $this->assertTrue($account->delete(), 'account-cant-delete'); - } - - public function testCreateEmptySubcuenta130Fails(): void - { - $subcuenta130 = new Subcuenta130(); - $subcuenta130->codsubcuenta = ' '; - - $this->assertFalse($subcuenta130->save(), 'empty-subcuenta130-should-fail'); - } - - public function testSubcuenta130TieneTipoDeduciblePorDefecto(): void - { - $sub130 = new Subcuenta130(); - $this->assertSame(Subcuenta130::TIPO_DEDUCIBLE, $sub130->tipo); - } - - public function testTipoInvalidoFuerzaTipoDeducible(): void - { - $exercise = $this->getRandomExercise(); - - $account = new Cuenta(); - $account->codcuenta = '9998'; - $account->codejercicio = $exercise->codejercicio; - $account->descripcion = 'Test tipo invalido'; - $this->assertTrue($account->save(), 'cant-save-account'); - - $subaccount = new Subcuenta(); - $subaccount->codcuenta = $account->codcuenta; - $subaccount->codejercicio = $exercise->codejercicio; - $subaccount->codsubcuenta = '9998000000'; - $subaccount->descripcion = 'Test tipo invalido'; - $this->assertTrue($subaccount->save(), 'cant-save-subaccount'); - - $sub130 = new Subcuenta130(); - $sub130->codsubcuenta = $subaccount->codsubcuenta; - $sub130->tipo = 'tipo_invalido_xyz'; - $this->assertTrue($sub130->save(), 'cant-save-subcuenta130'); - $this->assertSame(Subcuenta130::TIPO_DEDUCIBLE, $sub130->tipo, 'invalid-tipo-should-fallback-to-deducible'); - - $this->assertTrue($sub130->delete()); - $this->assertTrue($subaccount->delete()); - $this->assertTrue($account->delete()); - } - - public function testCreateSubcuenta130ConTipoIngreso(): void - { - $exercise = $this->getRandomExercise(); - - $account = new Cuenta(); - $account->codcuenta = '9997'; - $account->codejercicio = $exercise->codejercicio; - $account->descripcion = 'Test ingreso'; - $this->assertTrue($account->save(), 'cant-save-account'); - - $subaccount = new Subcuenta(); - $subaccount->codcuenta = $account->codcuenta; - $subaccount->codejercicio = $exercise->codejercicio; - $subaccount->codsubcuenta = '9997000000'; - $subaccount->descripcion = 'Test ingreso'; - $this->assertTrue($subaccount->save(), 'cant-save-subaccount'); - - $sub130 = new Subcuenta130(); - $sub130->codsubcuenta = $subaccount->codsubcuenta; - $sub130->tipo = Subcuenta130::TIPO_INGRESO; - $this->assertTrue($sub130->save(), 'cant-save-subcuenta130-ingreso'); - $this->assertSame(Subcuenta130::TIPO_INGRESO, $sub130->tipo, 'tipo-should-be-ingreso'); - - $this->assertTrue($sub130->delete()); - $this->assertTrue($subaccount->delete()); - $this->assertTrue($account->delete()); - } - - protected function getRandomExercise(): Ejercicio - { - $model = new Ejercicio(); - foreach ($model->all() as $ejercicio) { - return $ejercicio; - } - - // no hemos encontrado ninguno, creamos uno - $model->loadFromDate(date('d-m-Y')); - return $model; - } - - protected function tearDown(): void - { - $this->logErrors(); - } -} diff --git a/Translation/ca_ES.json b/Translation/ca_ES.json index c2f3e98..f3eb66a 100644 --- a/Translation/ca_ES.json +++ b/Translation/ca_ES.json @@ -17,5 +17,8 @@ "previous-model": "Ingressos trimestres anteriors", "tax-expenses": "Despeses deduïbles", "tax-incomes": "Ingressos computables", - "tax-negative-info": "Si dóna pèrdues (negatiu) = 0" + "tax-negative-info": "Si dóna pèrdues (negatiu) = 0", + "income": "Ingrés computable", + "expense": "Despesa deduïble", + "previous-payment": "Pagament fraccionat anterior" } \ No newline at end of file diff --git a/Translation/de_DE.json b/Translation/de_DE.json index fe09c0a..4961e74 100644 --- a/Translation/de_DE.json +++ b/Translation/de_DE.json @@ -17,5 +17,8 @@ "previous-model": "Einkünfte aus vorherigen Quartalen", "tax-expenses": "Absetzbare Ausgaben", "tax-incomes": "Steuerbare Einkommen", - "tax-negative-info": "Wenn Verluste (negativ) = 0 sind" + "tax-negative-info": "Wenn Verluste (negativ) = 0 sind", + "income": "Steuerpflichtige Einnahme", + "expense": "Abzugsfähige Ausgabe", + "previous-payment": "Vorherige Vorauszahlung" } \ No newline at end of file diff --git a/Translation/en_EN.json b/Translation/en_EN.json index 3bc2919..e4e6e0f 100644 --- a/Translation/en_EN.json +++ b/Translation/en_EN.json @@ -23,5 +23,8 @@ "previous-model": "Previous quarters income", "tax-expenses": "Deductible expenses", "tax-incomes": "Computable income", - "tax-negative-info": "If it gives losses (negative) = 0" + "tax-negative-info": "If it gives losses (negative) = 0", + "income": "Taxable income", + "expense": "Deductible expense", + "previous-payment": "Previous payment" } \ No newline at end of file diff --git a/Translation/es_ES.json b/Translation/es_ES.json index 0939904..db1824d 100644 --- a/Translation/es_ES.json +++ b/Translation/es_ES.json @@ -23,5 +23,8 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" + "tax-negative-info": "Si da pérdidas (negativo) = 0", + "income": "Ingreso computable", + "expense": "Gasto deducible", + "previous-payment": "Pago fraccionado anterior" } \ No newline at end of file diff --git a/Translation/eu_ES.json b/Translation/eu_ES.json index 8c91ee9..2ec74ee 100644 --- a/Translation/eu_ES.json +++ b/Translation/eu_ES.json @@ -17,5 +17,8 @@ "previous-model": "Aurreko hiruhilekoetako sarrerak", "tax-expenses": "Gastu kengarriak", "tax-incomes": "Errenta konputagarria", - "tax-negative-info": "Galerak ematen baditu (negatiboak) = 0" + "tax-negative-info": "Galerak ematen baditu (negatiboak) = 0", + "income": "Sarrera zenbagarria", + "expense": "Gastu kengarria", + "previous-payment": "Aurreko ordainketa zatikatua" } \ No newline at end of file diff --git a/Translation/fr_FR.json b/Translation/fr_FR.json index 11b712e..4e836cd 100644 --- a/Translation/fr_FR.json +++ b/Translation/fr_FR.json @@ -17,5 +17,8 @@ "previous-model": "Revenu du trimestre précédent", "tax-expenses": "Dépenses déductibles", "tax-incomes": "Revenu éligible", - "tax-negative-info": "Si déficitaire (négatif) = 0" + "tax-negative-info": "Si déficitaire (négatif) = 0", + "income": "Revenu imposable", + "expense": "Dépense déductible", + "previous-payment": "Paiement fractionné précédent" } \ No newline at end of file diff --git a/Translation/gl_ES.json b/Translation/gl_ES.json index b608956..e7e26b9 100644 --- a/Translation/gl_ES.json +++ b/Translation/gl_ES.json @@ -17,5 +17,8 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Se dá perdas (negativo) = 0" + "tax-negative-info": "Se dá perdas (negativo) = 0", + "income": "Ingreso computable", + "expense": "Gasto deducible", + "previous-payment": "Pagamento fraccionado anterior" } \ No newline at end of file diff --git a/Translation/it_IT.json b/Translation/it_IT.json index ce57388..488b987 100644 --- a/Translation/it_IT.json +++ b/Translation/it_IT.json @@ -17,5 +17,8 @@ "previous-model": "Modelli precedenti", "tax-expenses": "Spese fiscali deducibili", "tax-incomes": "Ricavi fiscali", - "tax-negative-info": "Se genera perdite (negative) = 0" + "tax-negative-info": "Se genera perdite (negative) = 0", + "income": "Reddito imponibile", + "expense": "Spesa deducibile", + "previous-payment": "Acconto precedente" } \ No newline at end of file diff --git a/Translation/pt_BR.json b/Translation/pt_BR.json index aa1f0b3..4174dcd 100644 --- a/Translation/pt_BR.json +++ b/Translation/pt_BR.json @@ -17,5 +17,8 @@ "previous-model": "Receitas de trimestres anteriores", "tax-expenses": "Despesas dedutíveis", "tax-incomes": "Rendimentos tributáveis", - "tax-negative-info": "Se der negativo = 0" + "tax-negative-info": "Se der negativo = 0", + "income": "Receita tributável", + "expense": "Despesa dedutível", + "previous-payment": "Pagamento por conta anterior" } \ No newline at end of file diff --git a/Translation/pt_PT.json b/Translation/pt_PT.json index 32727e1..c8ec92c 100644 --- a/Translation/pt_PT.json +++ b/Translation/pt_PT.json @@ -17,5 +17,8 @@ "previous-model": "Receitas de trimestres anteriores", "tax-expenses": "Despesas dedutíveis", "tax-incomes": "Impostos sobre rendimentos", - "tax-negative-info": "Se der perdas (negativo) = 0" + "tax-negative-info": "Se der perdas (negativo) = 0", + "income": "Receita tributável", + "expense": "Despesa dedutível", + "previous-payment": "Pagamento por conta anterior" } \ No newline at end of file diff --git a/View/Modelo130.html.twig b/View/Modelo130.html.twig index a239e16..87dba21 100644 --- a/View/Modelo130.html.twig +++ b/View/Modelo130.html.twig @@ -1,704 +1,453 @@ {% extends "Master/MenuTemplate.html.twig" %} -{% block css %} - {{ parent() }} - -{% endblock %} +{% block body %} +
+
+
+
+

+ + {{ fsc.title }} +

+

{{ trans('model-130-p') }}

+
+
-{% block javascripts %} - {{ parent() }} - - -{% endblock %} +
+
+ {{ trans('period') }} + +
+
-{% block body %} - -
-
-
-

- - {{ fsc.title }} -

-

{{ trans('model-130-p') }}

-
-
-
-
-
- {{ trans('exercise') }} - -
-
-
-
- {{ trans('period') }} - -
-
-
-
- - {% if fsc.result is not empty %} - - {% endif %} -
-
-
-
-
-

- - {{ trans('summary') }} -

-
-

{{ trans('model-130-r') }}

-

{{ trans('model-130-desc') }}

-
-
-
-

{{ trans('net-total') }}

-
-
-
- {{ trans('tax-incomes') }} -
- 01 - -
-
-
-
-
- {{ trans('tax-expenses') }} -
- 02 - -
-
-
-
-
- {{ trans('net-total') }} -
- 03 - -
-
-
-
-
-

{{ trans('pct-to-deduct') }}

-
-
-
- {{ trans('gastos-justificacion-pct') }} -
- - % -
-
-
-
-
-
- - -
-
- -
- {% if fsc.result.gastosJustificacion >= 2000 %} -

- - {{ trans('gastos-justificacion-limit') }} -

- {% endif %} -
-
-
-
- {{ trans('pct-to-deduct') }} -
- -
-
-
-
-
- {{ fsc.result.todeduct }}% - {{ trans('after-deduct') }} -
- 04 - -
-
-
-
-
-

{{ trans('result') }}

-
-
-
- {{ trans('previous-model') }} -
- 05 - -
-
-
-
-
- {{ trans('retentions') }} -
- 06 - -
-
-
-
-
- {{ trans('result') }} -
- 07 - -
-

{{ trans('tax-negative-info') }}

-
-
- {% if fsc.result.result > 0 %} -
- -
- {% endif %} -
-
- - -
-
-
- - - - - - - - - - - - - - - {% set salesTotalNeto = 0 %} - {% set salesTotalTax = 0 %} - {% set salesTotalSurcharge = 0 %} - {% set salesTotalRetention = 0 %} - {% set salesTotal = 0 %} - {% for item in fsc.result.customerInvoices %} - {% set salesTotalNeto = salesTotalNeto + item.neto %} - {% set salesTotalTax = salesTotalTax + item.totaliva %} - {% set salesTotalSurcharge = salesTotalSurcharge + item.totalrecargo %} - {% set salesTotalRetention = salesTotalRetention + item.totalirpf %} - {% set salesTotal = salesTotal + item.total %} - - - - - - - - - - - {% else %} - - - - {% endfor %} - -
{{ trans('invoice') }}{{ trans('customer') }}{{ trans('tax-base') }}{{ trans('vat') }}{{ trans('surcharge') }}{{ trans('irpf') }}{{ trans('total') }}{{ trans('date') }}
- {{ item.codigo }} - {{ item.nombrecliente | raw }}{{ money(item.neto) }}{{ money(item.totaliva) }}{{ money(item.totalrecargo) }}{{ money(item.totalirpf) }}{{ money(item.total) }}{{ item.fecha }}
{{ trans('no-data') }}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ trans('totals') }}
{{ trans('tax-base') }}{{ money(salesTotalNeto) }}
{{ trans('vat') }}{{ money(salesTotalTax) }}
{{ trans('surcharge') }}{{ money(salesTotalSurcharge) }}
{{ trans('irpf') }}{{ money(salesTotalRetention) }}
{{ trans('total-amount') }}{{ money(salesTotal) }}
-
-
-
-
- - - - - - - - - - - - - - - {% set purchasesTotalNeto = 0 %} - {% set purchasesTotalTax = 0 %} - {% set purchasesTotalSurcharge = 0 %} - {% set purchasesTotalRetention = 0 %} - {% set purchasesTotal = 0 %} - {% for item in fsc.result.supplierInvoices %} - {% set purchasesTotalNeto = purchasesTotalNeto + item.neto %} - {% set purchasesTotalTax = purchasesTotalTax + item.totaliva %} - {% set purchasesTotalSurcharge = purchasesTotalSurcharge + item.totalrecargo %} - {% set purchasesTotalRetention = purchasesTotalRetention + item.totalirpf %} - {% set purchasesTotal = purchasesTotal + item.total %} - - - - - - - - - - - {% else %} - - - - {% endfor %} - -
{{ trans('invoice') }}{{ trans('supplier') }}{{ trans('tax-base') }}{{ trans('vat') }}{{ trans('surcharge') }}{{ trans('irpf') }}{{ trans('total') }}{{ trans('date') }}
- {{ item.codigo }} - {{ item.nombre | raw }}{{ money(item.neto) }}{{ money(item.totaliva) }}{{ money(item.totalrecargo) }}{{ money(item.totalirpf) }}{{ money(item.total) }}{{ item.fecha }}
{{ trans('no-data') }}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ trans('totals') }}
{{ trans('tax-base') }}{{ money(purchasesTotalNeto) }}
{{ trans('vat') }}{{ money(purchasesTotalTax) }}
{{ trans('surcharge') }}{{ money(purchasesTotalSurcharge) }}
{{ trans('irpf') }}{{ money(purchasesTotalRetention) }}
{{ trans('total-amount') }}{{ money(purchasesTotal) }}
-
-
-
-
- - - - - - - - - - - - {% set accountingTotalNeto = 0 %} - {% for item in fsc.result.accountingEntries %} - {% set accountingTotalNeto = accountingTotalNeto + item.debe %} - - - - - - - - {% else %} - - - - {% endfor %} - -
{{ trans('accounting-entry') }}{{ trans('subaccount') }}{{ trans('concept') }}{{ trans('total') }}{{ trans('date') }}
- {{ item.numero }} - {{ item.codsubcuenta }}{{ item.concepto | raw }}{{ money(item.debe) }}{{ item.fecha }}
{{ trans('no-data') }}
- - - - - - - - - - - - - -
{{ trans('totals') }}
{{ trans('total-amount') }}{{ money(accountingTotalNeto) }}
-
-
-
-
- - - - - - - - - - {% for subaccount in fsc.getDeductibleSubaccounts() %} - - - - - - {% endfor %} - -
{{ trans('code') }}{{ trans('description') }}
{{ subaccount.codsubcuenta }}{{ subaccount.getSubcuenta().descripcion }} -
- {{ formToken() }} - - - -
-
-
-
- {{ formToken() }} - - -
-
-
- - -
-
-
-
-
-
-
- - - - - - - - - - {% for subaccount in fsc.getIncomeSubaccounts() %} - - - - - - {% endfor %} - -
{{ trans('code') }}{{ trans('description') }}
{{ subaccount.codsubcuenta }}{{ subaccount.getSubcuenta().descripcion }} -
- {{ formToken() }} - - - -
-
-
-
- {{ formToken() }} - - -
-
-
- - -
-
-
-
-
-
+
+
+ + {% if fsc.result is not empty %} + + {% endif %} +
+
+
+ +
+
+

+ + {{ trans('summary') }} +

+
+

{{ trans('model-130-r') }}

+

{{ trans('model-130-desc') }}

+
+
+
+ +

{{ trans('net-total') }}

+
+
+
+ {{ trans('tax-incomes') }} +
+ 01 + +
+
+
+
+
+ {{ trans('tax-expenses') }} +
+ 02 + +
+
+
+
+
+ {{ trans('net-total') }} +
+ 03 + +
+
+
+
+ +
+ +

{{ trans('pct-to-deduct') }}

+
+
+
+ {{ trans('gastos-justificacion-pct') }} +
+ + % +
+
+
+
+
+
+ + +
+
+ +
+ {% if fsc.result.gastosJustificacion >= 2000 %} +

+ + {{ trans('gastos-justificacion-limit') }} +

+ {% endif %} +
+
+
+
+ {{ trans('pct-to-deduct') }} +
+ +
+
+
+
+
+ {{ fsc.result.todeduct }}% {{ trans('after-deduct') }} +
+ 04 + +
+
+
+
+ +
+ +

{{ trans('result') }}

+
+
+
+ {{ trans('previous-model') }} +
+ 05 + +
+
+
+
+
+ {{ trans('retentions') }} +
+ 06 + +
+
+
+
+
+ {{ trans('result') }} +
+ 07 + +
+

{{ trans('tax-negative-info') }}

+
+
+ {% if fsc.result.result > 0 %} +
+ +
+ {% endif %} +
+ + + + + +
+
+
+ + + + + + + + + + + + + + {% set salesTotalBase = 0 %} + {% set salesTotalIrpf = 0 %} + {% for item in fsc.result.sales %} + {% set salesTotalBase = salesTotalBase + item.baseimponible %} + {% set salesTotalIrpf = salesTotalIrpf + item.irpf %} + + + + + + + + + + {% else %} + + + + {% endfor %} + + + + + + + + +
{{ trans('serie') }}{{ trans('invoice') }}{{ trans('accounting-entry') }}{{ trans('date') }}{{ trans('concept') }}{{ trans('tax-base') }}{{ trans('irpf') }}
{{ item.serie }} + {% if item.invoice is defined and item.invoice %} + {{ item.factura }} + {% else %} + {{ item.factura }} + {% endif %} + + {% if item.entry %} + {{ item.entry.numero }} + {% endif %} + {{ item.fecha }}{{ item.concepto | raw }}{{ money(item.baseimponible) }}{{ money(item.irpf) }}
{{ trans('no-data') }}
{{ trans('totals') }}{{ money(salesTotalBase) }}{{ money(salesTotalIrpf) }}
+
+
+ +
+
+ + + + + + + + + + + + + {% set purchasesTotalBase = 0 %} + {% for item in fsc.result.purchases %} + {% set purchasesTotalBase = purchasesTotalBase + item.baseimponible %} + + + + + + + + + {% else %} + + + + {% endfor %} + + + + + + + +
{{ trans('serie') }}{{ trans('invoice') }}{{ trans('accounting-entry') }}{{ trans('date') }}{{ trans('concept') }}{{ trans('tax-base') }}
{{ item.serie }} + {% if item.invoice is defined and item.invoice %} + {{ item.factura }} + {% else %} + {{ item.factura }} + {% endif %} + + {% if item.entry %} + {{ item.entry.numero }} + {% endif %} + {{ item.fecha }}{{ item.concepto | raw }}{{ money(item.baseimponible) }}
{{ trans('no-data') }}
{{ trans('totals') }}{{ money(purchasesTotalBase) }}
+
+
+ +
+
+ + + + + + + + + + + + + {% set accountingTotal = 0 %} + {% for item in fsc.result.accountingEntries %} + {% set accountingTotal = accountingTotal + item.amount %} + + + + + + + + + {% else %} + + + + {% endfor %} + +{# + + + + + + + +#} +
{{ trans('accounting-entry') }}{{ trans('subaccount') }}{{ trans('concept') }}{{ trans('type') }}{{ trans('total') }}{{ trans('date') }}
+ {{ item.entry.numero }} + {{ item.partida.codsubcuenta }}{{ item.partida.concepto | raw }}{{ trans(item.type) }}{{ money(item.amount) }}{{ item.entry.fecha }}
{{ trans('no-data') }}
{{ trans('totals') }}{{ money(accountingTotal) }}
+
+
+
- - + + {% endblock %} From 11e1cead0240629bcfb9d39437da07b81bd14231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mart=C3=ADn=20Gonz=C3=A1lez?= Date: Wed, 22 Jul 2026 16:57:15 +0200 Subject: [PATCH 02/15] =?UTF-8?q?Ordenadas=20traducciones=20alfab=C3=A9tic?= =?UTF-8?q?amente=20y=20agregadas=20otras=20faltantes=20en=20algunos=20idi?= =?UTF-8?q?omas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Translation/ca_ES.json | 16 +++++++++++----- Translation/cs_CZ.json | 2 +- Translation/de_DE.json | 16 +++++++++++----- Translation/en_EN.json | 18 +++++++++--------- Translation/es_AR.json | 2 +- Translation/es_CL.json | 2 +- Translation/es_CO.json | 2 +- Translation/es_CR.json | 2 +- Translation/es_DO.json | 2 +- Translation/es_EC.json | 2 +- Translation/es_ES.json | 16 ++++++++-------- Translation/es_GT.json | 2 +- Translation/es_MX.json | 2 +- Translation/es_PA.json | 2 +- Translation/es_PE.json | 2 +- Translation/es_UY.json | 2 +- Translation/eu_ES.json | 16 +++++++++++----- Translation/fr_FR.json | 16 +++++++++++----- Translation/gl_ES.json | 16 +++++++++++----- Translation/it_IT.json | 16 +++++++++++----- Translation/pl_PL.json | 2 +- Translation/pt_BR.json | 16 +++++++++++----- Translation/pt_PT.json | 16 +++++++++++----- Translation/tr_TR.json | 2 +- Translation/va_ES.json | 2 +- 25 files changed, 120 insertions(+), 72 deletions(-) diff --git a/Translation/ca_ES.json b/Translation/ca_ES.json index f3eb66a..6e4ad8d 100644 --- a/Translation/ca_ES.json +++ b/Translation/ca_ES.json @@ -1,12 +1,20 @@ { "acc-concept-irpf-130": "Regularització d'IRPF %period%", "accounting-created-130": "S'ha creat el seient en l'exercici %codejercicio% amb el concepte: %concepte%", + "aeat-file-invalid-exercise": "No es pot generar el fitxer AEAT: l'exercici seleccionat no és vàlid.", + "aeat-file-invalid-length": "No es pot generar el fitxer AEAT: el fitxer generat no té la longitud esperada.", + "aeat-file-invalid-nif": "No es pot generar el fitxer AEAT: el NIF de l'empresa és buit o no és vàlid.", + "aeat-file-missing-company-name": "No es pot generar el fitxer AEAT: el nom de l'empresa és buit.", "after-deduct": "sobre casella 04", + "company-not-found": "Empresa no trobada", "deductible-subaccounts": "Comptes deduïbles", + "download-aeat-file": "Descarrega el fitxer AEAT", "exists-accounting-130": "Ja existeix un seient en l'exercici %codejercicio% amb el concepte: %concepte%", + "expense": "Despesa deduïble", "gastos-justificacion": "Despeses de difícil justificació", "gastos-justificacion-limit": "Atenció: l'import supera el límit anual de 2.000 €", "gastos-justificacion-pct": "% Despeses de difícil justificació", + "income": "Ingrés computable", "income-subaccounts": "Comptes d'ingressos", "model-130": "Model 130", "model-130-desc": "Es calcula sumant i acumulant cada trimestre, per exemple, si visualitzem el 3r trimestre veurem la suma dels trimestres 1, 2 i 3.", @@ -15,10 +23,8 @@ "net-total": "Rendiment net", "pct-to-deduct": "% a deduir", "previous-model": "Ingressos trimestres anteriors", + "previous-payment": "Pagament fraccionat anterior", "tax-expenses": "Despeses deduïbles", "tax-incomes": "Ingressos computables", - "tax-negative-info": "Si dóna pèrdues (negatiu) = 0", - "income": "Ingrés computable", - "expense": "Despesa deduïble", - "previous-payment": "Pagament fraccionat anterior" -} \ No newline at end of file + "tax-negative-info": "Si dóna pèrdues (negatiu) = 0" +} diff --git a/Translation/cs_CZ.json b/Translation/cs_CZ.json index 7b1ae90..6c8a4b6 100644 --- a/Translation/cs_CZ.json +++ b/Translation/cs_CZ.json @@ -18,4 +18,4 @@ "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ No newline at end of file +} diff --git a/Translation/de_DE.json b/Translation/de_DE.json index 4961e74..6bacba1 100644 --- a/Translation/de_DE.json +++ b/Translation/de_DE.json @@ -1,12 +1,20 @@ { "acc-concept-irpf-130": "IRPF-Anpassung %period%", "accounting-created-130": "Der Buchungssatz wurde im Geschäftsjahr %codejercicio% mit dem Verwendungszweck: %concepto% erstellt.", + "aeat-file-invalid-exercise": "Die AEAT-Datei kann nicht erstellt werden: Das ausgewählte Geschäftsjahr ist ungültig.", + "aeat-file-invalid-length": "Die AEAT-Datei kann nicht erstellt werden: Die erzeugte Datei hat nicht die erwartete Länge.", + "aeat-file-invalid-nif": "Die AEAT-Datei kann nicht erstellt werden: Die Steuer-ID des Unternehmens fehlt oder ist ungültig.", + "aeat-file-missing-company-name": "Die AEAT-Datei kann nicht erstellt werden: Der Firmenname fehlt.", "after-deduct": "nach Feld 04", + "company-not-found": "Unternehmen nicht gefunden", "deductible-subaccounts": "Abziehbare Unterkonten", + "download-aeat-file": "AEAT-Datei herunterladen", "exists-accounting-130": "Es existiert bereits ein Buchungssatz im Geschäftsjahr %codejercicio% mit dem Verwendungszweck: %concepto%", + "expense": "Abzugsfähige Ausgabe", "gastos-justificacion": "Schwer belegbare Ausgaben", "gastos-justificacion-limit": "Achtung: Der Betrag überschreitet das jährliche Limit von 2.000 €", "gastos-justificacion-pct": "% Schwer belegbare Ausgaben", + "income": "Steuerpflichtige Einnahme", "income-subaccounts": "Unterkonten für Einnahmen", "model-130": "Modell 130", "model-130-desc": "Es wird berechnet, indem jeder Quartal summiert und akkumuliert wird. Zum Beispiel, wenn wir das 3. Quartal betrachten, sehen wir die Summe der Quartale 1, 2 und 3.", @@ -15,10 +23,8 @@ "net-total": "Nettoertrag", "pct-to-deduct": "% abzuziehen", "previous-model": "Einkünfte aus vorherigen Quartalen", + "previous-payment": "Vorherige Vorauszahlung", "tax-expenses": "Absetzbare Ausgaben", "tax-incomes": "Steuerbare Einkommen", - "tax-negative-info": "Wenn Verluste (negativ) = 0 sind", - "income": "Steuerpflichtige Einnahme", - "expense": "Abzugsfähige Ausgabe", - "previous-payment": "Vorherige Vorauszahlung" -} \ No newline at end of file + "tax-negative-info": "Wenn Verluste (negativ) = 0 sind" +} diff --git a/Translation/en_EN.json b/Translation/en_EN.json index e4e6e0f..c85611b 100644 --- a/Translation/en_EN.json +++ b/Translation/en_EN.json @@ -1,18 +1,20 @@ { "acc-concept-irpf-130": "IRPF regularization %period%", + "accounting-created-130": "An accounting entry has been created in fiscal year %codejercicio% with the concept: %concepto%", "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-invalid-nif": "Cannot generate the AEAT file: the company's tax identification number is missing or invalid.", "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", + "company-not-found": "Company not found", "deductible-subaccounts": "Deductible Accounts", + "download-aeat-file": "Download AEAT file", "exists-accounting-130": "An accounting entry already exists in fiscal year %codejercicio% with the concept: %concepto%", + "expense": "Deductible expense", "gastos-justificacion": "Difficult-to-justify expenses", "gastos-justificacion-limit": "Warning: the amount exceeds the annual limit of €2,000", "gastos-justificacion-pct": "% Hard-to-justify expenses", + "income": "Taxable income", "income-subaccounts": "Income accounts", "model-130": "Model 130", "model-130-desc": "It is calculated by adding and accumulating each quarter, for example, if we visualize the 3rd quarter we will see the sum of quarters 1, 2 and 3.", @@ -21,10 +23,8 @@ "net-total": "Net return", "pct-to-deduct": "% to deduct", "previous-model": "Previous quarters income", + "previous-payment": "Previous payment", "tax-expenses": "Deductible expenses", "tax-incomes": "Computable income", - "tax-negative-info": "If it gives losses (negative) = 0", - "income": "Taxable income", - "expense": "Deductible expense", - "previous-payment": "Previous payment" -} \ No newline at end of file + "tax-negative-info": "If it gives losses (negative) = 0" +} diff --git a/Translation/es_AR.json b/Translation/es_AR.json index 47c9b3d..fbc8a4c 100644 --- a/Translation/es_AR.json +++ b/Translation/es_AR.json @@ -18,4 +18,4 @@ "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ No newline at end of file +} diff --git a/Translation/es_CL.json b/Translation/es_CL.json index 47c9b3d..fbc8a4c 100644 --- a/Translation/es_CL.json +++ b/Translation/es_CL.json @@ -18,4 +18,4 @@ "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ No newline at end of file +} diff --git a/Translation/es_CO.json b/Translation/es_CO.json index 47c9b3d..fbc8a4c 100644 --- a/Translation/es_CO.json +++ b/Translation/es_CO.json @@ -18,4 +18,4 @@ "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ No newline at end of file +} diff --git a/Translation/es_CR.json b/Translation/es_CR.json index 47c9b3d..fbc8a4c 100644 --- a/Translation/es_CR.json +++ b/Translation/es_CR.json @@ -18,4 +18,4 @@ "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ No newline at end of file +} diff --git a/Translation/es_DO.json b/Translation/es_DO.json index 47c9b3d..fbc8a4c 100644 --- a/Translation/es_DO.json +++ b/Translation/es_DO.json @@ -18,4 +18,4 @@ "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ No newline at end of file +} diff --git a/Translation/es_EC.json b/Translation/es_EC.json index 47c9b3d..fbc8a4c 100644 --- a/Translation/es_EC.json +++ b/Translation/es_EC.json @@ -18,4 +18,4 @@ "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ No newline at end of file +} diff --git a/Translation/es_ES.json b/Translation/es_ES.json index db1824d..415a8a5 100644 --- a/Translation/es_ES.json +++ b/Translation/es_ES.json @@ -1,18 +1,20 @@ { "acc-concept-irpf-130": "Regularización de IRPF %period%", + "accounting-created-130": "Se ha creado el asiento en el ejercicio %codejercicio% con el concepto: %concepto%", "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", + "company-not-found": "Empresa no encontrada", "deductible-subaccounts": "Cuentas deducibles", + "download-aeat-file": "Descargar fichero AEAT", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "expense": "Gasto deducible", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", + "income": "Ingreso computable", "income-subaccounts": "Cuentas de ingresos", "model-130": "Modelo 130", "model-130-desc": "Se calcula sumando y acumulando cada trimestre, por ejemplo, si visualizamos el 3º trimestre veremos la suma de los trimestres 1, 2 y 3.", @@ -21,10 +23,8 @@ "net-total": "Rendimiento neto", "pct-to-deduct": "% a deducir", "previous-model": "Ingresos trimestres anteriores", + "previous-payment": "Pago fraccionado anterior", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0", - "income": "Ingreso computable", - "expense": "Gasto deducible", - "previous-payment": "Pago fraccionado anterior" -} \ No newline at end of file + "tax-negative-info": "Si da pérdidas (negativo) = 0" +} diff --git a/Translation/es_GT.json b/Translation/es_GT.json index 47c9b3d..fbc8a4c 100644 --- a/Translation/es_GT.json +++ b/Translation/es_GT.json @@ -18,4 +18,4 @@ "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ No newline at end of file +} diff --git a/Translation/es_MX.json b/Translation/es_MX.json index 47c9b3d..fbc8a4c 100644 --- a/Translation/es_MX.json +++ b/Translation/es_MX.json @@ -18,4 +18,4 @@ "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ No newline at end of file +} diff --git a/Translation/es_PA.json b/Translation/es_PA.json index 172433a..6e1bc4a 100644 --- a/Translation/es_PA.json +++ b/Translation/es_PA.json @@ -18,4 +18,4 @@ "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ No newline at end of file +} diff --git a/Translation/es_PE.json b/Translation/es_PE.json index 47c9b3d..fbc8a4c 100644 --- a/Translation/es_PE.json +++ b/Translation/es_PE.json @@ -18,4 +18,4 @@ "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ No newline at end of file +} diff --git a/Translation/es_UY.json b/Translation/es_UY.json index 47c9b3d..fbc8a4c 100644 --- a/Translation/es_UY.json +++ b/Translation/es_UY.json @@ -18,4 +18,4 @@ "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ No newline at end of file +} diff --git a/Translation/eu_ES.json b/Translation/eu_ES.json index 2ec74ee..bf3ec50 100644 --- a/Translation/eu_ES.json +++ b/Translation/eu_ES.json @@ -1,12 +1,20 @@ { "acc-concept-irpf-130": "IRPFaren zuzenketa %period%", "accounting-created-130": "%codejercicio% urterako asientoa sortu da honako kontzeptuarekin: %concepto%", + "aeat-file-invalid-exercise": "Ezin da AEAT fitxategia sortu: hautatutako ekitaldia ez da baliozkoa.", + "aeat-file-invalid-length": "Ezin da AEAT fitxategia sortu: sortutako fitxategiak ez du espero zen luzera.", + "aeat-file-invalid-nif": "Ezin da AEAT fitxategia sortu: enpresaren IFZ hutsik dago edo ez da baliozkoa.", + "aeat-file-missing-company-name": "Ezin da AEAT fitxategia sortu: enpresaren izena hutsik dago.", "after-deduct": "04 koadroari buruz", + "company-not-found": "Enpresa ez da aurkitu", "deductible-subaccounts": "Deduzi daitezkeen azpis kontuak", + "download-aeat-file": "Deskargatu AEAT fitxategia", "exists-accounting-130": "Dagoeneko badago idazpen bat %codejercicio% ekitaldian %concepto% kontzeptuarekin", + "expense": "Gastu kengarria", "gastos-justificacion": "Zail justifikatzen diren gastuak", "gastos-justificacion-limit": "Kontuz: zenbatekoa urteko 2.000 €-ko muga gainditzen du", "gastos-justificacion-pct": "% Zail justifikatzen diren gastuak", + "income": "Sarrera zenbagarria", "income-subaccounts": "Sarreren kontuak", "model-130": "130 Eredua", "model-130-desc": "Hiruhileko bakoitzean gehienetan batu eta akumulatu egiten da, adibidez, hirugarren hiruhilekoko informazioa ikusten dugunean, 1., 2. eta 3. hiruhilekoak batu eta akumulatu ikusiko dugu.", @@ -15,10 +23,8 @@ "net-total": "Etekin garbia", "pct-to-deduct": "Kentzeko %", "previous-model": "Aurreko hiruhilekoetako sarrerak", + "previous-payment": "Aurreko ordainketa zatikatua", "tax-expenses": "Gastu kengarriak", "tax-incomes": "Errenta konputagarria", - "tax-negative-info": "Galerak ematen baditu (negatiboak) = 0", - "income": "Sarrera zenbagarria", - "expense": "Gastu kengarria", - "previous-payment": "Aurreko ordainketa zatikatua" -} \ No newline at end of file + "tax-negative-info": "Galerak ematen baditu (negatiboak) = 0" +} diff --git a/Translation/fr_FR.json b/Translation/fr_FR.json index 4e836cd..3dd35c8 100644 --- a/Translation/fr_FR.json +++ b/Translation/fr_FR.json @@ -1,12 +1,20 @@ { "acc-concept-irpf-130": "Régularisation de l'IRPF %period%", "accounting-created-130": "L'écriture a été créée dans l'exercice %codejercicio% avec le libellé : %concepto%", + "aeat-file-invalid-exercise": "Impossible de générer le fichier AEAT : l'exercice sélectionné n'est pas valide.", + "aeat-file-invalid-length": "Impossible de générer le fichier AEAT : le fichier généré n'a pas la longueur attendue.", + "aeat-file-invalid-nif": "Impossible de générer le fichier AEAT : le numéro fiscal de l'entreprise est vide ou invalide.", + "aeat-file-missing-company-name": "Impossible de générer le fichier AEAT : le nom de l'entreprise est vide.", "after-deduct": "sur case 04", + "company-not-found": "Entreprise introuvable", "deductible-subaccounts": "Comptes déductibles", + "download-aeat-file": "Télécharger le fichier AEAT", "exists-accounting-130": "Il existe déjà une écriture pour l'exercice %codejercicio% avec le libellé : %concepto%", + "expense": "Dépense déductible", "gastos-justificacion": "Dépenses difficiles à justifier", "gastos-justificacion-limit": "Attention: le montant dépasse la limite annuelle de 2.000 €", "gastos-justificacion-pct": "% Dépenses difficiles à justifier", + "income": "Revenu imposable", "income-subaccounts": "Comptes de revenus", "model-130": "Formulaire 130", "model-130-desc": "Il est calculé en ajoutant et cumulant chaque trimestre, par exemple, si nous visualisons le 3ème trimestre, nous verrons la somme des trimestres 1, 2 et 3.", @@ -15,10 +23,8 @@ "net-total": "Rendement net", "pct-to-deduct": "% déduire", "previous-model": "Revenu du trimestre précédent", + "previous-payment": "Paiement fractionné précédent", "tax-expenses": "Dépenses déductibles", "tax-incomes": "Revenu éligible", - "tax-negative-info": "Si déficitaire (négatif) = 0", - "income": "Revenu imposable", - "expense": "Dépense déductible", - "previous-payment": "Paiement fractionné précédent" -} \ No newline at end of file + "tax-negative-info": "Si déficitaire (négatif) = 0" +} diff --git a/Translation/gl_ES.json b/Translation/gl_ES.json index e7e26b9..f6210d3 100644 --- a/Translation/gl_ES.json +++ b/Translation/gl_ES.json @@ -1,12 +1,20 @@ { "acc-concept-irpf-130": "Regularización do IRPF %period%", "accounting-created-130": "Creouse o asento no exercicio %codejercicio% co concepto: %concepto%", + "aeat-file-invalid-exercise": "Non se pode xerar o ficheiro AEAT: o exercicio seleccionado non é válido.", + "aeat-file-invalid-length": "Non se pode xerar o ficheiro AEAT: o ficheiro xerado non ten a lonxitude esperada.", + "aeat-file-invalid-nif": "Non se pode xerar o ficheiro AEAT: o NIF da empresa está baleiro ou non é válido.", + "aeat-file-missing-company-name": "Non se pode xerar o ficheiro AEAT: o nome da empresa está baleiro.", "after-deduct": "sobre casa 04", + "company-not-found": "Empresa non atopada", "deductible-subaccounts": "Contas deducibles", + "download-aeat-file": "Descargar ficheiro AEAT", "exists-accounting-130": "Xa existe un asento no exercicio %codejercicio% co concepto: %concepto%", + "expense": "Gasto deducible", "gastos-justificacion": "Gastos de difícil xustificación", "gastos-justificacion-limit": "Atención: o importe supera o límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil xustificación", + "income": "Ingreso computable", "income-subaccounts": "Contas de ingresos", "model-130": "Modelo 130", "model-130-desc": "Calcúlase sumando e acumulando cada trimestre, por exemplo, se visualizamos o 3º trimestre veremos a suma dos trimestres 1, 2 e 3.", @@ -15,10 +23,8 @@ "net-total": "Rendemento neto", "pct-to-deduct": "% a deducir", "previous-model": "Ingresos trimestres anteriores", + "previous-payment": "Pagamento fraccionado anterior", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Se dá perdas (negativo) = 0", - "income": "Ingreso computable", - "expense": "Gasto deducible", - "previous-payment": "Pagamento fraccionado anterior" -} \ No newline at end of file + "tax-negative-info": "Se dá perdas (negativo) = 0" +} diff --git a/Translation/it_IT.json b/Translation/it_IT.json index 488b987..6ba6085 100644 --- a/Translation/it_IT.json +++ b/Translation/it_IT.json @@ -1,12 +1,20 @@ { "acc-concept-irpf-130": "Regolarizzazione IRPF %period%", "accounting-created-130": "È stata creata la registrazione nell'esercizio %codejercicio% con la causale: %concepto%", + "aeat-file-invalid-exercise": "Impossibile generare il file AEAT: l'esercizio selezionato non è valido.", + "aeat-file-invalid-length": "Impossibile generare il file AEAT: il file generato non ha la lunghezza prevista.", + "aeat-file-invalid-nif": "Impossibile generare il file AEAT: il codice fiscale dell'azienda è mancante o non valido.", + "aeat-file-missing-company-name": "Impossibile generare il file AEAT: il nome dell'azienda è vuoto.", "after-deduct": "sulla casella 04", + "company-not-found": "Azienda non trovata", "deductible-subaccounts": "Conti deducibili", + "download-aeat-file": "Scarica file AEAT", "exists-accounting-130": "Esiste già una registrazione nell'esercizio %codejercicio% con la causale: %concepto%", + "expense": "Spesa deducibile", "gastos-justificacion": "Spese di difficile giustificazione", "gastos-justificacion-limit": "Attenzione: l'importo supera il limite annuo di 2.000 €", "gastos-justificacion-pct": "% Spese di difficile giustificazione", + "income": "Reddito imponibile", "income-subaccounts": "Conti dei ricavi", "model-130": "Modello 130", "model-130-desc": "Si calcola sommando e accumulando ogni trimestre, ad esempio, se visualizziamo il 3° trimestre vedremo la somma dei trimestri 1, 2 e 3.", @@ -15,10 +23,8 @@ "net-total": "Totale netto", "pct-to-deduct": "% da dedurre", "previous-model": "Modelli precedenti", + "previous-payment": "Acconto precedente", "tax-expenses": "Spese fiscali deducibili", "tax-incomes": "Ricavi fiscali", - "tax-negative-info": "Se genera perdite (negative) = 0", - "income": "Reddito imponibile", - "expense": "Spesa deducibile", - "previous-payment": "Acconto precedente" -} \ No newline at end of file + "tax-negative-info": "Se genera perdite (negative) = 0" +} diff --git a/Translation/pl_PL.json b/Translation/pl_PL.json index f602917..392a7f5 100644 --- a/Translation/pl_PL.json +++ b/Translation/pl_PL.json @@ -18,4 +18,4 @@ "tax-expenses": "Wydatki podatkowe", "tax-incomes": "Dochody opodatkowane", "tax-negative-info": "Jeśli wykazuje straty (ujemny) = 0" -} \ No newline at end of file +} diff --git a/Translation/pt_BR.json b/Translation/pt_BR.json index 4174dcd..6228efc 100644 --- a/Translation/pt_BR.json +++ b/Translation/pt_BR.json @@ -1,12 +1,20 @@ { "acc-concept-irpf-130": "Regularização do IRPF %period%", "accounting-created-130": "Foi criado o lançamento no exercício %codejercicio% com o conceito: %concepto%", + "aeat-file-invalid-exercise": "Não é possível gerar o arquivo AEAT: o exercício selecionado é inválido.", + "aeat-file-invalid-length": "Não é possível gerar o arquivo AEAT: o arquivo gerado não possui o tamanho esperado.", + "aeat-file-invalid-nif": "Não é possível gerar o arquivo AEAT: o CNPJ/NIF da empresa está vazio ou é inválido.", + "aeat-file-missing-company-name": "Não é possível gerar o arquivo AEAT: o nome da empresa está vazio.", "after-deduct": "sobre caixa 04", + "company-not-found": "Empresa não encontrada", "deductible-subaccounts": "Contas dedutíveis", + "download-aeat-file": "Baixar arquivo AEAT", "exists-accounting-130": "Já existe um lançamento no exercício %codejercicio% com o conceito: %concepto%", + "expense": "Despesa dedutível", "gastos-justificacion": "Gastos de difícil justificação", "gastos-justificacion-limit": "Atenção: o valor ultrapassa o limite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificação", + "income": "Receita tributável", "income-subaccounts": "Contas de receitas", "model-130": "Modelo 130", "model-130-desc": "É calculado somando e acumulando cada trimestre, por exemplo, se visualizarmos o 3º trimestre veremos a soma dos trimestres 1, 2 e 3.", @@ -15,10 +23,8 @@ "net-total": "Total líquido", "pct-to-deduct": "% a deduzir", "previous-model": "Receitas de trimestres anteriores", + "previous-payment": "Pagamento por conta anterior", "tax-expenses": "Despesas dedutíveis", "tax-incomes": "Rendimentos tributáveis", - "tax-negative-info": "Se der negativo = 0", - "income": "Receita tributável", - "expense": "Despesa dedutível", - "previous-payment": "Pagamento por conta anterior" -} \ No newline at end of file + "tax-negative-info": "Se der negativo = 0" +} diff --git a/Translation/pt_PT.json b/Translation/pt_PT.json index c8ec92c..1e1a88d 100644 --- a/Translation/pt_PT.json +++ b/Translation/pt_PT.json @@ -1,12 +1,20 @@ { "acc-concept-irpf-130": "Regularização do IRPF %period%", "accounting-created-130": "Foi criado o lançamento no exercício %codejercicio% com o conceito: %concepto%", + "aeat-file-invalid-exercise": "Não é possível gerar o ficheiro AEAT: o exercício selecionado não é válido.", + "aeat-file-invalid-length": "Não é possível gerar o ficheiro AEAT: o ficheiro gerado não tem o comprimento esperado.", + "aeat-file-invalid-nif": "Não é possível gerar o ficheiro AEAT: o NIF da empresa está vazio ou é inválido.", + "aeat-file-missing-company-name": "Não é possível gerar o ficheiro AEAT: o nome da empresa está vazio.", "after-deduct": "sobre a caixa 04", + "company-not-found": "Empresa não encontrada", "deductible-subaccounts": "Contas dedutíveis", + "download-aeat-file": "Descarregar ficheiro AEAT", "exists-accounting-130": "Já existe um lançamento no exercício %codejercicio% com o conceito: %concepto%", + "expense": "Despesa dedutível", "gastos-justificacion": "Despesas de difícil justificação", "gastos-justificacion-limit": "Atenção: o montante ultrapassa o limite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificação", + "income": "Receita tributável", "income-subaccounts": "Contas de receitas", "model-130": "Modelo 130", "model-130-desc": "É calculado somando e acumulando cada trimestre, por exemplo, se visualizarmos o 3º trimestre veremos a soma dos trimestres 1, 2 e 3.", @@ -15,10 +23,8 @@ "net-total": "Rendimento líquido", "pct-to-deduct": "% a deduzir", "previous-model": "Receitas de trimestres anteriores", + "previous-payment": "Pagamento por conta anterior", "tax-expenses": "Despesas dedutíveis", "tax-incomes": "Impostos sobre rendimentos", - "tax-negative-info": "Se der perdas (negativo) = 0", - "income": "Receita tributável", - "expense": "Despesa dedutível", - "previous-payment": "Pagamento por conta anterior" -} \ No newline at end of file + "tax-negative-info": "Se der perdas (negativo) = 0" +} diff --git a/Translation/tr_TR.json b/Translation/tr_TR.json index ed8f5be..6b39b6b 100644 --- a/Translation/tr_TR.json +++ b/Translation/tr_TR.json @@ -18,4 +18,4 @@ "tax-expenses": "Düşürülebilir Harcamalar", "tax-incomes": "Gelir hesaplamaları", "tax-negative-info": "Zarar veriyorsa (negatif) = 0" -} \ No newline at end of file +} diff --git a/Translation/va_ES.json b/Translation/va_ES.json index cf7c42d..1b479e1 100644 --- a/Translation/va_ES.json +++ b/Translation/va_ES.json @@ -18,4 +18,4 @@ "tax-expenses": "Despeses deduïbles", "tax-incomes": "Ingressos computables", "tax-negative-info": "Si dóna pèrdues (negatiu) = 0" -} \ No newline at end of file +} From 0b857dae3a866ae091ae056702fd3dc7f6a8ad4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mart=C3=ADn=20Gonz=C3=A1lez?= Date: Wed, 22 Jul 2026 17:42:03 +0200 Subject: [PATCH 03/15] Test en carpeta correcta --- Test/Modelo130Test.php | 190 ------------------------------------ Test/main/Modelo130Test.php | 22 ++++- 2 files changed, 18 insertions(+), 194 deletions(-) delete mode 100644 Test/Modelo130Test.php diff --git a/Test/Modelo130Test.php b/Test/Modelo130Test.php deleted file mode 100644 index ce60b49..0000000 --- a/Test/Modelo130Test.php +++ /dev/null @@ -1,190 +0,0 @@ - - * - * 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\Core\Tools; -use FacturaScripts\Core\Where; -use FacturaScripts\Dinamic\Model\Asiento; -use FacturaScripts\Dinamic\Model\Ejercicio; -use FacturaScripts\Dinamic\Model\FormaPago; -use FacturaScripts\Dinamic\Model\Partida; -use FacturaScripts\Plugins\Modelo130\Lib\Modelo130; -use FacturaScripts\Test\Traits\DefaultSettingsTrait; -use FacturaScripts\Test\Traits\LogErrorsTrait; -use PHPUnit\Framework\TestCase; - -/** - * Tests para la clase estática Modelo130\Lib\Modelo130. - * - * @author Daniel Fernández Giménez - */ -final class Modelo130Test extends TestCase -{ - use DefaultSettingsTrait; - use LogErrorsTrait; - - public static function setUpBeforeClass(): void - { - self::setDefaultSettings(); - self::installAccountingPlan(); - } - - public function testGenerate(): void - { - $ejercicios = (new Ejercicio())->all([], ['codejercicio' => 'DESC'], 0, 1); - $this->assertNotEmpty($ejercicios, 'No hay ningún ejercicio en la base de datos'); - - $ejercicio = $ejercicios[0]; - $codejercicio = $ejercicio->codejercicio; - $resultado = Modelo130::generate($codejercicio, 'T1'); - - $this->assertIsArray($resultado, 'generate() debe devolver un array'); - $this->assertNotEmpty($resultado, 'generate() no debe devolver un array vacío para un ejercicio válido'); - - $clavesEsperadas = [ - 'exercise', - 'period', - 'idempresa', - 'sales', - 'purchases', - 'accountingEntries', - 'applyGastosJustificacion', - 'todeduct', - 'gastosJustificacionPct', - 'taxbaseIngresos', - 'taxbaseRetenciones', - 'taxbaseGastos', - 'taxbase', - 'gastosJustificacion', - 'afterdeduct', - 'positivosTrimestres', - 'result', - ]; - - foreach ($clavesEsperadas as $clave) { - $this->assertArrayHasKey($clave, $resultado, "El resultado debe contener la clave '$clave'"); - } - - $this->assertInstanceOf(Ejercicio::class, $resultado['exercise']); - $this->assertSame('T1', $resultado['period']); - $this->assertSame($codejercicio, $resultado['exercise']->codejercicio); - $this->assertIsArray($resultado['sales']); - $this->assertIsArray($resultado['purchases']); - $this->assertIsArray($resultado['accountingEntries']); - - $resultadoVacio = Modelo130::generate('EJERCICIO_INEXISTENTE_XYZ', 'T1'); - $this->assertIsArray($resultadoVacio); - $this->assertEmpty($resultadoVacio); - } - - public function testGenerateEntries(): void - { - $ejercicios = (new Ejercicio())->all([], ['codejercicio' => 'DESC'], 0, 1); - $this->assertNotEmpty($ejercicios, 'No hay ningún ejercicio en la base de datos'); - - $ejercicio = $ejercicios[0]; - $codejercicio = $ejercicio->codejercicio; - $idempresa = $ejercicio->idempresa; - - $formasPago = (new FormaPago())->all([], [], 0, 1); - $paymentMethodId = empty($formasPago) ? null : $formasPago[0]->id(); - - $periodo = 'T1'; - $importe = 100.0; - $fecha = date('Y-m-d'); - - $creado = Modelo130::generateEntries( - $idempresa, - $codejercicio, - $periodo, - $fecha, - $importe, - $paymentMethodId - ); - $this->assertTrue($creado, 'generateEntries() debe devolver true al crear el asiento por primera vez'); - - $concepto = Tools::trans('acc-concept-irpf-130', ['%period%' => $periodo]); - $asiento = new Asiento(); - $encontrado = $asiento->loadWhere([ - Where::eq('codejercicio', $codejercicio), - Where::eq('concepto', $concepto), - ]); - - $this->assertTrue($encontrado, 'El asiento debe existir en la base de datos tras generateEntries()'); - $this->assertEquals($importe, $asiento->importe, 'El importe del asiento debe ser 100.0'); - - $partidas = (new Partida())->all([ - Where::eq('idasiento', $asiento->idasiento), - ], ['orden' => 'ASC']); - - $this->assertCount(2, $partidas, 'El asiento debe contener dos partidas'); - $this->assertSame('4730000000', $partidas[0]->codsubcuenta); - $this->assertEquals($importe, $partidas[0]->debe); - $this->assertEquals(0.0, (float)$partidas[0]->haber); - $this->assertEquals($importe, $partidas[1]->haber); - - $duplicado = Modelo130::generateEntries( - $idempresa, - $codejercicio, - $periodo, - $fecha, - $importe, - $paymentMethodId - ); - $this->assertFalse($duplicado, 'generateEntries() debe devolver false si ya existe el asiento'); - - $this->assertTrue($asiento->delete(), 'No se pudo eliminar el asiento de prueba'); - } - - public function testCalcGastosJustificacion(): void - { - $this->assertSame(0.0, Modelo130::calcGastosJustificacion(10000.0, false, 7.0)); - $this->assertSame(0.0, Modelo130::calcGastosJustificacion(0.0, true, 7.0)); - $this->assertSame(0.0, Modelo130::calcGastosJustificacion(-500.0, true, 7.0)); - $this->assertSame(700.0, Modelo130::calcGastosJustificacion(10000.0, true, 7.0)); - $this->assertSame(500.0, Modelo130::calcGastosJustificacion(10000.0, true, 5.0)); - $this->assertSame(70.35, Modelo130::calcGastosJustificacion(1005.0, true, 7.0)); - $this->assertSame(2000.0, Modelo130::calcGastosJustificacion(40000.0, true, 7.0)); - $this->assertSame(2000.0, Modelo130::calcGastosJustificacion(100000.0, true, 7.0)); - $this->assertSame(1995.0, Modelo130::calcGastosJustificacion(28500.0, true, 7.0)); - $this->assertSame(2000.0, Modelo130::LIMITE_GASTOS_JUSTIFICACION); - } - - public function testCalcAfterDeduct(): void - { - $this->assertSame(2000.0, Modelo130::calcAfterDeduct(10000.0, 0.0, 20.0)); - $this->assertSame(0.0, Modelo130::calcAfterDeduct(-500.0, 0.0, 20.0)); - $this->assertSame(0.0, Modelo130::calcAfterDeduct(0.0, 0.0, 20.0)); - $this->assertSame(1860.0, Modelo130::calcAfterDeduct(10000.0, 700.0, 20.0)); - } - - public function testCalcResult(): void - { - $this->assertSame(1200.0, Modelo130::calcResult(2000.0, 500.0, 300.0)); - $this->assertSame(0.0, Modelo130::calcResult(500.0, 400.0, 300.0)); - $this->assertSame(0.0, Modelo130::calcResult(100.0, 50.0, 50.0)); - $this->assertSame(33.33, Modelo130::calcResult(100.005, 33.33, 33.345)); - } - - protected function tearDown(): void - { - $this->logErrors(); - } -} diff --git a/Test/main/Modelo130Test.php b/Test/main/Modelo130Test.php index 86d4091..6d64e5a 100644 --- a/Test/main/Modelo130Test.php +++ b/Test/main/Modelo130Test.php @@ -24,6 +24,7 @@ use FacturaScripts\Dinamic\Model\Asiento; use FacturaScripts\Dinamic\Model\Ejercicio; use FacturaScripts\Dinamic\Model\FormaPago; +use FacturaScripts\Dinamic\Model\Partida; use FacturaScripts\Plugins\Modelo130\Lib\Modelo130; use FacturaScripts\Test\Traits\DefaultSettingsTrait; use FacturaScripts\Test\Traits\LogErrorsTrait; @@ -70,10 +71,9 @@ public function testGenerate(): void 'exercise', 'period', 'idempresa', - 'customerInvoices', - 'supplierInvoices', + 'sales', + 'purchases', 'accountingEntries', - 'incomeEntries', 'applyGastosJustificacion', 'todeduct', 'gastosJustificacionPct', @@ -94,6 +94,9 @@ public function testGenerate(): void // verificar tipos concretos de las claves principales $this->assertInstanceOf(Ejercicio::class, $resultado['exercise'], 'exercise debe ser una instancia de Ejercicio'); $this->assertSame('T1', $resultado['period'], 'El periodo debe ser T1'); + $this->assertIsArray($resultado['sales'], 'sales debe ser un array'); + $this->assertIsArray($resultado['purchases'], 'purchases debe ser un array'); + $this->assertIsArray($resultado['accountingEntries'], 'accountingEntries debe ser un array'); // comprobar que el ejercicio cargado es el correcto $this->assertSame($codejercicio, $resultado['exercise']->codejercicio, 'El ejercicio debe coincidir con el solicitado'); @@ -141,6 +144,17 @@ public function testGenerateEntries(): void $this->assertTrue($encontrado, 'El asiento debe existir en la base de datos tras generateEntries()'); $this->assertEquals($importe, $asiento->importe, 'El importe del asiento debe ser 100.0'); + // comprobar que el asiento contiene las dos partidas esperadas + $partidas = (new Partida())->all([ + Where::eq('idasiento', $asiento->idasiento), + ], ['orden' => 'ASC']); + + $this->assertCount(2, $partidas, 'El asiento debe contener dos partidas'); + $this->assertSame('4730000000', $partidas[0]->codsubcuenta); + $this->assertEquals($importe, $partidas[0]->debe); + $this->assertEquals(0.0, (float)$partidas[0]->haber); + $this->assertEquals($importe, $partidas[1]->haber); + // una segunda llamada con los mismos parámetros debe devolver false (ya existe) $duplicado = Modelo130::generateEntries($idempresa, $codejercicio, $periodo, $fecha, $importe, $paymentMethodId); $this->assertFalse($duplicado, 'generateEntries() debe devolver false si ya existe el asiento'); @@ -227,4 +241,4 @@ protected function tearDown(): void { $this->logErrors(); } -} +} \ No newline at end of file From 4c9866c6c76412897b653663d1eecfc4e69dd677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mart=C3=ADn=20Gonz=C3=A1lez?= Date: Thu, 23 Jul 2026 12:37:26 +0200 Subject: [PATCH 04/15] =?UTF-8?q?Modificaci=C3=B3n=20del=20orden=20de=20la?= =?UTF-8?q?s=20columnas=20en=20la=20vista=20de=20los=20asientos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- View/Modelo130.html.twig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/View/Modelo130.html.twig b/View/Modelo130.html.twig index 87dba21..1c04c03 100644 --- a/View/Modelo130.html.twig +++ b/View/Modelo130.html.twig @@ -350,10 +350,10 @@ {{ trans('accounting-entry') }} {{ trans('subaccount') }} + {{ trans('date') }} {{ trans('concept') }} {{ trans('type') }} {{ trans('total') }} - {{ trans('date') }} @@ -365,10 +365,10 @@ {{ item.entry.numero }} {{ item.partida.codsubcuenta }} + {{ item.entry.fecha }} {{ item.partida.concepto | raw }} {{ trans(item.type) }} {{ money(item.amount) }} - {{ item.entry.fecha }} {% else %} @@ -379,7 +379,7 @@ {# - {{ trans('totals') }} + {{ trans('totals') }} {{ money(accountingTotal) }} From 76e442a97584ef787edcdba6ba9e4121dcaa555a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mart=C3=ADn=20Gonz=C3=A1lez?= Date: Fri, 24 Jul 2026 16:56:34 +0200 Subject: [PATCH 05/15] =?UTF-8?q?Correcci=C3=B3n=20de=20nombres=20de=20sub?= =?UTF-8?q?grupos=20contables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lib/Modelo130Accounts.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Lib/Modelo130Accounts.php b/Lib/Modelo130Accounts.php index bd6f6ad..bef1a0f 100644 --- a/Lib/Modelo130Accounts.php +++ b/Lib/Modelo130Accounts.php @@ -54,7 +54,7 @@ public static function expenses(): array '64' => 'Gastos de personal', '65' => 'Otros gastos de gestión', '66' => 'Gastos financieros', - '67' => 'Pérdidas procedentes de activos y gastos excepcionales', + '67' => 'Pérdidas procedentes de activos no corrientes y gastos excepcionales', '68' => 'Dotaciones para amortizaciones', '69' => 'Pérdidas por deterioro y otras dotaciones', ]; @@ -83,16 +83,14 @@ public static function excludedExpenses(): array public static function incomes(): array { return [ - '70' => 'Ventas de mercaderías, producción propia y prestación de servicios', + '70' => 'Ventas de mercaderías, de producción propia, de servicios, etc.', '71' => 'Variación de existencias', - '72' => 'Trabajos realizados para la empresa', - '73' => 'Subvenciones, donaciones y legados', - '74' => 'Subvenciones, donaciones y legados a la explotación', + '73' => 'Trabajos realizados para la empresa', + '74' => 'Subvenciones, donaciones y legados', '75' => 'Otros ingresos de gestión', '76' => 'Ingresos financieros', - '77' => 'Beneficios procedentes de activos e ingresos excepcionales', - '78' => 'Excesos y aplicaciones de provisiones', - '79' => 'Excesos y aplicaciones de provisiones y deterioros', + '77' => 'Beneficios procedentes de activos no corrientes e ingresos excepcionales', + '79' => 'Excesos y aplicaciones de provisiones y de pérdidas por deterioro', ]; } From 7bab4c3f848e0cf98f76c066876c084aca01768c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mart=C3=ADn=20Gonz=C3=A1lez?= Date: Fri, 24 Jul 2026 19:25:58 +0200 Subject: [PATCH 06/15] =?UTF-8?q?Mejoradas=20las=20cuentas=20contables=20a?= =?UTF-8?q?=20excluir=20e=20incluir,=20as=C3=AD=20como=20el=20c=C3=A1lculo?= =?UTF-8?q?=20acumulado=20de=20las=20variaciones=20de=20mercanc=C3=ADas=20?= =?UTF-8?q?para=20considerarlas=20ingresos=20o=20gastos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lib/Modelo130.php | 229 +++++++++++++++++++++++++++----------- Lib/Modelo130Accounts.php | 169 ++++++++++++++++++++++++++-- 2 files changed, 322 insertions(+), 76 deletions(-) diff --git a/Lib/Modelo130.php b/Lib/Modelo130.php index 4a55ae2..54bbcb6 100644 --- a/Lib/Modelo130.php +++ b/Lib/Modelo130.php @@ -317,16 +317,16 @@ protected static function getSqlValueCondition( protected static function loadAccountingData(): void { $conditions = []; - + foreach (Modelo130Accounts::queryPrefixes() as $prefix) { $conditions[] = 'p.codsubcuenta LIKE ' . static::$dataBase->var2str($prefix . '%'); } - + if (empty($conditions)) { return; } - + $sql = 'SELECT p.*' . ' FROM ' . Partida::tableName() . ' p' . ' INNER JOIN ' . Asiento::tableName() . ' a' @@ -350,7 +350,7 @@ protected static function loadAccountingData(): void ) . ' AND (' . implode(' OR ', $conditions) . ')' . ' ORDER BY a.fecha ASC, a.numero ASC, p.orden ASC'; - + /** * Agrupamos las partidas por asiento. * @@ -358,11 +358,36 @@ protected static function loadAccountingData(): void * aparece una única vez en la pestaña correspondiente. */ $groups = []; - + + /** + * Las cuentas 61 y 71 no pueden clasificarse hasta conocer su saldo + * acumulado completo: + * + * - saldo deudor: gasto; + * - saldo acreedor: ingreso. + */ + $stockVariationBalances = [ + '61' => 0.0, + '71' => 0.0, + ]; + + /** + * Posiciones de las partidas 61 y 71 pendientes de clasificación. + * + * Solo estas partidas se recorren de nuevo al terminar la consulta. + * + * @var array + */ + $pendingStockVariations = []; + foreach (static::$dataBase->select($sql) as $row) { $partida = new Partida($row); $idasiento = (int)$partida->idasiento; - + if (!isset($groups[$idasiento])) { $groups[$idasiento] = [ 'entries' => [], @@ -371,36 +396,47 @@ protected static function loadAccountingData(): void 'retention' => 0.0, ]; } - - $code = (string)$partida->codsubcuenta; - - /** - * En ingresos sumamos haber - debe. - * - * Así una rectificación o un asiento inverso reduce el ingreso - * computable en lugar de incrementarlo. - */ - $income = Modelo130Accounts::isIncome($code) - ? round( - (float)$partida->haber - - (float)$partida->debe, - 2 - ) - : 0.0; - - /** - * En gastos sumamos debe - haber. - * - * Así una devolución o regularización inversa reduce el gasto. - */ - $expense = Modelo130Accounts::isExpense($code) - ? round( - (float)$partida->debe - - (float)$partida->haber, - 2 - ) - : 0.0; - + + $code = trim((string)$partida->codsubcuenta); + $debit = (float)$partida->debe; + $credit = (float)$partida->haber; + + $income = 0.0; + $expense = 0.0; + + $isStockVariation = Modelo130Accounts::isStockVariation($code); + + if ($isStockVariation) { + /** + * Acumulamos el saldo, pero aplazamos la clasificación hasta + * haber leído todas las partidas del período. + */ + $prefix = substr($code, 0, 2); + + if (isset($stockVariationBalances[$prefix])) { + $stockVariationBalances[$prefix] += $debit - $credit; + } + } else { + /** + * En ingresos sumamos haber - debe. + * + * Así una rectificación o un asiento inverso reduce el ingreso + * computable en lugar de incrementarlo. + */ + if (Modelo130Accounts::isIncome($code)) { + $income = round($credit - $debit, 2); + } + + /** + * En gastos sumamos debe - haber. + * + * Así una devolución o regularización inversa reduce el gasto. + */ + if (Modelo130Accounts::isExpense($code)) { + $expense = round($debit - $credit, 2); + } + } + /** * En la cuenta 473 el movimiento habitual está en el debe. * @@ -409,71 +445,129 @@ protected static function loadAccountingData(): void * el asiento está asociado a una factura de cliente. */ $retention = Modelo130Accounts::isWithholding($code) - ? round( - (float)$partida->debe - - (float)$partida->haber, - 2 - ) + ? round($debit - $credit, 2) : 0.0; - + $groups[$idasiento]['income'] += $income; $groups[$idasiento]['expense'] += $expense; $groups[$idasiento]['retention'] += $retention; - + + $entryIndex = count($groups[$idasiento]['entries']); + $groups[$idasiento]['entries'][] = [ 'partida' => $partida, 'income' => $income, 'expense' => $expense, 'retention' => $retention, ]; + + if ($isStockVariation) { + $prefix = substr($code, 0, 2); + + if (isset($stockVariationBalances[$prefix])) { + $pendingStockVariations[] = [ + 'idasiento' => $idasiento, + 'entryIndex' => $entryIndex, + 'prefix' => $prefix, + ]; + } + } } - + + /** + * Clasificamos únicamente las partidas 61 y 71 pendientes. + */ + foreach ($pendingStockVariations as $pending) { + $idasiento = $pending['idasiento']; + $entryIndex = $pending['entryIndex']; + $prefix = $pending['prefix']; + + $balance = round( + $stockVariationBalances[$prefix] ?? 0.0, + 2 + ); + + if ($balance == 0.0) { + continue; + } + + $partida = $groups[$idasiento]['entries'][$entryIndex]['partida']; + $debit = (float)$partida->debe; + $credit = (float)$partida->haber; + + $income = 0.0; + $expense = 0.0; + + if ($balance > 0.0) { + /** + * Saldo deudor: gasto. + * + * Las partidas acreedoras producen importes negativos y + * reducen correctamente el gasto acumulado. + */ + $expense = round($debit - $credit, 2); + } else { + /** + * Saldo acreedor: ingreso. + * + * Las partidas deudoras producen importes negativos y + * reducen correctamente el ingreso acumulado. + */ + $income = round($credit - $debit, 2); + } + + $groups[$idasiento]['income'] += $income; + $groups[$idasiento]['expense'] += $expense; + + $groups[$idasiento]['entries'][$entryIndex]['income'] = $income; + $groups[$idasiento]['entries'][$entryIndex]['expense'] = $expense; + } + if (empty($groups)) { return; } - + $entryIds = array_keys($groups); - + /** * Cargamos en bloque los asientos y las facturas relacionadas para no * ejecutar una consulta adicional por cada partida. */ $accountingEntries = static::loadEntriesByIds($entryIds); - + $customerInvoices = static::loadInvoicesByEntries( new FacturaCliente(), $entryIds ); - + $supplierInvoices = static::loadInvoicesByEntries( new FacturaProveedor(), $entryIds ); - + foreach ($groups as $idasiento => $group) { $entry = $accountingEntries[$idasiento] ?? null; - + if (null === $entry) { continue; } - + $customerInvoice = $customerInvoices[$idasiento] ?? null; $supplierInvoice = $supplierInvoices[$idasiento] ?? null; - + static::$taxbaseIncomes += $group['income']; static::$taxbaseExpenses += $group['expense']; - + /** * Factura de cliente. * * El ingreso y el IRPF se obtienen desde las partidas contables, * pero se muestran agrupados utilizando los datos identificativos * de la factura. - * */ if ($customerInvoice) { static::$taxbaseRetentions += $group['retention']; - + if ( $group['income'] != 0.0 || $group['retention'] != 0.0 @@ -496,10 +590,10 @@ protected static function loadAccountingData(): void ), ]; } - + continue; } - + /** * Factura de proveedor. * @@ -527,10 +621,10 @@ protected static function loadAccountingData(): void 'irpf' => 0.0, ]; } - + continue; } - + /** * El asiento no está asociado a ninguna factura. * @@ -540,12 +634,13 @@ protected static function loadAccountingData(): void * - Seguridad Social; * - gastos manuales; * - ingresos manuales; + * - variaciones de existencias; * - pagos fraccionados de trimestres anteriores. */ foreach ($group['entries'] as $item) { $type = ''; $amount = 0.0; - + if ($item['income'] != 0.0) { $type = 'income'; $amount = $item['income']; @@ -564,14 +659,14 @@ protected static function loadAccountingData(): void */ $type = 'previous-payment'; $amount = $item['retention']; - + static::$previousPayments += $amount; } - + if ($type === '') { continue; } - + static::$accountingEntries[] = [ 'entry' => $entry, 'partida' => $item['partida'], @@ -580,22 +675,22 @@ protected static function loadAccountingData(): void ]; } } - + static::$taxbaseIncomes = round( static::$taxbaseIncomes, 2 ); - + static::$taxbaseExpenses = round( static::$taxbaseExpenses, 2 ); - + static::$taxbaseRetentions = round( static::$taxbaseRetentions, 2 ); - + static::$previousPayments = round( static::$previousPayments, 2 diff --git a/Lib/Modelo130Accounts.php b/Lib/Modelo130Accounts.php index bef1a0f..cfb10e7 100644 --- a/Lib/Modelo130Accounts.php +++ b/Lib/Modelo130Accounts.php @@ -24,6 +24,14 @@ * * Los códigos indicados son prefijos. Por ejemplo, el código 64 incluye * cualquier subcuenta que comience por 64. + * + * Esta clase aplica una clasificación automática y conservadora basada + * exclusivamente en el código contable: + * + * - incluye las cuentas normalmente computables en estimación directa; + * - excluye las partidas que, con carácter general, no forman parte del + * rendimiento de la actividad o se calculan fuera de esta clase; + * - trata las variaciones de existencias según su saldo acumulado. */ final class Modelo130Accounts { @@ -42,13 +50,15 @@ final class Modelo130Accounts /** * Cuentas de gastos computables. * + * Las variaciones de existencias 61 y 71 se tratan por separado porque + * su clasificación depende del saldo deudor o acreedor acumulado. + * * @return array */ public static function expenses(): array { return [ '60' => 'Compras', - '61' => 'Variación de existencias', '62' => 'Servicios exteriores', '63' => 'Tributos', '64' => 'Gastos de personal', @@ -61,41 +71,124 @@ public static function expenses(): array } /** - * Cuentas excluidas aunque pertenezcan a uno de los grupos de gastos. + * Cuentas excluidas aunque pertenezcan a un subgrupo de gastos. * - * La cuenta 678 suele utilizarse para multas, sanciones y otros gastos - * excepcionales no deducibles. + * Se excluyen: + * + * - el impuesto sobre beneficios y sus ajustes; + * - resultados de instrumentos financieros y créditos no comerciales; + * - pérdidas derivadas de activos no corrientes; + * - gastos excepcionales, por ser una cuenta mixta utilizada habitualmente + * para sanciones, recargos y otros conceptos no deducibles; + * - deterioros fiscalmente no deducibles; + * - provisiones calculadas posteriormente por el módulo fiscal. * * @return array */ public static function excludedExpenses(): array { return [ - '678' => 'Gastos excepcionales (subcuenta habitual para no deducibles', + '630' => 'Impuesto sobre beneficios', + '633' => 'Ajustes negativos en la imposición sobre beneficios', + '638' => 'Ajustes positivos en la imposición sobre beneficios', + + '660' => 'Actualización financiera de provisiones calculadas fuera de esta clase', + '663' => 'Pérdidas por valoración de instrumentos financieros', + '664' => 'Dividendos de instrumentos considerados pasivos financieros', + '666' => 'Pérdidas en participaciones y valores representativos de deuda', + '667' => 'Pérdidas de créditos no comerciales', + + '670' => 'Pérdidas procedentes del inmovilizado intangible', + '671' => 'Pérdidas procedentes del inmovilizado material', + '672' => 'Pérdidas procedentes de inversiones inmobiliarias', + '673' => 'Pérdidas procedentes de participaciones a largo plazo', + '675' => 'Pérdidas por operaciones con obligaciones propias', + '678' => 'Gastos excepcionales excluidos por defecto', + + '690' => 'Deterioro del inmovilizado intangible', + '691' => 'Deterioro del inmovilizado material', + '692' => 'Deterioro de inversiones inmobiliarias', + '695' => 'Provisiones calculadas posteriormente por el módulo fiscal', + '696' => 'Deterioro de participaciones y valores a largo plazo', + '697' => 'Deterioro de créditos no comerciales a largo plazo', + '698' => 'Deterioro de participaciones y valores a corto plazo', + '699' => 'Deterioro de créditos no comerciales a corto plazo', ]; } /** * Cuentas de ingresos computables. * + * El subgrupo 76 no se incluye porque el código contable no permite + * distinguir los intereses comerciales computables de los rendimientos + * del capital mobiliario ajenos al Modelo 130. + * + * Las variaciones de existencias 61 y 71 se tratan por separado. + * * @return array */ public static function incomes(): array { return [ '70' => 'Ventas de mercaderías, de producción propia, de servicios, etc.', - '71' => 'Variación de existencias', '73' => 'Trabajos realizados para la empresa', '74' => 'Subvenciones, donaciones y legados', '75' => 'Otros ingresos de gestión', - '76' => 'Ingresos financieros', '77' => 'Beneficios procedentes de activos no corrientes e ingresos excepcionales', '79' => 'Excesos y aplicaciones de provisiones y de pérdidas por deterioro', ]; } + /** + * Cuentas excluidas aunque pertenezcan a un subgrupo de ingresos. + * + * Se mantienen como ingresos computables del grupo 77 únicamente los + * ingresos excepcionales de la cuenta 778, y del grupo 79 las reversiones + * de existencias y créditos comerciales de las cuentas 793 y 794. + * + * @return array + */ + public static function excludedIncomes(): array + { + return [ + '770' => 'Beneficios procedentes del inmovilizado intangible', + '771' => 'Beneficios procedentes del inmovilizado material', + '772' => 'Beneficios procedentes de inversiones inmobiliarias', + '773' => 'Beneficios procedentes de participaciones a largo plazo', + '774' => 'Diferencia negativa en combinaciones de negocios', + '775' => 'Beneficios por operaciones con obligaciones propias', + + '790' => 'Reversión del deterioro del inmovilizado intangible', + '791' => 'Reversión del deterioro del inmovilizado material', + '792' => 'Reversión del deterioro de inversiones inmobiliarias', + '795' => 'Excesos de provisiones calculadas fuera de esta clase', + '796' => 'Reversión de deterioros financieros a largo plazo', + '797' => 'Reversión de deterioros de créditos no comerciales a largo plazo', + '798' => 'Reversión de deterioros financieros a corto plazo', + '799' => 'Reversión de deterioros de créditos no comerciales a corto plazo', + ]; + } + + /** + * Cuentas de variación de existencias. + * + * Un saldo deudor se lleva a gastos y un saldo acreedor a ingresos. + * + * @return array + */ + public static function stockVariations(): array + { + return [ + '61' => 'Variación de existencias de compras', + '71' => 'Variación de existencias de producción', + ]; + } + /** * Comprueba si una subcuenta debe considerarse gasto. + * + * No incluye las cuentas 61 y 71, que deben procesarse con los métodos + * stockVariationExpense() y stockVariationIncome(). */ public static function isExpense(string $code): bool { @@ -109,20 +202,73 @@ public static function isExpense(string $code): bool return false; } - return static::matches($code, array_keys(static::expenses())); + return static::matches( + $code, + array_keys(static::expenses()) + ); } /** * Comprueba si una subcuenta debe considerarse ingreso. + * + * No incluye las cuentas 61 y 71, que deben procesarse con los métodos + * stockVariationExpense() y stockVariationIncome(). */ public static function isIncome(string $code): bool { + $code = trim($code); + + if ($code === '') { + return false; + } + + if (static::matches($code, array_keys(static::excludedIncomes()))) { + return false; + } + return static::matches( - trim($code), + $code, array_keys(static::incomes()) ); } + /** + * Comprueba si una subcuenta corresponde a una variación de existencias. + */ + public static function isStockVariation(string $code): bool + { + return static::matches( + trim($code), + array_keys(static::stockVariations()) + ); + } + + /** + * Devuelve la parte de una variación de existencias que debe computarse + * como gasto. + * + * Los importes deben ser los acumulados desde el 1 de enero. + */ + public static function stockVariationExpense( + float $debit, + float $credit + ): float { + return max($debit - $credit, 0.0); + } + + /** + * Devuelve la parte de una variación de existencias que debe computarse + * como ingreso. + * + * Los importes deben ser los acumulados desde el 1 de enero. + */ + public static function stockVariationIncome( + float $debit, + float $credit + ): float { + return max($credit - $debit, 0.0); + } + /** * Comprueba si una subcuenta pertenece a la cuenta 473. */ @@ -147,6 +293,7 @@ public static function queryPrefixes(): array return array_values(array_unique(array_merge( array_keys(static::expenses()), array_keys(static::incomes()), + array_keys(static::stockVariations()), [static::WITHHOLDING_ACCOUNT] ))); } @@ -158,6 +305,10 @@ public static function queryPrefixes(): array */ private static function matches(string $code, array $prefixes): bool { + if ($code === '') { + return false; + } + foreach ($prefixes as $prefix) { if (str_starts_with($code, $prefix)) { return true; From 632ae0d78d2943cf71c333c49e3d1d58b60be14a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mart=C3=ADn=20Gonz=C3=A1lez?= Date: Fri, 24 Jul 2026 19:30:49 +0200 Subject: [PATCH 07/15] =?UTF-8?q?El=207%=20para=20gastos=20de=20dif=C3=ADc?= =?UTF-8?q?il=20justificaci=C3=B3n=20fue=20una=20excepci=C3=B3n=20en=20202?= =?UTF-8?q?3=20y=202024,=20el=20est=C3=A1ndar=20debe=20ser=205%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Controller/Modelo130.php | 4 ++-- Lib/Modelo130.php | 6 +++--- Test/main/Modelo130Test.php | 20 ++++++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Controller/Modelo130.php b/Controller/Modelo130.php index 1fefb04..f9f99fe 100644 --- a/Controller/Modelo130.php +++ b/Controller/Modelo130.php @@ -43,7 +43,7 @@ class Modelo130 extends Controller public $applyGastosJustificacion = false; /** @var float */ - public $gastosJustificacionPct = 7.0; + public $gastosJustificacionPct = 5.0; /** @var string */ public $codejercicio; @@ -133,7 +133,7 @@ public function privateCore(&$response, $user, $permissions) $this->todeduct = (float)$this->request->request->get('todeduct', 20.0); $this->gastosJustificacionPct = (float)$this->request->request->get( 'gastosJustificacionPct', - 7.0 + 5.0 ); $this->result = DinModelo130::generate( diff --git a/Lib/Modelo130.php b/Lib/Modelo130.php index 54bbcb6..fcdb840 100644 --- a/Lib/Modelo130.php +++ b/Lib/Modelo130.php @@ -107,7 +107,7 @@ public static function generate( string $period, bool $applyGastosJustificacion = false, float $todeduct = 20.0, - float $gastosJustificacionPct = 7.0 + float $gastosJustificacionPct = 5.0 ): array { static::$exercise = new Ejercicio(); if (false === static::$exercise->load($codejercicio)) { @@ -231,7 +231,7 @@ public static function generateEntries( public static function calcGastosJustificacion( float $taxbase, bool $apply, - float $gastosJustificacionPct = 7.0 + float $gastosJustificacionPct = 5.0 ): float { if (false === $apply || $taxbase <= 0) { return 0.0; @@ -816,7 +816,7 @@ protected static function loadInvoicesByEntries( protected static function loadResults( bool $applyGastosJustificacion, float $todeduct, - float $gastosJustificacionPct = 7.0 + float $gastosJustificacionPct = 5.0 ): array { $taxbase = round( static::$taxbaseIncomes diff --git a/Test/main/Modelo130Test.php b/Test/main/Modelo130Test.php index 6d64e5a..0ccae94 100644 --- a/Test/main/Modelo130Test.php +++ b/Test/main/Modelo130Test.php @@ -170,29 +170,29 @@ public function testGenerateEntries(): void public function testCalcGastosJustificacion(): void { // desactivado: siempre 0 aunque haya base - $this->assertSame(0.0, Modelo130::calcGastosJustificacion(10000.0, false, 7.0)); + $this->assertSame(0.0, Modelo130::calcGastosJustificacion(10000.0, false, 5.0)); // base cero o negativa: 0 - $this->assertSame(0.0, Modelo130::calcGastosJustificacion(0.0, true, 7.0)); - $this->assertSame(0.0, Modelo130::calcGastosJustificacion(-500.0, true, 7.0)); + $this->assertSame(0.0, Modelo130::calcGastosJustificacion(0.0, true, 5.0)); + $this->assertSame(0.0, Modelo130::calcGastosJustificacion(-500.0, true, 5.0)); // porcentaje por defecto (7%) sobre una base por debajo del límite - $this->assertSame(700.0, Modelo130::calcGastosJustificacion(10000.0, true, 7.0)); + $this->assertSame(500.0, Modelo130::calcGastosJustificacion(10000.0, true, 5.0)); // el 5% anterior sigue siendo posible de forma explícita - $this->assertSame(500.0, Modelo130::calcGastosJustificacion(10000.0, true, 5.0)); + $this->assertSame(700.0, Modelo130::calcGastosJustificacion(10000.0, true, 7.0)); // redondeo a 2 decimales - $this->assertSame(70.35, Modelo130::calcGastosJustificacion(1005.0, true, 7.0)); + $this->assertSame(50.25, Modelo130::calcGastosJustificacion(1005.0, true, 5.0)); // tope anual de 2.000 €: 7% de 40.000 = 2.800, pero se limita a 2.000 - $this->assertSame(2000.0, Modelo130::calcGastosJustificacion(40000.0, true, 7.0)); + $this->assertSame(2000.0, Modelo130::calcGastosJustificacion(40000.0, true, 5.0)); // muy por encima del tope: se limita igualmente a 2.000 - $this->assertSame(2000.0, Modelo130::calcGastosJustificacion(100000.0, true, 7.0)); + $this->assertSame(2000.0, Modelo130::calcGastosJustificacion(100000.0, true, 5.0)); // justo por debajo del límite (7% de 28.500 = 1.995): no se topa - $this->assertSame(1995.0, Modelo130::calcGastosJustificacion(28500.0, true, 7.0)); + $this->assertSame(1995.0, Modelo130::calcGastosJustificacion(39900.0, true, 5.0)); // la constante del límite es la esperada $this->assertSame(2000.0, Modelo130::LIMITE_GASTOS_JUSTIFICACION); @@ -215,7 +215,7 @@ public function testCalcAfterDeduct(): void $this->assertSame(0.0, Modelo130::calcAfterDeduct(0.0, 0.0, 20.0)); // con gastos de difícil justificación descontados - $this->assertSame(1860.0, Modelo130::calcAfterDeduct(10000.0, 700.0, 20.0)); + $this->assertSame(1900.0, Modelo130::calcAfterDeduct(10000.0, 500.0, 20.0)); } /** From f98e35e9243a4f108afc6e3b475ff881e70ddafb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mart=C3=ADn=20Gonz=C3=A1lez?= Date: Fri, 24 Jul 2026 20:09:12 +0200 Subject: [PATCH 08/15] Resultado en casilla 19 solo positivo y no en casilla 07 que si puede ser un resultado parcial negativo --- Lib/Modelo130.php | 36 +++++++++++++++++++++++------------- Test/main/Modelo130Test.php | 27 +++++++++++++++++++++------ Translation/ca_ES.json | 3 ++- Translation/cs_CZ.json | 3 ++- Translation/de_DE.json | 3 ++- Translation/en_EN.json | 3 ++- Translation/es_AR.json | 3 ++- Translation/es_CL.json | 3 ++- Translation/es_CO.json | 3 ++- Translation/es_CR.json | 3 ++- Translation/es_DO.json | 3 ++- Translation/es_EC.json | 3 ++- Translation/es_ES.json | 3 ++- Translation/es_GT.json | 3 ++- Translation/es_MX.json | 3 ++- Translation/es_PA.json | 3 ++- Translation/es_PE.json | 3 ++- Translation/es_UY.json | 3 ++- Translation/eu_ES.json | 3 ++- Translation/fr_FR.json | 3 ++- Translation/gl_ES.json | 3 ++- Translation/it_IT.json | 3 ++- Translation/pl_PL.json | 3 ++- Translation/pt_BR.json | 3 ++- Translation/pt_PT.json | 3 ++- Translation/tr_TR.json | 3 ++- Translation/va_ES.json | 3 ++- View/Modelo130.html.twig | 13 +++++++++++-- 28 files changed, 105 insertions(+), 46 deletions(-) diff --git a/Lib/Modelo130.php b/Lib/Modelo130.php index fcdb840..c164b60 100644 --- a/Lib/Modelo130.php +++ b/Lib/Modelo130.php @@ -271,27 +271,34 @@ public static function calcAfterDeduct( ); } + /** - * Calcula el resultado final del modelo (casilla 07) restando retenciones e - * ingresos de trimestres anteriores. Si el resultado es negativo, la - * casilla no puede ser negativa. + * Calcula el pago fraccionado previo del trimestre (casilla 07) + * restando retenciones e ingresos de trimestres anteriores. + * Si el resultado es negativo, el pago fraccionado no puede ser negativo. */ - public static function calcResult( + public static function calcFractionalPayment( float $afterdeduct, float $taxbaseRetenciones, float $positivosTrimestres ): float { - return max( - 0.0, - round( - $afterdeduct - - $taxbaseRetenciones - - $positivosTrimestres, - 2 - ) + return round( + $afterdeduct + - $taxbaseRetenciones + - $positivosTrimestres, + 2 ); } + /** + * Calcula el resultado final del modelo (casilla 19) en base al pago fraccionado previo. + * Si el resultado es negativo, el resultado final no puede ser negativo. + */ + public static function calcResult(float $fractionalPayment): float + { + return max(0.0, $fractionalPayment); + } + protected static function getSqlValueCondition( string $field, $value @@ -836,12 +843,14 @@ protected static function loadResults( $todeduct ); - $result = static::calcResult( + $fractionalPayment = static::calcFractionalPayment( $afterdeduct, static::$taxbaseRetentions, static::$previousPayments ); + $result = static::calcResult($fractionalPayment); + return [ 'taxbaseIngresos' => static::$taxbaseIncomes, 'taxbaseRetenciones' => static::$taxbaseRetentions, @@ -850,6 +859,7 @@ protected static function loadResults( 'gastosJustificacion' => $gastosJustificacion, 'afterdeduct' => $afterdeduct, 'positivosTrimestres' => static::$previousPayments, + 'fractionalPayment' => $fractionalPayment, 'result' => $result, ]; } diff --git a/Test/main/Modelo130Test.php b/Test/main/Modelo130Test.php index 0ccae94..2917a08 100644 --- a/Test/main/Modelo130Test.php +++ b/Test/main/Modelo130Test.php @@ -84,6 +84,7 @@ public function testGenerate(): void 'gastosJustificacion', 'afterdeduct', 'positivosTrimestres', + 'fractionalPayment', 'result', ]; @@ -222,19 +223,33 @@ public function testCalcAfterDeduct(): void * Verifica que el resultado final nunca es negativo tras descontar * retenciones e ingresos de trimestres anteriores. */ - public function testCalcResult(): void + public function testCalcFractionalPayment(): void { // caso normal: 2.000 - 500 - 300 = 1.200 - $this->assertSame(1200.0, Modelo130::calcResult(2000.0, 500.0, 300.0)); + $this->assertSame(1200.0, Modelo130::calcFractionalPayment(2000.0, 500.0, 300.0)); - // resultado negativo: la casilla no baja de 0 - $this->assertSame(0.0, Modelo130::calcResult(500.0, 400.0, 300.0)); + // resultado negativo + $this->assertSame(-200.0, Modelo130::calcFractionalPayment(500.0, 400.0, 300.0)); // resultado exactamente 0 - $this->assertSame(0.0, Modelo130::calcResult(100.0, 50.0, 50.0)); + $this->assertSame(0.0, Modelo130::calcFractionalPayment(100.0, 50.0, 50.0)); // redondeo a 2 decimales - $this->assertSame(33.33, Modelo130::calcResult(100.005, 33.33, 33.345)); + $this->assertSame(33.33, Modelo130::calcFractionalPayment(100.005, 33.33, 33.345)); + } + + /** + * Verifica que el resultado final nunca es negativo + * en base al pago fraccionado previo del trimestre. + */ + public function testCalcResult(): void + { + // caso normal: 1.200 + $this->assertSame(1200.0, Modelo130::calcResult(1200.0)); + // resultado negativo: la casilla no baja de 0 + $this->assertSame(0.0, Modelo130::calcResult(-200.0)); + // resultado exactamente 0 + $this->assertSame(0.0, Modelo130::calcResult(0.0)); } protected function tearDown(): void diff --git a/Translation/ca_ES.json b/Translation/ca_ES.json index 6e4ad8d..3071b0c 100644 --- a/Translation/ca_ES.json +++ b/Translation/ca_ES.json @@ -11,6 +11,7 @@ "download-aeat-file": "Descarrega el fitxer AEAT", "exists-accounting-130": "Ja existeix un seient en l'exercici %codejercicio% amb el concepte: %concepte%", "expense": "Despesa deduïble", + "fractional-payment": "Pagament fraccionat trimestre anterior", "gastos-justificacion": "Despeses de difícil justificació", "gastos-justificacion-limit": "Atenció: l'import supera el límit anual de 2.000 €", "gastos-justificacion-pct": "% Despeses de difícil justificació", @@ -26,5 +27,5 @@ "previous-payment": "Pagament fraccionat anterior", "tax-expenses": "Despeses deduïbles", "tax-incomes": "Ingressos computables", - "tax-negative-info": "Si dóna pèrdues (negatiu) = 0" + "tax-negative-info": "Si 07 dona pèrdues (negatiu) = 0" } diff --git a/Translation/cs_CZ.json b/Translation/cs_CZ.json index 6c8a4b6..86fa895 100644 --- a/Translation/cs_CZ.json +++ b/Translation/cs_CZ.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 04", "deductible-subaccounts": "Odečitatelné účty", "exists-accounting-130": "V účetním období %codejercicio% již existuje účetní zápis s popisem: %concepto%", + "fractional-payment": "Zálohová platba za předchozí čtvrtletí", "gastos-justificacion": "Výdaje obtížně ospravedlnitelné", "gastos-justificacion-limit": "Pozor: částka překračuje roční limit 2 000 €", "gastos-justificacion-pct": "% Výdaje obtížně ospravedlnitelné", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" + "tax-negative-info": "Pokud je 07 záporné (ztráta) = 0" } diff --git a/Translation/de_DE.json b/Translation/de_DE.json index 6bacba1..90b9c08 100644 --- a/Translation/de_DE.json +++ b/Translation/de_DE.json @@ -11,6 +11,7 @@ "download-aeat-file": "AEAT-Datei herunterladen", "exists-accounting-130": "Es existiert bereits ein Buchungssatz im Geschäftsjahr %codejercicio% mit dem Verwendungszweck: %concepto%", "expense": "Abzugsfähige Ausgabe", + "fractional-payment": "Vorauszahlung vorheriges Quartal", "gastos-justificacion": "Schwer belegbare Ausgaben", "gastos-justificacion-limit": "Achtung: Der Betrag überschreitet das jährliche Limit von 2.000 €", "gastos-justificacion-pct": "% Schwer belegbare Ausgaben", @@ -26,5 +27,5 @@ "previous-payment": "Vorherige Vorauszahlung", "tax-expenses": "Absetzbare Ausgaben", "tax-incomes": "Steuerbare Einkommen", - "tax-negative-info": "Wenn Verluste (negativ) = 0 sind" + "tax-negative-info": "Wenn 07 negativ ist (Verlust) = 0" } diff --git a/Translation/en_EN.json b/Translation/en_EN.json index c85611b..cdbfd56 100644 --- a/Translation/en_EN.json +++ b/Translation/en_EN.json @@ -11,6 +11,7 @@ "download-aeat-file": "Download AEAT file", "exists-accounting-130": "An accounting entry already exists in fiscal year %codejercicio% with the concept: %concepto%", "expense": "Deductible expense", + "fractional-payment": "Previous-quarter instalment payment", "gastos-justificacion": "Difficult-to-justify expenses", "gastos-justificacion-limit": "Warning: the amount exceeds the annual limit of €2,000", "gastos-justificacion-pct": "% Hard-to-justify expenses", @@ -26,5 +27,5 @@ "previous-payment": "Previous payment", "tax-expenses": "Deductible expenses", "tax-incomes": "Computable income", - "tax-negative-info": "If it gives losses (negative) = 0" + "tax-negative-info": "If 07 is a loss (negative) = 0" } diff --git a/Translation/es_AR.json b/Translation/es_AR.json index fbc8a4c..b2ba2d4 100644 --- a/Translation/es_AR.json +++ b/Translation/es_AR.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" } diff --git a/Translation/es_CL.json b/Translation/es_CL.json index fbc8a4c..b2ba2d4 100644 --- a/Translation/es_CL.json +++ b/Translation/es_CL.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" } diff --git a/Translation/es_CO.json b/Translation/es_CO.json index fbc8a4c..b2ba2d4 100644 --- a/Translation/es_CO.json +++ b/Translation/es_CO.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" } diff --git a/Translation/es_CR.json b/Translation/es_CR.json index fbc8a4c..b2ba2d4 100644 --- a/Translation/es_CR.json +++ b/Translation/es_CR.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" } diff --git a/Translation/es_DO.json b/Translation/es_DO.json index fbc8a4c..b2ba2d4 100644 --- a/Translation/es_DO.json +++ b/Translation/es_DO.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" } diff --git a/Translation/es_EC.json b/Translation/es_EC.json index fbc8a4c..b2ba2d4 100644 --- a/Translation/es_EC.json +++ b/Translation/es_EC.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" } diff --git a/Translation/es_ES.json b/Translation/es_ES.json index 415a8a5..0653fa3 100644 --- a/Translation/es_ES.json +++ b/Translation/es_ES.json @@ -11,6 +11,7 @@ "download-aeat-file": "Descargar fichero AEAT", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", "expense": "Gasto deducible", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -26,5 +27,5 @@ "previous-payment": "Pago fraccionado anterior", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" } diff --git a/Translation/es_GT.json b/Translation/es_GT.json index fbc8a4c..b2ba2d4 100644 --- a/Translation/es_GT.json +++ b/Translation/es_GT.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" } diff --git a/Translation/es_MX.json b/Translation/es_MX.json index fbc8a4c..b2ba2d4 100644 --- a/Translation/es_MX.json +++ b/Translation/es_MX.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" } diff --git a/Translation/es_PA.json b/Translation/es_PA.json index 6e1bc4a..f67fd46 100644 --- a/Translation/es_PA.json +++ b/Translation/es_PA.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" } diff --git a/Translation/es_PE.json b/Translation/es_PE.json index fbc8a4c..b2ba2d4 100644 --- a/Translation/es_PE.json +++ b/Translation/es_PE.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" } diff --git a/Translation/es_UY.json b/Translation/es_UY.json index fbc8a4c..b2ba2d4 100644 --- a/Translation/es_UY.json +++ b/Translation/es_UY.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" } diff --git a/Translation/eu_ES.json b/Translation/eu_ES.json index bf3ec50..1ea38f7 100644 --- a/Translation/eu_ES.json +++ b/Translation/eu_ES.json @@ -11,6 +11,7 @@ "download-aeat-file": "Deskargatu AEAT fitxategia", "exists-accounting-130": "Dagoeneko badago idazpen bat %codejercicio% ekitaldian %concepto% kontzeptuarekin", "expense": "Gastu kengarria", + "fractional-payment": "Aurreko hiruhilekoko zatikako ordainketa", "gastos-justificacion": "Zail justifikatzen diren gastuak", "gastos-justificacion-limit": "Kontuz: zenbatekoa urteko 2.000 €-ko muga gainditzen du", "gastos-justificacion-pct": "% Zail justifikatzen diren gastuak", @@ -26,5 +27,5 @@ "previous-payment": "Aurreko ordainketa zatikatua", "tax-expenses": "Gastu kengarriak", "tax-incomes": "Errenta konputagarria", - "tax-negative-info": "Galerak ematen baditu (negatiboak) = 0" + "tax-negative-info": "07 galerak badira (negatiboa) = 0" } diff --git a/Translation/fr_FR.json b/Translation/fr_FR.json index 3dd35c8..505e1bb 100644 --- a/Translation/fr_FR.json +++ b/Translation/fr_FR.json @@ -11,6 +11,7 @@ "download-aeat-file": "Télécharger le fichier AEAT", "exists-accounting-130": "Il existe déjà une écriture pour l'exercice %codejercicio% avec le libellé : %concepto%", "expense": "Dépense déductible", + "fractional-payment": "Acompte du trimestre précédent", "gastos-justificacion": "Dépenses difficiles à justifier", "gastos-justificacion-limit": "Attention: le montant dépasse la limite annuelle de 2.000 €", "gastos-justificacion-pct": "% Dépenses difficiles à justifier", @@ -26,5 +27,5 @@ "previous-payment": "Paiement fractionné précédent", "tax-expenses": "Dépenses déductibles", "tax-incomes": "Revenu éligible", - "tax-negative-info": "Si déficitaire (négatif) = 0" + "tax-negative-info": "Si 07 est déficitaire (négatif) = 0" } diff --git a/Translation/gl_ES.json b/Translation/gl_ES.json index f6210d3..dd03910 100644 --- a/Translation/gl_ES.json +++ b/Translation/gl_ES.json @@ -11,6 +11,7 @@ "download-aeat-file": "Descargar ficheiro AEAT", "exists-accounting-130": "Xa existe un asento no exercicio %codejercicio% co concepto: %concepto%", "expense": "Gasto deducible", + "fractional-payment": "Pagamento fraccionado trimestre anterior", "gastos-justificacion": "Gastos de difícil xustificación", "gastos-justificacion-limit": "Atención: o importe supera o límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil xustificación", @@ -26,5 +27,5 @@ "previous-payment": "Pagamento fraccionado anterior", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Se dá perdas (negativo) = 0" + "tax-negative-info": "Se 07 dá perdas (negativo) = 0" } diff --git a/Translation/it_IT.json b/Translation/it_IT.json index 6ba6085..15d07e4 100644 --- a/Translation/it_IT.json +++ b/Translation/it_IT.json @@ -11,6 +11,7 @@ "download-aeat-file": "Scarica file AEAT", "exists-accounting-130": "Esiste già una registrazione nell'esercizio %codejercicio% con la causale: %concepto%", "expense": "Spesa deducibile", + "fractional-payment": "Acconto del trimestre precedente", "gastos-justificacion": "Spese di difficile giustificazione", "gastos-justificacion-limit": "Attenzione: l'importo supera il limite annuo di 2.000 €", "gastos-justificacion-pct": "% Spese di difficile giustificazione", @@ -26,5 +27,5 @@ "previous-payment": "Acconto precedente", "tax-expenses": "Spese fiscali deducibili", "tax-incomes": "Ricavi fiscali", - "tax-negative-info": "Se genera perdite (negative) = 0" + "tax-negative-info": "Se 07 è in perdita (negativo) = 0" } diff --git a/Translation/pl_PL.json b/Translation/pl_PL.json index 392a7f5..febbec8 100644 --- a/Translation/pl_PL.json +++ b/Translation/pl_PL.json @@ -4,6 +4,7 @@ "after-deduct": "na rubrykę 04", "deductible-subaccounts": "Podkonty do odliczeń", "exists-accounting-130": "Istnieje już zapis księgowy w okresie %codejercicio% o treści: %concepto%", + "fractional-payment": "Zaliczka za poprzedni kwartał", "gastos-justificacion": "Wydatki trudne do uzasadnienia", "gastos-justificacion-limit": "Uwaga: kwota przekracza roczny limit 2 000 €", "gastos-justificacion-pct": "% Wydatki trudne do uzasadnienia", @@ -17,5 +18,5 @@ "previous-model": "Poprzednie kwartały dochodów", "tax-expenses": "Wydatki podatkowe", "tax-incomes": "Dochody opodatkowane", - "tax-negative-info": "Jeśli wykazuje straty (ujemny) = 0" + "tax-negative-info": "Jeśli 07 oznacza stratę (ujemne) = 0" } diff --git a/Translation/pt_BR.json b/Translation/pt_BR.json index 6228efc..96ca3cf 100644 --- a/Translation/pt_BR.json +++ b/Translation/pt_BR.json @@ -11,6 +11,7 @@ "download-aeat-file": "Baixar arquivo AEAT", "exists-accounting-130": "Já existe um lançamento no exercício %codejercicio% com o conceito: %concepto%", "expense": "Despesa dedutível", + "fractional-payment": "Pagamento parcelado do trimestre anterior", "gastos-justificacion": "Gastos de difícil justificação", "gastos-justificacion-limit": "Atenção: o valor ultrapassa o limite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificação", @@ -26,5 +27,5 @@ "previous-payment": "Pagamento por conta anterior", "tax-expenses": "Despesas dedutíveis", "tax-incomes": "Rendimentos tributáveis", - "tax-negative-info": "Se der negativo = 0" + "tax-negative-info": "Se 07 der prejuízo (negativo) = 0" } diff --git a/Translation/pt_PT.json b/Translation/pt_PT.json index 1e1a88d..b4979a6 100644 --- a/Translation/pt_PT.json +++ b/Translation/pt_PT.json @@ -11,6 +11,7 @@ "download-aeat-file": "Descarregar ficheiro AEAT", "exists-accounting-130": "Já existe um lançamento no exercício %codejercicio% com o conceito: %concepto%", "expense": "Despesa dedutível", + "fractional-payment": "Pagamento fracionado do trimestre anterior", "gastos-justificacion": "Despesas de difícil justificação", "gastos-justificacion-limit": "Atenção: o montante ultrapassa o limite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificação", @@ -26,5 +27,5 @@ "previous-payment": "Pagamento por conta anterior", "tax-expenses": "Despesas dedutíveis", "tax-incomes": "Impostos sobre rendimentos", - "tax-negative-info": "Se der perdas (negativo) = 0" + "tax-negative-info": "Se 07 der prejuízo (negativo) = 0" } diff --git a/Translation/tr_TR.json b/Translation/tr_TR.json index 6b39b6b..8f4f3ed 100644 --- a/Translation/tr_TR.json +++ b/Translation/tr_TR.json @@ -4,6 +4,7 @@ "after-deduct": "04 numaralı kutu hakkında", "deductible-subaccounts": "İndirilebilir alt hesaplar", "exists-accounting-130": "%codejercicio% döneminde %concepto% açıklamalı bir kayıt zaten mevcut.", + "fractional-payment": "Önceki çeyrek taksit ödemesi", "gastos-justificacion": "Gerekçelendirilmesi zor giderler", "gastos-justificacion-limit": "Dikkat: tutar yıllık 2.000 € limitini aşıyor", "gastos-justificacion-pct": "% Gerekçelendirilmesi zor giderler", @@ -17,5 +18,5 @@ "previous-model": "Önceki çeyrek gelirleri", "tax-expenses": "Düşürülebilir Harcamalar", "tax-incomes": "Gelir hesaplamaları", - "tax-negative-info": "Zarar veriyorsa (negatif) = 0" + "tax-negative-info": "07 zarar verirse (negatif) = 0" } diff --git a/Translation/va_ES.json b/Translation/va_ES.json index 1b479e1..b4d9f90 100644 --- a/Translation/va_ES.json +++ b/Translation/va_ES.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casella 04", "deductible-subaccounts": "Comptes deduïbles", "exists-accounting-130": "Ja existeix un assentament a l'exercici %codejercicio% amb el concepte: %concepto%", + "fractional-payment": "Pagament fraccionat trimestre anterior", "gastos-justificacion": "Despeses de difícil justificació", "gastos-justificacion-limit": "Atenció: l'import supera el límit anual de 2.000 €", "gastos-justificacion-pct": "% Despeses de difícil justificació", @@ -17,5 +18,5 @@ "previous-model": "Ingressos trimestrals anteriors", "tax-expenses": "Despeses deduïbles", "tax-incomes": "Ingressos computables", - "tax-negative-info": "Si dóna pèrdues (negatiu) = 0" + "tax-negative-info": "Si 07 dona pèrdues (negatiu) = 0" } diff --git a/View/Modelo130.html.twig b/View/Modelo130.html.twig index 1c04c03..4ff3bd3 100644 --- a/View/Modelo130.html.twig +++ b/View/Modelo130.html.twig @@ -185,10 +185,19 @@
- {{ trans('result') }} + {{ trans('fractional-payment') }}
07 - + +
+
+
+
+
+ {{ trans('result') }} +
+ 19 +

{{ trans('tax-negative-info') }}

From 73a4fa4769291fc8e4fd7ed7d55f22a91c946e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mart=C3=ADn=20Gonz=C3=A1lez?= Date: Fri, 24 Jul 2026 20:30:20 +0200 Subject: [PATCH 09/15] El resultado del modelo siempre es el mismo incluso tras regualizar el asiento del trimestre, pero sin dejar crear otro asiento igual --- Lib/Modelo130.php | 25 +++++++++++++++++++++---- View/Modelo130.html.twig | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Lib/Modelo130.php b/Lib/Modelo130.php index c164b60..483676e 100644 --- a/Lib/Modelo130.php +++ b/Lib/Modelo130.php @@ -102,6 +102,9 @@ class Modelo130 /** @var float */ protected static $previousPayments = 0.0; + /** @var bool */ + protected static $currentPaymentEntryExists = false; + public static function generate( string $codejercicio, string $period, @@ -125,6 +128,7 @@ public static function generate( static::$taxbaseIncomes = 0.0; static::$taxbaseRetentions = 0.0; static::$previousPayments = 0.0; + static::$currentPaymentEntryExists = false; static::loadDates(); static::loadAccountingData(); @@ -142,6 +146,7 @@ public static function generate( 'sales' => static::$sales, 'purchases' => static::$purchases, 'accountingEntries' => static::$accountingEntries, + 'currentPaymentEntryExists' => static::$currentPaymentEntryExists, 'applyGastosJustificacion' => $applyGastosJustificacion, 'todeduct' => $todeduct, 'gastosJustificacionPct' => $gastosJustificacionPct, @@ -551,6 +556,11 @@ protected static function loadAccountingData(): void new FacturaProveedor(), $entryIds ); + + $currentPaymentConcept = Tools::trans( + 'acc-concept-irpf-130', + ['%period%' => static::$period] + ); foreach ($groups as $idasiento => $group) { $entry = $accountingEntries[$idasiento] ?? null; @@ -558,6 +568,11 @@ protected static function loadAccountingData(): void if (null === $entry) { continue; } + + $isCurrentPaymentEntry = $entry->concepto === $currentPaymentConcept; + if ($isCurrentPaymentEntry) { + static::$currentPaymentEntryExists = true; + } $customerInvoice = $customerInvoices[$idasiento] ?? null; $supplierInvoice = $supplierInvoices[$idasiento] ?? null; @@ -659,11 +674,13 @@ protected static function loadAccountingData(): void * Toda partida de la cuenta 473 sin factura asociada se considera * un pago fraccionado del Modelo 130. * - * También se incluye el pago correspondiente al propio trimestre. - * El asiento se genera con fecha del último día del período, por lo - * que al volver a calcular el modelo ese importe aparece en la - * casilla 05 y evita crear el mismo pago de nuevo. + * El pago correspondiente al propio trimestre se detecta, pero no + * se incluye en la casilla 05 para garantizar la idempotencia. */ + if ($isCurrentPaymentEntry) { + continue; + } + $type = 'previous-payment'; $amount = $item['retention']; diff --git a/View/Modelo130.html.twig b/View/Modelo130.html.twig index 4ff3bd3..46ea479 100644 --- a/View/Modelo130.html.twig +++ b/View/Modelo130.html.twig @@ -202,7 +202,7 @@

{{ trans('tax-negative-info') }}

- {% if fsc.result.result > 0 %} + {% if fsc.result.result > 0 and not fsc.result.currentPaymentEntryExists %}
- {% if fsc.result.result > 0 and not fsc.result.currentPaymentEntryExists %} + {% if fsc.result.currentPaymentEntry %} + + {% elseif fsc.result.result > 0 %}
+ +
+ + {% for rule in expenseRules %} + + {% else %}{% endfor %} +
{{ trans('code') }}{{ trans('description') }}
{{ rule.codigo }}{{ rule.display_description }} +
{{ formToken() }}
+
{{ trans('no-data') }}
+
+
{{ formToken() }}
+ + + +
+ {% set incomeRules = fsc.getIncomeRules() %} +
+
{{ trans('model-130-prefix-help') }}
+
+ {{ formToken() }} + + +
+ + +
+
+
+
+ + {% for rule in incomeRules %} + + {% else %}{% endfor %} +
{{ trans('code') }}{{ trans('description') }}
{{ rule.codigo }}{{ rule.display_description }} +
{{ formToken() }}
+
{{ trans('no-data') }}
+
+
{{ formToken() }}
+
+
From d5e76b2a36ec9188cb99ab95c2aafdd3491956c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mart=C3=ADn=20Gonz=C3=A1lez?= Date: Tue, 28 Jul 2026 21:09:30 +0200 Subject: [PATCH 12/15] Eliminado test innecesario --- Test/main/Modelo130ConfigTest.php | 35 ------------------------------- 1 file changed, 35 deletions(-) diff --git a/Test/main/Modelo130ConfigTest.php b/Test/main/Modelo130ConfigTest.php index 0612aa3..c151b2f 100644 --- a/Test/main/Modelo130ConfigTest.php +++ b/Test/main/Modelo130ConfigTest.php @@ -80,41 +80,6 @@ public function testDefaultRulesOnlyUseExistingAccounts(): void } } - /** - * Cada inclusión declarada en DEFAULTS debe añadirse cuando la cuenta - * exacta está disponible en el PGC instalado. - */ - public function testAvailableDeclaredDefaultsAreIncluded(): void - { - $defaults = Modelo130Config::buildDefaultRules(); - $declared = [ - Mod130Conf::TIPO_GASTO => [ - '60', '61', '62', '631', '634', '636', '639', - '64', '65', '661', '662', '665', '668', '669', - '68', '693', '694', - ], - Mod130Conf::TIPO_INGRESO => [ - '70', '71', '73', '74', '75', '778', '793', '794', - ], - ]; - - foreach ($declared as $type => $codes) { - foreach ($codes as $code) { - $account = new Cuenta(); - - if (!$account->loadWhere([Where::eq('codcuenta', $code)])) { - continue; - } - - $this->assertContains( - $code, - $defaults[$type], - "La cuenta existente {$code} debe incluirse en los valores predeterminados" - ); - } - } - } - /** * Las cuentas 61 y 71 se procesan como variación de existencias y no * como gastos o ingresos ordinarios. El signo del saldo decide la casilla. From 27acd4e36adcc33a5287a18b6d521fb9b3f32790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mart=C3=ADn=20Gonz=C3=A1lez?= Date: Tue, 28 Jul 2026 21:15:44 +0200 Subject: [PATCH 13/15] Modificado test menor con errores en pipeline --- Test/main/Modelo130ConfigTest.php | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/Test/main/Modelo130ConfigTest.php b/Test/main/Modelo130ConfigTest.php index c151b2f..b0eeaaa 100644 --- a/Test/main/Modelo130ConfigTest.php +++ b/Test/main/Modelo130ConfigTest.php @@ -25,7 +25,6 @@ use FacturaScripts\Core\Where; use FacturaScripts\Dinamic\Model\Cuenta; use FacturaScripts\Dinamic\Model\Mod130Conf; -use FacturaScripts\Dinamic\Model\User; use FacturaScripts\Plugins\Modelo130\Lib\Modelo130Accounts; use FacturaScripts\Plugins\Modelo130\Lib\Modelo130Config; use FacturaScripts\Test\Traits\DefaultSettingsTrait; @@ -146,31 +145,27 @@ public function testQueryPrefixesRespectStockVariationConfiguration(): void } /** - * Al crear una regla se guardan el usuario y la fecha de creación, y el - * nick corresponde con un usuario existente. + * Al crear una regla se guardan el usuario y la fecha de creación. */ public function testRuleCreationIsLinkedToUser(): void { $originalRules = $this->snapshotRules(); try { - $this->replaceRules([ - [Mod130Conf::TIPO_GASTO, '699999999999999'], - ]); + $this->assertTrue($rule->loadWhere([Where::eq('codigo', '699999999999999')])); $rule = new Mod130Conf(); - $this->assertTrue($rule->loadWhere([Where::eq('codigo', '699999999999999')])); - $currentNick = Session::user()->nick; - $this->assertSame($currentNick, $rule->nick); - $this->assertNotEmpty($rule->creation_date); + $this->assertSame( + Session::user()->nick, + $rule->nick, + 'El nick de creación debe ser el usuario de la sesión' + ); - $user = new User(); - $this->assertTrue( - $user->loadWhere([Where::eq('nick', $rule->nick)]), - 'El nick de creación debe corresponder con un usuario existente' + $this->assertNotEmpty( + $rule->creation_date, + 'La fecha de creación debe guardarse' ); - $this->assertSame($currentNick, $rule->user()?->nick); } finally { $this->replaceRules($originalRules); } From f290642045df5e9e301b7d4ae4199b446ca61694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mart=C3=ADn=20Gonz=C3=A1lez?= Date: Tue, 28 Jul 2026 21:18:48 +0200 Subject: [PATCH 14/15] Modificando test --- Test/main/Modelo130ConfigTest.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Test/main/Modelo130ConfigTest.php b/Test/main/Modelo130ConfigTest.php index b0eeaaa..db452a4 100644 --- a/Test/main/Modelo130ConfigTest.php +++ b/Test/main/Modelo130ConfigTest.php @@ -152,10 +152,19 @@ public function testRuleCreationIsLinkedToUser(): void $originalRules = $this->snapshotRules(); try { - $this->assertTrue($rule->loadWhere([Where::eq('codigo', '699999999999999')])); + $this->replaceRules([ + [Mod130Conf::TIPO_GASTO, '699999999999999'], + ]); $rule = new Mod130Conf(); + $this->assertTrue( + $rule->loadWhere([ + Where::eq('codigo', '699999999999999'), + ]), + 'La regla de prueba debe existir' + ); + $this->assertSame( Session::user()->nick, $rule->nick, From 630f5743435bc0fa037bece49286c6965c36c1b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mart=C3=ADn=20Gonz=C3=A1lez?= Date: Tue, 28 Jul 2026 22:19:31 +0200 Subject: [PATCH 15/15] =?UTF-8?q?Peque=C3=B1a=20traducci=C3=B3n=20para=20d?= =?UTF-8?q?enominarlo=20igual=20que=20en=20el=20core?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Translation/ca_ES.json | 2 +- Translation/cs_CZ.json | 2 +- Translation/en_EN.json | 2 +- Translation/es_AR.json | 2 +- Translation/es_CL.json | 2 +- Translation/es_CO.json | 2 +- Translation/es_CR.json | 2 +- Translation/es_DO.json | 2 +- Translation/es_EC.json | 2 +- Translation/es_ES.json | 2 +- Translation/es_GT.json | 2 +- Translation/es_MX.json | 2 +- Translation/es_PA.json | 2 +- Translation/es_PE.json | 2 +- Translation/es_UY.json | 2 +- Translation/eu_ES.json | 2 +- Translation/fr_FR.json | 2 +- Translation/gl_ES.json | 2 +- Translation/it_IT.json | 2 +- Translation/pt_BR.json | 2 +- Translation/pt_PT.json | 2 +- Translation/va_ES.json | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Translation/ca_ES.json b/Translation/ca_ES.json index c61d58d..18a1f74 100644 --- a/Translation/ca_ES.json +++ b/Translation/ca_ES.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "El compte ja està inclòs per una altra regla o engloba una regla existent.", "model-130-p": "El Model 130 és una declaració trimestral de l'impost de la renda de les persones físiques (IRPF) en el qual es liquida el pagament fraccionat d'aquest impost, a compte de la declaració anual que es realitza l'any següent.", "model-130-prefix-help": "Els codis s’interpreten com a prefixos. Per exemple, 62 inclou tots els subcomptes que comencen per 62. El compte 473 es processa sempre internament i no apareix en aquestes llistes.", - "model-130-prefix-includes": "Prefix %code% amb comptes descendents al pla comptable", + "model-130-prefix-includes": "Prefix %code% amb comptes fills al pla comptable", "model-130-prefix-not-found": "Prefix %code% sense un compte exacte al pla comptable actual", "model-130-r": "Dades acumulades del període comprès entre el primer dia de l'any i el darrer dia del trimestre seleccionat.", "model-130-restore-confirm": "Se suprimirà la configuració actual i es reconstruirà a partir del pla comptable instal·lat. Voleu continuar?", diff --git a/Translation/cs_CZ.json b/Translation/cs_CZ.json index d0030f9..cbb15d1 100644 --- a/Translation/cs_CZ.json +++ b/Translation/cs_CZ.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "Účet je již zahrnut jiným pravidlem nebo zahrnuje existující pravidlo.", "model-130-p": "El Modelo 130 es una declaración trimestral del impuesto de la renta de las personas físicas (IRPF) en el que se liquida el pago fraccionado de este impuesto, a cuenta de la declaración anual que se realiza el año siguiente.", "model-130-prefix-help": "Kódy jsou interpretovány jako prefixy. Například 62 zahrnuje všechny podúčty začínající číslem 62. Účet 473 je vždy zpracován interně a v těchto seznamech se nezobrazuje.", - "model-130-prefix-includes": "Prefix %code% má v účtovém plánu podřízené účty", + "model-130-prefix-includes": "Prefix %code% s podřízenými účty v účtovém plánu", "model-130-prefix-not-found": "Prefix %code% nemá v aktuálním účtovém plánu přesný účet", "model-130-r": "Datos acumulados del período comprendido entre el primer día del año y el último día del trimestre seleccionado.", "model-130-restore-confirm": "Aktuální nastavení bude odstraněno a znovu vytvořeno z nainstalovaného účtového plánu. Pokračovat?", diff --git a/Translation/en_EN.json b/Translation/en_EN.json index 841f288..e50dca8 100644 --- a/Translation/en_EN.json +++ b/Translation/en_EN.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "The account is already included by another rule or includes an existing rule.", "model-130-p": "Model 130 is a quarterly declaration of personal income tax (IRPF) in which the installment payment of this tax is settled, on account of the annual declaration that is made the following year.", "model-130-prefix-help": "Codes are interpreted as prefixes. For example, 62 includes all subaccounts beginning with 62. Account 473 is always processed internally and does not appear in these lists.", - "model-130-prefix-includes": "Prefix %code% with descendant accounts in the chart of accounts", + "model-130-prefix-includes": "Prefix %code% with child accounts in the chart of accounts", "model-130-prefix-not-found": "Prefix %code% without an exact account in the current chart of accounts", "model-130-r": "Accumulated data for the period between the first day of the year and the last day of the selected quarter.", "model-130-restore-confirm": "The current configuration will be deleted and rebuilt from the installed chart of accounts. Continue?", diff --git a/Translation/es_AR.json b/Translation/es_AR.json index 7a1d109..ab3cf51 100644 --- a/Translation/es_AR.json +++ b/Translation/es_AR.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "La cuenta ya está incluida por otra regla o engloba una regla existente.", "model-130-p": "El Modelo 130 es una declaración trimestral del impuesto de la renta de las personas físicas (IRPF) en el que se liquida el pago fraccionado de este impuesto, a cuenta de la declaración anual que se realiza el año siguiente.", "model-130-prefix-help": "Los códigos se interpretan como prefijos. Por ejemplo, 62 incluye todas las subcuentas que comienzan por 62. La cuenta 473 se procesa siempre de forma interna y no aparece en estas listas.", - "model-130-prefix-includes": "Prefijo %code% con cuentas descendientes en el plan contable", + "model-130-prefix-includes": "Prefijo %code% con cuentas hijas en el plan contable", "model-130-prefix-not-found": "Prefijo %code% sin una cuenta exacta en el plan contable actual", "model-130-r": "Datos acumulados del período comprendido entre el primer día del año y el último día del trimestre seleccionado.", "model-130-restore-confirm": "Se eliminará la configuración actual y se reconstruirá desde el plan contable instalado. ¿Continuar?", diff --git a/Translation/es_CL.json b/Translation/es_CL.json index 7a1d109..ab3cf51 100644 --- a/Translation/es_CL.json +++ b/Translation/es_CL.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "La cuenta ya está incluida por otra regla o engloba una regla existente.", "model-130-p": "El Modelo 130 es una declaración trimestral del impuesto de la renta de las personas físicas (IRPF) en el que se liquida el pago fraccionado de este impuesto, a cuenta de la declaración anual que se realiza el año siguiente.", "model-130-prefix-help": "Los códigos se interpretan como prefijos. Por ejemplo, 62 incluye todas las subcuentas que comienzan por 62. La cuenta 473 se procesa siempre de forma interna y no aparece en estas listas.", - "model-130-prefix-includes": "Prefijo %code% con cuentas descendientes en el plan contable", + "model-130-prefix-includes": "Prefijo %code% con cuentas hijas en el plan contable", "model-130-prefix-not-found": "Prefijo %code% sin una cuenta exacta en el plan contable actual", "model-130-r": "Datos acumulados del período comprendido entre el primer día del año y el último día del trimestre seleccionado.", "model-130-restore-confirm": "Se eliminará la configuración actual y se reconstruirá desde el plan contable instalado. ¿Continuar?", diff --git a/Translation/es_CO.json b/Translation/es_CO.json index 7a1d109..ab3cf51 100644 --- a/Translation/es_CO.json +++ b/Translation/es_CO.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "La cuenta ya está incluida por otra regla o engloba una regla existente.", "model-130-p": "El Modelo 130 es una declaración trimestral del impuesto de la renta de las personas físicas (IRPF) en el que se liquida el pago fraccionado de este impuesto, a cuenta de la declaración anual que se realiza el año siguiente.", "model-130-prefix-help": "Los códigos se interpretan como prefijos. Por ejemplo, 62 incluye todas las subcuentas que comienzan por 62. La cuenta 473 se procesa siempre de forma interna y no aparece en estas listas.", - "model-130-prefix-includes": "Prefijo %code% con cuentas descendientes en el plan contable", + "model-130-prefix-includes": "Prefijo %code% con cuentas hijas en el plan contable", "model-130-prefix-not-found": "Prefijo %code% sin una cuenta exacta en el plan contable actual", "model-130-r": "Datos acumulados del período comprendido entre el primer día del año y el último día del trimestre seleccionado.", "model-130-restore-confirm": "Se eliminará la configuración actual y se reconstruirá desde el plan contable instalado. ¿Continuar?", diff --git a/Translation/es_CR.json b/Translation/es_CR.json index 7a1d109..ab3cf51 100644 --- a/Translation/es_CR.json +++ b/Translation/es_CR.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "La cuenta ya está incluida por otra regla o engloba una regla existente.", "model-130-p": "El Modelo 130 es una declaración trimestral del impuesto de la renta de las personas físicas (IRPF) en el que se liquida el pago fraccionado de este impuesto, a cuenta de la declaración anual que se realiza el año siguiente.", "model-130-prefix-help": "Los códigos se interpretan como prefijos. Por ejemplo, 62 incluye todas las subcuentas que comienzan por 62. La cuenta 473 se procesa siempre de forma interna y no aparece en estas listas.", - "model-130-prefix-includes": "Prefijo %code% con cuentas descendientes en el plan contable", + "model-130-prefix-includes": "Prefijo %code% con cuentas hijas en el plan contable", "model-130-prefix-not-found": "Prefijo %code% sin una cuenta exacta en el plan contable actual", "model-130-r": "Datos acumulados del período comprendido entre el primer día del año y el último día del trimestre seleccionado.", "model-130-restore-confirm": "Se eliminará la configuración actual y se reconstruirá desde el plan contable instalado. ¿Continuar?", diff --git a/Translation/es_DO.json b/Translation/es_DO.json index 7a1d109..ab3cf51 100644 --- a/Translation/es_DO.json +++ b/Translation/es_DO.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "La cuenta ya está incluida por otra regla o engloba una regla existente.", "model-130-p": "El Modelo 130 es una declaración trimestral del impuesto de la renta de las personas físicas (IRPF) en el que se liquida el pago fraccionado de este impuesto, a cuenta de la declaración anual que se realiza el año siguiente.", "model-130-prefix-help": "Los códigos se interpretan como prefijos. Por ejemplo, 62 incluye todas las subcuentas que comienzan por 62. La cuenta 473 se procesa siempre de forma interna y no aparece en estas listas.", - "model-130-prefix-includes": "Prefijo %code% con cuentas descendientes en el plan contable", + "model-130-prefix-includes": "Prefijo %code% con cuentas hijas en el plan contable", "model-130-prefix-not-found": "Prefijo %code% sin una cuenta exacta en el plan contable actual", "model-130-r": "Datos acumulados del período comprendido entre el primer día del año y el último día del trimestre seleccionado.", "model-130-restore-confirm": "Se eliminará la configuración actual y se reconstruirá desde el plan contable instalado. ¿Continuar?", diff --git a/Translation/es_EC.json b/Translation/es_EC.json index 7a1d109..ab3cf51 100644 --- a/Translation/es_EC.json +++ b/Translation/es_EC.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "La cuenta ya está incluida por otra regla o engloba una regla existente.", "model-130-p": "El Modelo 130 es una declaración trimestral del impuesto de la renta de las personas físicas (IRPF) en el que se liquida el pago fraccionado de este impuesto, a cuenta de la declaración anual que se realiza el año siguiente.", "model-130-prefix-help": "Los códigos se interpretan como prefijos. Por ejemplo, 62 incluye todas las subcuentas que comienzan por 62. La cuenta 473 se procesa siempre de forma interna y no aparece en estas listas.", - "model-130-prefix-includes": "Prefijo %code% con cuentas descendientes en el plan contable", + "model-130-prefix-includes": "Prefijo %code% con cuentas hijas en el plan contable", "model-130-prefix-not-found": "Prefijo %code% sin una cuenta exacta en el plan contable actual", "model-130-r": "Datos acumulados del período comprendido entre el primer día del año y el último día del trimestre seleccionado.", "model-130-restore-confirm": "Se eliminará la configuración actual y se reconstruirá desde el plan contable instalado. ¿Continuar?", diff --git a/Translation/es_ES.json b/Translation/es_ES.json index 7a1d109..ab3cf51 100644 --- a/Translation/es_ES.json +++ b/Translation/es_ES.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "La cuenta ya está incluida por otra regla o engloba una regla existente.", "model-130-p": "El Modelo 130 es una declaración trimestral del impuesto de la renta de las personas físicas (IRPF) en el que se liquida el pago fraccionado de este impuesto, a cuenta de la declaración anual que se realiza el año siguiente.", "model-130-prefix-help": "Los códigos se interpretan como prefijos. Por ejemplo, 62 incluye todas las subcuentas que comienzan por 62. La cuenta 473 se procesa siempre de forma interna y no aparece en estas listas.", - "model-130-prefix-includes": "Prefijo %code% con cuentas descendientes en el plan contable", + "model-130-prefix-includes": "Prefijo %code% con cuentas hijas en el plan contable", "model-130-prefix-not-found": "Prefijo %code% sin una cuenta exacta en el plan contable actual", "model-130-r": "Datos acumulados del período comprendido entre el primer día del año y el último día del trimestre seleccionado.", "model-130-restore-confirm": "Se eliminará la configuración actual y se reconstruirá desde el plan contable instalado. ¿Continuar?", diff --git a/Translation/es_GT.json b/Translation/es_GT.json index 7a1d109..ab3cf51 100644 --- a/Translation/es_GT.json +++ b/Translation/es_GT.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "La cuenta ya está incluida por otra regla o engloba una regla existente.", "model-130-p": "El Modelo 130 es una declaración trimestral del impuesto de la renta de las personas físicas (IRPF) en el que se liquida el pago fraccionado de este impuesto, a cuenta de la declaración anual que se realiza el año siguiente.", "model-130-prefix-help": "Los códigos se interpretan como prefijos. Por ejemplo, 62 incluye todas las subcuentas que comienzan por 62. La cuenta 473 se procesa siempre de forma interna y no aparece en estas listas.", - "model-130-prefix-includes": "Prefijo %code% con cuentas descendientes en el plan contable", + "model-130-prefix-includes": "Prefijo %code% con cuentas hijas en el plan contable", "model-130-prefix-not-found": "Prefijo %code% sin una cuenta exacta en el plan contable actual", "model-130-r": "Datos acumulados del período comprendido entre el primer día del año y el último día del trimestre seleccionado.", "model-130-restore-confirm": "Se eliminará la configuración actual y se reconstruirá desde el plan contable instalado. ¿Continuar?", diff --git a/Translation/es_MX.json b/Translation/es_MX.json index 7a1d109..ab3cf51 100644 --- a/Translation/es_MX.json +++ b/Translation/es_MX.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "La cuenta ya está incluida por otra regla o engloba una regla existente.", "model-130-p": "El Modelo 130 es una declaración trimestral del impuesto de la renta de las personas físicas (IRPF) en el que se liquida el pago fraccionado de este impuesto, a cuenta de la declaración anual que se realiza el año siguiente.", "model-130-prefix-help": "Los códigos se interpretan como prefijos. Por ejemplo, 62 incluye todas las subcuentas que comienzan por 62. La cuenta 473 se procesa siempre de forma interna y no aparece en estas listas.", - "model-130-prefix-includes": "Prefijo %code% con cuentas descendientes en el plan contable", + "model-130-prefix-includes": "Prefijo %code% con cuentas hijas en el plan contable", "model-130-prefix-not-found": "Prefijo %code% sin una cuenta exacta en el plan contable actual", "model-130-r": "Datos acumulados del período comprendido entre el primer día del año y el último día del trimestre seleccionado.", "model-130-restore-confirm": "Se eliminará la configuración actual y se reconstruirá desde el plan contable instalado. ¿Continuar?", diff --git a/Translation/es_PA.json b/Translation/es_PA.json index ab66fa4..cd634af 100644 --- a/Translation/es_PA.json +++ b/Translation/es_PA.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "La cuenta ya está incluida por otra regla o engloba una regla existente.", "model-130-p": "El Modelo 130 es una declaración trimestral del impuesto de la renta de las personas físicas (IRPF) en el que se liquida el pago fraccionado de este impuesto, a cuenta de la declaración anual que se realiza el año siguiente.", "model-130-prefix-help": "Los códigos se interpretan como prefijos. Por ejemplo, 62 incluye todas las subcuentas que comienzan por 62. La cuenta 473 se procesa siempre de forma interna y no aparece en estas listas.", - "model-130-prefix-includes": "Prefijo %code% con cuentas descendientes en el plan contable", + "model-130-prefix-includes": "Prefijo %code% con cuentas hijas en el plan contable", "model-130-prefix-not-found": "Prefijo %code% sin una cuenta exacta en el plan contable actual", "model-130-r": "Datos acumulados del período comprendido entre el primer día del año y el último día del trimestre", "model-130-restore-confirm": "Se eliminará la configuración actual y se reconstruirá desde el plan contable instalado. ¿Continuar?", diff --git a/Translation/es_PE.json b/Translation/es_PE.json index 7a1d109..ab3cf51 100644 --- a/Translation/es_PE.json +++ b/Translation/es_PE.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "La cuenta ya está incluida por otra regla o engloba una regla existente.", "model-130-p": "El Modelo 130 es una declaración trimestral del impuesto de la renta de las personas físicas (IRPF) en el que se liquida el pago fraccionado de este impuesto, a cuenta de la declaración anual que se realiza el año siguiente.", "model-130-prefix-help": "Los códigos se interpretan como prefijos. Por ejemplo, 62 incluye todas las subcuentas que comienzan por 62. La cuenta 473 se procesa siempre de forma interna y no aparece en estas listas.", - "model-130-prefix-includes": "Prefijo %code% con cuentas descendientes en el plan contable", + "model-130-prefix-includes": "Prefijo %code% con cuentas hijas en el plan contable", "model-130-prefix-not-found": "Prefijo %code% sin una cuenta exacta en el plan contable actual", "model-130-r": "Datos acumulados del período comprendido entre el primer día del año y el último día del trimestre seleccionado.", "model-130-restore-confirm": "Se eliminará la configuración actual y se reconstruirá desde el plan contable instalado. ¿Continuar?", diff --git a/Translation/es_UY.json b/Translation/es_UY.json index 7a1d109..ab3cf51 100644 --- a/Translation/es_UY.json +++ b/Translation/es_UY.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "La cuenta ya está incluida por otra regla o engloba una regla existente.", "model-130-p": "El Modelo 130 es una declaración trimestral del impuesto de la renta de las personas físicas (IRPF) en el que se liquida el pago fraccionado de este impuesto, a cuenta de la declaración anual que se realiza el año siguiente.", "model-130-prefix-help": "Los códigos se interpretan como prefijos. Por ejemplo, 62 incluye todas las subcuentas que comienzan por 62. La cuenta 473 se procesa siempre de forma interna y no aparece en estas listas.", - "model-130-prefix-includes": "Prefijo %code% con cuentas descendientes en el plan contable", + "model-130-prefix-includes": "Prefijo %code% con cuentas hijas en el plan contable", "model-130-prefix-not-found": "Prefijo %code% sin una cuenta exacta en el plan contable actual", "model-130-r": "Datos acumulados del período comprendido entre el primer día del año y el último día del trimestre seleccionado.", "model-130-restore-confirm": "Se eliminará la configuración actual y se reconstruirá desde el plan contable instalado. ¿Continuar?", diff --git a/Translation/eu_ES.json b/Translation/eu_ES.json index b0bd1a9..41806f9 100644 --- a/Translation/eu_ES.json +++ b/Translation/eu_ES.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "Kontua dagoeneko beste arau batek barne hartzen du edo lehendik dagoen arau bat hartzen du barne.", "model-130-p": "130 eredua hiru hilean behin pertsona fisikoen errentaren gaineko zergaren aitorpena da (IRPF) bertan zerga honen ordainketa zatikatua likidatzen da, hurrengo urtean egiten den urteko aitorpenaren kontura.", "model-130-prefix-help": "Kodeak aurrizki gisa interpretatzen dira. Adibidez, 62 kodeak 62rekin hasten diren azpikontu guztiak hartzen ditu barne. 473 kontua beti barnean prozesatzen da eta ez da zerrenda hauetan agertzen.", - "model-130-prefix-includes": "%code% aurrizkiak ondorengo kontuak ditu kontabilitate-planean", + "model-130-prefix-includes": "%code% aurrizkiak azpikontuak ditu kontabilitate-planean", "model-130-prefix-not-found": "%code% aurrizkiak ez du kontu zehatzik uneko kontabilitate-planean", "model-130-r": "Urteko lehen egunaren eta hautatutako hiruhilekoaren azken egunaren arteko aldirako metatutako datuak.", "model-130-restore-confirm": "Uneko konfigurazioa ezabatu eta instalatutako kontabilitate-planetik berreraikiko da. Jarraitu?", diff --git a/Translation/fr_FR.json b/Translation/fr_FR.json index ad1ebdd..d3f162d 100644 --- a/Translation/fr_FR.json +++ b/Translation/fr_FR.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "Le compte est déjà inclus par une autre règle ou englobe une règle existante.", "model-130-p": "Le formulaire 130 est une déclaration trimestrielle de l'impôt sur le revenu des personnes physiques (IRPF) dans laquelle le paiement de cet impôt est réglé par acomptes, compte tenu de la déclaration annuelle à effectuer l'année suivante.", "model-130-prefix-help": "Les codes sont interprétés comme des préfixes. Par exemple, 62 inclut tous les sous-comptes commençant par 62. Le compte 473 est toujours traité en interne et n'apparaît pas dans ces listes.", - "model-130-prefix-includes": "Préfixe %code% avec des comptes descendants dans le plan comptable", + "model-130-prefix-includes": "Préfixe %code% avec des comptes enfants dans le plan comptable", "model-130-prefix-not-found": "Préfixe %code% sans compte exact dans le plan comptable actuel", "model-130-r": "Données cumulées pour la période comprise entre le premier jour de l'année et le dernier jour du trimestre sélectionné.", "model-130-restore-confirm": "La configuration actuelle sera supprimée et reconstruite à partir du plan comptable installé. Continuer ?", diff --git a/Translation/gl_ES.json b/Translation/gl_ES.json index 2556015..561f3be 100644 --- a/Translation/gl_ES.json +++ b/Translation/gl_ES.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "A conta xa está incluída por outra regra ou engloba unha regra existente.", "model-130-p": "O Modelo 130 é unha declaración trimestral do imposto da renta das personas físicas (IRPF) no que se liquida o pago fraccionado deste imposto, a conta da declaración anual que se realiza no ano seguinte.", "model-130-prefix-help": "Os códigos interprétanse como prefixos. Por exemplo, 62 inclúe todas as subcontas que comezan por 62. A conta 473 procésase sempre internamente e non aparece nestas listas.", - "model-130-prefix-includes": "Prefixo %code% con contas descendentes no plan contable", + "model-130-prefix-includes": "Prefixo %code% con contas fillas no plan contable", "model-130-prefix-not-found": "Prefixo %code% sen unha conta exacta no plan contable actual", "model-130-r": "Datos acumulados do período comprendido entre o primeiro día do ano e o último día do trimestre seleccionado.", "model-130-restore-confirm": "Eliminarase a configuración actual e reconstruirase desde o plan contable instalado. Continuar?", diff --git a/Translation/it_IT.json b/Translation/it_IT.json index 11644b6..a8e998e 100644 --- a/Translation/it_IT.json +++ b/Translation/it_IT.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "Il conto è già incluso da un’altra regola o comprende una regola esistente.", "model-130-p": "Il Modello 130 è una dichiarazione trimestrale dell'imposta sul reddito delle persone fisiche (IRPF) in cui si liquida il pagamento rateale di questa imposta, a titolo di acconto della dichiarazione annuale che verrà effettuata l'anno successivo", "model-130-prefix-help": "I codici vengono interpretati come prefissi. Ad esempio, 62 include tutti i sottoconti che iniziano con 62. Il conto 473 viene sempre elaborato internamente e non compare in questi elenchi.", - "model-130-prefix-includes": "Prefisso %code% con conti discendenti nel piano dei conti", + "model-130-prefix-includes": "Prefisso %code% con conti figli nel piano dei conti", "model-130-prefix-not-found": "Prefisso %code% senza un conto esatto nel piano dei conti corrente", "model-130-r": "Dati accumulati per il periodo compreso tra il primo giorno dell'anno e l'ultimo giorno del trimestre selezionato.", "model-130-restore-confirm": "La configurazione attuale verrà eliminata e ricostruita dal piano dei conti installato. Continuare?", diff --git a/Translation/pt_BR.json b/Translation/pt_BR.json index c710070..59086a3 100644 --- a/Translation/pt_BR.json +++ b/Translation/pt_BR.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "A conta já está incluída por outra regra ou abrange uma regra existente.", "model-130-p": "O Modelo 130 é uma declaração trimestral do imposto sobre a renda das pessoas físicas (IRPF) na qual se liquida o pagamento fracionado deste imposto, a título da declaração anual que é realizada no ano seguinte.", "model-130-prefix-help": "Os códigos são interpretados como prefixos. Por exemplo, 62 inclui todas as subcontas que começam por 62. A conta 473 é sempre processada internamente e não aparece nestas listas.", - "model-130-prefix-includes": "Prefixo %code% com contas descendentes no plano de contas", + "model-130-prefix-includes": "Prefixo %code% com contas filhas no plano de contas", "model-130-prefix-not-found": "Prefixo %code% sem uma conta exata no plano de contas atual", "model-130-r": "Dados acumulados para o período entre o primeiro dia do ano e o último dia do trimestre selecionado.", "model-130-restore-confirm": "A configuração atual será excluída e reconstruída a partir do plano de contas instalado. Continuar?", diff --git a/Translation/pt_PT.json b/Translation/pt_PT.json index bd0fe9a..a9604d3 100644 --- a/Translation/pt_PT.json +++ b/Translation/pt_PT.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "A conta já está incluída por outra regra ou abrange uma regra existente.", "model-130-p": "O Modelo 130 é uma declaração trimestral do imposto sobre o rendimento das pessoas singulares (IRPF) em que se liquida o pagamento fracionado deste imposto, a título da declaração anual que é feita no ano seguinte.", "model-130-prefix-help": "Os códigos são interpretados como prefixos. Por exemplo, 62 inclui todas as subcontas que começam por 62. A conta 473 é sempre processada internamente e não aparece nestas listas.", - "model-130-prefix-includes": "Prefixo %code% com contas descendentes no plano de contas", + "model-130-prefix-includes": "Prefixo %code% com contas filhas no plano de contas", "model-130-prefix-not-found": "Prefixo %code% sem uma conta exata no plano de contas atual", "model-130-r": "Dados acumulados para o período entre o primeiro dia do ano e o último dia do trimestre selecionado.", "model-130-restore-confirm": "A configuração atual será eliminada e reconstruída a partir do plano de contas instalado. Continuar?", diff --git a/Translation/va_ES.json b/Translation/va_ES.json index 599452c..ea1d58f 100644 --- a/Translation/va_ES.json +++ b/Translation/va_ES.json @@ -30,7 +30,7 @@ "model-130-overlapping-prefix": "El compte ja està inclòs per una altra regla o engloba una regla existent.", "model-130-p": "El Modelo 130 es una declaración trimestral del impuesto de la renta de las personas físicas (IRPF) en el que se liquida el pago fraccionado de este impuesto, a cuenta de la declaración anual que se realiza el año siguiente.", "model-130-prefix-help": "Els codis s’interpreten com a prefixos. Per exemple, 62 inclou tots els subcomptes que comencen per 62. El compte 473 es processa sempre internament i no apareix en estes llistes.", - "model-130-prefix-includes": "Prefix %code% amb comptes descendents en el pla comptable", + "model-130-prefix-includes": "Prefix %code% amb comptes fills en el pla comptable", "model-130-prefix-not-found": "Prefix %code% sense un compte exacte en el pla comptable actual", "model-130-r": "Datos acumulados del período comprendido entre el primer día del año y el último día del trimestre seleccionado.", "model-130-restore-confirm": "S’eliminarà la configuració actual i es reconstruirà des del pla comptable instal·lat. Voleu continuar?",