diff --git a/Controller/Modelo130.php b/Controller/Modelo130.php index 76e3752..f9f99fe 100644 --- a/Controller/Modelo130.php +++ b/Controller/Modelo130.php @@ -23,11 +23,9 @@ use FacturaScripts\Core\DataSrc\Ejercicios; use FacturaScripts\Core\Response; use FacturaScripts\Core\Tools; -use FacturaScripts\Core\Where; use FacturaScripts\Dinamic\Model\Ejercicio; use FacturaScripts\Dinamic\Model\Empresa; use FacturaScripts\Dinamic\Model\FormaPago; -use FacturaScripts\Dinamic\Model\Subcuenta130; use FacturaScripts\Dinamic\Lib\Modelo130 as DinModelo130; use FacturaScripts\Dinamic\Lib\Modelo130Export as DinModelo130Export; @@ -41,24 +39,15 @@ */ class Modelo130 extends Controller { - /** @var string */ - public $activeTab = ''; - /** bool */ 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 +73,8 @@ public function getAllExercises(?int $idempresa): array $list[] = $exercise; } } - return $list; - } - public function getDeductibleSubaccounts(): array - { - $where = [Where::eq('tipo', Subcuenta130::TIPO_DEDUCIBLE)]; - return (new Subcuenta130())->all($where, ['codsubcuenta' => 'ASC'], 0, 0); + return $list; } /** @@ -104,12 +88,6 @@ public function getExercise(?string $codejercicio): Ejercicio return $exercise; } - public function getIncomeSubaccounts(): array - { - $where = [Where::eq('tipo', Subcuenta130::TIPO_INGRESO)]; - return (new Subcuenta130())->all($where, ['codsubcuenta' => 'ASC'], 0, 0); - } - public function getPageData(): array { $data = parent::getPageData(); @@ -138,31 +116,9 @@ public function getPeriodsForComboBoxHtml(): array public function privateCore(&$response, $user, $permissions) { parent::privateCore($response, $user, $permissions); - $this->deductibleSubaccount = new Subcuenta130(); - $this->incomeSubaccount = new Subcuenta130(); $action = $this->request->request->get('action', $this->request->input('action')); switch ($action) { - case 'autocomplete-subaccount': - $this->autocompleteSubaccount(); - return; - - case 'add-deductible-subaccount': - $this->addDeductibleSubaccount(); - return; - - case 'delete-deductible-subaccount': - $this->deleteDeductibleSubaccount(); - return; - - case 'add-income-subaccount': - $this->addIncomeSubaccount(); - return; - - case 'delete-income-subaccount': - $this->deleteIncomeSubaccount(); - return; - case 'gen-accounting': $this->createAccountingEntry(); return; @@ -170,11 +126,23 @@ public function privateCore(&$response, $user, $permissions) $this->codejercicio = $this->request->request->get('codejercicio', ''); $this->period = $this->request->request->get('period', $this->period); - $this->applyGastosJustificacion = (bool)$this->request->request->get('applyGastosJustificacion', false); + $this->applyGastosJustificacion = (bool)$this->request->request->get( + 'applyGastosJustificacion', + false + ); $this->todeduct = (float)$this->request->request->get('todeduct', 20.0); - $this->gastosJustificacionPct = (float)$this->request->request->get('gastosJustificacionPct', 7.0); - - $this->result = DinModelo130::generate($this->codejercicio, $this->period, $this->applyGastosJustificacion, $this->todeduct, $this->gastosJustificacionPct); + $this->gastosJustificacionPct = (float)$this->request->request->get( + 'gastosJustificacionPct', + 5.0 + ); + + $this->result = DinModelo130::generate( + $this->codejercicio, + $this->period, + $this->applyGastosJustificacion, + $this->todeduct, + $this->gastosJustificacionPct + ); if ($action === 'download') { $this->downloadFile($response); @@ -212,78 +180,40 @@ protected function downloadFile(Response $response): void return; } - $content = DinModelo130Export::generate($this->result, $empresa, $this->period, $year); + $content = DinModelo130Export::generate( + $this->result, + $empresa, + $this->period, + $year + ); + if (strlen($content) !== DinModelo130Export::FILE_LENGTH) { Tools::log()->error('aeat-file-invalid-length'); return; } - $filename = 'modelo130_' . $year . '_' . DinModelo130Export::getPeriodNumber($this->period) . '.txt'; + $filename = 'modelo130_' + . $year + . '_' + . DinModelo130Export::getPeriodNumber($this->period) + . '.txt'; - $response->headers->set('Content-Type', 'text/plain; charset=ISO-8859-1'); - $response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"'); - $response->setContent(mb_convert_encoding($content, 'ISO-8859-1', 'UTF-8')); - $response->send(); - exit; - } + $response->headers->set( + 'Content-Type', + 'text/plain; charset=ISO-8859-1' + ); - protected function addDeductibleSubaccount(): void - { - $this->activeTab = 'deductible-subaccount'; + $response->headers->set( + 'Content-Disposition', + 'attachment; filename="' . $filename . '"' + ); - if (false === $this->validateFormToken()) { - return; - } + $response->setContent( + mb_convert_encoding($content, 'ISO-8859-1', 'UTF-8') + ); - $subaccount130 = new Subcuenta130(); - $subaccount130->codsubcuenta = $this->request->request->get('codsubcuenta'); - if (false === $subaccount130->save()) { - Tools::log()->error('record-save-error'); - return; - } - - Tools::log()->notice('record-updated-correctly'); - } - - protected function addIncomeSubaccount(): void - { - $this->activeTab = 'income-subaccount'; - - if (false === $this->validateFormToken()) { - return; - } - - $subaccount = new Subcuenta130(); - $subaccount->codsubcuenta = $this->request->request->get('codsubcuenta'); - $subaccount->tipo = Subcuenta130::TIPO_INGRESO; - if (false === $subaccount->save()) { - Tools::log()->error('record-save-error'); - return; - } - - Tools::log()->notice('record-updated-correctly'); - } - - protected function autocompleteSubaccount(): void - { - $this->setTemplate(false); - - $list = []; - $term = $this->request->get('term'); - $sql = 'SELECT DISTINCT codsubcuenta, descripcion FROM subcuentas WHERE codsubcuenta LIKE "' . $term . '%";'; - - foreach ($this->dataBase->select($sql) as $value) { - $list[] = [ - 'key' => Tools::fixHtml($value['codsubcuenta']), - 'value' => Tools::fixHtml($value['descripcion']) - ]; - } - - if (empty($list)) { - $list[] = ['key' => null, 'value' => Tools::lang()->trans('no-data')]; - } - - $this->response->setContent(json_encode($list)); + $response->send(); + exit; } protected function createAccountingEntry(): void @@ -299,52 +229,17 @@ protected function createAccountingEntry(): void $amount = (float)$this->request->request->get('amount'); $paymentMethodId = (int)$this->request->request->get('paymentMethod'); - if (DinModelo130::generateEntries($idempresa, $codejercicio, $period, $date, $amount, $paymentMethodId)) { + if ( + DinModelo130::generateEntries( + $idempresa, + $codejercicio, + $period, + $date, + $amount, + $paymentMethodId + ) + ) { Tools::log()->notice('record-updated-correctly'); } } - - protected function deleteDeductibleSubaccount(): void - { - $this->activeTab = 'deductible-subaccount'; - - if (false === $this->validateFormToken()) { - return; - } - - $subaccount130 = new Subcuenta130(); - if (false === $subaccount130->load($this->request->request->get('id'))) { - Tools::log()->error('record-not-found'); - return; - } - - if (false === $subaccount130->delete()) { - Tools::log()->error('record-deleted-error'); - return; - } - - Tools::log()->notice('record-deleted-correctly'); - } - - protected function deleteIncomeSubaccount(): void - { - $this->activeTab = 'income-subaccount'; - - if (false === $this->validateFormToken()) { - return; - } - - $subaccount = new Subcuenta130(); - if (false === $subaccount->load($this->request->request->get('id'))) { - Tools::log()->error('record-not-found'); - return; - } - - if (false === $subaccount->delete()) { - Tools::log()->error('record-deleted-error'); - return; - } - - Tools::log()->notice('record-deleted-correctly'); - } -} +} \ No newline at end of file diff --git a/Data/Lang/ES/subcuentas_130.csv b/Data/Lang/ES/subcuentas_130.csv deleted file mode 100644 index 8ba0807..0000000 --- a/Data/Lang/ES/subcuentas_130.csv +++ /dev/null @@ -1,3 +0,0 @@ -"codsubcuenta" -"6420000000" -"4730000000" \ No newline at end of file diff --git a/Init.php b/Init.php index 98da1ff..db8eed9 100644 --- a/Init.php +++ b/Init.php @@ -33,33 +33,5 @@ public function uninstall(): void public function update(): void { - $this->cleanInvalidUsers(); - } - - private function cleanInvalidUsers(): void - { - // ver si existe la tabla subcuentas o usuario - $db = new DataBase(); - if (false === $db->tableExists('subcuentas_130') || - false === $db->tableExists('users')) { - return; - } - - // consulta compatible con mysql y postgresql - // elimina el registro foráneo si no existe en la tabla original - $templateSql = "UPDATE subcuentas_130 - SET REPLACE_COLUMN = NULL - WHERE REPLACE_COLUMN IS NOT NULL - AND NOT EXISTS ( - SELECT 1 - FROM users - WHERE users.nick = subcuentas_130.REPLACE_COLUMN - );"; - - foreach (['nick', 'last_nick'] as $column) { - // reemplazar la columna de user en la tabla subcuentas_130 y ejecutar - $sql = str_replace("REPLACE_COLUMN", $column, $templateSql); - $db->exec($sql); - } } } diff --git a/Lib/Modelo130.php b/Lib/Modelo130.php index b84b556..483676e 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 bool */ + protected static $currentPaymentEntryExists = false; 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,64 @@ 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::$currentPaymentEntryExists = false; 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, + 'currentPaymentEntryExists' => static::$currentPaymentEntryExists, 'applyGastosJustificacion' => $applyGastosJustificacion, 'todeduct' => $todeduct, 'gastosJustificacionPct' => $gastosJustificacionPct, ], $results); } - public static function generateEntries(int $idempresa, string $codejercicio, string $period, string $date, float $amount, ?string $paymentMethodId): bool - { + public static function generateEntries( + int $idempresa, + string $codejercicio, + string $period, + string $date, + float $amount, + ?string $paymentMethodId + ): bool { $asiento = new Asiento(); - $concepto = Tools::trans('acc-concept-irpf-130', ['%period%' => $period]); + $concepto = Tools::trans( + 'acc-concept-irpf-130', + ['%period%' => $period] + ); // si ya existe un asiento igual, no lo creamos - if ($asiento->loadWhere( - [ - Where::eq('codejercicio', $codejercicio), - Where::eq('concepto', $concepto), - ] - )) { - Tools::log()->warning('exists-accounting-130', ['%codejercicio%' => $codejercicio, '%concepto%' => $concepto]); + if ($asiento->loadWhere([ + Where::eq('codejercicio', $codejercicio), + Where::eq('concepto', $concepto), + ])) { + Tools::log()->warning('exists-accounting-130', [ + '%codejercicio%' => $codejercicio, + '%concepto%' => $concepto, + ]); + return false; } @@ -129,6 +185,7 @@ public static function generateEntries(int $idempresa, string $codejercicio, str $asiento->concepto = $concepto; $asiento->fecha = $date; $asiento->importe = $amount; + if (false === $asiento->save()) { return false; } @@ -138,12 +195,15 @@ public static function generateEntries(int $idempresa, string $codejercicio, str $partida1->concepto = $asiento->concepto; $partida1->debe = $amount; $partida1->codsubcuenta = '4730000000'; + if (false === $partida1->save()) { $asiento->delete(); return false; } + $bankAccount = null; $paymentMethod = new FormaPago(); + if ($paymentMethod->load($paymentMethodId)) { $bankAccount = $paymentMethod->getBankAccount(); } @@ -152,29 +212,102 @@ public static function generateEntries(int $idempresa, string $codejercicio, str $partida2->idasiento = $asiento->idasiento; $partida2->concepto = $asiento->concepto; $partida2->haber = $amount; - $partida2->codsubcuenta = !empty($bankAccount->codsubcuenta) ? $bankAccount->codsubcuenta : '5720000000'; + $partida2->codsubcuenta = !empty($bankAccount->codsubcuenta) + ? $bankAccount->codsubcuenta + : '5720000000'; + if (false === $partida2->save()) { $asiento->delete(); return false; } - Tools::log()->notice('accounting-created-130', ['%codejercicio%' => $codejercicio, '%concepto%' => $concepto]); + Tools::log()->notice('accounting-created-130', [ + '%codejercicio%' => $codejercicio, + '%concepto%' => $concepto, + ]); + return true; } - protected static function getAccountingEntrySubaccounts(): array - { - $codsubs = []; - $where = [Where::eq('tipo', Subcuenta130::TIPO_DEDUCIBLE)]; - foreach ((new Subcuenta130())->all($where, [], 0, 0) as $subaccount) { - $codsubs[] = $subaccount->codsubcuenta; + /** + * Calcula la deducción por gastos de difícil justificación aplicando el + * porcentaje indicado sobre la base y topándola al límite anual (2.000 €). + */ + public static function calcGastosJustificacion( + float $taxbase, + bool $apply, + float $gastosJustificacionPct = 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 +315,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::$currentPaymentEntryExists = true; + } + + $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..cfb10e7 --- /dev/null +++ b/Lib/Modelo130Accounts.php @@ -0,0 +1,320 @@ + + * + * 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'; + + /** + * Cuentas de gastos computables. + * + * Las variaciones de existencias 61 y 71 se tratan por separado porque + * su clasificación depende del saldo deudor o acreedor acumulado. + * + * @return array + */ + public static function expenses(): array + { + return [ + '60' => 'Compras', + '62' => 'Servicios exteriores', + '63' => 'Tributos', + '64' => 'Gastos de personal', + '65' => 'Otros gastos de gestión', + '66' => 'Gastos financieros', + '67' => 'Pérdidas procedentes de activos no corrientes y gastos excepcionales', + '68' => 'Dotaciones para amortizaciones', + '69' => 'Pérdidas por deterioro y otras dotaciones', + ]; + } + + /** + * Cuentas excluidas aunque pertenezcan a un subgrupo de gastos. + * + * Se excluyen: + * + * - el impuesto sobre beneficios y sus ajustes; + * - resultados de instrumentos financieros y créditos no comerciales; + * - pérdidas derivadas de activos no corrientes; + * - gastos excepcionales, por ser una cuenta mixta utilizada habitualmente + * para sanciones, recargos y otros conceptos no deducibles; + * - deterioros fiscalmente no deducibles; + * - provisiones calculadas posteriormente por el módulo fiscal. + * + * @return array + */ + public static function excludedExpenses(): array + { + return [ + '630' => 'Impuesto sobre beneficios', + '633' => 'Ajustes negativos en la imposición sobre beneficios', + '638' => 'Ajustes positivos en la imposición sobre beneficios', + + '660' => 'Actualización financiera de provisiones calculadas fuera de esta clase', + '663' => 'Pérdidas por valoración de instrumentos financieros', + '664' => 'Dividendos de instrumentos considerados pasivos financieros', + '666' => 'Pérdidas en participaciones y valores representativos de deuda', + '667' => 'Pérdidas de créditos no comerciales', + + '670' => 'Pérdidas procedentes del inmovilizado intangible', + '671' => 'Pérdidas procedentes del inmovilizado material', + '672' => 'Pérdidas procedentes de inversiones inmobiliarias', + '673' => 'Pérdidas procedentes de participaciones a largo plazo', + '675' => 'Pérdidas por operaciones con obligaciones propias', + '678' => 'Gastos excepcionales excluidos por defecto', + + '690' => 'Deterioro del inmovilizado intangible', + '691' => 'Deterioro del inmovilizado material', + '692' => 'Deterioro de inversiones inmobiliarias', + '695' => 'Provisiones calculadas posteriormente por el módulo fiscal', + '696' => 'Deterioro de participaciones y valores a largo plazo', + '697' => 'Deterioro de créditos no comerciales a largo plazo', + '698' => 'Deterioro de participaciones y valores a corto plazo', + '699' => 'Deterioro de créditos no comerciales a corto plazo', + ]; + } + + /** + * Cuentas de ingresos computables. + * + * El subgrupo 76 no se incluye porque el código contable no permite + * distinguir los intereses comerciales computables de los rendimientos + * del capital mobiliario ajenos al Modelo 130. + * + * Las variaciones de existencias 61 y 71 se tratan por separado. + * + * @return array + */ + public static function incomes(): array + { + return [ + '70' => 'Ventas de mercaderías, de producción propia, de servicios, etc.', + '73' => 'Trabajos realizados para la empresa', + '74' => 'Subvenciones, donaciones y legados', + '75' => 'Otros ingresos de gestión', + '77' => 'Beneficios procedentes de activos no corrientes e ingresos excepcionales', + '79' => 'Excesos y aplicaciones de provisiones y de pérdidas por deterioro', + ]; + } + + /** + * Cuentas excluidas aunque pertenezcan a un subgrupo de ingresos. + * + * Se mantienen como ingresos computables del grupo 77 únicamente los + * ingresos excepcionales de la cuenta 778, y del grupo 79 las reversiones + * de existencias y créditos comerciales de las cuentas 793 y 794. + * + * @return array + */ + public static function excludedIncomes(): array + { + return [ + '770' => 'Beneficios procedentes del inmovilizado intangible', + '771' => 'Beneficios procedentes del inmovilizado material', + '772' => 'Beneficios procedentes de inversiones inmobiliarias', + '773' => 'Beneficios procedentes de participaciones a largo plazo', + '774' => 'Diferencia negativa en combinaciones de negocios', + '775' => 'Beneficios por operaciones con obligaciones propias', + + '790' => 'Reversión del deterioro del inmovilizado intangible', + '791' => 'Reversión del deterioro del inmovilizado material', + '792' => 'Reversión del deterioro de inversiones inmobiliarias', + '795' => 'Excesos de provisiones calculadas fuera de esta clase', + '796' => 'Reversión de deterioros financieros a largo plazo', + '797' => 'Reversión de deterioros de créditos no comerciales a largo plazo', + '798' => 'Reversión de deterioros financieros a corto plazo', + '799' => 'Reversión de deterioros de créditos no comerciales a corto plazo', + ]; + } + + /** + * Cuentas de variación de existencias. + * + * Un saldo deudor se lleva a gastos y un saldo acreedor a ingresos. + * + * @return array + */ + public static function stockVariations(): array + { + return [ + '61' => 'Variación de existencias de compras', + '71' => 'Variación de existencias de producción', + ]; + } + + /** + * Comprueba si una subcuenta debe considerarse gasto. + * + * No incluye las cuentas 61 y 71, que deben procesarse con los métodos + * stockVariationExpense() y stockVariationIncome(). + */ + public static function isExpense(string $code): bool + { + $code = trim($code); + + if ($code === '') { + return false; + } + + if (static::matches($code, array_keys(static::excludedExpenses()))) { + return false; + } + + return static::matches( + $code, + array_keys(static::expenses()) + ); + } + + /** + * Comprueba si una subcuenta debe considerarse ingreso. + * + * No incluye las cuentas 61 y 71, que deben procesarse con los métodos + * stockVariationExpense() y stockVariationIncome(). + */ + public static function isIncome(string $code): bool + { + $code = trim($code); + + if ($code === '') { + return false; + } + + if (static::matches($code, array_keys(static::excludedIncomes()))) { + return false; + } + + return static::matches( + $code, + array_keys(static::incomes()) + ); + } + + /** + * Comprueba si una subcuenta corresponde a una variación de existencias. + */ + public static function isStockVariation(string $code): bool + { + return static::matches( + trim($code), + array_keys(static::stockVariations()) + ); + } + + /** + * Devuelve la parte de una variación de existencias que debe computarse + * como gasto. + * + * Los importes deben ser los acumulados desde el 1 de enero. + */ + public static function stockVariationExpense( + float $debit, + float $credit + ): float { + return max($debit - $credit, 0.0); + } + + /** + * Devuelve la parte de una variación de existencias que debe computarse + * como ingreso. + * + * Los importes deben ser los acumulados desde el 1 de enero. + */ + public static function stockVariationIncome( + float $debit, + float $credit + ): float { + return max($credit - $debit, 0.0); + } + + /** + * Comprueba si una subcuenta pertenece a la cuenta 473. + */ + public static function isWithholding(string $code): bool + { + return str_starts_with( + trim($code), + static::WITHHOLDING_ACCOUNT + ); + } + + /** + * Devuelve todos los prefijos que deben buscarse en contabilidad. + * + * Se usa para construir la consulta SQL inicial y evitar cargar partidas + * que nunca van a intervenir en el Modelo 130. + * + * @return string[] + */ + public static function queryPrefixes(): array + { + return array_values(array_unique(array_merge( + array_keys(static::expenses()), + array_keys(static::incomes()), + array_keys(static::stockVariations()), + [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/Model/Subcuenta130.php b/Model/Subcuenta130.php deleted file mode 100644 index 8d0f7e7..0000000 --- a/Model/Subcuenta130.php +++ /dev/null @@ -1,119 +0,0 @@ - - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -namespace FacturaScripts\Plugins\Modelo130\Model; - -use FacturaScripts\Core\Session; -use FacturaScripts\Core\Template\ModelClass; -use FacturaScripts\Core\Template\ModelTrait; -use FacturaScripts\Core\Tools; -use FacturaScripts\Core\Where; -use FacturaScripts\Dinamic\Model\Subcuenta; -use FacturaScripts\Dinamic\Model\User; - -class Subcuenta130 extends ModelClass -{ - use ModelTrait; - - const TIPO_DEDUCIBLE = 'deducible'; - const TIPO_INGRESO = 'ingreso'; - - /** @var string */ - public $codsubcuenta; - - /** @var string */ - public $creation_date; - - /** @var int */ - public $id; - - /** @var string */ - public $last_nick; - - /** @var string */ - public $last_update; - - /** @var string */ - public $name; - - /** @var string */ - public $nick; - - /** @var string */ - public $tipo = self::TIPO_DEDUCIBLE; - - public function clear(): void - { - parent::clear(); - $this->tipo = self::TIPO_DEDUCIBLE; - } - - public function getSubcuenta(): Subcuenta - { - $subcuenta = new Subcuenta(); - $where = [Where::eq('codsubcuenta', $this->codsubcuenta)]; - $subcuenta->loadWhere($where); - return $subcuenta; - } - - public function install(): string - { - new User(); - new Subcuenta(); - - return parent::install(); - } - - public static function tableName(): string - { - return "subcuentas_130"; - } - - public function test(): bool - { - if (empty($this->id())) { - $this->creation_date = Tools::dateTime(); - $this->last_nick = null; - $this->last_update = null; - $this->nick = Session::user()->nick; - } else { - $this->creation_date = $this->creationdate ?? Tools::dateTime(); - $this->last_nick = Session::user()->nick; - $this->last_update = Tools::dateTime(); - $this->nick = $this->nick ?? Session::user()->nick; - } - - $this->codsubcuenta = trim(Tools::noHtml((string)$this->codsubcuenta)); - $this->name = Tools::noHtml($this->name); - $this->tipo = in_array($this->tipo, [self::TIPO_DEDUCIBLE, self::TIPO_INGRESO], true) - ? $this->tipo - : self::TIPO_DEDUCIBLE; - - if (strlen($this->codsubcuenta) < 1 || strlen($this->codsubcuenta) > 15) { - Tools::log()->warning('invalid-column-lenght', [ - '%column%' => 'codsubcuenta', - '%min%' => '1', - '%max%' => '15' - ]); - return false; - } - - return parent::test(); - } -} diff --git a/Table/subcuentas_130.xml b/Table/subcuentas_130.xml deleted file mode 100644 index 0bd5775..0000000 --- a/Table/subcuentas_130.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - codsubcuenta - character varying(15) - NO - - - creation_date - timestamp - - - id - serial - NO - - - last_nick - character varying(50) - - - last_update - timestamp - - - name - character varying(100) - - - nick - character varying(50) - - - tipo - character varying(20) - 'deducible' - NO - - - subcuentas_130_pkey - PRIMARY KEY (id) - - - ca_subcuentas_130_users_last_nick - FOREIGN KEY (last_nick) REFERENCES users (nick) ON DELETE SET NULL ON UPDATE CASCADE - - - ca_subcuentas_130_users_nick - FOREIGN KEY (nick) REFERENCES users (nick) ON DELETE SET NULL ON UPDATE CASCADE - - - uniq_subcuentas_130 - UNIQUE (codsubcuenta) - -
\ No newline at end of file diff --git a/Test/main/Modelo130Test.php b/Test/main/Modelo130Test.php index 86d4091..f4c4bcf 100644 --- a/Test/main/Modelo130Test.php +++ b/Test/main/Modelo130Test.php @@ -24,6 +24,7 @@ use FacturaScripts\Dinamic\Model\Asiento; use FacturaScripts\Dinamic\Model\Ejercicio; use FacturaScripts\Dinamic\Model\FormaPago; +use FacturaScripts\Dinamic\Model\Partida; use FacturaScripts\Plugins\Modelo130\Lib\Modelo130; use FacturaScripts\Test\Traits\DefaultSettingsTrait; use FacturaScripts\Test\Traits\LogErrorsTrait; @@ -70,10 +71,9 @@ public function testGenerate(): void 'exercise', 'period', 'idempresa', - 'customerInvoices', - 'supplierInvoices', + 'sales', + 'purchases', 'accountingEntries', - 'incomeEntries', 'applyGastosJustificacion', 'todeduct', 'gastosJustificacionPct', @@ -84,6 +84,7 @@ public function testGenerate(): void 'gastosJustificacion', 'afterdeduct', 'positivosTrimestres', + 'fractionalPayment', 'result', ]; @@ -94,6 +95,9 @@ public function testGenerate(): void // verificar tipos concretos de las claves principales $this->assertInstanceOf(Ejercicio::class, $resultado['exercise'], 'exercise debe ser una instancia de Ejercicio'); $this->assertSame('T1', $resultado['period'], 'El periodo debe ser T1'); + $this->assertIsArray($resultado['sales'], 'sales debe ser un array'); + $this->assertIsArray($resultado['purchases'], 'purchases debe ser un array'); + $this->assertIsArray($resultado['accountingEntries'], 'accountingEntries debe ser un array'); // comprobar que el ejercicio cargado es el correcto $this->assertSame($codejercicio, $resultado['exercise']->codejercicio, 'El ejercicio debe coincidir con el solicitado'); @@ -141,6 +145,17 @@ public function testGenerateEntries(): void $this->assertTrue($encontrado, 'El asiento debe existir en la base de datos tras generateEntries()'); $this->assertEquals($importe, $asiento->importe, 'El importe del asiento debe ser 100.0'); + // comprobar que el asiento contiene las dos partidas esperadas + $partidas = (new Partida())->all([ + Where::eq('idasiento', $asiento->idasiento), + ], ['orden' => 'ASC']); + + $this->assertCount(2, $partidas, 'El asiento debe contener dos partidas'); + $this->assertSame('4730000000', $partidas[0]->codsubcuenta); + $this->assertEquals($importe, $partidas[0]->debe); + $this->assertEquals(0.0, (float)$partidas[0]->haber); + $this->assertEquals($importe, $partidas[1]->haber); + // una segunda llamada con los mismos parámetros debe devolver false (ya existe) $duplicado = Modelo130::generateEntries($idempresa, $codejercicio, $periodo, $fecha, $importe, $paymentMethodId); $this->assertFalse($duplicado, 'generateEntries() debe devolver false si ya existe el asiento'); @@ -156,29 +171,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 +216,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..3071b0c 100644 --- a/Translation/ca_ES.json +++ b/Translation/ca_ES.json @@ -1,12 +1,21 @@ { "acc-concept-irpf-130": "Regularització d'IRPF %period%", "accounting-created-130": "S'ha creat el seient en l'exercici %codejercicio% amb el concepte: %concepte%", + "aeat-file-invalid-exercise": "No es pot generar el fitxer AEAT: l'exercici seleccionat no és vàlid.", + "aeat-file-invalid-length": "No es pot generar el fitxer AEAT: el fitxer generat no té la longitud esperada.", + "aeat-file-invalid-nif": "No es pot generar el fitxer AEAT: el NIF de l'empresa és buit o no és vàlid.", + "aeat-file-missing-company-name": "No es pot generar el fitxer AEAT: el nom de l'empresa és buit.", "after-deduct": "sobre casella 04", + "company-not-found": "Empresa no trobada", "deductible-subaccounts": "Comptes deduïbles", + "download-aeat-file": "Descarrega el fitxer AEAT", "exists-accounting-130": "Ja existeix un seient en l'exercici %codejercicio% amb el concepte: %concepte%", + "expense": "Despesa deduïble", + "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-desc": "Es calcula sumant i acumulant cada trimestre, per exemple, si visualitzem el 3r trimestre veurem la suma dels trimestres 1, 2 i 3.", @@ -15,7 +24,8 @@ "net-total": "Rendiment net", "pct-to-deduct": "% a deduir", "previous-model": "Ingressos trimestres anteriors", + "previous-payment": "Pagament fraccionat anterior", "tax-expenses": "Despeses deduïbles", "tax-incomes": "Ingressos computables", - "tax-negative-info": "Si dóna pèrdues (negatiu) = 0" -} \ 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..86fa895 100644 --- a/Translation/cs_CZ.json +++ b/Translation/cs_CZ.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 04", "deductible-subaccounts": "Odečitatelné účty", "exists-accounting-130": "V účetním období %codejercicio% již existuje účetní zápis s popisem: %concepto%", + "fractional-payment": "Zálohová platba za předchozí čtvrtletí", "gastos-justificacion": "Výdaje obtížně ospravedlnitelné", "gastos-justificacion-limit": "Pozor: částka překračuje roční limit 2 000 €", "gastos-justificacion-pct": "% Výdaje obtížně ospravedlnitelné", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ 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..90b9c08 100644 --- a/Translation/de_DE.json +++ b/Translation/de_DE.json @@ -1,12 +1,21 @@ { "acc-concept-irpf-130": "IRPF-Anpassung %period%", "accounting-created-130": "Der Buchungssatz wurde im Geschäftsjahr %codejercicio% mit dem Verwendungszweck: %concepto% erstellt.", + "aeat-file-invalid-exercise": "Die AEAT-Datei kann nicht erstellt werden: Das ausgewählte Geschäftsjahr ist ungültig.", + "aeat-file-invalid-length": "Die AEAT-Datei kann nicht erstellt werden: Die erzeugte Datei hat nicht die erwartete Länge.", + "aeat-file-invalid-nif": "Die AEAT-Datei kann nicht erstellt werden: Die Steuer-ID des Unternehmens fehlt oder ist ungültig.", + "aeat-file-missing-company-name": "Die AEAT-Datei kann nicht erstellt werden: Der Firmenname fehlt.", "after-deduct": "nach Feld 04", + "company-not-found": "Unternehmen nicht gefunden", "deductible-subaccounts": "Abziehbare Unterkonten", + "download-aeat-file": "AEAT-Datei herunterladen", "exists-accounting-130": "Es existiert bereits ein Buchungssatz im Geschäftsjahr %codejercicio% mit dem Verwendungszweck: %concepto%", + "expense": "Abzugsfähige Ausgabe", + "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-desc": "Es wird berechnet, indem jeder Quartal summiert und akkumuliert wird. Zum Beispiel, wenn wir das 3. Quartal betrachten, sehen wir die Summe der Quartale 1, 2 und 3.", @@ -15,7 +24,8 @@ "net-total": "Nettoertrag", "pct-to-deduct": "% abzuziehen", "previous-model": "Einkünfte aus vorherigen Quartalen", + "previous-payment": "Vorherige Vorauszahlung", "tax-expenses": "Absetzbare Ausgaben", "tax-incomes": "Steuerbare Einkommen", - "tax-negative-info": "Wenn Verluste (negativ) = 0 sind" -} \ 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..cdbfd56 100644 --- a/Translation/en_EN.json +++ b/Translation/en_EN.json @@ -1,18 +1,21 @@ { "acc-concept-irpf-130": "IRPF regularization %period%", + "accounting-created-130": "An accounting entry has been created in fiscal year %codejercicio% with the concept: %concepto%", "aeat-file-invalid-exercise": "Cannot generate the AEAT file: the selected fiscal year is not valid.", "aeat-file-invalid-length": "Cannot generate the AEAT file: the generated file does not have the expected length.", - "aeat-file-invalid-nif": "Cannot generate the AEAT file: the company tax ID is empty or not valid.", + "aeat-file-invalid-nif": "Cannot generate the AEAT file: the company's tax identification number is missing or invalid.", "aeat-file-missing-company-name": "Cannot generate the AEAT file: the company name is empty.", - "company-not-found": "Company not found", - "download-aeat-file": "Download AEAT file", - "accounting-created-130": "An accounting entry has been created in fiscal year %codejercicio% with the concept: %concepto%", "after-deduct": "about box 04", + "company-not-found": "Company not found", "deductible-subaccounts": "Deductible Accounts", + "download-aeat-file": "Download AEAT file", "exists-accounting-130": "An accounting entry already exists in fiscal year %codejercicio% with the concept: %concepto%", + "expense": "Deductible expense", + "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-desc": "It is calculated by adding and accumulating each quarter, for example, if we visualize the 3rd quarter we will see the sum of quarters 1, 2 and 3.", @@ -21,7 +24,8 @@ "net-total": "Net return", "pct-to-deduct": "% to deduct", "previous-model": "Previous quarters income", + "previous-payment": "Previous payment", "tax-expenses": "Deductible expenses", "tax-incomes": "Computable income", - "tax-negative-info": "If it gives losses (negative) = 0" -} \ 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..b2ba2d4 100644 --- a/Translation/es_AR.json +++ b/Translation/es_AR.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ 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..b2ba2d4 100644 --- a/Translation/es_CL.json +++ b/Translation/es_CL.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ 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..b2ba2d4 100644 --- a/Translation/es_CO.json +++ b/Translation/es_CO.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ 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..b2ba2d4 100644 --- a/Translation/es_CR.json +++ b/Translation/es_CR.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ 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..b2ba2d4 100644 --- a/Translation/es_DO.json +++ b/Translation/es_DO.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ 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..b2ba2d4 100644 --- a/Translation/es_EC.json +++ b/Translation/es_EC.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ 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..0653fa3 100644 --- a/Translation/es_ES.json +++ b/Translation/es_ES.json @@ -1,18 +1,21 @@ { "acc-concept-irpf-130": "Regularización de IRPF %period%", + "accounting-created-130": "Se ha creado el asiento en el ejercicio %codejercicio% con el concepto: %concepto%", "aeat-file-invalid-exercise": "No se puede generar el fichero AEAT: el ejercicio seleccionado no es válido.", "aeat-file-invalid-length": "No se puede generar el fichero AEAT: el fichero generado no tiene la longitud esperada.", "aeat-file-invalid-nif": "No se puede generar el fichero AEAT: el NIF de la empresa está vacío o no es válido.", "aeat-file-missing-company-name": "No se puede generar el fichero AEAT: el nombre de la empresa está vacío.", - "company-not-found": "Empresa no encontrada", - "download-aeat-file": "Descargar fichero AEAT", - "accounting-created-130": "Se ha creado el asiento en el ejercicio %codejercicio% con el concepto: %concepto%", "after-deduct": "sobre casilla 03", + "company-not-found": "Empresa no encontrada", "deductible-subaccounts": "Cuentas deducibles", + "download-aeat-file": "Descargar fichero AEAT", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "expense": "Gasto deducible", + "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-desc": "Se calcula sumando y acumulando cada trimestre, por ejemplo, si visualizamos el 3º trimestre veremos la suma de los trimestres 1, 2 y 3.", @@ -21,7 +24,8 @@ "net-total": "Rendimiento neto", "pct-to-deduct": "% a deducir", "previous-model": "Ingresos trimestres anteriores", + "previous-payment": "Pago fraccionado anterior", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ 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..b2ba2d4 100644 --- a/Translation/es_GT.json +++ b/Translation/es_GT.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ 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..b2ba2d4 100644 --- a/Translation/es_MX.json +++ b/Translation/es_MX.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ 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..f67fd46 100644 --- a/Translation/es_PA.json +++ b/Translation/es_PA.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ 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..b2ba2d4 100644 --- a/Translation/es_PE.json +++ b/Translation/es_PE.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ 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..b2ba2d4 100644 --- a/Translation/es_UY.json +++ b/Translation/es_UY.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casilla 03", "deductible-subaccounts": "Cuentas deducibles", "exists-accounting-130": "Ya existe un asiento en el ejercicio %codejercicio% con el concepto: %concepto%", + "fractional-payment": "Pago fraccionado previo trimestre", "gastos-justificacion": "Gastos de difícil justificación", "gastos-justificacion-limit": "Atención: el importe supera el límite anual de 2.000 €", "gastos-justificacion-pct": "% Gastos de difícil justificación", @@ -17,5 +18,5 @@ "previous-model": "Ingresos trimestres anteriores", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Si da pérdidas (negativo) = 0" -} \ 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..1ea38f7 100644 --- a/Translation/eu_ES.json +++ b/Translation/eu_ES.json @@ -1,12 +1,21 @@ { "acc-concept-irpf-130": "IRPFaren zuzenketa %period%", "accounting-created-130": "%codejercicio% urterako asientoa sortu da honako kontzeptuarekin: %concepto%", + "aeat-file-invalid-exercise": "Ezin da AEAT fitxategia sortu: hautatutako ekitaldia ez da baliozkoa.", + "aeat-file-invalid-length": "Ezin da AEAT fitxategia sortu: sortutako fitxategiak ez du espero zen luzera.", + "aeat-file-invalid-nif": "Ezin da AEAT fitxategia sortu: enpresaren IFZ hutsik dago edo ez da baliozkoa.", + "aeat-file-missing-company-name": "Ezin da AEAT fitxategia sortu: enpresaren izena hutsik dago.", "after-deduct": "04 koadroari buruz", + "company-not-found": "Enpresa ez da aurkitu", "deductible-subaccounts": "Deduzi daitezkeen azpis kontuak", + "download-aeat-file": "Deskargatu AEAT fitxategia", "exists-accounting-130": "Dagoeneko badago idazpen bat %codejercicio% ekitaldian %concepto% kontzeptuarekin", + "expense": "Gastu kengarria", + "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-desc": "Hiruhileko bakoitzean gehienetan batu eta akumulatu egiten da, adibidez, hirugarren hiruhilekoko informazioa ikusten dugunean, 1., 2. eta 3. hiruhilekoak batu eta akumulatu ikusiko dugu.", @@ -15,7 +24,8 @@ "net-total": "Etekin garbia", "pct-to-deduct": "Kentzeko %", "previous-model": "Aurreko hiruhilekoetako sarrerak", + "previous-payment": "Aurreko ordainketa zatikatua", "tax-expenses": "Gastu kengarriak", "tax-incomes": "Errenta konputagarria", - "tax-negative-info": "Galerak ematen baditu (negatiboak) = 0" -} \ 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..505e1bb 100644 --- a/Translation/fr_FR.json +++ b/Translation/fr_FR.json @@ -1,12 +1,21 @@ { "acc-concept-irpf-130": "Régularisation de l'IRPF %period%", "accounting-created-130": "L'écriture a été créée dans l'exercice %codejercicio% avec le libellé : %concepto%", + "aeat-file-invalid-exercise": "Impossible de générer le fichier AEAT : l'exercice sélectionné n'est pas valide.", + "aeat-file-invalid-length": "Impossible de générer le fichier AEAT : le fichier généré n'a pas la longueur attendue.", + "aeat-file-invalid-nif": "Impossible de générer le fichier AEAT : le numéro fiscal de l'entreprise est vide ou invalide.", + "aeat-file-missing-company-name": "Impossible de générer le fichier AEAT : le nom de l'entreprise est vide.", "after-deduct": "sur case 04", + "company-not-found": "Entreprise introuvable", "deductible-subaccounts": "Comptes déductibles", + "download-aeat-file": "Télécharger le fichier AEAT", "exists-accounting-130": "Il existe déjà une écriture pour l'exercice %codejercicio% avec le libellé : %concepto%", + "expense": "Dépense déductible", + "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-desc": "Il est calculé en ajoutant et cumulant chaque trimestre, par exemple, si nous visualisons le 3ème trimestre, nous verrons la somme des trimestres 1, 2 et 3.", @@ -15,7 +24,8 @@ "net-total": "Rendement net", "pct-to-deduct": "% déduire", "previous-model": "Revenu du trimestre précédent", + "previous-payment": "Paiement fractionné précédent", "tax-expenses": "Dépenses déductibles", "tax-incomes": "Revenu éligible", - "tax-negative-info": "Si déficitaire (négatif) = 0" -} \ 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..dd03910 100644 --- a/Translation/gl_ES.json +++ b/Translation/gl_ES.json @@ -1,12 +1,21 @@ { "acc-concept-irpf-130": "Regularización do IRPF %period%", "accounting-created-130": "Creouse o asento no exercicio %codejercicio% co concepto: %concepto%", + "aeat-file-invalid-exercise": "Non se pode xerar o ficheiro AEAT: o exercicio seleccionado non é válido.", + "aeat-file-invalid-length": "Non se pode xerar o ficheiro AEAT: o ficheiro xerado non ten a lonxitude esperada.", + "aeat-file-invalid-nif": "Non se pode xerar o ficheiro AEAT: o NIF da empresa está baleiro ou non é válido.", + "aeat-file-missing-company-name": "Non se pode xerar o ficheiro AEAT: o nome da empresa está baleiro.", "after-deduct": "sobre casa 04", + "company-not-found": "Empresa non atopada", "deductible-subaccounts": "Contas deducibles", + "download-aeat-file": "Descargar ficheiro AEAT", "exists-accounting-130": "Xa existe un asento no exercicio %codejercicio% co concepto: %concepto%", + "expense": "Gasto deducible", + "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-desc": "Calcúlase sumando e acumulando cada trimestre, por exemplo, se visualizamos o 3º trimestre veremos a suma dos trimestres 1, 2 e 3.", @@ -15,7 +24,8 @@ "net-total": "Rendemento neto", "pct-to-deduct": "% a deducir", "previous-model": "Ingresos trimestres anteriores", + "previous-payment": "Pagamento fraccionado anterior", "tax-expenses": "Gastos deducibles", "tax-incomes": "Ingresos computables", - "tax-negative-info": "Se dá perdas (negativo) = 0" -} \ 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..15d07e4 100644 --- a/Translation/it_IT.json +++ b/Translation/it_IT.json @@ -1,12 +1,21 @@ { "acc-concept-irpf-130": "Regolarizzazione IRPF %period%", "accounting-created-130": "È stata creata la registrazione nell'esercizio %codejercicio% con la causale: %concepto%", + "aeat-file-invalid-exercise": "Impossibile generare il file AEAT: l'esercizio selezionato non è valido.", + "aeat-file-invalid-length": "Impossibile generare il file AEAT: il file generato non ha la lunghezza prevista.", + "aeat-file-invalid-nif": "Impossibile generare il file AEAT: il codice fiscale dell'azienda è mancante o non valido.", + "aeat-file-missing-company-name": "Impossibile generare il file AEAT: il nome dell'azienda è vuoto.", "after-deduct": "sulla casella 04", + "company-not-found": "Azienda non trovata", "deductible-subaccounts": "Conti deducibili", + "download-aeat-file": "Scarica file AEAT", "exists-accounting-130": "Esiste già una registrazione nell'esercizio %codejercicio% con la causale: %concepto%", + "expense": "Spesa deducibile", + "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-desc": "Si calcola sommando e accumulando ogni trimestre, ad esempio, se visualizziamo il 3° trimestre vedremo la somma dei trimestri 1, 2 e 3.", @@ -15,7 +24,8 @@ "net-total": "Totale netto", "pct-to-deduct": "% da dedurre", "previous-model": "Modelli precedenti", + "previous-payment": "Acconto precedente", "tax-expenses": "Spese fiscali deducibili", "tax-incomes": "Ricavi fiscali", - "tax-negative-info": "Se genera perdite (negative) = 0" -} \ 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..febbec8 100644 --- a/Translation/pl_PL.json +++ b/Translation/pl_PL.json @@ -4,6 +4,7 @@ "after-deduct": "na rubrykę 04", "deductible-subaccounts": "Podkonty do odliczeń", "exists-accounting-130": "Istnieje już zapis księgowy w okresie %codejercicio% o treści: %concepto%", + "fractional-payment": "Zaliczka za poprzedni kwartał", "gastos-justificacion": "Wydatki trudne do uzasadnienia", "gastos-justificacion-limit": "Uwaga: kwota przekracza roczny limit 2 000 €", "gastos-justificacion-pct": "% Wydatki trudne do uzasadnienia", @@ -17,5 +18,5 @@ "previous-model": "Poprzednie kwartały dochodów", "tax-expenses": "Wydatki podatkowe", "tax-incomes": "Dochody opodatkowane", - "tax-negative-info": "Jeśli wykazuje straty (ujemny) = 0" -} \ 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..96ca3cf 100644 --- a/Translation/pt_BR.json +++ b/Translation/pt_BR.json @@ -1,12 +1,21 @@ { "acc-concept-irpf-130": "Regularização do IRPF %period%", "accounting-created-130": "Foi criado o lançamento no exercício %codejercicio% com o conceito: %concepto%", + "aeat-file-invalid-exercise": "Não é possível gerar o arquivo AEAT: o exercício selecionado é inválido.", + "aeat-file-invalid-length": "Não é possível gerar o arquivo AEAT: o arquivo gerado não possui o tamanho esperado.", + "aeat-file-invalid-nif": "Não é possível gerar o arquivo AEAT: o CNPJ/NIF da empresa está vazio ou é inválido.", + "aeat-file-missing-company-name": "Não é possível gerar o arquivo AEAT: o nome da empresa está vazio.", "after-deduct": "sobre caixa 04", + "company-not-found": "Empresa não encontrada", "deductible-subaccounts": "Contas dedutíveis", + "download-aeat-file": "Baixar arquivo AEAT", "exists-accounting-130": "Já existe um lançamento no exercício %codejercicio% com o conceito: %concepto%", + "expense": "Despesa dedutível", + "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-desc": "É calculado somando e acumulando cada trimestre, por exemplo, se visualizarmos o 3º trimestre veremos a soma dos trimestres 1, 2 e 3.", @@ -15,7 +24,8 @@ "net-total": "Total líquido", "pct-to-deduct": "% a deduzir", "previous-model": "Receitas de trimestres anteriores", + "previous-payment": "Pagamento por conta anterior", "tax-expenses": "Despesas dedutíveis", "tax-incomes": "Rendimentos tributáveis", - "tax-negative-info": "Se der negativo = 0" -} \ 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..b4979a6 100644 --- a/Translation/pt_PT.json +++ b/Translation/pt_PT.json @@ -1,12 +1,21 @@ { "acc-concept-irpf-130": "Regularização do IRPF %period%", "accounting-created-130": "Foi criado o lançamento no exercício %codejercicio% com o conceito: %concepto%", + "aeat-file-invalid-exercise": "Não é possível gerar o ficheiro AEAT: o exercício selecionado não é válido.", + "aeat-file-invalid-length": "Não é possível gerar o ficheiro AEAT: o ficheiro gerado não tem o comprimento esperado.", + "aeat-file-invalid-nif": "Não é possível gerar o ficheiro AEAT: o NIF da empresa está vazio ou é inválido.", + "aeat-file-missing-company-name": "Não é possível gerar o ficheiro AEAT: o nome da empresa está vazio.", "after-deduct": "sobre a caixa 04", + "company-not-found": "Empresa não encontrada", "deductible-subaccounts": "Contas dedutíveis", + "download-aeat-file": "Descarregar ficheiro AEAT", "exists-accounting-130": "Já existe um lançamento no exercício %codejercicio% com o conceito: %concepto%", + "expense": "Despesa dedutível", + "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-desc": "É calculado somando e acumulando cada trimestre, por exemplo, se visualizarmos o 3º trimestre veremos a soma dos trimestres 1, 2 e 3.", @@ -15,7 +24,8 @@ "net-total": "Rendimento líquido", "pct-to-deduct": "% a deduzir", "previous-model": "Receitas de trimestres anteriores", + "previous-payment": "Pagamento por conta anterior", "tax-expenses": "Despesas dedutíveis", "tax-incomes": "Impostos sobre rendimentos", - "tax-negative-info": "Se der perdas (negativo) = 0" -} \ 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..8f4f3ed 100644 --- a/Translation/tr_TR.json +++ b/Translation/tr_TR.json @@ -4,6 +4,7 @@ "after-deduct": "04 numaralı kutu hakkında", "deductible-subaccounts": "İndirilebilir alt hesaplar", "exists-accounting-130": "%codejercicio% döneminde %concepto% açıklamalı bir kayıt zaten mevcut.", + "fractional-payment": "Önceki çeyrek taksit ödemesi", "gastos-justificacion": "Gerekçelendirilmesi zor giderler", "gastos-justificacion-limit": "Dikkat: tutar yıllık 2.000 € limitini aşıyor", "gastos-justificacion-pct": "% Gerekçelendirilmesi zor giderler", @@ -17,5 +18,5 @@ "previous-model": "Önceki çeyrek gelirleri", "tax-expenses": "Düşürülebilir Harcamalar", "tax-incomes": "Gelir hesaplamaları", - "tax-negative-info": "Zarar veriyorsa (negatif) = 0" -} \ No newline at end of file + "tax-negative-info": "07 zarar verirse (negatif) = 0" +} diff --git a/Translation/va_ES.json b/Translation/va_ES.json index cf7c42d..b4d9f90 100644 --- a/Translation/va_ES.json +++ b/Translation/va_ES.json @@ -4,6 +4,7 @@ "after-deduct": "sobre casella 04", "deductible-subaccounts": "Comptes deduïbles", "exists-accounting-130": "Ja existeix un assentament a l'exercici %codejercicio% amb el concepte: %concepto%", + "fractional-payment": "Pagament fraccionat trimestre anterior", "gastos-justificacion": "Despeses de difícil justificació", "gastos-justificacion-limit": "Atenció: l'import supera el límit anual de 2.000 €", "gastos-justificacion-pct": "% Despeses de difícil justificació", @@ -17,5 +18,5 @@ "previous-model": "Ingressos trimestrals anteriors", "tax-expenses": "Despeses deduïbles", "tax-incomes": "Ingressos computables", - "tax-negative-info": "Si dóna pèrdues (negatiu) = 0" -} \ 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..46ea479 100644 --- a/View/Modelo130.html.twig +++ b/View/Modelo130.html.twig @@ -1,704 +1,462 @@ {% extends "Master/MenuTemplate.html.twig" %} -{% block css %} - {{ parent() }} - -{% endblock %} +{% block body %} +
+
+
+
+

+ + {{ fsc.title }} +

+

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

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

- - {{ fsc.title }} -

-

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

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

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

-
-

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

-

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

-
-
-
-

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

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

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

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

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

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

{{ trans('result') }}

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

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

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

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

+
+

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

+

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

+
+
+
+ +

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

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

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

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

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

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

{{ trans('result') }}

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

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

+
+
+ {% if fsc.result.result > 0 and not fsc.result.currentPaymentEntryExists %} +
+ +
+ {% 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) }}
+
+
+
- - + + {% endblock %}