diff --git a/Controller/Modelo130.php b/Controller/Modelo130.php index 76e3752..287a0f6 100644 --- a/Controller/Modelo130.php +++ b/Controller/Modelo130.php @@ -27,9 +27,10 @@ use FacturaScripts\Dinamic\Model\Ejercicio; use FacturaScripts\Dinamic\Model\Empresa; use FacturaScripts\Dinamic\Model\FormaPago; -use FacturaScripts\Dinamic\Model\Subcuenta130; +use FacturaScripts\Dinamic\Model\Mod130Conf; use FacturaScripts\Dinamic\Lib\Modelo130 as DinModelo130; use FacturaScripts\Dinamic\Lib\Modelo130Export as DinModelo130Export; +use FacturaScripts\Dinamic\Lib\Modelo130Config as DinModelo130Config; /** * Description of Modelo130 @@ -48,17 +49,11 @@ class Modelo130 extends Controller public $applyGastosJustificacion = false; /** @var float */ - public $gastosJustificacionPct = 7.0; + public $gastosJustificacionPct = 5.0; /** @var string */ public $codejercicio; - /** @var Subcuenta130 */ - public $deductibleSubaccount; - - /** @var Subcuenta130 */ - public $incomeSubaccount; - /** @var string */ public $period = 'T1'; @@ -84,13 +79,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,10 +94,24 @@ public function getExercise(?string $codejercicio): Ejercicio return $exercise; } - public function getIncomeSubaccounts(): array + + public function getAccountRules(string $type): array { - $where = [Where::eq('tipo', Subcuenta130::TIPO_INGRESO)]; - return (new Subcuenta130())->all($where, ['codsubcuenta' => 'ASC'], 0, 0); + $rules = DinModelo130Config::rules($type); + foreach ($rules as $rule) { + $rule->display_description = DinModelo130Config::description($rule->codigo); + } + return $rules; + } + + public function getExpenseRules(): array + { + return $this->getAccountRules(Mod130Conf::TIPO_GASTO); + } + + public function getIncomeRules(): array + { + return $this->getAccountRules(Mod130Conf::TIPO_INGRESO); } public function getPageData(): array @@ -138,125 +142,77 @@ 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(); + case 'autocomplete-account-prefix': + $this->autocompleteAccountPrefix(); return; - case 'add-deductible-subaccount': - $this->addDeductibleSubaccount(); - return; + case 'add-account-prefix': + $this->addAccountPrefix(); + break; - case 'delete-deductible-subaccount': - $this->deleteDeductibleSubaccount(); - return; - - case 'add-income-subaccount': - $this->addIncomeSubaccount(); - return; + case 'delete-account-prefix': + $this->deleteAccountPrefix(); + break; - case 'delete-income-subaccount': - $this->deleteIncomeSubaccount(); - return; + case 'restore-account-prefixes': + $this->restoreAccountPrefixes(); + break; case 'gen-accounting': $this->createAccountingEntry(); return; } + DinModelo130Config::ensureDefaults(); + $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', + 5.0 + ); + + $this->result = DinModelo130::generate( + $this->codejercicio, + $this->period, + $this->applyGastosJustificacion, + $this->todeduct, + $this->gastosJustificacionPct + ); if ($action === 'download') { $this->downloadFile($response); } } - protected function downloadFile(Response $response): void - { - if (empty($this->result)) { - Tools::log()->warning('no-data'); - return; - } - - $empresa = new Empresa(); - if (false === $empresa->load($this->result['exercise']->idempresa)) { - Tools::log()->error('company-not-found'); - return; - } - - // validamos los datos mínimos para no generar un fichero corrupto - $nif = DinModelo130Export::formatNif($empresa->cifnif ?? ''); - if (strlen($nif) !== 9) { - Tools::log()->error('aeat-file-invalid-nif'); - return; - } - - if (empty(trim($empresa->nombre ?? ''))) { - Tools::log()->error('aeat-file-missing-company-name'); - return; - } - - $year = date('Y', strtotime($this->result['exercise']->fechainicio)); - if (strlen($year) !== 4) { - Tools::log()->error('aeat-file-invalid-exercise'); - return; - } - - $content = DinModelo130Export::generate($this->result, $empresa, $this->period, $year); - if (strlen($content) !== DinModelo130Export::FILE_LENGTH) { - Tools::log()->error('aeat-file-invalid-length'); - return; - } - $filename = 'modelo130_' . $year . '_' . DinModelo130Export::getPeriodNumber($this->period) . '.txt'; - - $response->headers->set('Content-Type', 'text/plain; charset=ISO-8859-1'); - $response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"'); - $response->setContent(mb_convert_encoding($content, 'ISO-8859-1', 'UTF-8')); - $response->send(); - exit; - } - - protected function addDeductibleSubaccount(): void + protected function addAccountPrefix(): void { - $this->activeTab = 'deductible-subaccount'; + $type = (string) $this->request->request->get('tipo'); + $this->activeTab = $type === Mod130Conf::TIPO_INGRESO ? 'income-accounts' : 'expense-accounts'; if (false === $this->validateFormToken()) { return; } - $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()) { + $code = trim((string) $this->request->request->get('codigo')); + if (DinModelo130Config::hasOverlap($code)) { + Tools::log()->warning('model-130-overlapping-prefix'); return; } - $subaccount = new Subcuenta130(); - $subaccount->codsubcuenta = $this->request->request->get('codsubcuenta'); - $subaccount->tipo = Subcuenta130::TIPO_INGRESO; - if (false === $subaccount->save()) { + $rule = new Mod130Conf(); + $rule->codigo = $code; + $rule->tipo = $type; + if (false === $rule->save()) { Tools::log()->error('record-save-error'); return; } @@ -264,18 +220,31 @@ protected function addIncomeSubaccount(): void Tools::log()->notice('record-updated-correctly'); } - protected function autocompleteSubaccount(): void + protected function autocompleteAccountPrefix(): void { $this->setTemplate(false); - $list = []; - $term = $this->request->get('term'); - $sql = 'SELECT DISTINCT codsubcuenta, descripcion FROM subcuentas WHERE codsubcuenta LIKE "' . $term . '%";'; + $type = (string) $this->request->get('tipo'); + $firstDigit = $type === Mod130Conf::TIPO_INGRESO ? '7' : '6'; + $term = trim((string) $this->request->get('term')); + $safeTerm = $this->dataBase->var2str($term . '%'); + $safeGroup = $this->dataBase->var2str($firstDigit . '%'); - foreach ($this->dataBase->select($sql) as $value) { + $sql = 'SELECT DISTINCT codcuenta, descripcion FROM cuentas' + . ' WHERE codcuenta LIKE ' . $safeGroup + . ' AND (codcuenta LIKE ' . $safeTerm + . ' OR descripcion LIKE ' . $this->dataBase->var2str('%' . $term . '%') . ')' + . ' ORDER BY codcuenta ASC'; + + $list = []; + foreach ($this->dataBase->selectLimit($sql, 50) as $row) { + $code = trim((string) $row['codcuenta']); + if ($code === '' || !ctype_digit($code)) { + continue; + } $list[] = [ - 'key' => Tools::fixHtml($value['codsubcuenta']), - 'value' => Tools::fixHtml($value['descripcion']) + 'key' => Tools::fixHtml($code), + 'value' => Tools::fixHtml($code . ' · ' . $row['descripcion']) ]; } @@ -286,65 +255,134 @@ protected function autocompleteSubaccount(): void $this->response->setContent(json_encode($list)); } - protected function createAccountingEntry(): void + protected function deleteAccountPrefix(): void { + $type = (string) $this->request->request->get('tipo'); + $this->activeTab = $type === Mod130Conf::TIPO_INGRESO ? 'income-accounts' : 'expense-accounts'; + if (false === $this->validateFormToken()) { return; } - $idempresa = (int)$this->request->request->get('idempresa'); - $codejercicio = (string)$this->request->request->get('codejercicio'); - $period = (string)$this->request->request->get('period'); - $date = (string)$this->request->request->get('date'); - $amount = (float)$this->request->request->get('amount'); - $paymentMethodId = (int)$this->request->request->get('paymentMethod'); + $rule = new Mod130Conf(); + if (false === $rule->load((int) $this->request->request->get('id'))) { + Tools::log()->error('record-not-found'); + return; + } - if (DinModelo130::generateEntries($idempresa, $codejercicio, $period, $date, $amount, $paymentMethodId)) { - Tools::log()->notice('record-updated-correctly'); + if (false === $rule->delete()) { + Tools::log()->error('record-deleted-error'); + return; } + + Tools::log()->notice('record-deleted-correctly'); } - protected function deleteDeductibleSubaccount(): void + protected function restoreAccountPrefixes(): void { - $this->activeTab = 'deductible-subaccount'; - + $this->activeTab = (string) $this->request->request->get('tab', 'expense-accounts'); if (false === $this->validateFormToken()) { return; } - $subaccount130 = new Subcuenta130(); - if (false === $subaccount130->load($this->request->request->get('id'))) { - Tools::log()->error('record-not-found'); + if (DinModelo130Config::restoreDefaults()) { + Tools::log()->notice('model-130-defaults-restored'); return; } + Tools::log()->error('record-save-error'); + } - if (false === $subaccount130->delete()) { - Tools::log()->error('record-deleted-error'); + protected function downloadFile(Response $response): void + { + if (empty($this->result)) { + Tools::log()->warning('no-data'); return; } - Tools::log()->notice('record-deleted-correctly'); - } + $empresa = new Empresa(); + if (false === $empresa->load($this->result['exercise']->idempresa)) { + Tools::log()->error('company-not-found'); + return; + } - protected function deleteIncomeSubaccount(): void - { - $this->activeTab = 'income-subaccount'; + // validamos los datos mínimos para no generar un fichero corrupto + $nif = DinModelo130Export::formatNif($empresa->cifnif ?? ''); + if (strlen($nif) !== 9) { + Tools::log()->error('aeat-file-invalid-nif'); + return; + } - if (false === $this->validateFormToken()) { + if (empty(trim($empresa->nombre ?? ''))) { + Tools::log()->error('aeat-file-missing-company-name'); return; } - $subaccount = new Subcuenta130(); - if (false === $subaccount->load($this->request->request->get('id'))) { - Tools::log()->error('record-not-found'); + $year = date('Y', strtotime($this->result['exercise']->fechainicio)); + if (strlen($year) !== 4) { + Tools::log()->error('aeat-file-invalid-exercise'); return; } - if (false === $subaccount->delete()) { - Tools::log()->error('record-deleted-error'); + $content = DinModelo130Export::generate( + $this->result, + $empresa, + $this->period, + $year + ); + + if (strlen($content) !== DinModelo130Export::FILE_LENGTH) { + Tools::log()->error('aeat-file-invalid-length'); return; } - Tools::log()->notice('record-deleted-correctly'); + $filename = 'modelo130_' + . $year + . '_' + . DinModelo130Export::getPeriodNumber($this->period) + . '.txt'; + + $response->headers->set( + 'Content-Type', + 'text/plain; charset=ISO-8859-1' + ); + + $response->headers->set( + 'Content-Disposition', + 'attachment; filename="' . $filename . '"' + ); + + $response->setContent( + mb_convert_encoding($content, 'ISO-8859-1', 'UTF-8') + ); + + $response->send(); + exit; + } + + protected function createAccountingEntry(): void + { + if (false === $this->validateFormToken()) { + return; + } + + $idempresa = (int)$this->request->request->get('idempresa'); + $codejercicio = (string)$this->request->request->get('codejercicio'); + $period = (string)$this->request->request->get('period'); + $date = (string)$this->request->request->get('date'); + $amount = (float)$this->request->request->get('amount'); + $paymentMethodId = (int)$this->request->request->get('paymentMethod'); + + if ( + DinModelo130::generateEntries( + $idempresa, + $codejercicio, + $period, + $date, + $amount, + $paymentMethodId + ) + ) { + Tools::log()->notice('record-updated-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..41f5c79 100644 --- a/Init.php +++ b/Init.php @@ -18,8 +18,11 @@ namespace FacturaScripts\Plugins\Modelo130; -use FacturaScripts\Core\Base\DataBase; use FacturaScripts\Core\Template\InitClass; +use FacturaScripts\Plugins\Modelo130\Lib\Modelo130Config; + +use FacturaScripts\Core\Migrations; +use FacturaScripts\Plugins\Modelo130\Migration\MigrateV5; final class Init extends InitClass { @@ -33,33 +36,7 @@ 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); - } + Migrations::runPluginMigration(new MigrateV5()); + Modelo130Config::ensureDefaults(); } -} +} \ No newline at end of file diff --git a/Lib/Modelo130.php b/Lib/Modelo130.php index b84b556..b3c19b9 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,21 +70,47 @@ 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; + + /** @var Asiento|null */ + protected static $currentPaymentEntry; public static function generate( string $codejercicio, 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)) { @@ -82,45 +119,65 @@ 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::$currentPaymentEntry = null; 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, + 'currentPaymentEntry' => static::$currentPaymentEntry, + 'currentPaymentEntryExists' => null !== static::$currentPaymentEntry, '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,52 +186,129 @@ public static function generateEntries(int $idempresa, string $codejercicio, str $asiento->concepto = $concepto; $asiento->fecha = $date; $asiento->importe = $amount; + if (false === $asiento->save()) { return false; } $partida1 = new Partida(); $partida1->idasiento = $asiento->idasiento; - $partida1->concepto = $asiento->concepto; + $partida1->concepto = Tools::trans('acc-concept-irpf-130-lines'); $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(); } $partida2 = new Partida(); $partida2->idasiento = $asiento->idasiento; - $partida2->concepto = $asiento->concepto; + $partida2->concepto = Tools::trans('acc-concept-irpf-130-lines'); $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 = 5.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 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 calcFractionalPayment( + float $afterdeduct, + float $taxbaseRetenciones, + float $positivosTrimestres + ): float { + 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 + ): string { if (null === $value) { return $field . ' IS NULL'; } @@ -182,229 +316,569 @@ 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); - } - - protected static function loadAsientos(): void - { - $codsubs = static::getAccountingEntrySubaccounts(); - - if (empty($codsubs)) { + + if (empty($conditions)) { 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'; - + + $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 = []; + + /** + * 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) { - static::$accountingEntries[] = new Partida($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 = 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. + * + * 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($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 + ); + + $currentPaymentConcept = Tools::trans( + 'acc-concept-irpf-130', + ['%period%' => static::$period] + ); + + foreach ($groups as $idasiento => $group) { + $entry = $accountingEntries[$idasiento] ?? null; + + if (null === $entry) { + continue; + } + + $isCurrentPaymentEntry = $entry->concepto === $currentPaymentConcept; + if ($isCurrentPaymentEntry) { + static::$currentPaymentEntry = $entry; + } + + $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; + * - 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']; + } 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. + * + * 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']; + + 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 = 5.0 + ): array { + $taxbase = round( + static::$taxbaseIncomes + - static::$taxbaseExpenses, + 2 + ); + + $gastosJustificacion = static::calcGastosJustificacion( + $taxbase, + $applyGastosJustificacion, + $gastosJustificacionPct + ); + + $afterdeduct = static::calcAfterDeduct( + $taxbase, + $gastosJustificacion, + $todeduct + ); + + $fractionalPayment = static::calcFractionalPayment( + $afterdeduct, + static::$taxbaseRetentions, + static::$previousPayments + ); + + $result = static::calcResult($fractionalPayment); 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, + 'fractionalPayment' => $fractionalPayment, '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..e6b7f5a --- /dev/null +++ b/Lib/Modelo130Accounts.php @@ -0,0 +1,197 @@ + + * + * 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. + * + * 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 +{ + /** + * 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'; + + /** @return string[] */ + public static function expenses(): array + { + Modelo130Config::ensureDefaults(); + return Modelo130Config::prefixes(\FacturaScripts\Dinamic\Model\Mod130Conf::TIPO_GASTO); + } + + /** @return string[] */ + public static function incomes(): array + { + Modelo130Config::ensureDefaults(); + return Modelo130Config::prefixes(\FacturaScripts\Dinamic\Model\Mod130Conf::TIPO_INGRESO); + } + + /** + * 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 + { + $code = trim($code); + + if ($code === '' || static::isStockVariation($code)) { + return false; + } + + return static::matches($code, 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 === '' || static::isStockVariation($code)) { + return false; + } + + return static::matches($code, 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. + */ + 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( + static::expenses(), + 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 + { + if ($code === '') { + return false; + } + + foreach ($prefixes as $prefix) { + if (str_starts_with($code, $prefix)) { + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/Lib/Modelo130Config.php b/Lib/Modelo130Config.php new file mode 100644 index 0000000..b3cbd6d --- /dev/null +++ b/Lib/Modelo130Config.php @@ -0,0 +1,249 @@ + [ + '60', // Compras. + '61', // Variación de existencias. + '62', // Servicios exteriores. + '631', // Otros tributos. + '634', // Ajustes negativos en la imposición indirecta. + '636', // Devolución de impuestos. + '639', // Ajustes positivos en la imposición indirecta. + '64', // Gastos de personal. + '65', // Otros gastos de gestión. + '66', // Gastos financieros. + '675', // Pérdidas por operaciones con obligaciones propias. + '678', // Gastos excepcionales. + '68', // Dotaciones para amortizaciones. + '693', // Pérdidas por deterioro de existencias. + '694', // Pérdidas por deterioro de créditos por operaciones comerciales. + '695', // Dotación a la provisión por operaciones comerciales. + '697', // Pérdidas por deterioro de créditos a largo plazo. + '699', // Pérdidas por deterioro de créditos a corto plazo. + ], + + Mod130Conf::TIPO_INGRESO => [ + '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. + '778', // Ingresos excepcionales. + '793', // Reversión del deterioro de existencias. + '794', // Reversión del deterioro de créditos por operaciones comerciales. + ], + ]; + + public static function rules(string $type): array + { + if (!in_array( + $type, + [Mod130Conf::TIPO_GASTO, Mod130Conf::TIPO_INGRESO], + true + )) { + return []; + } + + return (new Mod130Conf())->all( + [Where::eq('tipo', $type)], + ['codigo' => 'ASC'], + 0, + 0 + ); + } + + public static function prefixes(string $type): array + { + $result = []; + + foreach (self::rules($type) as $rule) { + if (self::isValidRule($rule)) { + $result[] = $rule->codigo; + } + } + + return array_values(array_unique($result)); + } + + public static function ensureDefaults(): void + { + if ((new Mod130Conf())->count() === 0) { + self::restoreDefaults(); + } + } + + public static function restoreDefaults(): bool + { + foreach ((new Mod130Conf())->all([], [], 0, 0) as $rule) { + if (!$rule->delete()) { + return false; + } + } + + foreach (self::buildDefaultRules() as $type => $codes) { + foreach ($codes as $code) { + $rule = new Mod130Conf(); + $rule->codigo = $code; + $rule->tipo = $type; + + if (!$rule->save()) { + return false; + } + } + } + + return true; + } + + public static function buildDefaultRules(): array + { + $available = self::availableAccounts(); + + $result = [ + Mod130Conf::TIPO_GASTO => [], + Mod130Conf::TIPO_INGRESO => [], + ]; + + foreach (self::DEFAULTS as $type => $codes) { + foreach ($codes as $code) { + /* + * DEFAULTS contiene prefijos. + * + * Se añade el código cuando existe la cuenta exacta o alguna + * cuenta hija en el plan contable instalado. + * + * Ejemplo: 697 se añade cuando existe 6970, aunque no exista + * una cuenta cuyo código sea exactamente 697. + */ + if (self::prefixExists($code, $available)) { + $result[$type][] = $code; + } + } + + $result[$type] = array_values(array_unique($result[$type])); + sort($result[$type]); + } + + return $result; + } + + public static function description(string $code): string + { + $code = trim($code); + + $account = new Cuenta(); + + if ($account->loadWhere([Where::eq('codcuenta', $code)])) { + return (string) $account->descripcion; + } + + /* + * Las reglas pueden no corresponder con una cuenta exacta, pero sí + * actuar como prefijo de cuentas hijas. + */ + foreach ((new Cuenta())->all([], ['codcuenta' => 'ASC'], 0, 0) as $candidate) { + if (str_starts_with((string) $candidate->codcuenta, $code)) { + return Tools::lang()->trans( + 'model-130-prefix-includes', + ['%code%' => $code] + ); + } + } + + return Tools::lang()->trans( + 'model-130-prefix-not-found', + ['%code%' => $code] + ); + } + + public static function hasOverlap(string $code, ?int $excludeId = null): bool + { + $code = trim($code); + + foreach ((new Mod130Conf())->all([], [], 0, 0) as $rule) { + /* + * Al editar una regla no debe detectarse a sí misma como + * solapamiento. + */ + if ($excludeId !== null && (int) $rule->id === $excludeId) { + continue; + } + + if ( + str_starts_with($code, (string) $rule->codigo) + || str_starts_with((string) $rule->codigo, $code) + ) { + return true; + } + } + + return false; + } + + /** + * Devuelve exclusivamente las cuentas que existen realmente en Cuenta. + * + * La comprobación de prefijos se realiza posteriormente mediante prefixExists(). + */ + private static function availableAccounts(): array + { + $result = []; + + foreach ((new Cuenta())->all([], ['codcuenta' => 'ASC'], 0, 0) as $account) { + $code = trim((string) $account->codcuenta); + + if ($code === '' || !ctype_digit($code)) { + continue; + } + + $result[$code] = (string) $account->descripcion; + } + + return $result; + } + + /** + * Comprueba si existe la cuenta exacta o alguna cuenta hija. + */ + private static function prefixExists(string $prefix, array $available): bool + { + foreach (array_keys($available) as $code) { + if (str_starts_with($code, $prefix)) { + return true; + } + } + + return false; + } + + private static function isValidRule(Mod130Conf $rule): bool + { + return ( + $rule->tipo === Mod130Conf::TIPO_GASTO + && str_starts_with((string) $rule->codigo, '6') + ) || ( + $rule->tipo === Mod130Conf::TIPO_INGRESO + && str_starts_with((string) $rule->codigo, '7') + ); + } +} \ No newline at end of file diff --git a/Migration/MigrateV5.php b/Migration/MigrateV5.php new file mode 100644 index 0000000..7ebcb4c --- /dev/null +++ b/Migration/MigrateV5.php @@ -0,0 +1,301 @@ + + * + * 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\Migration; + +use FacturaScripts\Core\DbUpdater; +use FacturaScripts\Core\Template\MigrationClass; +use FacturaScripts\Core\Tools; +use FacturaScripts\Dinamic\Model\Mod130Conf; +use RuntimeException; + +/** + * Migra la configuración de subcuentas de la versión 4 (subcuentas_130) + * al nuevo formato de prefijos (mod130_conf) de la versión 5. + * + * Asimismo, actualiza los conceptos de los asientos de regularización al nuevo formato para que su + * categorización sea consistente con el nuevo controlador y se visualice la información más coherente. + */ +class MigrateV5 extends MigrationClass +{ + /** + * Nombre completo registrado: + * Modelo130::migrate_v5 + */ + const MIGRATION_NAME = 'migrate_v5'; + + private const LEGACY_TABLE = 'subcuentas_130'; + + /** @var array */ + private const LEGACY_DEFAULTS = [ + '4730000000' => 'deducible', + '6420000000' => 'deducible', + ]; + + private const LEGACY_TIPO_DEDUCIBLE = 'deducible'; + private const LEGACY_TIPO_INGRESO = 'ingreso'; + + public function run(): void + { + $this->migrateAsientoConcepts(); + + /** + * Si no existe subcuentas_130, el usuario ya está en el sistema + * nuevo o es una instalación nueva. No hacemos nada. + */ + if (!$this->db()->tableExists(self::LEGACY_TABLE)) { + return; + } + + $rows = $this->db()->select( + 'SELECT codsubcuenta, tipo FROM ' . self::LEGACY_TABLE + ); + + /** + * Si no había configuración o únicamente estaban los defaults + * antiguos, no hay personalización que conservar. + * + * Eliminamos la tabla antigua y Modelo130Config::ensureDefaults() + * cargará posteriormente los defaults actuales. + */ + if (empty($rows) || $this->isOnlyDefaultConfig($rows)) { + $this->dropLegacyTable(); + return; + } + + /** + * Existe configuración personalizada antigua en subcuentas_130: + * creamos/comprobamos mod130_conf, la dejamos vacía y volcamos + * la configuración antigua convertida al nuevo formato. + */ + $this->ensureTargetTable(); + $this->clearCurrentConfig(); + $this->migrateCustomConfig($rows); + + /** + * La tabla antigua solo se elimina cuando la migración + * ha terminado correctamente. + */ + $this->dropLegacyTable(); + } + + /** + * @param array> $rows + */ + private function isOnlyDefaultConfig(array $rows): bool + { + if (count($rows) !== count(self::LEGACY_DEFAULTS)) { + return false; + } + + $found = []; + + foreach ($rows as $row) { + $code = trim((string) ($row['codsubcuenta'] ?? '')); + $tipo = trim((string) ($row['tipo'] ?? '')); + $found[$code] = $tipo; + } + + + $defaults = self::LEGACY_DEFAULTS; + + ksort($found); + ksort($defaults); + + return $found === $defaults; + } + + /** + * Asegura que mod130_conf exista antes de insertar. + */ + private function ensureTargetTable(): void + { + if (!$this->db()->tableExists(Mod130Conf::tableName())) { + /** + * Limpiamos la caché de tablas comprobadas antes de + * instanciar el modelo. + */ + DbUpdater::rebuild(); + + /** + * El constructor del modelo crea/comprueba su tabla. + */ + new Mod130Conf(); + } + + if (!$this->db()->tableExists(Mod130Conf::tableName())) { + throw new RuntimeException( + 'No se pudo crear la tabla ' . Mod130Conf::tableName() + ); + } + } + + /** + * Dejamos mod130_conf vacía antes de volcar la configuración legacy. + * + * Mientras exista subcuentas_130, la configuración legacy es la que debe priorizarse. + */ + private function clearCurrentConfig(): void + { + foreach ((new Mod130Conf())->all([], [], 0, 0) as $rule) { + if (!$rule->delete()) { + throw new RuntimeException( + 'No se pudo vaciar la tabla ' . Mod130Conf::tableName() + ); + } + } + } + + /** + * @param array> $rows + */ + private function migrateCustomConfig(array $rows): void + { + /** + * Toda configuración personalizada anterior, siempre + * partía de los prefijos generales de gastos e ingresos. + */ + $rules = [ + Mod130Conf::TIPO_GASTO => ['60'], + Mod130Conf::TIPO_INGRESO => ['70'], + ]; + + foreach ($rows as $row) { + $code = trim((string) ($row['codsubcuenta'] ?? '')); + + /** + * La 473 ya se trata internamente y no debe convertirse + * en una regla configurable. + */ + if ($code === '' || str_starts_with($code, '473')) { + continue; + } + + $prefix = self::extractPrefix($code); + $tipo = $this->mapLegacyType((string) ($row['tipo'] ?? '')); + + if ( + $tipo === null + || !self::isValidPrefixForType($prefix, $tipo) + ) { + continue; + } + + $rules[$tipo][] = $prefix; + } + + foreach ($rules as $tipo => $codes) { + foreach (array_values(array_unique($codes)) as $code) { + $this->insertRule($code, $tipo); + } + } + } + + private function insertRule(string $code, string $tipo): void + { + $sql = 'INSERT INTO ' . Mod130Conf::tableName() + . ' (codigo, tipo, creation_date) VALUES (' + . $this->db()->var2str($code) . ', ' + . $this->db()->var2str($tipo) . ', ' + . $this->db()->var2str(Tools::dateTime()) . ');'; + + if (!$this->db()->exec($sql)) { + throw new RuntimeException( + 'No se pudo migrar la regla ' + . $code + . ' (' + . $tipo + . ')' + ); + } + } + + private function mapLegacyType(string $tipo): ?string + { + return match (trim($tipo)) { + self::LEGACY_TIPO_DEDUCIBLE => Mod130Conf::TIPO_GASTO, + self::LEGACY_TIPO_INGRESO => Mod130Conf::TIPO_INGRESO, + default => null, + }; + } + + private static function extractPrefix(string $codsubcuenta): string + { + $code = trim($codsubcuenta); + $prefix = rtrim($code, '0'); + + return $prefix === '' ? $code : $prefix; + } + + private static function isValidPrefixForType( + string $prefix, + string $tipo + ): bool { + if ($tipo === Mod130Conf::TIPO_GASTO) { + return str_starts_with($prefix, '6'); + } + + if ($tipo === Mod130Conf::TIPO_INGRESO) { + return str_starts_with($prefix, '7'); + } + + return false; + } + + /** + * Modificamos los conceptos de los asientos (en español) para que sean consistentes con la nueva configuración. + * + * La misma se ha realizado para compatibilizarse con laS configuraciones por defecto de AsientosPredefinidos. + */ + + private function migrateAsientoConcepts(): void + { + if (!$this->db()->tableExists('asientos')) { + return; + } + + for ($trimestre = 1; $trimestre <= 4; $trimestre++) { + $oldConcepto = 'Regularización de IRPF T' . $trimestre; + $newConcepto = 'Pago fraccionado IRPF T' . $trimestre; + + $sql = 'UPDATE asientos SET concepto = ' + . $this->db()->var2str($newConcepto) + . ' WHERE concepto = ' + . $this->db()->var2str($oldConcepto) + . ';'; + + if (!$this->db()->exec($sql)) { + throw new RuntimeException( + 'No se pudo actualizar el concepto del asiento ' + . $oldConcepto + ); + } + } + } + + private function dropLegacyTable(): void + { + if (!$this->db()->exec('DROP TABLE ' . self::LEGACY_TABLE)) { + throw new RuntimeException( + 'No se pudo eliminar la tabla antigua ' + . self::LEGACY_TABLE + ); + } + } +} \ No newline at end of file diff --git a/Model/Mod130Conf.php b/Model/Mod130Conf.php new file mode 100644 index 0000000..85a1e10 --- /dev/null +++ b/Model/Mod130Conf.php @@ -0,0 +1,80 @@ +tipo = self::TIPO_GASTO; + } + + public function install(): string + { + new User(); + return parent::install(); + } + + public static function tableName(): string + { + return 'mod130_conf'; + } + + public function user(): ?User + { + return $this->belongsTo(User::class, 'nick'); + } + + public function test(): bool + { + $this->codigo = trim(Tools::noHtml((string) $this->codigo)); + $this->tipo = trim(Tools::noHtml((string) $this->tipo)); + + if ($this->codigo === '' || !ctype_digit($this->codigo) || strlen($this->codigo) > 15) { + Tools::log()->warning('model-130-invalid-account-prefix'); + return false; + } + + if (!in_array($this->tipo, [self::TIPO_GASTO, self::TIPO_INGRESO], true)) { + Tools::log()->warning('model-130-invalid-account-type'); + return false; + } + + if ($this->tipo === self::TIPO_GASTO && !str_starts_with($this->codigo, '6')) { + Tools::log()->warning('model-130-expense-must-start-with-6'); + return false; + } + + if ($this->tipo === self::TIPO_INGRESO && !str_starts_with($this->codigo, '7')) { + Tools::log()->warning('model-130-income-must-start-with-7'); + return false; + } + + if (empty($this->id())) { + $this->creation_date = Tools::dateTime(); + $this->nick = Session::user()->nick; + } else { + $this->creation_date = $this->creation_date ?? Tools::dateTime(); + $this->nick = $this->nick ?? Session::user()->nick; + } + + return parent::test(); + } +} diff --git a/Model/Subcuenta130.php b/Model/Subcuenta130.php deleted file mode 100644 index c3757e7..0000000 --- a/Model/Subcuenta130.php +++ /dev/null @@ -1,122 +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; - -/** - * Relación entre una subcuenta contable y su tipo para el modelo 130 - */ -class Subcuenta130 extends ModelClass -{ - use ModelTrait; - - const TIPO_DEDUCIBLE = 'deducible'; - const TIPO_INGRESO = 'ingreso'; - - /** @var string código de la subcuenta contable asociada */ - public $codsubcuenta; - - /** @var string fecha de creación del registro */ - public $creation_date; - - /** @var int clave primaria */ - public $id; - - /** @var string nick del usuario que realizó la última modificación */ - public $last_nick; - - /** @var string fecha de la última modificación del registro */ - public $last_update; - - /** @var string nombre descriptivo de la subcuenta */ - public $name; - - /** @var string nick del usuario que creó el registro */ - public $nick; - - /** @var string tipo de subcuenta: deducible o ingreso */ - 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/mod130_conf.xml similarity index 51% rename from Table/subcuentas_130.xml rename to Table/mod130_conf.xml index 0bd5775..88dac6e 100644 --- a/Table/subcuentas_130.xml +++ b/Table/mod130_conf.xml @@ -1,55 +1,45 @@ - - codsubcuenta - character varying(15) - NO - - - creation_date - timestamp - id serial NO + - last_nick - character varying(50) + codigo + character varying(15) + NO + - last_update - timestamp + tipo + character varying(20) + NO + - name - character varying(100) + creation_date + timestamp + nick character varying(50) - - tipo - character varying(20) - 'deducible' - NO - + - subcuentas_130_pkey + mod130_conf_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 + ca_mod130_conf_users_nick FOREIGN KEY (nick) REFERENCES users (nick) ON DELETE SET NULL ON UPDATE CASCADE + - uniq_subcuentas_130 - UNIQUE (codsubcuenta) + uniq_mod130_conf_codigo + UNIQUE (codigo) -
\ No newline at end of file + diff --git a/Test/main/MigrateV5Test.php b/Test/main/MigrateV5Test.php new file mode 100644 index 0000000..0407af3 --- /dev/null +++ b/Test/main/MigrateV5Test.php @@ -0,0 +1,262 @@ + + * + * 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\Base\DataBase; +use FacturaScripts\Dinamic\Model\Mod130Conf; +use FacturaScripts\Plugins\Modelo130\Lib\Modelo130Config; +use FacturaScripts\Plugins\Modelo130\Migration\MigrateV5; +use FacturaScripts\Test\Traits\DefaultSettingsTrait; +use FacturaScripts\Test\Traits\LogErrorsTrait; +use PHPUnit\Framework\TestCase; + +/** + * Pruebas de la migración de subcuentas_130 a mod130_conf. + */ +final class MigrateV5Test extends TestCase +{ + use DefaultSettingsTrait; + use LogErrorsTrait; + + private const LEGACY_TABLE = 'subcuentas_130'; + + public static function setUpBeforeClass(): void + { + self::setDefaultSettings(); + self::installAccountingPlan(); + } + + protected function tearDown(): void + { + $this->dropLegacyTable(); + $this->clearConfig(); + $this->logErrors(); + } + + /** + * Si no existe la tabla antigua, la migración no debe alterar la configuración. + */ + public function testMigrationDoesNothingWithoutLegacyTable(): void + { + $this->dropLegacyTable(); + $this->clearConfig(); + + $defaults = Modelo130Config::buildDefaultRules(); + $this->seedConfig($defaults); + + (new MigrateV5())->run(); + + $this->assertFalse($this->db()->tableExists(self::LEGACY_TABLE)); + $this->assertSame( + $this->normalizeRules($defaults), + $this->currentRules() + ); + } + + /** + * Con solo las subcuentas por defecto de la v4, se elimina la tabla antigua + * y se mantiene la configuración para que ensureDefaults la rellene. + */ + public function testMigrationWithOnlyLegacyDefaults(): void + { + $this->clearConfig(); + $this->createLegacyTable(); + $this->insertLegacyRow('4730000000', 'deducible'); + $this->insertLegacyRow('6420000000', 'deducible'); + + (new MigrateV5())->run(); + + $this->assertFalse($this->db()->tableExists(self::LEGACY_TABLE)); + $this->assertSame(0, (new Mod130Conf())->count()); + + Modelo130Config::ensureDefaults(); + $this->assertNotEmpty((new Mod130Conf())->all([], [], 0, 0)); + } + + /** + * Con configuración personalizada, se migran los prefijos conservando 60/70 + * y excluyendo la cuenta 473 del core. + */ + public function testMigrationWithCustomLegacyConfig(): void + { + if ($this->db()->tableExists(Mod130Conf::tableName())) { + $this->db()->exec('DROP TABLE ' . Mod130Conf::tableName()); + } + \FacturaScripts\Core\DbUpdater::rebuild(); + + $this->createLegacyTable(); + $this->insertLegacyRow('4730000000', 'deducible'); + $this->insertLegacyRow('6420000000', 'deducible'); + $this->insertLegacyRow('7550000000', 'ingreso'); + + (new MigrateV5())->run(); + + $this->assertFalse($this->db()->tableExists(self::LEGACY_TABLE)); + $this->assertSame( + [ + Mod130Conf::TIPO_GASTO => ['60', '642'], + Mod130Conf::TIPO_INGRESO => ['70', '755'], + ], + $this->currentRules() + ); + } + + + /** + * Actualiza el concepto antiguo de los asientos al nuevo formato. + */ + public function testMigrationUpdatesAsientoConcept(): void + { + $ejercicios = $this->db()->select( + 'SELECT idempresa, codejercicio, fechainicio FROM ejercicios LIMIT 1' + ); + $this->assertNotEmpty($ejercicios); + + $asiento = new \FacturaScripts\Dinamic\Model\Asiento(); + $asiento->idempresa = $ejercicios[0]['idempresa']; + $asiento->codejercicio = $ejercicios[0]['codejercicio']; + $asiento->concepto = 'Regularización de IRPF T1'; + $asiento->fecha = $ejercicios[0]['fechainicio']; + $asiento->importe = 100.0; + + $this->assertTrue($asiento->save()); + + (new MigrateV5())->run(); + + $updated = new \FacturaScripts\Dinamic\Model\Asiento(); + $this->assertTrue($updated->load($asiento->idasiento)); + $this->assertSame( + 'Pago fraccionado IRPF T1', + $updated->concepto + ); + + $this->assertTrue($updated->delete()); + } + + /** + * @param array $rules + */ + private function seedConfig(array $rules): void + { + foreach ($rules as $tipo => $codes) { + foreach ($codes as $code) { + $rule = new Mod130Conf(); + $rule->codigo = $code; + $rule->tipo = $tipo; + $this->assertTrue($rule->save()); + } + } + } + + private function clearConfig(): void + { + foreach ((new Mod130Conf())->all([], [], 0, 0) as $rule) { + $this->assertTrue($rule->delete()); + } + } + + private function createLegacyTable(): void + { + $this->dropLegacyTable(); + + $sql = 'CREATE TABLE ' . self::LEGACY_TABLE . ' (' + . 'id serial NOT NULL,' + . 'codsubcuenta character varying(15) NOT NULL,' + . 'tipo character varying(20) NOT NULL DEFAULT \'deducible\',' + . 'creation_date timestamp,' + . 'nick character varying(50),' + . 'last_nick character varying(50),' + . 'last_update timestamp,' + . 'name character varying(100),' + . 'PRIMARY KEY (id),' + . 'UNIQUE (codsubcuenta)' + . ');'; + + $this->assertTrue($this->db()->exec($sql)); + } + + private function dropLegacyTable(): void + { + if ($this->db()->tableExists(self::LEGACY_TABLE)) { + $this->db()->exec('DROP TABLE ' . self::LEGACY_TABLE); + } + } + + private function insertLegacyRow(string $codsubcuenta, string $tipo): void + { + $sql = 'INSERT INTO ' . self::LEGACY_TABLE + . ' (codsubcuenta, tipo) VALUES (' + . $this->db()->var2str($codsubcuenta) . ', ' + . $this->db()->var2str($tipo) . ');'; + + $this->assertTrue($this->db()->exec($sql)); + } + + /** + * @return array + */ + private function currentRules(): array + { + $result = [ + Mod130Conf::TIPO_GASTO => [], + Mod130Conf::TIPO_INGRESO => [], + ]; + + foreach ((new Mod130Conf())->all([], ['codigo' => 'ASC'], 0, 0) as $rule) { + $result[(string) $rule->tipo][] = (string) $rule->codigo; + } + + sort($result[Mod130Conf::TIPO_GASTO]); + sort($result[Mod130Conf::TIPO_INGRESO]); + + return $result; + } + + /** + * @param array $rules + * + * @return array + */ + private function normalizeRules(array $rules): array + { + $result = [ + Mod130Conf::TIPO_GASTO => $rules[Mod130Conf::TIPO_GASTO] ?? [], + Mod130Conf::TIPO_INGRESO => $rules[Mod130Conf::TIPO_INGRESO] ?? [], + ]; + + sort($result[Mod130Conf::TIPO_GASTO]); + sort($result[Mod130Conf::TIPO_INGRESO]); + + return $result; + } + + private function db(): DataBase + { + static $db = null; + + if ($db === null) { + $db = new DataBase(); + $db->connect(); + } + + return $db; + } +} diff --git a/Test/main/Modelo130ConfigTest.php b/Test/main/Modelo130ConfigTest.php new file mode 100644 index 0000000..db452a4 --- /dev/null +++ b/Test/main/Modelo130ConfigTest.php @@ -0,0 +1,273 @@ + + * + * 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\Base\DataBase; +use FacturaScripts\Core\Session; +use FacturaScripts\Core\Tools; +use FacturaScripts\Core\Where; +use FacturaScripts\Dinamic\Model\Cuenta; +use FacturaScripts\Dinamic\Model\Mod130Conf; +use FacturaScripts\Plugins\Modelo130\Lib\Modelo130Accounts; +use FacturaScripts\Plugins\Modelo130\Lib\Modelo130Config; +use FacturaScripts\Test\Traits\DefaultSettingsTrait; +use FacturaScripts\Test\Traits\LogErrorsTrait; +use PHPUnit\Framework\TestCase; + +/** + * Pruebas de la configuración de cuentas del Modelo 130. + */ +final class Modelo130ConfigTest extends TestCase +{ + use DefaultSettingsTrait; + use LogErrorsTrait; + + public static function setUpBeforeClass(): void + { + self::setDefaultSettings(); + self::installAccountingPlan(); + } + + /** + * Los valores predeterminados deben corresponder con cuentas que existan + * exactamente en el plan contable y deben mostrar su descripción real. + */ + public function testDefaultRulesOnlyUseExistingAccounts(): void + { + $defaults = Modelo130Config::buildDefaultRules(); + + $this->assertArrayHasKey(Mod130Conf::TIPO_GASTO, $defaults); + $this->assertArrayHasKey(Mod130Conf::TIPO_INGRESO, $defaults); + $this->assertNotEmpty($defaults[Mod130Conf::TIPO_GASTO]); + $this->assertNotEmpty($defaults[Mod130Conf::TIPO_INGRESO]); + + foreach ($defaults as $type => $codes) { + $firstDigit = $type === Mod130Conf::TIPO_GASTO ? '6' : '7'; + + foreach ($codes as $code) { + $account = new Cuenta(); + $exists = $account->loadWhere([Where::eq('codcuenta', $code)]); + + $this->assertTrue( + $exists, + "El prefijo predeterminado {$code} debe existir exactamente en Cuenta" + ); + $this->assertStringStartsWith($firstDigit, $code); + $this->assertSame( + (string) $account->descripcion, + Modelo130Config::description($code), + "La descripción de {$code} debe proceder del plan contable" + ); + } + } + } + + /** + * 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. + */ + public function testStockVariationClassification(): void + { + $this->assertTrue(Modelo130Accounts::isStockVariation('6100000000')); + $this->assertTrue(Modelo130Accounts::isStockVariation('7199999999')); + $this->assertFalse(Modelo130Accounts::isStockVariation('6000000000')); + $this->assertFalse(Modelo130Accounts::isStockVariation('7000000000')); + + // Nunca deben pasar por la clasificación ordinaria. + $this->assertFalse(Modelo130Accounts::isExpense('6100000000')); + $this->assertFalse(Modelo130Accounts::isIncome('6100000000')); + $this->assertFalse(Modelo130Accounts::isExpense('7100000000')); + $this->assertFalse(Modelo130Accounts::isIncome('7100000000')); + + // Saldo deudor: gasto. + $this->assertSame(750.0, Modelo130Accounts::stockVariationExpense(1000.0, 250.0)); + $this->assertSame(0.0, Modelo130Accounts::stockVariationIncome(1000.0, 250.0)); + + // Saldo acreedor: ingreso. + $this->assertSame(0.0, Modelo130Accounts::stockVariationExpense(250.0, 1000.0)); + $this->assertSame(750.0, Modelo130Accounts::stockVariationIncome(250.0, 1000.0)); + + // Saldo compensado: no computa en ninguna de las dos casillas. + $this->assertSame(0.0, Modelo130Accounts::stockVariationExpense(500.0, 500.0)); + $this->assertSame(0.0, Modelo130Accounts::stockVariationIncome(500.0, 500.0)); + } + + /** + * La consulta contable debe incluir 61/71 únicamente cuando estén en la + * configuración. La cuenta 473 permanece siempre incluida por código. + */ + public function testQueryPrefixesRespectStockVariationConfiguration(): void + { + $originalRules = $this->snapshotRules(); + + try { + $this->replaceRules([ + [Mod130Conf::TIPO_GASTO, '60'], + [Mod130Conf::TIPO_GASTO, '61'], + [Mod130Conf::TIPO_INGRESO, '70'], + [Mod130Conf::TIPO_INGRESO, '71'], + ]); + + $prefixes = Modelo130Accounts::queryPrefixes(); + $this->assertContains('61', $prefixes); + $this->assertContains('71', $prefixes); + $this->assertContains(Modelo130Accounts::WITHHOLDING_ACCOUNT, $prefixes); + + $this->replaceRules([ + [Mod130Conf::TIPO_GASTO, '60'], + [Mod130Conf::TIPO_INGRESO, '70'], + ]); + + $prefixes = Modelo130Accounts::queryPrefixes(); + $this->assertNotContains('61', $prefixes); + $this->assertNotContains('71', $prefixes); + $this->assertContains(Modelo130Accounts::WITHHOLDING_ACCOUNT, $prefixes); + } finally { + $this->replaceRules($originalRules); + } + } + + /** + * 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'], + ]); + + $rule = new Mod130Conf(); + + $this->assertTrue( + $rule->loadWhere([ + Where::eq('codigo', '699999999999999'), + ]), + 'La regla de prueba debe existir' + ); + + $this->assertSame( + Session::user()->nick, + $rule->nick, + 'El nick de creación debe ser el usuario de la sesión' + ); + + $this->assertNotEmpty( + $rule->creation_date, + 'La fecha de creación debe guardarse' + ); + } finally { + $this->replaceRules($originalRules); + } + } + + /** + * Cuando las claves foráneas están habilitadas en FacturaScripts, la tabla + * debe contener la relación física de nick con users.nick. + */ + public function testUserForeignKeyExistsWhenEnabled(): void + { + if (!Tools::config('db_foreign_keys')) { + $this->markTestSkipped('Las claves foráneas están desactivadas en la configuración.'); + } + + $db = new DataBase(); + $db->connect(); + $constraints = $db->getConstraints(Mod130Conf::tableName()); + $names = array_column($constraints, 'name'); + + $this->assertContains('ca_mod130_conf_users_nick', $names); + $this->assertNotContains('ca_mod130_conf_users_last_nick', $names); + $this->assertContains('uniq_mod130_conf_codigo', $names); + + $columns = array_column( + $db->getColumns(Mod130Conf::tableName()), + null, + 'name' + ); + $this->assertArrayNotHasKey('last_nick', $columns); + $this->assertArrayNotHasKey('last_update', $columns); + } + + /** + * No se permiten reglas que engloben o queden englobadas por otra regla. + */ + public function testOverlappingPrefixesAreDetected(): void + { + $originalRules = $this->snapshotRules(); + + try { + $this->replaceRules([ + [Mod130Conf::TIPO_GASTO, '64'], + [Mod130Conf::TIPO_INGRESO, '70'], + ]); + + $this->assertTrue(Modelo130Config::hasOverlap('642')); + $this->assertTrue(Modelo130Config::hasOverlap('6')); + $this->assertTrue(Modelo130Config::hasOverlap('700')); + $this->assertFalse(Modelo130Config::hasOverlap('62')); + $this->assertFalse(Modelo130Config::hasOverlap('71')); + } finally { + $this->replaceRules($originalRules); + } + } + + /** + * @return array + */ + private function snapshotRules(): array + { + $result = []; + + foreach ((new Mod130Conf())->all([], ['codigo' => 'ASC'], 0, 0) as $rule) { + $result[] = [(string) $rule->tipo, (string) $rule->codigo]; + } + + return $result; + } + + /** + * @param array $rules + */ + private function replaceRules(array $rules): void + { + foreach ((new Mod130Conf())->all([], [], 0, 0) as $rule) { + $this->assertTrue($rule->delete(), 'No se pudo eliminar una regla de configuración de prueba'); + } + + foreach ($rules as [$type, $code]) { + $rule = new Mod130Conf(); + $rule->tipo = $type; + $rule->codigo = $code; + + $this->assertTrue( + $rule->save(), + "No se pudo guardar la regla de configuración {$code}" + ); + } + } + + protected function tearDown(): void + { + $this->logErrors(); + } +} diff --git a/Test/main/Modelo130Test.php b/Test/main/Modelo130Test.php index 86d4091..0ccf928 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,11 @@ public function testGenerate(): void 'exercise', 'period', 'idempresa', - 'customerInvoices', - 'supplierInvoices', + 'sales', + 'purchases', 'accountingEntries', - 'incomeEntries', + 'currentPaymentEntry', + 'currentPaymentEntryExists', 'applyGastosJustificacion', 'todeduct', 'gastosJustificacionPct', @@ -84,6 +86,7 @@ public function testGenerate(): void 'gastosJustificacion', 'afterdeduct', 'positivosTrimestres', + 'fractionalPayment', 'result', ]; @@ -94,6 +97,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'); @@ -124,7 +130,8 @@ public function testGenerateEntries(): void $periodo = 'T1'; $importe = 100.0; - $fecha = date('Y-m-d'); + $year = date('Y', strtotime($ejercicio->fechainicio)); + $fecha = $year . '-03-31'; // la primera llamada debe crear el asiento correctamente $creado = Modelo130::generateEntries($idempresa, $codejercicio, $periodo, $fecha, $importe, $paymentMethodId); @@ -141,6 +148,28 @@ 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); + + // generate() debe devolver el propio asiento para que la interfaz + // pueda mostrar un enlace directo cuando ya existe. + $resultado = Modelo130::generate($codejercicio, $periodo); + $this->assertTrue($resultado['currentPaymentEntryExists']); + $this->assertInstanceOf(Asiento::class, $resultado['currentPaymentEntry']); + $this->assertSame( + (int) $asiento->idasiento, + (int) $resultado['currentPaymentEntry']->idasiento + ); + $this->assertNotEmpty($resultado['currentPaymentEntry']->url()); + // 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'); @@ -156,29 +185,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)); + // porcentaje por defecto (5%) sobre una base por debajo del límite + $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)); + // tope anual de 2.000 €: 5% de 45.000 = 2.250, pero se limita a 2.000 + $this->assertSame(2000.0, Modelo130::calcGastosJustificacion(45000.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)); + // justo por debajo del límite (5% de 39.900 = 1.995): no se topa + $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); @@ -201,30 +230,44 @@ 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)); } /** * 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 { $this->logErrors(); } -} +} \ No newline at end of file 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..6cb04d4 100644 --- a/Translation/ca_ES.json +++ b/Translation/ca_ES.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularització d'IRPF %period%", + "acc-concept-irpf-130": "Pagament fraccionat IRPF %period%", + "acc-concept-irpf-130-lines": "Pagament fraccionat IRPF model 130", "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", + "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ó", + "income": "Ingrés computable", "income-subaccounts": "Comptes d'ingressos", "model-130": "Model 130", + "model-130-account-prefix": "Compte o prefix comptable", + "model-130-defaults-restored": "S'ha restaurat la configuració inicial del 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.", + "model-130-expense-accounts": "Comptes de despeses", + "model-130-expense-must-start-with-6": "Els comptes de despeses han de començar per 6.", + "model-130-income-accounts": "Comptes d'ingressos", + "model-130-income-must-start-with-7": "Els comptes d'ingressos han de començar per 7.", + "model-130-invalid-account-prefix": "El prefix comptable no és vàlid.", + "model-130-invalid-account-type": "El tipus de compte del Model 130 no és vàlid.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Si 07 dona pèrdues (negatiu) = 0" +} diff --git a/Translation/cs_CZ.json b/Translation/cs_CZ.json index 7b1ae90..e7be6c5 100644 --- a/Translation/cs_CZ.json +++ b/Translation/cs_CZ.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Vyrovnání IRPF %period%", + "acc-concept-irpf-130": "Zálohová platba IRPF %period%", + "acc-concept-irpf-130-lines": "Zálohová platba IRPF model 130", "accounting-created-130": "Účetní zápis byl vytvořen v účetním období %codejercicio% s popisem: %concepto%", + "aeat-file-invalid-exercise": "Soubor AEAT nelze vygenerovat: vybrané účetní období není platné.", + "aeat-file-invalid-length": "Soubor AEAT nelze vygenerovat: vytvořený soubor nemá očekávanou délku.", + "aeat-file-invalid-nif": "Soubor AEAT nelze vygenerovat: daňové identifikační číslo společnosti je prázdné nebo neplatné.", + "aeat-file-missing-company-name": "Soubor AEAT nelze vygenerovat: název společnosti je prázdný.", "after-deduct": "sobre casilla 04", + "company-not-found": "Společnost nebyla nalezena", "deductible-subaccounts": "Odečitatelné účty", + "download-aeat-file": "Stáhnout soubor AEAT", "exists-accounting-130": "V účetním období %codejercicio% již existuje účetní zápis s popisem: %concepto%", + "expense": "Daňově uznatelný výdaj", + "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é", + "income": "Započitatelný příjem", "income-subaccounts": "Podúčty příjmů", "model-130": "Modelo 130", + "model-130-account-prefix": "Účet nebo účetní prefix", + "model-130-defaults-restored": "Výchozí nastavení Modelu 130 bylo obnoveno.", "model-130-desc": "Vypočítává se sčítáním a akumulací každého čtvrtletí, například pokud zobrazíme 3. čtvrtletí, uvidíme součet prvních tří čtvrtletí.", + "model-130-expense-accounts": "Výdajové účty", + "model-130-expense-must-start-with-6": "Výdajové účty musí začínat číslicí 6.", + "model-130-income-accounts": "Příjmové účty", + "model-130-income-must-start-with-7": "Příjmové účty musí začínat číslicí 7.", + "model-130-invalid-account-prefix": "Účetní prefix není platný.", + "model-130-invalid-account-type": "Typ účtu Modelu 130 není platný.", + "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% 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?", "net-total": "Rendimiento neto", "pct-to-deduct": "% a deducir", "previous-model": "Ingresos trimestres anteriores", + "previous-payment": "Předchozí zálohová platba", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ No newline at end of file + "tax-negative-info": "Pokud je 07 záporné (ztráta) = 0" +} diff --git a/Translation/de_DE.json b/Translation/de_DE.json index fe09c0a..67bc9bf 100644 --- a/Translation/de_DE.json +++ b/Translation/de_DE.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "IRPF-Anpassung %period%", + "acc-concept-irpf-130": "IRPF-Teilzahlung %period%", + "acc-concept-irpf-130-lines": "IRPF-Teilzahlung Modell 130", "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", + "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", + "income": "Steuerpflichtige Einnahme", "income-subaccounts": "Unterkonten für Einnahmen", "model-130": "Modell 130", + "model-130-account-prefix": "Konto oder Kontenpräfix", + "model-130-defaults-restored": "Die Standardkonfiguration für Modell 130 wurde wiederhergestellt.", "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.", + "model-130-expense-accounts": "Aufwandskonten", + "model-130-expense-must-start-with-6": "Aufwandskonten müssen mit 6 beginnen.", + "model-130-income-accounts": "Ertragskonten", + "model-130-income-must-start-with-7": "Ertragskonten müssen mit 7 beginnen.", + "model-130-invalid-account-prefix": "Das Kontenpräfix ist ungültig.", + "model-130-invalid-account-type": "Der Kontotyp für Modell 130 ist ungültig.", + "model-130-overlapping-prefix": "Das Konto ist bereits durch eine andere Regel enthalten oder umfasst eine bestehende Regel.", "model-130-p": "Model 130 ist eine quartalsweise Erklärung der Einkommensteuer (IRPF), in der die Ratenzahlung dieser Steuer beglichen wird, aufgrund der jährlichen Erklärung, die im folgenden Jahr erstellt wird.", + "model-130-prefix-help": "Die Codes werden als Präfixe interpretiert. Beispielsweise umfasst 62 alle Unterkonten, die mit 62 beginnen. Das Konto 473 wird immer intern verarbeitet und erscheint nicht in diesen Listen.", + "model-130-prefix-includes": "Präfix %code% mit untergeordneten Konten im Kontenplan", + "model-130-prefix-not-found": "Präfix %code% ohne exaktes Konto im aktuellen Kontenplan", "model-130-r": "Kumulierte Daten für den Zeitraum zwischen dem ersten Tag des Jahres und dem letzten Tag des ausgewählten Quartals.", + "model-130-restore-confirm": "Die aktuelle Konfiguration wird gelöscht und aus dem installierten Kontenplan neu erstellt. Fortfahren?", "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" -} \ No newline at end of file + "tax-negative-info": "Wenn 07 negativ ist (Verlust) = 0" +} diff --git a/Translation/en_EN.json b/Translation/en_EN.json index 3bc2919..cf3410d 100644 --- a/Translation/en_EN.json +++ b/Translation/en_EN.json @@ -1,27 +1,45 @@ { - "acc-concept-irpf-130": "IRPF regularization %period%", + "acc-concept-irpf-130": "Fractional IRPF payment %period%", + "acc-concept-irpf-130-lines": "Fractional IRPF payment model 130", + "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", + "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", + "income": "Taxable income", "income-subaccounts": "Income accounts", "model-130": "Model 130", + "model-130-account-prefix": "Account or accounting prefix", + "model-130-defaults-restored": "The default Model 130 configuration has been restored.", "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.", + "model-130-expense-accounts": "Expense accounts", + "model-130-expense-must-start-with-6": "Expense accounts must start with 6.", + "model-130-income-accounts": "Income accounts", + "model-130-income-must-start-with-7": "Income accounts must start with 7.", + "model-130-invalid-account-prefix": "The accounting prefix is not valid.", + "model-130-invalid-account-type": "The Model 130 account type is not valid.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "If 07 is a loss (negative) = 0" +} diff --git a/Translation/es_AR.json b/Translation/es_AR.json index 47c9b3d..534cbba 100644 --- a/Translation/es_AR.json +++ b/Translation/es_AR.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularización de IRPF %period%", + "acc-concept-irpf-130": "Pago fraccionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pago fraccionado IRPF modelo 130", "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.", "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", + "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", + "income": "Ingreso computable", "income-subaccounts": "Cuentas de ingresos", "model-130": "Modelo 130", + "model-130-account-prefix": "Cuenta o prefijo contable", + "model-130-defaults-restored": "Se ha restaurado la configuración inicial del 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.", + "model-130-expense-accounts": "Cuentas de gastos", + "model-130-expense-must-start-with-6": "Las cuentas de gastos deben comenzar por 6.", + "model-130-income-accounts": "Cuentas de ingresos", + "model-130-income-must-start-with-7": "Las cuentas de ingresos deben comenzar por 7.", + "model-130-invalid-account-prefix": "El prefijo contable no es válido.", + "model-130-invalid-account-type": "El tipo de cuenta del Modelo 130 no es válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" +} diff --git a/Translation/es_CL.json b/Translation/es_CL.json index 47c9b3d..534cbba 100644 --- a/Translation/es_CL.json +++ b/Translation/es_CL.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularización de IRPF %period%", + "acc-concept-irpf-130": "Pago fraccionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pago fraccionado IRPF modelo 130", "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.", "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", + "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", + "income": "Ingreso computable", "income-subaccounts": "Cuentas de ingresos", "model-130": "Modelo 130", + "model-130-account-prefix": "Cuenta o prefijo contable", + "model-130-defaults-restored": "Se ha restaurado la configuración inicial del 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.", + "model-130-expense-accounts": "Cuentas de gastos", + "model-130-expense-must-start-with-6": "Las cuentas de gastos deben comenzar por 6.", + "model-130-income-accounts": "Cuentas de ingresos", + "model-130-income-must-start-with-7": "Las cuentas de ingresos deben comenzar por 7.", + "model-130-invalid-account-prefix": "El prefijo contable no es válido.", + "model-130-invalid-account-type": "El tipo de cuenta del Modelo 130 no es válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" +} diff --git a/Translation/es_CO.json b/Translation/es_CO.json index 47c9b3d..534cbba 100644 --- a/Translation/es_CO.json +++ b/Translation/es_CO.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularización de IRPF %period%", + "acc-concept-irpf-130": "Pago fraccionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pago fraccionado IRPF modelo 130", "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.", "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", + "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", + "income": "Ingreso computable", "income-subaccounts": "Cuentas de ingresos", "model-130": "Modelo 130", + "model-130-account-prefix": "Cuenta o prefijo contable", + "model-130-defaults-restored": "Se ha restaurado la configuración inicial del 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.", + "model-130-expense-accounts": "Cuentas de gastos", + "model-130-expense-must-start-with-6": "Las cuentas de gastos deben comenzar por 6.", + "model-130-income-accounts": "Cuentas de ingresos", + "model-130-income-must-start-with-7": "Las cuentas de ingresos deben comenzar por 7.", + "model-130-invalid-account-prefix": "El prefijo contable no es válido.", + "model-130-invalid-account-type": "El tipo de cuenta del Modelo 130 no es válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" +} diff --git a/Translation/es_CR.json b/Translation/es_CR.json index 47c9b3d..534cbba 100644 --- a/Translation/es_CR.json +++ b/Translation/es_CR.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularización de IRPF %period%", + "acc-concept-irpf-130": "Pago fraccionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pago fraccionado IRPF modelo 130", "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.", "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", + "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", + "income": "Ingreso computable", "income-subaccounts": "Cuentas de ingresos", "model-130": "Modelo 130", + "model-130-account-prefix": "Cuenta o prefijo contable", + "model-130-defaults-restored": "Se ha restaurado la configuración inicial del 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.", + "model-130-expense-accounts": "Cuentas de gastos", + "model-130-expense-must-start-with-6": "Las cuentas de gastos deben comenzar por 6.", + "model-130-income-accounts": "Cuentas de ingresos", + "model-130-income-must-start-with-7": "Las cuentas de ingresos deben comenzar por 7.", + "model-130-invalid-account-prefix": "El prefijo contable no es válido.", + "model-130-invalid-account-type": "El tipo de cuenta del Modelo 130 no es válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" +} diff --git a/Translation/es_DO.json b/Translation/es_DO.json index 47c9b3d..534cbba 100644 --- a/Translation/es_DO.json +++ b/Translation/es_DO.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularización de IRPF %period%", + "acc-concept-irpf-130": "Pago fraccionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pago fraccionado IRPF modelo 130", "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.", "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", + "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", + "income": "Ingreso computable", "income-subaccounts": "Cuentas de ingresos", "model-130": "Modelo 130", + "model-130-account-prefix": "Cuenta o prefijo contable", + "model-130-defaults-restored": "Se ha restaurado la configuración inicial del 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.", + "model-130-expense-accounts": "Cuentas de gastos", + "model-130-expense-must-start-with-6": "Las cuentas de gastos deben comenzar por 6.", + "model-130-income-accounts": "Cuentas de ingresos", + "model-130-income-must-start-with-7": "Las cuentas de ingresos deben comenzar por 7.", + "model-130-invalid-account-prefix": "El prefijo contable no es válido.", + "model-130-invalid-account-type": "El tipo de cuenta del Modelo 130 no es válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" +} diff --git a/Translation/es_EC.json b/Translation/es_EC.json index 47c9b3d..534cbba 100644 --- a/Translation/es_EC.json +++ b/Translation/es_EC.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularización de IRPF %period%", + "acc-concept-irpf-130": "Pago fraccionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pago fraccionado IRPF modelo 130", "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.", "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", + "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", + "income": "Ingreso computable", "income-subaccounts": "Cuentas de ingresos", "model-130": "Modelo 130", + "model-130-account-prefix": "Cuenta o prefijo contable", + "model-130-defaults-restored": "Se ha restaurado la configuración inicial del 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.", + "model-130-expense-accounts": "Cuentas de gastos", + "model-130-expense-must-start-with-6": "Las cuentas de gastos deben comenzar por 6.", + "model-130-income-accounts": "Cuentas de ingresos", + "model-130-income-must-start-with-7": "Las cuentas de ingresos deben comenzar por 7.", + "model-130-invalid-account-prefix": "El prefijo contable no es válido.", + "model-130-invalid-account-type": "El tipo de cuenta del Modelo 130 no es válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" +} diff --git a/Translation/es_ES.json b/Translation/es_ES.json index 0939904..534cbba 100644 --- a/Translation/es_ES.json +++ b/Translation/es_ES.json @@ -1,27 +1,45 @@ { - "acc-concept-irpf-130": "Regularización de IRPF %period%", + "acc-concept-irpf-130": "Pago fraccionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pago fraccionado IRPF modelo 130", + "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", + "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", + "income": "Ingreso computable", "income-subaccounts": "Cuentas de ingresos", "model-130": "Modelo 130", + "model-130-account-prefix": "Cuenta o prefijo contable", + "model-130-defaults-restored": "Se ha restaurado la configuración inicial del 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.", + "model-130-expense-accounts": "Cuentas de gastos", + "model-130-expense-must-start-with-6": "Las cuentas de gastos deben comenzar por 6.", + "model-130-income-accounts": "Cuentas de ingresos", + "model-130-income-must-start-with-7": "Las cuentas de ingresos deben comenzar por 7.", + "model-130-invalid-account-prefix": "El prefijo contable no es válido.", + "model-130-invalid-account-type": "El tipo de cuenta del Modelo 130 no es válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" +} diff --git a/Translation/es_GT.json b/Translation/es_GT.json index 47c9b3d..534cbba 100644 --- a/Translation/es_GT.json +++ b/Translation/es_GT.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularización de IRPF %period%", + "acc-concept-irpf-130": "Pago fraccionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pago fraccionado IRPF modelo 130", "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.", "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", + "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", + "income": "Ingreso computable", "income-subaccounts": "Cuentas de ingresos", "model-130": "Modelo 130", + "model-130-account-prefix": "Cuenta o prefijo contable", + "model-130-defaults-restored": "Se ha restaurado la configuración inicial del 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.", + "model-130-expense-accounts": "Cuentas de gastos", + "model-130-expense-must-start-with-6": "Las cuentas de gastos deben comenzar por 6.", + "model-130-income-accounts": "Cuentas de ingresos", + "model-130-income-must-start-with-7": "Las cuentas de ingresos deben comenzar por 7.", + "model-130-invalid-account-prefix": "El prefijo contable no es válido.", + "model-130-invalid-account-type": "El tipo de cuenta del Modelo 130 no es válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" +} diff --git a/Translation/es_MX.json b/Translation/es_MX.json index 47c9b3d..534cbba 100644 --- a/Translation/es_MX.json +++ b/Translation/es_MX.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularización de IRPF %period%", + "acc-concept-irpf-130": "Pago fraccionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pago fraccionado IRPF modelo 130", "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.", "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", + "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", + "income": "Ingreso computable", "income-subaccounts": "Cuentas de ingresos", "model-130": "Modelo 130", + "model-130-account-prefix": "Cuenta o prefijo contable", + "model-130-defaults-restored": "Se ha restaurado la configuración inicial del 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.", + "model-130-expense-accounts": "Cuentas de gastos", + "model-130-expense-must-start-with-6": "Las cuentas de gastos deben comenzar por 6.", + "model-130-income-accounts": "Cuentas de ingresos", + "model-130-income-must-start-with-7": "Las cuentas de ingresos deben comenzar por 7.", + "model-130-invalid-account-prefix": "El prefijo contable no es válido.", + "model-130-invalid-account-type": "El tipo de cuenta del Modelo 130 no es válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" +} diff --git a/Translation/es_PA.json b/Translation/es_PA.json index 172433a..0adf739 100644 --- a/Translation/es_PA.json +++ b/Translation/es_PA.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularización de IRPF %period%", + "acc-concept-irpf-130": "Pago fraccionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pago fraccionado IRPF modelo 130", "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.", "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", + "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", + "income": "Ingreso computable", "income-subaccounts": "Cuentas de ingresos", "model-130": "Modelo 130", + "model-130-account-prefix": "Cuenta o prefijo contable", + "model-130-defaults-restored": "Se ha restaurado la configuración inicial del 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.", + "model-130-expense-accounts": "Cuentas de gastos", + "model-130-expense-must-start-with-6": "Las cuentas de gastos deben comenzar por 6.", + "model-130-income-accounts": "Cuentas de ingresos", + "model-130-income-must-start-with-7": "Las cuentas de ingresos deben comenzar por 7.", + "model-130-invalid-account-prefix": "El prefijo contable no es válido.", + "model-130-invalid-account-type": "El tipo de cuenta del Modelo 130 no es válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" +} diff --git a/Translation/es_PE.json b/Translation/es_PE.json index 47c9b3d..534cbba 100644 --- a/Translation/es_PE.json +++ b/Translation/es_PE.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularización de IRPF %period%", + "acc-concept-irpf-130": "Pago fraccionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pago fraccionado IRPF modelo 130", "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.", "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", + "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", + "income": "Ingreso computable", "income-subaccounts": "Cuentas de ingresos", "model-130": "Modelo 130", + "model-130-account-prefix": "Cuenta o prefijo contable", + "model-130-defaults-restored": "Se ha restaurado la configuración inicial del 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.", + "model-130-expense-accounts": "Cuentas de gastos", + "model-130-expense-must-start-with-6": "Las cuentas de gastos deben comenzar por 6.", + "model-130-income-accounts": "Cuentas de ingresos", + "model-130-income-must-start-with-7": "Las cuentas de ingresos deben comenzar por 7.", + "model-130-invalid-account-prefix": "El prefijo contable no es válido.", + "model-130-invalid-account-type": "El tipo de cuenta del Modelo 130 no es válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" +} diff --git a/Translation/es_UY.json b/Translation/es_UY.json index 47c9b3d..534cbba 100644 --- a/Translation/es_UY.json +++ b/Translation/es_UY.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularización de IRPF %period%", + "acc-concept-irpf-130": "Pago fraccionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pago fraccionado IRPF modelo 130", "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.", "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", + "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", + "income": "Ingreso computable", "income-subaccounts": "Cuentas de ingresos", "model-130": "Modelo 130", + "model-130-account-prefix": "Cuenta o prefijo contable", + "model-130-defaults-restored": "Se ha restaurado la configuración inicial del 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.", + "model-130-expense-accounts": "Cuentas de gastos", + "model-130-expense-must-start-with-6": "Las cuentas de gastos deben comenzar por 6.", + "model-130-income-accounts": "Cuentas de ingresos", + "model-130-income-must-start-with-7": "Las cuentas de ingresos deben comenzar por 7.", + "model-130-invalid-account-prefix": "El prefijo contable no es válido.", + "model-130-invalid-account-type": "El tipo de cuenta del Modelo 130 no es válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Si 07 da pérdidas (negativo) = 0" +} diff --git a/Translation/eu_ES.json b/Translation/eu_ES.json index 8c91ee9..7fcf666 100644 --- a/Translation/eu_ES.json +++ b/Translation/eu_ES.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "IRPFaren zuzenketa %period%", + "acc-concept-irpf-130": "IRPF ordainketa zatikatua %period%", + "acc-concept-irpf-130-lines": "130 ereduko IRPF ordainketa zatikatua", "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", + "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", + "income": "Sarrera zenbagarria", "income-subaccounts": "Sarreren kontuak", "model-130": "130 Eredua", + "model-130-account-prefix": "Kontua edo kontabilitate-aurrizkia", + "model-130-defaults-restored": "130 ereduaren hasierako konfigurazioa berrezarri da.", "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.", + "model-130-expense-accounts": "Gastu-kontuak", + "model-130-expense-must-start-with-6": "Gastu-kontuek 6 zenbakiarekin hasi behar dute.", + "model-130-income-accounts": "Sarrera-kontuak", + "model-130-income-must-start-with-7": "Sarrera-kontuek 7 zenbakiarekin hasi behar dute.", + "model-130-invalid-account-prefix": "Kontabilitate-aurrizkia ez da baliozkoa.", + "model-130-invalid-account-type": "130 ereduko kontu mota ez da baliozkoa.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "07 galerak badira (negatiboa) = 0" +} diff --git a/Translation/fr_FR.json b/Translation/fr_FR.json index 11b712e..6c988d4 100644 --- a/Translation/fr_FR.json +++ b/Translation/fr_FR.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Régularisation de l'IRPF %period%", + "acc-concept-irpf-130": "Paiement fractionné IRPF %period%", + "acc-concept-irpf-130-lines": "Paiement fractionné IRPF modèle 130", "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", + "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", + "income": "Revenu imposable", "income-subaccounts": "Comptes de revenus", "model-130": "Formulaire 130", + "model-130-account-prefix": "Compte ou préfixe comptable", + "model-130-defaults-restored": "La configuration initiale du Modèle 130 a été restaurée.", "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.", + "model-130-expense-accounts": "Comptes de charges", + "model-130-expense-must-start-with-6": "Les comptes de charges doivent commencer par 6.", + "model-130-income-accounts": "Comptes de produits", + "model-130-income-must-start-with-7": "Les comptes de produits doivent commencer par 7.", + "model-130-invalid-account-prefix": "Le préfixe comptable n'est pas valide.", + "model-130-invalid-account-type": "Le type de compte du Modèle 130 n'est pas valide.", + "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 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 ?", "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" -} \ No newline at end of file + "tax-negative-info": "Si 07 est déficitaire (négatif) = 0" +} diff --git a/Translation/gl_ES.json b/Translation/gl_ES.json index b608956..7baf2f5 100644 --- a/Translation/gl_ES.json +++ b/Translation/gl_ES.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularización do IRPF %period%", + "acc-concept-irpf-130": "Pagamento fraccionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pagamento fraccionado IRPF modelo 130", "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", + "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", + "income": "Ingreso computable", "income-subaccounts": "Contas de ingresos", "model-130": "Modelo 130", + "model-130-account-prefix": "Conta ou prefixo contable", + "model-130-defaults-restored": "Restaurouse a configuración inicial do 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.", + "model-130-expense-accounts": "Contas de gastos", + "model-130-expense-must-start-with-6": "As contas de gastos deben comezar por 6.", + "model-130-income-accounts": "Contas de ingresos", + "model-130-income-must-start-with-7": "As contas de ingresos deben comezar por 7.", + "model-130-invalid-account-prefix": "O prefixo contable non é válido.", + "model-130-invalid-account-type": "O tipo de conta do Modelo 130 non é válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Se 07 dá perdas (negativo) = 0" +} diff --git a/Translation/it_IT.json b/Translation/it_IT.json index ce57388..d84ada4 100644 --- a/Translation/it_IT.json +++ b/Translation/it_IT.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regolarizzazione IRPF %period%", + "acc-concept-irpf-130": "Pagamento frazionato IRPF %period%", + "acc-concept-irpf-130-lines": "Pagamento frazionato IRPF modello 130", "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", + "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", + "income": "Reddito imponibile", "income-subaccounts": "Conti dei ricavi", "model-130": "Modello 130", + "model-130-account-prefix": "Conto o prefisso contabile", + "model-130-defaults-restored": "La configurazione predefinita del Modello 130 è stata ripristinata.", "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.", + "model-130-expense-accounts": "Conti di costo", + "model-130-expense-must-start-with-6": "I conti di costo devono iniziare con 6.", + "model-130-income-accounts": "Conti di ricavo", + "model-130-income-must-start-with-7": "I conti di ricavo devono iniziare con 7.", + "model-130-invalid-account-prefix": "Il prefisso contabile non è valido.", + "model-130-invalid-account-type": "Il tipo di conto del Modello 130 non è valido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Se 07 è in perdita (negativo) = 0" +} diff --git a/Translation/pl_PL.json b/Translation/pl_PL.json index f602917..e324331 100644 --- a/Translation/pl_PL.json +++ b/Translation/pl_PL.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Korekta IRPF %period%", + "acc-concept-irpf-130": "Zaliczka IRPF %period%", + "acc-concept-irpf-130-lines": "Zaliczka IRPF model 130", "accounting-created-130": "Utworzono zapis księgowy w roku obrotowym %codejercicio% z tytułem: %concepto%", + "aeat-file-invalid-exercise": "Nie można wygenerować pliku AEAT: wybrany rok obrotowy jest nieprawidłowy.", + "aeat-file-invalid-length": "Nie można wygenerować pliku AEAT: wygenerowany plik nie ma oczekiwanej długości.", + "aeat-file-invalid-nif": "Nie można wygenerować pliku AEAT: numer identyfikacji podatkowej firmy jest pusty lub nieprawidłowy.", + "aeat-file-missing-company-name": "Nie można wygenerować pliku AEAT: nazwa firmy jest pusta.", "after-deduct": "na rubrykę 04", + "company-not-found": "Nie znaleziono firmy", "deductible-subaccounts": "Podkonty do odliczeń", + "download-aeat-file": "Pobierz plik AEAT", "exists-accounting-130": "Istnieje już zapis księgowy w okresie %codejercicio% o treści: %concepto%", + "expense": "Wydatek podlegający odliczeniu", + "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", + "income": "Przychód podlegający opodatkowaniu", "income-subaccounts": "Konta przychodów", "model-130": "Model 130", + "model-130-account-prefix": "Konto lub prefiks księgowy", + "model-130-defaults-restored": "Przywrócono domyślną konfigurację Modelu 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.", + "model-130-expense-accounts": "Konta kosztów", + "model-130-expense-must-start-with-6": "Konta kosztów muszą zaczynać się od 6.", + "model-130-income-accounts": "Konta przychodów", + "model-130-income-must-start-with-7": "Konta przychodów muszą zaczynać się od 7.", + "model-130-invalid-account-prefix": "Prefiks księgowy jest nieprawidłowy.", + "model-130-invalid-account-type": "Typ konta Modelu 130 jest nieprawidłowy.", + "model-130-overlapping-prefix": "Konto jest już uwzględnione przez inną regułę lub obejmuje istniejącą regułę.", "model-130-p": "Model 130 to kwartalne zgłoszenie podatku dochodowego od osób fizycznych (IRPF), w którym rozlicza się zaliczkę na ten podatek, na podstawie rocznego zeznania dokonywanego w roku następnym.", + "model-130-prefix-help": "Kody są interpretowane jako prefiksy. Na przykład 62 obejmuje wszystkie podkonta zaczynające się od 62. Konto 473 jest zawsze przetwarzane wewnętrznie i nie pojawia się na tych listach.", + "model-130-prefix-includes": "Prefiks %code% z kontami podrzędnymi w planie kont", + "model-130-prefix-not-found": "Prefiks %code% bez dokładnego konta w bieżącym planie kont", "model-130-r": "Skumulowane dane za okres od pierwszego dnia roku do ostatniego dnia wybranego kwartału.", + "model-130-restore-confirm": "Bieżąca konfiguracja zostanie usunięta i odtworzona na podstawie zainstalowanego planu kont. Kontynuować?", "net-total": "Wynik netto", "pct-to-deduct": "% do odliczenia", "previous-model": "Poprzednie kwartały dochodów", + "previous-payment": "Poprzednia zaliczka", "tax-expenses": "Wydatki podatkowe", "tax-incomes": "Dochody opodatkowane", - "tax-negative-info": "Jeśli wykazuje straty (ujemny) = 0" -} \ No newline at end of file + "tax-negative-info": "Jeśli 07 oznacza stratę (ujemne) = 0" +} diff --git a/Translation/pt_BR.json b/Translation/pt_BR.json index aa1f0b3..fc283ab 100644 --- a/Translation/pt_BR.json +++ b/Translation/pt_BR.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularização do IRPF %period%", + "acc-concept-irpf-130": "Pagamento fracionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pagamento fracionado IRPF modelo 130", "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", + "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", + "income": "Receita tributável", "income-subaccounts": "Contas de receitas", "model-130": "Modelo 130", + "model-130-account-prefix": "Conta ou prefixo contábil", + "model-130-defaults-restored": "A configuração padrão do Modelo 130 foi restaurada.", "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.", + "model-130-expense-accounts": "Contas de despesas", + "model-130-expense-must-start-with-6": "As contas de despesas devem começar por 6.", + "model-130-income-accounts": "Contas de receitas", + "model-130-income-must-start-with-7": "As contas de receitas devem começar por 7.", + "model-130-invalid-account-prefix": "O prefixo contábil não é válido.", + "model-130-invalid-account-type": "O tipo de conta do Modelo 130 não é válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Se 07 der prejuízo (negativo) = 0" +} diff --git a/Translation/pt_PT.json b/Translation/pt_PT.json index 32727e1..589438f 100644 --- a/Translation/pt_PT.json +++ b/Translation/pt_PT.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularização do IRPF %period%", + "acc-concept-irpf-130": "Pagamento fracionado IRPF %period%", + "acc-concept-irpf-130-lines": "Pagamento fracionado IRPF modelo 130", "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", + "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", + "income": "Receita tributável", "income-subaccounts": "Contas de receitas", "model-130": "Modelo 130", + "model-130-account-prefix": "Conta ou prefixo contabilístico", + "model-130-defaults-restored": "A configuração predefinida do Modelo 130 foi restaurada.", "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.", + "model-130-expense-accounts": "Contas de despesas", + "model-130-expense-must-start-with-6": "As contas de despesas devem começar por 6.", + "model-130-income-accounts": "Contas de rendimentos", + "model-130-income-must-start-with-7": "As contas de rendimentos devem começar por 7.", + "model-130-invalid-account-prefix": "O prefixo contabilístico não é válido.", + "model-130-invalid-account-type": "O tipo de conta do Modelo 130 não é válido.", + "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 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?", "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" -} \ No newline at end of file + "tax-negative-info": "Se 07 der prejuízo (negativo) = 0" +} diff --git a/Translation/tr_TR.json b/Translation/tr_TR.json index ed8f5be..7b48004 100644 --- a/Translation/tr_TR.json +++ b/Translation/tr_TR.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "IRPF düzenlemesi %period%", + "acc-concept-irpf-130": "IRPF taksit ödemesi %period%", + "acc-concept-irpf-130-lines": "IRPF taksit ödemesi 130 modeli", "accounting-created-130": "%codejercicio% döneminde %concepto% açıklamasıyla yevmiye kaydı oluşturuldu", + "aeat-file-invalid-exercise": "AEAT dosyası oluşturulamıyor: seçilen mali yıl geçerli değil.", + "aeat-file-invalid-length": "AEAT dosyası oluşturulamıyor: oluşturulan dosya beklenen uzunlukta değil.", + "aeat-file-invalid-nif": "AEAT dosyası oluşturulamıyor: şirketin vergi kimlik numarası boş veya geçersiz.", + "aeat-file-missing-company-name": "AEAT dosyası oluşturulamıyor: şirket adı boş.", "after-deduct": "04 numaralı kutu hakkında", + "company-not-found": "Şirket bulunamadı", "deductible-subaccounts": "İndirilebilir alt hesaplar", + "download-aeat-file": "AEAT dosyasını indir", "exists-accounting-130": "%codejercicio% döneminde %concepto% açıklamalı bir kayıt zaten mevcut.", + "expense": "İndirilebilir gider", + "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", + "income": "Hesaplanabilir gelir", "income-subaccounts": "Gelir alt hesapları", "model-130": "Model 130", + "model-130-account-prefix": "Hesap veya muhasebe öneki", + "model-130-defaults-restored": "Model 130 varsayılan yapılandırması geri yüklendi.", "model-130-desc": "Her çeyreği toplama ve biriktirme yoluyla hesaplanır. Örneğin, 3. çeyreği görüntülediğimizde 1, 2 ve 3. çeyreklerin toplamını göreceğiz.", + "model-130-expense-accounts": "Gider hesapları", + "model-130-expense-must-start-with-6": "Gider hesapları 6 ile başlamalıdır.", + "model-130-income-accounts": "Gelir hesapları", + "model-130-income-must-start-with-7": "Gelir hesapları 7 ile başlamalıdır.", + "model-130-invalid-account-prefix": "Muhasebe öneki geçerli değil.", + "model-130-invalid-account-type": "Model 130 hesap türü geçerli değil.", + "model-130-overlapping-prefix": "Hesap başka bir kural tarafından zaten kapsanıyor veya mevcut bir kuralı kapsıyor.", "model-130-p": "Model 130, gelir vergisi beyannamesinin (IRPF) üç aylık bir beyanıdır; bu beyannamede, bir sonraki yıl yapılacak yıllık beyannamede mahsup edilecek bu verginin taksitli ödemesi yapılır.", + "model-130-prefix-help": "Kodlar önek olarak yorumlanır. Örneğin 62, 62 ile başlayan tüm alt hesapları kapsar. 473 hesabı her zaman dahili olarak işlenir ve bu listelerde görünmez.", + "model-130-prefix-includes": "Hesap planında alt hesapları bulunan %code% öneki", + "model-130-prefix-not-found": "Geçerli hesap planında tam hesabı bulunmayan %code% öneki", "model-130-r": "Seçilen çeyreğin ilk günü ile son günü arasındaki birikmiş veriler.", + "model-130-restore-confirm": "Mevcut yapılandırma silinecek ve kurulu hesap planından yeniden oluşturulacaktır. Devam edilsin mi?", "net-total": "Net performans", "pct-to-deduct": "Düşülecek %", "previous-model": "Önceki çeyrek gelirleri", + "previous-payment": "Önceki taksit ödemesi", "tax-expenses": "Düşürülebilir Harcamalar", "tax-incomes": "Gelir hesaplamaları", - "tax-negative-info": "Zarar veriyorsa (negatif) = 0" -} \ No newline at end of file + "tax-negative-info": "07 zarar verirse (negatif) = 0" +} diff --git a/Translation/updater.php b/Translation/updater.php index 9faa61e..3512d50 100644 --- a/Translation/updater.php +++ b/Translation/updater.php @@ -6,7 +6,7 @@ // scan json files chdir(__DIR__); $files = []; -$langs = 'ca_ES,cs_CZ,de_DE,en_EN,es_AR,es_CL,es_CO,es_CR,es_DO,es_EC,es_ES,es_GT,es_MX,es_PA,es_PE,es_UY,eu_ES,fr_FR,gl_ES,it_IT,pl_PL,pt_BR,pt_PT,va_ES'; +$langs = 'ca_ES,cs_CZ,de_DE,en_EN,es_AR,es_CL,es_CO,es_CR,es_DO,es_EC,es_ES,es_GT,es_MX,es_PA,es_PE,es_UY,eu_ES,fr_FR,gl_ES,it_IT,pl_PL,pt_BR,pt_PT,tr_TR,va_ES'; foreach (explode(',', $langs) as $lang) { $files[] = $lang . '.json'; } diff --git a/Translation/va_ES.json b/Translation/va_ES.json index cf7c42d..964bff2 100644 --- a/Translation/va_ES.json +++ b/Translation/va_ES.json @@ -1,21 +1,45 @@ { - "acc-concept-irpf-130": "Regularització de l'IRPF %period%", + "acc-concept-irpf-130": "Pagament fraccionat IRPF %period%", + "acc-concept-irpf-130-lines": "Pagament fraccionat IRPF model 130", "accounting-created-130": "S'ha creat l'assentament a l'exercici %codejercicio% amb el concepte: %concepto%", + "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 està buit o no és vàlid.", + "aeat-file-missing-company-name": "No es pot generar el fitxer AEAT: el nom de l'empresa està buit.", "after-deduct": "sobre casella 04", + "company-not-found": "Empresa no trobada", "deductible-subaccounts": "Comptes deduïbles", + "download-aeat-file": "Descarregar el fitxer AEAT", "exists-accounting-130": "Ja existeix un assentament a l'exercici %codejercicio% amb el concepte: %concepto%", + "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ó", + "income": "Ingrés computable", "income-subaccounts": "Comptes d'ingressos", "model-130": "Modelo 130", + "model-130-account-prefix": "Compte o prefix comptable", + "model-130-defaults-restored": "S'ha restaurat la configuració inicial del 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.", + "model-130-expense-accounts": "Comptes de despeses", + "model-130-expense-must-start-with-6": "Els comptes de despeses han de començar per 6.", + "model-130-income-accounts": "Comptes d'ingressos", + "model-130-income-must-start-with-7": "Els comptes d'ingressos han de començar per 7.", + "model-130-invalid-account-prefix": "El prefix comptable no és vàlid.", + "model-130-invalid-account-type": "El tipus de compte del Model 130 no és vàlid.", + "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 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?", "net-total": "Rendiment net", "pct-to-deduct": "% a deduir", "previous-model": "Ingressos trimestrals 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" -} \ No newline at end of file + "tax-negative-info": "Si 07 dona pèrdues (negatiu) = 0" +} diff --git a/View/Modelo130.html.twig b/View/Modelo130.html.twig index a239e16..62424de 100644 --- a/View/Modelo130.html.twig +++ b/View/Modelo130.html.twig @@ -1,704 +1,581 @@ {% extends "Master/MenuTemplate.html.twig" %} + {% block css %} - {{ parent() }} - + {{ parent() }} + {% endblock %} {% block javascripts %} - {{ parent() }} - - + + $(document).ready(function () { + initModelo130AccountAutocomplete('expenseAccountPrefix', 'gasto'); + initModelo130AccountAutocomplete('incomeAccountPrefix', 'ingreso'); + }); + {% endblock %} {% 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() }} - - -
-
-
- - -
-
-
-
-
-
+
+
+
+
+

+ + {{ 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('fractional-payment') }} +
+ 07 + +
+
+
+
+
+ {{ trans('result') }} +
+ 19 + +
+

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

+
+
+ {% if fsc.result.currentPaymentEntry %} + + {% elseif 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('date') }}{{ trans('concept') }}{{ trans('type') }}{{ trans('total') }}
+ {{ item.entry.numero }} + {{ item.partida.codsubcuenta }}{{ item.entry.fecha }}{{ item.partida.concepto | raw }}{{ trans(item.type) }}{{ money(item.amount) }}
{{ trans('no-data') }}
{{ trans('totals') }}{{ money(accountingTotal) }}
+
+
+ +
+ {% set expenseRules = fsc.getExpenseRules() %} +
+
{{ trans('model-130-prefix-help') }}
+
+ {{ formToken() }} + + +
+ + +
+
+
+
+ + {% 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() }}
+
+
+
- - + + {% endblock %} diff --git a/facturascripts.ini b/facturascripts.ini index 7bd7ee7..9cb2eee 100644 --- a/facturascripts.ini +++ b/facturascripts.ini @@ -1,4 +1,4 @@ name = 'Modelo130' description = 'Modelo 130 de la AEAT (agencia tributaria española)' -version = 4 +version = 5 min_version = 2026 \ No newline at end of file