44class AccountMove (models .Model ):
55 _inherit = "account.move"
66
7- tax_override_ids = fields .One2many (
8- "account.move.tax.override" ,
9- "move_id" ,
10- string = "Tax Overrides" ,
7+ # Stores manually-overridden tax amounts keyed by tax id (as string).
8+ # Structure: { "<tax_id>": {"amount": float, "amount_company_currency": float} }
9+ # Only fixed-amount taxes are stored here; percentage taxes are always
10+ # recomputed automatically.
11+ tax_override_data = fields .Json (
12+ string = "Tax Override Data" ,
1113 copy = False ,
14+ default = dict ,
1215 )
1316
17+ # ------------------------------------------------------------------
18+ # Helpers to read/write the JSON field in a structured way
19+ # ------------------------------------------------------------------
20+
21+ def _get_tax_overrides (self ):
22+ """Return the override dict keyed by integer tax id.
23+
24+ Structure: { tax_id (int): {"amount": float, "amount_company_currency": float} }
25+ """
26+ self .ensure_one ()
27+ raw = self .tax_override_data or {}
28+ result = {}
29+ for str_id , vals in raw .items ():
30+ try :
31+ result [int (str_id )] = vals
32+ except (ValueError , TypeError ):
33+ continue
34+ return result
35+
36+ def _set_tax_overrides (self , overrides_by_tax_id ):
37+ """Persist the override dict to the JSON field.
38+
39+ :param overrides_by_tax_id: dict mapping integer tax_id →
40+ {"amount": float, "amount_company_currency": float}
41+ """
42+ self .ensure_one ()
43+ self .tax_override_data = {
44+ str (tax_id ): vals for tax_id , vals in overrides_by_tax_id .items ()
45+ }
46+
1447 # ------------------------------------------------------------------
1548 # Tax-totals widget: inject override amounts so the widget reflects
1649 # the manually-set values instead of the recomputed ones.
1750 # ------------------------------------------------------------------
1851
1952 def _compute_tax_totals (self ):
2053 for move in self :
21- # Only fixed-amount tax overrides affect the totals widget;
22- # percentage taxes are always recomputed.
23- overrides = {o .tax_id .id : o for o in move .tax_override_ids if o .tax_id .amount_type == "fixed" }
54+ overrides = move ._get_tax_overrides ()
2455 if not overrides :
2556 super (AccountMove , move )._compute_tax_totals ()
2657 continue
@@ -35,14 +66,22 @@ def _compute_tax_totals(self):
3566 # is not stored (always 0); use `amount` directly with rate=1.0.
3667 # If the invoice is in a foreign currency, use amount_company_currency
3768 # as fixed_amount and derive the rate from both fields.
38- "fixed_amount" : (override .amount if is_company_currency else override .amount_company_currency ),
69+ "fixed_amount" : (
70+ vals ["amount" ]
71+ if is_company_currency
72+ else vals ["amount_company_currency" ]
73+ ),
3974 "rate" : (
4075 1.0
4176 if is_company_currency
42- else (override .amount_company_currency / override .amount if override .amount else 1.0 )
77+ else (
78+ vals ["amount_company_currency" ] / vals ["amount" ]
79+ if vals .get ("amount" )
80+ else 1.0
81+ )
4382 ),
4483 }
45- for tax_id , override in overrides .items ()
84+ for tax_id , vals in overrides .items ()
4685 },
4786 }
4887 super (AccountMove , move .with_context (** tax_context ))._compute_tax_totals ()
@@ -67,14 +106,46 @@ def _recompute_tax_lines(
67106 move ._apply_tax_overrides ()
68107
69108 def _apply_tax_overrides (self ):
70- """Re-write values from ``tax_override_ids `` onto the matching tax lines.
109+ """Re-write values from ``tax_override_data `` onto the matching tax lines.
71110
72111 Only overrides for fixed-amount taxes are applied; percentage-based
73112 taxes must always reflect their recomputed values.
74113 """
75- if not self .tax_override_ids :
114+ overrides = self ._get_tax_overrides ()
115+ if not overrides :
76116 return
77- overrides = {o .tax_id : o for o in self .tax_override_ids if o .tax_id .amount_type == "fixed" }
78- for line in self .line_ids .filtered (lambda l : l .tax_line_id in overrides ):
79- override = overrides [line .tax_line_id ]
80- line .write (override ._get_line_values (self ))
117+
118+ move_currency = self .currency_id
119+ company_currency = self .company_currency_id
120+ not_company_currency = move_currency and move_currency != company_currency
121+
122+ for line in self .line_ids .filtered (
123+ lambda l : l .tax_line_id and l .tax_line_id .id in overrides
124+ ):
125+ vals = overrides [line .tax_line_id .id ]
126+ amount = vals .get ("amount" , 0.0 )
127+ amount_cc = vals .get ("amount_company_currency" , 0.0 )
128+
129+ debit = credit = debit_cc = credit_cc = 0.0
130+ if self .move_type in ("in_invoice" , "in_receipt" ):
131+ if amount > 0 :
132+ debit , debit_cc = amount , amount_cc
133+ elif amount < 0 :
134+ credit , credit_cc = - amount , - amount_cc
135+ else :
136+ if amount > 0 :
137+ credit , credit_cc = amount , amount_cc
138+ elif amount < 0 :
139+ debit , debit_cc = - amount , - amount_cc
140+
141+ line_vals = {
142+ "debit" : debit_cc if not_company_currency else debit ,
143+ "credit" : credit_cc if not_company_currency else credit ,
144+ "balance" : (
145+ (amount_cc if not_company_currency else amount )
146+ * (1 if debit or debit_cc else - 1 )
147+ ),
148+ }
149+ if not_company_currency and amount :
150+ line_vals ["amount_currency" ] = amount
151+ line .write (line_vals )
0 commit comments