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 ()
30+ if rec .move_type == "in_refund" :
31+ prorate_date = (
32+ rec .reversed_entry_id .date or rec .reversed_entry_id .invoice_date
33+ if rec .reversed_entry_id
34+ else prorate_date
35+ )
2636 rec .prorate_id = rec .company_id .get_prorate (prorate_date )
2737 rec .with_special_vat_prorate = rec .prorate_id .type == "special"
38+
39+ def button_draft (self ):
40+ res = super ().button_draft ()
41+ for move in self :
42+ move ._apply_vat_prorate ()
43+ return res
44+
45+ def _calculate_vat_prorate (self , invoice_lines ):
46+ """This method calculates tax_total and prorate_total for each line of
47+ the invoice that has taxes with with_vat_prorate set to True.
48+ """
49+ prorate_vals = {}
50+ for invoice_line in invoice_lines :
51+ prorate = invoice_line .move_id .prorate_id .vat_prorate / 100.0
52+ prec = invoice_line .move_id .currency_id .rounding
53+ for tax in invoice_line .tax_ids .filtered (lambda t : t .with_vat_prorate ):
54+ if tax .id in prorate_vals :
55+ tax_total = invoice_line .balance * tax .amount / 100
56+ prorate_vals [tax .id ]["tax_total" ] += float_round (
57+ tax_total , precision_rounding = prec
58+ )
59+ prorate_vals [tax .id ]["prorate_total" ] += float_round (
60+ tax_total * prorate , precision_rounding = prec
61+ )
62+ else :
63+ tax_total = invoice_line .balance * tax .amount / 100
64+ prorate_vals [tax .id ] = {
65+ "tax_total" : float_round (tax_total , precision_rounding = prec ),
66+ "prorate_total" : float_round (
67+ tax_total * prorate , precision_rounding = prec
68+ ),
69+ "account_id" : invoice_line .account_id ,
70+ }
71+ return prorate_vals
72+
73+ def _get_lines_with_tax_prorate (self ):
74+ return self .line_ids .filtered_domain (
75+ [
76+ ("vat_prorate" , "=" , False ),
77+ ("tax_line_id.with_vat_prorate" , "=" , True ),
78+ ]
79+ )
80+
81+ def _get_invoice_line_with_prorate (self ):
82+ self .ensure_one ()
83+ return self .invoice_line_ids .filtered_domain ([("with_vat_prorate" , "=" , True )])
84+
85+ def _apply_vat_prorate (self ):
86+ """Recalculate move.line_ids by applying the prorate.
87+ If a move line with with_tax_prorate=True already has a prorate_line,
88+ we need to recalculate the prorate and tax.
89+ If a move line with with_tax_prorate=True does not have a
90+ prorate_line, we need to use the copy method to create a new line
91+ with the prorate applied and recalculate prorate balance and tax balance
92+ """
93+ invoice_lines_with_prorate = self ._get_invoice_line_with_prorate ()
94+ prorate_vals = self ._calculate_vat_prorate (invoice_lines_with_prorate )
95+ line_to_update = {"line_ids" : []}
96+ tax_lines = self ._get_lines_with_tax_prorate ()
97+ for line in tax_lines :
98+ base_balance = line .balance
99+ prorate_line = line .prorate_line
100+ prorate_val = prorate_vals .get (line .tax_line_id .id , None )
101+ if not prorate_line and prorate_val :
102+ prorate_line = line .copy ()
28103 else :
29- rec .prorate_id = rec .with_special_vat_prorate = False
104+ base_balance += prorate_line .balance
105+ if prorate_val :
106+ expense_account = prorate_val ["account_id" ]
107+ tax_total = prorate_val .get ("tax_total" , 0 )
108+ prorate_total = prorate_val .get ("prorate_total" , 0 )
109+ line_to_update ["line_ids" ].append (
110+ [
111+ 1 ,
112+ prorate_line .id ,
113+ {
114+ "balance" : tax_total - prorate_total ,
115+ "account_id" : expense_account .id ,
116+ "vat_prorate" : True ,
117+ },
118+ ]
119+ )
120+ line_to_update ["line_ids" ].append (
121+ [
122+ 1 ,
123+ line .id ,
124+ {
125+ "prorate_line" : prorate_line .id ,
126+ "balance" : (base_balance - tax_total + prorate_total ),
127+ },
128+ ]
129+ )
130+ else :
131+ line_to_update ["line_ids" ].append (
132+ [
133+ 1 ,
134+ prorate_line .id ,
135+ {
136+ "balance" : 0 ,
137+ },
138+ ]
139+ )
140+ line_to_update ["line_ids" ].append (
141+ [1 , line .id , {"prorate_line" : False , "balance" : base_balance }]
142+ )
143+ self .with_context (skip_vat_prorate = True ).write (line_to_update )
144+
145+ @api .model_create_multi
146+ def create (self , vals_list ):
147+ moves = super ().create (vals_list )
148+ for move in moves :
149+ if move .move_type in ["in_invoice" , "in_refund" ]:
150+ move ._apply_vat_prorate ()
151+ return moves
152+
153+ def write (self , vals ):
154+ res = super ().write (vals )
155+ for move in self :
156+ if (
157+ move .move_type in ["in_invoice" , "in_refund" ]
158+ and not self .env .context .get ("skip_vat_prorate" , False )
159+ and move .state == "draft"
160+ and (
161+ "line_ds" in vals
162+ and len (vals ["line_ids" ])
163+ or "invoice_line_ids" in vals
164+ )
165+ or "partner_id" in vals
166+ or "currency_id" in vals
167+ or "invoice_currency_rate" in vals
168+ or any (key in vals for key in ["prorate_id" , "date" , "invoice_date" ])
169+ ):
170+ move ._apply_vat_prorate ()
171+ return res
30172
31173
32174class AccountMoveLine (models .Model ):
33175 _inherit = "account.move.line"
34176
35177 vat_prorate = fields .Boolean (
36- string = "Is vat prorate" , help = "The line is a vat prorate"
178+ string = "VAT Prorate" ,
179+ help = "This line represents the non-deductible part of the prorated VAT." ,
180+ copy = False ,
37181 )
38182
39183 with_vat_prorate = fields .Boolean (
40- string = "With VAT Prorate" ,
41- help = "The line will create a vat prorate" ,
42184 compute = "_compute_with_vat_prorate" ,
43185 store = True ,
44186 readonly = False ,
45187 )
46188
189+ prorate_line = fields .Many2one ("account.move.line" , default = False )
190+
47191 @api .depends ("move_id.prorate_id" , "company_id" )
48192 def _compute_with_vat_prorate (self ):
49193 for rec in self :
@@ -57,57 +201,3 @@ def _process_aeat_tax_fee_info(self, res, tax, sign):
57201 if self .vat_prorate :
58202 res [tax ]["deductible_amount" ] -= self .balance * sign
59203 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