Skip to content

Commit a3d6d74

Browse files
authored
Merge pull request #17 from Yopli2k/Model303_New
Model303 Nueva versión para Core 2025
2 parents 55a3b3e + 745371b commit a3d6d74

9 files changed

Lines changed: 559 additions & 474 deletions

Controller/EditRegularizacionImpuesto.php

Lines changed: 174 additions & 278 deletions
Large diffs are not rendered by default.

Controller/ListRegularizacionImpuesto.php

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,39 +49,47 @@ protected function createViews(): void
4949
$this->createViewsModel390();
5050
}
5151

52+
/**
53+
* Create the list view for Model 303.
54+
*
55+
* @param string $viewName
56+
* @return void
57+
*/
5258
protected function createViewsModel303(string $viewName = 'ListRegularizacionImpuesto'): void
5359
{
60+
$exercises = $this->codeModel->all('ejercicios', 'codejercicio', 'nombre');
5461
$this->addView($viewName, 'RegularizacionImpuesto', 'model-303', 'fa-solid fa-book')
62+
// Search and Orderby
63+
->addSearchFields(['codsubcuentaacr', 'codsubcuentadeu'])
5564
->addOrderBy(['fechainicio'], 'start-date', 2)
5665
->addOrderBy(['codejercicio||periodo'], 'period')
57-
->addSearchFields(['codsubcuentaacr', 'codsubcuentadeu']);
58-
59-
// añadimos filtros
60-
$this->addFilterSelectWhere($viewName, 'status', [
61-
['label' => Tools::lang()->trans('model-303'), 'where' => [new DataBaseWhere('periodo', 'Y', '!=')]]
62-
]);
63-
64-
$this->addFilterSelect($viewName, 'idempresa', 'company', 'idempresa', Empresas::codeModel());
65-
66-
$exercises = $this->codeModel->all('ejercicios', 'codejercicio', 'nombre');
67-
$this->addFilterSelect($viewName, 'codejercicio', 'exercise', 'codejercicio', $exercises);
66+
// Filters
67+
->addFilterSelect('idempresa', 'company', 'idempresa', Empresas::codeModel())
68+
->addFilterSelect('codejercicio', 'exercise', 'codejercicio', $exercises)
69+
->addFilterSelectWhere('status', [
70+
['label' => Tools::lang()->trans('model-303'), 'where' => [new DataBaseWhere('periodo', 'Y', '!=')]]
71+
]);
6872
}
6973

74+
/**
75+
* Create the list view for Model 390.
76+
*
77+
* @param string $viewName
78+
* @return void
79+
*/
7080
protected function createViewsModel390(string $viewName = 'ListRegularizacionImpuesto-390'): void
7181
{
82+
$exercises = $this->codeModel->all('ejercicios', 'codejercicio', 'nombre');
7283
$this->addView($viewName, 'RegularizacionImpuesto', 'model-390', 'fa-solid fa-book')
84+
// Search and Orderby
7385
->addOrderBy(['fechainicio'], 'start-date', 2)
7486
->addOrderBy(['codejercicio||periodo'], 'period')
75-
->addSearchFields(['codsubcuentaacr', 'codsubcuentadeu']);
76-
77-
// añadimos filtros
78-
$this->addFilterSelectWhere($viewName, 'status', [
79-
['label' => Tools::lang()->trans('model-390'), 'where' => [new DataBaseWhere('periodo', 'Y')]]
80-
]);
81-
82-
$this->addFilterSelect($viewName, 'idempresa', 'company', 'idempresa', Empresas::codeModel());
83-
84-
$exercises = $this->codeModel->all('ejercicios', 'codejercicio', 'nombre');
85-
$this->addFilterSelect($viewName, 'codejercicio', 'exercise', 'codejercicio', $exercises);
87+
->addSearchFields(['codsubcuentaacr', 'codsubcuentadeu'])
88+
// Filters
89+
->addFilterSelect('idempresa', 'company', 'idempresa', Empresas::codeModel())
90+
->addFilterSelect('codejercicio', 'exercise', 'codejercicio', $exercises)
91+
->addFilterSelectWhere('status', [
92+
['label' => Tools::lang()->trans('model-390'), 'where' => [new DataBaseWhere('periodo', 'Y')]]
93+
]);
8694
}
8795
}

Lib/Accounting/VatRegularizationToAccounting.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,12 @@ private function checkInvoicesWithoutAccEntry($reg): bool
175175
new DataBaseWhere('idasiento', 'IS NULL')
176176
];
177177

178-
$facturasClienteSinAsiento = FacturaCliente::all($where);
179-
180-
$facturasProveedorSinAsiento = FacturaProveedor::all($where);
181-
182-
if (count($facturasClienteSinAsiento) > 0 || count($facturasProveedorSinAsiento) > 0) {
178+
$facturasSinAsiento = FacturaCliente::all($where, [], 0, 1);
179+
if (false === empty($facturasSinAsiento)) {
183180
return false;
184181
}
185182

186-
return true;
183+
$facturasSinAsiento = FacturaProveedor::all($where, [], 0, 1);
184+
return empty($facturasSinAsiento);
187185
}
188186
}

Lib/Modelo303.php

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
<?php
2+
/**
3+
* This file is part of Modelo303 plugin for FacturaScripts
4+
* Copyright (C) 2017-2025 Carlos Garcia Gomez <carlos@facturascripts.com>
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Lesser General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
namespace FacturaScripts\Plugins\Modelo303\Lib;
20+
21+
use FacturaScripts\Core\DataSrc\Impuestos;
22+
use FacturaScripts\Core\Tools;
23+
use FacturaScripts\Plugins\Modelo303\Model\Join\PartidaImpuestoResumen;
24+
25+
/**
26+
* Class to handle Modelo 303 tax form data.
27+
*
28+
* @author Jose Antonio Cuello Principal <yopli2000@gmail.com>
29+
*/
30+
class Modelo303
31+
{
32+
private const MAX_SQUARE = 200;
33+
34+
/**
35+
* Stores all model squares.
36+
* Each key is the AEAT square number.
37+
* '01' => 0.00, '02' => 0.00, ...
38+
*/
39+
private array $square;
40+
41+
/**
42+
* Structure for know to assign values to squares.
43+
*
44+
* @var array<string, array<string, array<string, ?string>>>
45+
*/
46+
private array $casillaMap = [
47+
/*
48+
* IVA devengado (repercutido).
49+
*/
50+
// Ventas nacionales (régimen general)
51+
'IVAREP' => [
52+
'2' => ['base' => '165', 'cuota' => '167'],
53+
'4' => ['base' => '01', 'cuota' => '03'],
54+
'7.5' => ['base' => '153', 'cuota' => '155'],
55+
'10' => ['base' => '04', 'cuota' => '06'],
56+
'21' => ['base' => '07', 'cuota' => '09'],
57+
],
58+
59+
// Adquisiciones intracomunitarias
60+
'IVARUE' => ['21' => ['base' => '10', 'cuota' => '11']],
61+
62+
// Operaciones con inversión del sujeto pasivo
63+
// TODO: 'xxxxx' => ['21' => ['base' => '12', 'cuota' => '13']],
64+
65+
// Recargo de equivalencia
66+
'IVARRE' => [
67+
'1.75' => ['base' => '156', 'cuota' => '158'],
68+
'0.26' => ['base' => '168', 'cuota' => '170'],
69+
'1' => ['base' => '16', 'cuota' => '18'],
70+
'1.4' => ['base' => '19', 'cuota' => '21'],
71+
'5.2' => ['base' => '22', 'cuota' => '24'],
72+
],
73+
74+
// Operaciones exentas
75+
'IVAREX' => ['0' => ['base' => '150', 'cuota' => null]],
76+
77+
/*
78+
* IVA soportado (deducible)
79+
*/
80+
// Compras nacionales (régimen general)
81+
'IVASOP' => [
82+
'21' => ['base' => '28', 'cuota' => '29'],
83+
'10' => ['base' => '28', 'cuota' => '29'],
84+
'4' => ['base' => '28', 'cuota' => '29'],
85+
],
86+
87+
// Compras en importaciones
88+
'IVASIM' => ['21' => ['base' => '32', 'cuota' => '33']],
89+
90+
// Compras en adquisiciones intracomunitarias
91+
'IVASUE' => ['21' => ['base' => '36', 'cuota' => '37']],
92+
93+
// Operaciones exentas
94+
'IVASEX' => ['0' => ['base' => '60', 'cuota' => null]],
95+
];
96+
97+
/**
98+
* Initializes the tax rates for each square.
99+
*/
100+
public function __construct()
101+
{
102+
$this->square = array_fill_keys(
103+
array_map(fn($i) => sprintf('%02d', $i), range(0, self::MAX_SQUARE)),
104+
0.00
105+
);
106+
107+
$this->square['02'] = 4.00;
108+
$this->square['05'] = 10.00;
109+
$this->square['08'] = 21.00;
110+
$this->square['17'] = 1.00;
111+
$this->square['20'] = 1.40;
112+
$this->square['23'] = 5.20;
113+
$this->square['154'] = 7.50;
114+
$this->square['157'] = 1.75;
115+
$this->square['169'] = 0.26;
116+
$this->square['166'] = 2.00;
117+
}
118+
119+
/**
120+
* Get the value of a specific square.
121+
*
122+
* @param string $square
123+
* @return float
124+
*/
125+
public function casilla(string $square): float
126+
{
127+
return $this->square[$square] ?? 0.00;
128+
}
129+
130+
/**
131+
* Get the value of a specific square formatted as a string.
132+
*
133+
* @param string $square
134+
* @return string
135+
*/
136+
public function casillaStr(string $square, bool $showEmpty = false): string
137+
{
138+
$value = $this->casilla($square);
139+
if (empty($value) && false === $showEmpty ) {
140+
return '';
141+
}
142+
return Tools::number($value, 2);
143+
}
144+
145+
/**
146+
* Loads summary data from an array of PartidaImpuestoResumen.
147+
*
148+
* @param PartidaImpuestoResumen[] $resumen
149+
*/
150+
public function loadFromResumen(array $resumen): void
151+
{
152+
foreach ($resumen as $item) {
153+
$this->addMovimiento(
154+
$item->codcuentaesp ?? '',
155+
(float) $item->iva,
156+
(float) $item->recargo,
157+
(float) $item->baseimponible,
158+
(float) $item->cuota
159+
);
160+
}
161+
$this->calculateTotals();
162+
}
163+
164+
/**
165+
* Add a tax movement to the model (base + quota by type and rate)
166+
* - Determine the correct square based on the type and tax rate.
167+
* - Update the base and quota squares accordingly.
168+
*
169+
* @param string $tipo
170+
* @param float $iva
171+
* @param float $recargo
172+
* @param float $base
173+
* @param float $cuota
174+
* @return void
175+
*/
176+
private function addMovimiento(string $tipo, float $iva, float $recargo, float $base, float $cuota): void
177+
{
178+
if (false === isset($this->casillaMap[$tipo])) {
179+
return;
180+
}
181+
182+
// Determine the correct group based on the tax rate.
183+
$tax = ($tipo === 'IVARRE') ? $recargo : $iva;
184+
$key = rtrim(rtrim(number_format($tax, 1, '.', ''), '0'), '.');
185+
$grupo = $this->casillaMap[$tipo][$key]
186+
?? $this->casillaMap[$tipo][(string)(int)$tax]
187+
?? $this->casillaMap[$tipo]['*']
188+
?? null;
189+
190+
if ($grupo === null) {
191+
return;
192+
}
193+
194+
// Update base and quota squares.
195+
if (false === empty($grupo['base'])) {
196+
$this->square[$grupo['base']] += $base;
197+
}
198+
199+
if (false === empty($grupo['cuota'])) {
200+
// For recargo, if cuota is zero, calculate it from base and recargo rate
201+
if ($tipo === 'IVARRE' && $cuota == 0.0 && $recargo > 0.0) {
202+
$cuota = $base * ($recargo / 100.0);
203+
}
204+
$this->square[$grupo['cuota']] += $cuota;
205+
}
206+
}
207+
208+
/**
209+
* Calculate total squares based on individual entries.
210+
*
211+
* @return void
212+
*/
213+
private function calculateTotals(): void
214+
{
215+
// Total cuota devengada
216+
$this->square['27'] = $this->square['03']
217+
+ $this->square['06']
218+
+ $this->square['09']
219+
+ $this->square['11']
220+
+ $this->square['13']
221+
+ $this->square['15']
222+
+ $this->square['18']
223+
+ $this->square['21']
224+
+ $this->square['24']
225+
+ $this->square['26'];
226+
227+
// Total a deducir
228+
$this->square['45'] = $this->square['29']
229+
+ $this->square['31']
230+
+ $this->square['33']
231+
+ $this->square['35']
232+
+ $this->square['37']
233+
+ $this->square['39']
234+
+ $this->square['41']
235+
+ $this->square['42']
236+
+ $this->square['43']
237+
+ $this->square['44'];
238+
239+
// Resultado régimen general
240+
$this->square['46'] = $this->square['27'] - $this->square['45'];
241+
}
242+
}

0 commit comments

Comments
 (0)