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
56from odoo import api , fields , models
6- from odoo .tools import float_round , frozendict
7+ from odoo .tools import float_round
78
89
910class 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
32177class 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