Skip to content

Commit 11f1408

Browse files
committed
[MIG] l10n_es_vat_prorate: Migration to 18.0
1 parent 2edd330 commit 11f1408

File tree

9 files changed

+268
-135
lines changed

9 files changed

+268
-135
lines changed

l10n_es_vat_prorate/README.rst

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
.. image:: https://odoo-community.org/readme-banner-image
2-
:target: https://odoo-community.org/get-involved?utm_source=readme
3-
:alt: Odoo Community Association
4-
51
===============
62
Prorrata de IVA
73
===============
@@ -17,17 +13,17 @@ Prorrata de IVA
1713
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
1814
:target: https://odoo-community.org/page/development-status
1915
:alt: Beta
20-
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
16+
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
2117
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
2218
:alt: License: AGPL-3
2319
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fl10n--spain-lightgray.png?logo=github
24-
:target: https://github.com/OCA/l10n-spain/tree/17.0/l10n_es_vat_prorate
20+
:target: https://github.com/OCA/l10n-spain/tree/18.0/l10n_es_vat_prorate
2521
:alt: OCA/l10n-spain
2622
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
27-
:target: https://translation.odoo-community.org/projects/l10n-spain-17-0/l10n-spain-17-0-l10n_es_vat_prorate
23+
:target: https://translation.odoo-community.org/projects/l10n-spain-18-0/l10n-spain-18-0-l10n_es_vat_prorate
2824
:alt: Translate me on Weblate
2925
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
30-
:target: https://runboat.odoo-community.org/builds?repo=OCA/l10n-spain&target_branch=17.0
26+
:target: https://runboat.odoo-community.org/builds?repo=OCA/l10n-spain&target_branch=18.0
3127
:alt: Try me on Runboat
3228

3329
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -70,7 +66,7 @@ Bug Tracker
7066
Bugs are tracked on `GitHub Issues <https://github.com/OCA/l10n-spain/issues>`_.
7167
In case of trouble, please check there if your issue has already been reported.
7268
If you spotted it first, help us to smash it by providing a detailed and welcomed
73-
`feedback <https://github.com/OCA/l10n-spain/issues/new?body=module:%20l10n_es_vat_prorate%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
69+
`feedback <https://github.com/OCA/l10n-spain/issues/new?body=module:%20l10n_es_vat_prorate%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
7470

7571
Do not contact contributors directly about support or help with technical issues.
7672

@@ -98,6 +94,12 @@ Contributors
9894
- Manuel Regidor
9995
- Alberto Martínez
10096

97+
- `Moduon <https://www.moduon.team/>`__:
98+
99+
- Rafael Blasco
100+
- Andrii Kompaniiets
101+
- Emilio Pascual
102+
101103
Maintainers
102104
-----------
103105

@@ -111,6 +113,20 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
111113
mission is to support the collaborative development of Odoo features and
112114
promote its widespread use.
113115

114-
This module is part of the `OCA/l10n-spain <https://github.com/OCA/l10n-spain/tree/17.0/l10n_es_vat_prorate>`_ project on GitHub.
116+
.. |maintainer-rafaelbn| image:: https://github.com/rafaelbn.png?size=40px
117+
:target: https://github.com/rafaelbn
118+
:alt: rafaelbn
119+
.. |maintainer-Andrii9090| image:: https://github.com/Andrii9090.png?size=40px
120+
:target: https://github.com/Andrii9090
121+
:alt: Andrii9090
122+
.. |maintainer-EmilioPascual| image:: https://github.com/EmilioPascual.png?size=40px
123+
:target: https://github.com/EmilioPascual
124+
:alt: EmilioPascual
125+
126+
Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:
127+
128+
|maintainer-rafaelbn| |maintainer-Andrii9090| |maintainer-EmilioPascual|
129+
130+
This module is part of the `OCA/l10n-spain <https://github.com/OCA/l10n-spain/tree/18.0/l10n_es_vat_prorate>`_ project on GitHub.
115131

116132
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

l10n_es_vat_prorate/__manifest__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
{
66
"name": "Prorrata de IVA",
77
"summary": "Prorrata de IVA para la localización española",
8-
"version": "17.0.3.1.2",
8+
"version": "18.0.1.0.0",
99
"license": "AGPL-3",
1010
"author": "Creu Blanca, Tecnativa, Odoo Community Association (OCA)",
1111
"website": "https://github.com/OCA/l10n-spain",
1212
"pre_init_hook": "pre_init_hook",
1313
"depends": ["l10n_es_aeat"],
14+
"maintainers": ["rafaelbn", "Andrii9090", "EmilioPascual"],
1415
"data": [
1516
"security/ir.model.access.csv",
1617
"views/account_move_views.xml",

l10n_es_vat_prorate/models/account_move.py

Lines changed: 157 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,196 @@
11
# Copyright 2021 Creu Blanca
22
# Copyright 2023 Tecnativa - Pedro M. Baeza
3-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
# License AGPL-3.0 or later[](http://www.gnu.org/licenses/agpl).
4+
45

56
from odoo import api, fields, models
6-
from odoo.tools import float_round, frozendict
7+
from odoo.tools import float_round
78

89

910
class AccountMove(models.Model):
1011
_inherit = "account.move"
1112

1213
prorate_id = fields.Many2one(
13-
string="Prorate",
14-
comodel_name="res.company.vat.prorate",
14+
"res.company.vat.prorate",
15+
compute="_compute_prorate_id",
16+
store=True,
17+
)
18+
with_special_vat_prorate = fields.Boolean(
1519
compute="_compute_prorate_id",
16-
ondelete="restrict",
1720
store=True,
1821
)
19-
with_special_vat_prorate = fields.Boolean(compute="_compute_prorate_id", store=True)
2022

2123
@api.depends("company_id", "date", "invoice_date")
2224
def _compute_prorate_id(self):
25+
self.prorate_id = False
26+
self.with_special_vat_prorate = False
2327
for rec in self:
2428
if rec.company_id.with_vat_prorate:
2529
prorate_date = rec.date or rec.invoice_date or fields.Date.today()
2630
rec.prorate_id = rec.company_id.get_prorate(prorate_date)
2731
rec.with_special_vat_prorate = rec.prorate_id.type == "special"
32+
33+
def button_draft(self):
34+
res = super().button_draft()
35+
for move in self:
36+
move._apply_vat_prorate()
37+
return res
38+
39+
def _calculate_vat_prorate(self, invoice_lines):
40+
"""This method calculates tax_total and prorate_total for each line of
41+
the invoice that has taxes with with_vat_prorate set to True.
42+
"""
43+
prorate_vals = {}
44+
for invoice_line in invoice_lines:
45+
prorate = invoice_line.move_id.prorate_id.vat_prorate / 100.0
46+
prec = invoice_line.move_id.currency_id.rounding
47+
for tax in invoice_line.tax_ids.filtered(lambda t: t.with_vat_prorate):
48+
if tax.id in prorate_vals:
49+
tax_total = invoice_line.balance * tax.amount / 100
50+
prorate_vals[tax.id]["tax_total"] += float_round(
51+
tax_total, precision_rounding=prec
52+
)
53+
prorate_vals[tax.id]["prorate_total"] += float_round(
54+
tax_total * prorate, precision_rounding=prec
55+
)
56+
else:
57+
tax_total = invoice_line.balance * tax.amount / 100
58+
prorate_vals[tax.id] = {
59+
"tax_total": float_round(tax_total, precision_rounding=prec),
60+
"prorate_total": float_round(
61+
tax_total * prorate, precision_rounding=prec
62+
),
63+
}
64+
return prorate_vals
65+
66+
def _get_lines_with_tax_prorate(self):
67+
return self.line_ids.filtered_domain(
68+
[
69+
("vat_prorate", "=", False),
70+
("tax_line_id.with_vat_prorate", "=", True),
71+
]
72+
)
73+
74+
def _get_invoice_line_with_prorate(self):
75+
self.ensure_one()
76+
return self.invoice_line_ids.filtered_domain([("with_vat_prorate", "=", True)])
77+
78+
def _apply_vat_prorate(self):
79+
"""Recalculate move.line_ids by applying the prorate.
80+
If a move line with with_tax_prorate=True already has a prorate_line,
81+
we need to recalculate the prorate and tax.
82+
If a move line with with_tax_prorate=True does not have a
83+
prorate_line, we need to use the copy method to create a new line
84+
with the prorate applied and recalculate prorate balance and tax balance
85+
"""
86+
invoice_lines_with_prorate = self._get_invoice_line_with_prorate()
87+
prorate_vals = self._calculate_vat_prorate(invoice_lines_with_prorate)
88+
expense_account = self._get_prorate_expense_account()
89+
line_to_update = {"line_ids": []}
90+
tax_lines = self._get_lines_with_tax_prorate()
91+
for line in tax_lines:
92+
base_balance = line.balance
93+
prorate_line = line.prorate_line
94+
prorate_val = prorate_vals.get(line.tax_line_id.id, None)
95+
if not prorate_line and prorate_val:
96+
prorate_line = line.copy()
97+
else:
98+
base_balance += prorate_line.balance
99+
if prorate_val:
100+
tax_total = prorate_val.get("tax_total", 0)
101+
prorate_total = prorate_val.get("prorate_total", 0)
102+
line_to_update["line_ids"].append(
103+
[
104+
1,
105+
prorate_line.id,
106+
{
107+
"balance": tax_total - prorate_total,
108+
"account_id": expense_account.id,
109+
"vat_prorate": True,
110+
},
111+
]
112+
)
113+
line_to_update["line_ids"].append(
114+
[
115+
1,
116+
line.id,
117+
{
118+
"prorate_line": prorate_line.id,
119+
"balance": (base_balance - tax_total + prorate_total),
120+
},
121+
]
122+
)
28123
else:
29-
rec.prorate_id = rec.with_special_vat_prorate = False
124+
line_to_update["line_ids"].append(
125+
[
126+
1,
127+
prorate_line.id,
128+
{
129+
"balance": 0,
130+
},
131+
]
132+
)
133+
line_to_update["line_ids"].append(
134+
[1, line.id, {"prorate_line": False, "balance": base_balance}]
135+
)
136+
self.with_context(skip_vat_prorate=True).write(line_to_update)
137+
138+
@api.model_create_multi
139+
def create(self, vals_list):
140+
moves = super().create(vals_list)
141+
for move in moves:
142+
if move.move_type in ["in_invoice", "in_refund"]:
143+
move._apply_vat_prorate()
144+
return moves
145+
146+
def write(self, vals):
147+
res = super().write(vals)
148+
for move in self:
149+
if (
150+
move.move_type in ["in_invoice", "in_refund"]
151+
and not self.env.context.get("skip_vat_prorate", False)
152+
and move.state == "draft"
153+
and (
154+
"line_ds" in vals
155+
and len(vals["line_ids"])
156+
or "invoice_line_ids" in vals
157+
)
158+
or "partner_id" in vals
159+
or "currency_id" in vals
160+
or "invoice_currency_rate" in vals
161+
or any(key in vals for key in ["prorate_id", "date", "invoice_date"])
162+
):
163+
move._apply_vat_prorate()
164+
return res
165+
166+
def _get_prorate_expense_account(self):
167+
for line in self.line_ids:
168+
if line.debit > 0 and not line.tax_line_id:
169+
if line.account_id.account_type not in (
170+
"asset_receivable",
171+
"liability_payable",
172+
):
173+
return line.account_id
174+
return self.company_id.expense_currency_exchange_account_id
30175

31176

32177
class AccountMoveLine(models.Model):
33178
_inherit = "account.move.line"
34179

35180
vat_prorate = fields.Boolean(
36-
string="Is vat prorate", help="The line is a vat prorate"
181+
string="VAT Prorate",
182+
help="This line represents the non-deductible part of the prorated VAT.",
183+
copy=False,
37184
)
38185

39186
with_vat_prorate = fields.Boolean(
40-
string="With VAT Prorate",
41-
help="The line will create a vat prorate",
42187
compute="_compute_with_vat_prorate",
43188
store=True,
44189
readonly=False,
45190
)
46191

192+
prorate_line = fields.Many2one("account.move.line", default=False)
193+
47194
@api.depends("move_id.prorate_id", "company_id")
48195
def _compute_with_vat_prorate(self):
49196
for rec in self:
@@ -57,57 +204,3 @@ def _process_aeat_tax_fee_info(self, res, tax, sign):
57204
if self.vat_prorate:
58205
res[tax]["deductible_amount"] -= self.balance * sign
59206
return result
60-
61-
@api.depends("with_vat_prorate")
62-
def _compute_all_tax(self):
63-
"""After getting normal taxes dict that is dumped into this field, we loop
64-
into it to check if any of them applies VAT prorate, and if it's the case,
65-
we modify its amount and add the corresponding extra tax line.
66-
"""
67-
res = None
68-
for line in self:
69-
res = super(AccountMoveLine, line)._compute_all_tax()
70-
prorate_tax_list = {}
71-
for tax_key, tax_vals in line.compute_all_tax.items():
72-
tax_vals["vat_prorate"] = False
73-
tax = (
74-
self.env["account.tax.repartition.line"]
75-
.browse(tax_key.get("tax_repartition_line_id", False))
76-
.tax_id
77-
)
78-
if (
79-
line.with_vat_prorate
80-
and tax.with_vat_prorate
81-
and tax_key.get("account_id")
82-
and (
83-
not tax.prorate_account_ids
84-
or tax_key.get("account_id") in tax.prorate_account_ids.ids
85-
)
86-
):
87-
prec = line.move_id.currency_id.rounding
88-
prorate = line.move_id.prorate_id.vat_prorate
89-
new_vals = tax_vals.copy()
90-
for field in {"amount_currency", "balance"}:
91-
tax_vals[field] = float_round(
92-
tax_vals[field] * (prorate / 100),
93-
precision_rounding=prec,
94-
)
95-
new_vals[field] -= tax_vals[field]
96-
new_vals["vat_prorate"] = True
97-
new_key = dict(tax_key)
98-
new_key.update(
99-
{
100-
"vat_prorate": True,
101-
"account_id": line.account_id.id,
102-
"analytic_distribution": line.analytic_distribution,
103-
}
104-
)
105-
new_key = frozendict(new_key)
106-
if prorate_tax_list.get(new_key):
107-
for field in {"amount_currency", "balance"}:
108-
prorate_tax_list[new_key][field] += new_vals[field]
109-
else:
110-
prorate_tax_list[new_key] = new_vals
111-
if prorate_tax_list:
112-
line.compute_all_tax.update(prorate_tax_list)
113-
return res

0 commit comments

Comments
 (0)