blob: bc38d08b809c1e255759e75a333f18088a5d5c08 [file] [log] [blame]
Anand Doshi885e0742015-03-03 14:55:30 +05301# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
Nabin Hait3237c752015-02-17 11:11:11 +05302# License: GNU General Public License v3. See license.txt
3
Chillar Anand915b3432021-09-02 16:44:59 +05304
Nabin Hait3237c752015-02-17 11:11:11 +05305import json
Chillar Anand915b3432021-09-02 16:44:59 +05306
7import frappe
Nabin Hait3769d872015-12-18 13:12:02 +05308from frappe import _, scrub
Nabin Haitb962fc12017-07-17 18:02:31 +05309from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
Chillar Anand915b3432021-09-02 16:44:59 +053010
11import erpnext
Deepesh Gargbfc17e42020-12-25 18:34:39 +053012from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
Chillar Anand915b3432021-09-02 16:44:59 +053013from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
14from erpnext.controllers.accounts_controller import (
15 validate_conversion_rate,
16 validate_inclusive_tax,
17 validate_taxes_and_charges,
18)
19from erpnext.stock.get_item_details import _get_item_tax_template
20
Nabin Hait3237c752015-02-17 11:11:11 +053021
Nabin Haitfe81da22015-02-18 12:23:18 +053022class calculate_taxes_and_totals(object):
Nabin Hait3237c752015-02-17 11:11:11 +053023 def __init__(self, doc):
24 self.doc = doc
Deepesh Garg6a5ef262021-02-19 14:30:23 +053025 frappe.flags.round_off_applicable_accounts = []
26 get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
Nabin Haitfe81da22015-02-18 12:23:18 +053027 self.calculate()
28
Nabin Hait3237c752015-02-17 11:11:11 +053029 def calculate(self):
Nabin Haitb315acb2019-07-12 14:27:19 +053030 if not len(self.doc.get("items")):
31 return
32
Nabin Hait3237c752015-02-17 11:11:11 +053033 self.discount_amount_applied = False
34 self._calculate()
35
36 if self.doc.meta.get_field("discount_amount"):
Nabin Hait3769d872015-12-18 13:12:02 +053037 self.set_discount_amount()
Nabin Hait3237c752015-02-17 11:11:11 +053038 self.apply_discount_amount()
39
Deepesh Garg3b159662022-08-21 17:51:05 +053040 # Update grand total as per cash and non trade discount
41 if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
42 self.doc.grand_total -= self.doc.discount_amount
43 self.doc.base_grand_total -= self.doc.base_discount_amount
44
Deepesh Gargd596e0e2022-03-10 20:56:36 +053045 self.calculate_shipping_charges()
46
Nabin Haitbd00e812015-02-17 12:50:51 +053047 if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
Nabin Hait3237c752015-02-17 11:11:11 +053048 self.calculate_total_advance()
Vishal Dhayaguded42242d2017-11-29 16:09:59 +053049
Nabin Hait852cb642017-07-05 12:58:19 +053050 if self.doc.meta.get_field("other_charges_calculation"):
51 self.set_item_wise_tax_breakup()
Nabin Hait3237c752015-02-17 11:11:11 +053052
53 def _calculate(self):
Faris Ansarieae2dda2018-05-02 12:19:30 +053054 self.validate_conversion_rate()
Nabin Haite7679702015-02-20 14:40:35 +053055 self.calculate_item_values()
Deepesh Gargef0d26c2020-01-06 15:34:15 +053056 self.validate_item_tax_template()
Nabin Haite7679702015-02-20 14:40:35 +053057 self.initialize_taxes()
58 self.determine_exclusive_rate()
59 self.calculate_net_total()
60 self.calculate_taxes()
Nabin Haita1bf43b2015-03-17 10:50:47 +053061 self.manipulate_grand_total_for_inclusive_tax()
Nabin Haite7679702015-02-20 14:40:35 +053062 self.calculate_totals()
63 self._cleanup()
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +053064 self.calculate_total_net_weight()
Nabin Haite7679702015-02-20 14:40:35 +053065
Deepesh Gargef0d26c2020-01-06 15:34:15 +053066 def validate_item_tax_template(self):
Ankush Menat494bd9e2022-03-28 18:52:46 +053067 for item in self.doc.get("items"):
68 if item.item_code and item.get("item_tax_template"):
Deepesh Gargef0d26c2020-01-06 15:34:15 +053069 item_doc = frappe.get_cached_doc("Item", item.item_code)
70 args = {
Ankush Menat494bd9e2022-03-28 18:52:46 +053071 "net_rate": item.net_rate or item.rate,
72 "tax_category": self.doc.get("tax_category"),
73 "posting_date": self.doc.get("posting_date"),
74 "bill_date": self.doc.get("bill_date"),
75 "transaction_date": self.doc.get("transaction_date"),
76 "company": self.doc.get("company"),
Deepesh Gargef0d26c2020-01-06 15:34:15 +053077 }
78
79 item_group = item_doc.item_group
80 item_group_taxes = []
81
82 while item_group:
Ankush Menat494bd9e2022-03-28 18:52:46 +053083 item_group_doc = frappe.get_cached_doc("Item Group", item_group)
Deepesh Gargef0d26c2020-01-06 15:34:15 +053084 item_group_taxes += item_group_doc.taxes or []
85 item_group = item_group_doc.parent_item_group
86
87 item_taxes = item_doc.taxes or []
88
89 if not item_group_taxes and (not item_taxes):
90 # No validation if no taxes in item or item group
91 continue
92
93 taxes = _get_item_tax_template(args, item_taxes + item_group_taxes, for_validate=True)
94
Deepesh Garg18be7672021-06-06 13:25:34 +053095 if taxes:
96 if item.item_tax_template not in taxes:
97 item.item_tax_template = taxes[0]
Ankush Menat494bd9e2022-03-28 18:52:46 +053098 frappe.msgprint(
99 _("Row {0}: Item Tax template updated as per validity and rate applied").format(
100 item.idx, frappe.bold(item.item_code)
101 )
102 )
Deepesh Gargef0d26c2020-01-06 15:34:15 +0530103
Nabin Haite7679702015-02-20 14:40:35 +0530104 def validate_conversion_rate(self):
Nabin Hait3237c752015-02-17 11:11:11 +0530105 # validate conversion rate
Rushabh Mehtacc8b2b22017-03-31 12:44:29 +0530106 company_currency = erpnext.get_company_currency(self.doc.company)
Nabin Hait3237c752015-02-17 11:11:11 +0530107 if not self.doc.currency or self.doc.currency == company_currency:
108 self.doc.currency = company_currency
109 self.doc.conversion_rate = 1.0
110 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530111 validate_conversion_rate(
112 self.doc.currency,
113 self.doc.conversion_rate,
114 self.doc.meta.get_label("conversion_rate"),
115 self.doc.company,
116 )
Nabin Hait3237c752015-02-17 11:11:11 +0530117
118 self.doc.conversion_rate = flt(self.doc.conversion_rate)
119
Nabin Hait3237c752015-02-17 11:11:11 +0530120 def calculate_item_values(self):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530121 if self.doc.get("is_consolidated"):
Saqib Ansari0452d7d2022-02-08 11:26:23 +0530122 return
123
Nabin Hait3237c752015-02-17 11:11:11 +0530124 if not self.discount_amount_applied:
125 for item in self.doc.get("items"):
126 self.doc.round_floats_in(item)
127
128 if item.discount_percentage == 100:
129 item.rate = 0.0
Nabin Hait593242f2019-04-05 19:35:02 +0530130 elif item.price_list_rate:
Deepesh Garga83a0a02022-03-25 12:28:55 +0530131 if not item.rate or (item.pricing_rules and item.discount_percentage > 0):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530132 item.rate = flt(
133 item.price_list_rate * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")
134 )
Deepesh Gargd95f8932022-03-01 23:09:59 +0530135
Deepesh Garga83a0a02022-03-25 12:28:55 +0530136 item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
Deepesh Gargd95f8932022-03-01 23:09:59 +0530137
Deepesh Garg97e102c2022-03-25 12:39:59 +0530138 elif item.discount_amount and item.pricing_rules:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530139 item.rate = item.price_list_rate - item.discount_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530140
Ankush Menat494bd9e2022-03-28 18:52:46 +0530141 if item.doctype in [
142 "Quotation Item",
143 "Sales Order Item",
144 "Delivery Note Item",
145 "Sales Invoice Item",
146 "POS Invoice Item",
147 "Purchase Invoice Item",
148 "Purchase Order Item",
149 "Purchase Receipt Item",
150 ]:
Shreya Shahbe690ef2017-11-14 17:22:41 +0530151 item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
Nabin Hait64bfdd92019-04-23 13:37:19 +0530152 if flt(item.rate_with_margin) > 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530153 item.rate = flt(
154 item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")
155 )
Nabin Hait10c61372021-04-13 15:46:01 +0530156
Walstan Baptista37b826b2021-04-03 19:48:46 +0530157 if item.discount_amount and not item.discount_percentage:
Nabin Hait10c61372021-04-13 15:46:01 +0530158 item.rate = item.rate_with_margin - item.discount_amount
Walstan Baptista37b826b2021-04-03 19:48:46 +0530159 else:
160 item.discount_amount = item.rate_with_margin - item.rate
Nabin Hait10c61372021-04-13 15:46:01 +0530161
Nabin Hait64bfdd92019-04-23 13:37:19 +0530162 elif flt(item.price_list_rate) > 0:
163 item.discount_amount = item.price_list_rate - item.rate
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530164 elif flt(item.price_list_rate) > 0 and not item.discount_amount:
165 item.discount_amount = item.price_list_rate - item.rate
mbauskara52472c2016-03-05 15:10:25 +0530166
Nabin Haite7679702015-02-20 14:40:35 +0530167 item.net_rate = item.rate
Deepesh Gargb65c7612019-07-31 15:58:01 +0530168
deepeshgarg0078bf19ce2019-08-03 13:40:37 +0530169 if not item.qty and self.doc.get("is_return"):
Deepesh Gargb65c7612019-07-31 15:58:01 +0530170 item.amount = flt(-1 * item.rate, item.precision("amount"))
Saqib Ansari04ea42c2021-12-22 13:23:42 +0530171 elif not item.qty and self.doc.get("is_debit_note"):
172 item.amount = flt(item.rate, item.precision("amount"))
Deepesh Gargb65c7612019-07-31 15:58:01 +0530173 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530174 item.amount = flt(item.rate * item.qty, item.precision("amount"))
Deepesh Gargb65c7612019-07-31 15:58:01 +0530175
Nabin Haite7679702015-02-20 14:40:35 +0530176 item.net_amount = item.amount
Nabin Hait3237c752015-02-17 11:11:11 +0530177
Ankush Menat494bd9e2022-03-28 18:52:46 +0530178 self._set_in_company_currency(
179 item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"]
180 )
Nabin Hait3237c752015-02-17 11:11:11 +0530181
Nabin Haite7679702015-02-20 14:40:35 +0530182 item.item_tax_amount = 0.0
183
184 def _set_in_company_currency(self, doc, fields):
Nabin Hait3237c752015-02-17 11:11:11 +0530185 """set values in base currency"""
Nabin Haite7679702015-02-20 14:40:35 +0530186 for f in fields:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530187 val = flt(
188 flt(doc.get(f), doc.precision(f)) * self.doc.conversion_rate, doc.precision("base_" + f)
189 )
Nabin Haite7679702015-02-20 14:40:35 +0530190 doc.set("base_" + f, val)
Nabin Hait3237c752015-02-17 11:11:11 +0530191
192 def initialize_taxes(self):
193 for tax in self.doc.get("taxes"):
Nabin Hait86cd4cc2015-02-28 19:11:51 +0530194 if not self.discount_amount_applied:
195 validate_taxes_and_charges(tax)
196 validate_inclusive_tax(tax, self.doc)
Nabin Hait613d0812015-02-23 11:58:15 +0530197
Ankush Menat494bd9e2022-03-28 18:52:46 +0530198 if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530199 tax.item_wise_tax_detail = {}
200
Ankush Menat494bd9e2022-03-28 18:52:46 +0530201 tax_fields = [
202 "total",
203 "tax_amount_after_discount_amount",
204 "tax_amount_for_current_item",
205 "grand_total_for_current_item",
206 "tax_fraction_for_current_item",
207 "grand_total_fraction_for_current_item",
208 ]
Nabin Hait3237c752015-02-17 11:11:11 +0530209
Ankush Menat494bd9e2022-03-28 18:52:46 +0530210 if tax.charge_type != "Actual" and not (
211 self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
212 ):
213 tax_fields.append("tax_amount")
Nabin Hait3237c752015-02-17 11:11:11 +0530214
215 for fieldname in tax_fields:
216 tax.set(fieldname, 0.0)
217
Nabin Hait3237c752015-02-17 11:11:11 +0530218 self.doc.round_floats_in(tax)
219
Nabin Hait3237c752015-02-17 11:11:11 +0530220 def determine_exclusive_rate(self):
Ankush Menat8fe5feb2021-11-04 19:48:32 +0530221 if not any(cint(tax.included_in_print_rate) for tax in self.doc.get("taxes")):
Nabin Hait37b047d2015-02-23 16:01:33 +0530222 return
Nabin Hait3237c752015-02-17 11:11:11 +0530223
224 for item in self.doc.get("items"):
225 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
226 cumulated_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530227 total_inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530228 for i, tax in enumerate(self.doc.get("taxes")):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530229 (
230 tax.tax_fraction_for_current_item,
231 inclusive_tax_amount_per_qty,
232 ) = self.get_current_tax_fraction(tax, item_tax_map)
Nabin Hait3237c752015-02-17 11:11:11 +0530233
Ankush Menat494bd9e2022-03-28 18:52:46 +0530234 if i == 0:
Nabin Hait3237c752015-02-17 11:11:11 +0530235 tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
236 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530237 tax.grand_total_fraction_for_current_item = (
238 self.doc.get("taxes")[i - 1].grand_total_fraction_for_current_item
Nabin Hait3237c752015-02-17 11:11:11 +0530239 + tax.tax_fraction_for_current_item
Ankush Menat494bd9e2022-03-28 18:52:46 +0530240 )
Nabin Hait3237c752015-02-17 11:11:11 +0530241
242 cumulated_tax_fraction += tax.tax_fraction_for_current_item
Nabin Haite2ee4552020-08-12 20:58:03 +0530243 total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
Nabin Hait3237c752015-02-17 11:11:11 +0530244
Ankush Menat494bd9e2022-03-28 18:52:46 +0530245 if (
246 not self.discount_amount_applied
247 and item.qty
248 and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty)
249 ):
Nabin Hait19ea7212020-08-11 20:34:57 +0530250 amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
251
252 item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
Nabin Haite7679702015-02-20 14:40:35 +0530253 item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
Ankush Menat494bd9e2022-03-28 18:52:46 +0530254 item.discount_percentage = flt(item.discount_percentage, item.precision("discount_percentage"))
Nabin Hait3237c752015-02-17 11:11:11 +0530255
Nabin Haite7679702015-02-20 14:40:35 +0530256 self._set_in_company_currency(item, ["net_rate", "net_amount"])
257
Nabin Hait3237c752015-02-17 11:11:11 +0530258 def _load_item_tax_rate(self, item_tax_rate):
259 return json.loads(item_tax_rate) if item_tax_rate else {}
260
261 def get_current_tax_fraction(self, tax, item_tax_map):
262 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530263 Get tax fraction for calculating tax exclusive amount
264 from tax inclusive amount
Nabin Hait3237c752015-02-17 11:11:11 +0530265 """
266 current_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530267 inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530268
269 if cint(tax.included_in_print_rate):
270 tax_rate = self._get_tax_rate(tax, item_tax_map)
271
272 if tax.charge_type == "On Net Total":
273 current_tax_fraction = tax_rate / 100.0
274
275 elif tax.charge_type == "On Previous Row Amount":
Ankush Menat494bd9e2022-03-28 18:52:46 +0530276 current_tax_fraction = (tax_rate / 100.0) * self.doc.get("taxes")[
277 cint(tax.row_id) - 1
278 ].tax_fraction_for_current_item
Nabin Hait3237c752015-02-17 11:11:11 +0530279
280 elif tax.charge_type == "On Previous Row Total":
Ankush Menat494bd9e2022-03-28 18:52:46 +0530281 current_tax_fraction = (tax_rate / 100.0) * self.doc.get("taxes")[
282 cint(tax.row_id) - 1
283 ].grand_total_fraction_for_current_item
marination733fd5f2020-08-26 18:23:12 +0530284
Nabin Hait19ea7212020-08-11 20:34:57 +0530285 elif tax.charge_type == "On Item Quantity":
286 inclusive_tax_amount_per_qty = flt(tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530287
Nabin Hait19ea7212020-08-11 20:34:57 +0530288 if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
289 current_tax_fraction *= -1.0
290 inclusive_tax_amount_per_qty *= -1.0
291
292 return current_tax_fraction, inclusive_tax_amount_per_qty
Nabin Hait3237c752015-02-17 11:11:11 +0530293
294 def _get_tax_rate(self, tax, item_tax_map):
Achilles Rasquinha87dab142018-03-08 14:21:48 +0530295 if tax.account_head in item_tax_map:
Nabin Hait3237c752015-02-17 11:11:11 +0530296 return flt(item_tax_map.get(tax.account_head), self.doc.precision("rate", tax))
297 else:
298 return tax.rate
299
300 def calculate_net_total(self):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530301 self.doc.total_qty = (
302 self.doc.total
303 ) = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
rohitwaghchaure3a595d02018-06-25 10:10:29 +0530304
Nabin Hait3237c752015-02-17 11:11:11 +0530305 for item in self.doc.get("items"):
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530306 self.doc.total += item.amount
Shreya Shahe3290382018-05-28 11:49:08 +0530307 self.doc.total_qty += item.qty
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530308 self.doc.base_total += item.base_amount
Nabin Haite7679702015-02-20 14:40:35 +0530309 self.doc.net_total += item.net_amount
310 self.doc.base_net_total += item.base_net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530311
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530312 self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530313
Subin Toma8e2c022021-11-16 19:06:49 +0530314 def calculate_shipping_charges(self):
Deepesh Garg714fc082022-04-04 20:05:10 +0530315
316 # Do not apply shipping rule for POS
Deepesh Garg631545a2022-04-06 09:27:40 +0530317 if self.doc.get("is_pos"):
Deepesh Garg714fc082022-04-04 20:05:10 +0530318 return
319
Subin Tomaf1fce02021-11-10 16:49:12 +0530320 if hasattr(self.doc, "shipping_rule") and self.doc.shipping_rule:
Subin Tom18ae03d2021-11-10 15:57:41 +0530321 shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule)
322 shipping_rule.apply(self.doc)
323
Deepesh Gargd596e0e2022-03-10 20:56:36 +0530324 self._calculate()
325
Nabin Hait3237c752015-02-17 11:11:11 +0530326 def calculate_taxes(self):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530327 rounding_adjustment_computed = self.doc.get("is_consolidated") and self.doc.get(
328 "rounding_adjustment"
329 )
Saqib Ansari17445c72022-03-07 18:01:07 +0530330 if not rounding_adjustment_computed:
Subin Tom75a76e62021-10-29 16:45:04 +0530331 self.doc.rounding_adjustment = 0
332
Nabin Hait3237c752015-02-17 11:11:11 +0530333 # maintain actual tax rate based on idx
Ankush Menat494bd9e2022-03-28 18:52:46 +0530334 actual_tax_dict = dict(
335 [
336 [tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
337 for tax in self.doc.get("taxes")
338 if tax.charge_type == "Actual"
339 ]
340 )
Nabin Hait3237c752015-02-17 11:11:11 +0530341
342 for n, item in enumerate(self.doc.get("items")):
343 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530344 for i, tax in enumerate(self.doc.get("taxes")):
345 # tax_amount represents the amount of tax for the current step
346 current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map)
347
348 # Adjust divisional loss to the last item
349 if tax.charge_type == "Actual":
350 actual_tax_dict[tax.idx] -= current_tax_amount
351 if n == len(self.doc.get("items")) - 1:
352 current_tax_amount += actual_tax_dict[tax.idx]
353
Nabin Hait2b019ed2015-02-22 23:03:07 +0530354 # accumulate tax amount into tax.tax_amount
Ankush Menat494bd9e2022-03-28 18:52:46 +0530355 if tax.charge_type != "Actual" and not (
356 self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
357 ):
358 tax.tax_amount += current_tax_amount
Nabin Hait2b019ed2015-02-22 23:03:07 +0530359
Nabin Hait3237c752015-02-17 11:11:11 +0530360 # store tax_amount for current item as it will be used for
361 # charge type = 'On Previous Row Amount'
362 tax.tax_amount_for_current_item = current_tax_amount
363
Nabin Hait2b019ed2015-02-22 23:03:07 +0530364 # set tax after discount
Nabin Hait3237c752015-02-17 11:11:11 +0530365 tax.tax_amount_after_discount_amount += current_tax_amount
366
Nabin Haitcd951342017-07-31 18:07:45 +0530367 current_tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(current_tax_amount, tax)
Nabin Hait3237c752015-02-17 11:11:11 +0530368
Nabin Hait3237c752015-02-17 11:11:11 +0530369 # note: grand_total_for_current_item contains the contribution of
370 # item's amount, previously applied tax and the current tax on that item
Ankush Menat494bd9e2022-03-28 18:52:46 +0530371 if i == 0:
Nabin Haitcd951342017-07-31 18:07:45 +0530372 tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530373 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530374 tax.grand_total_for_current_item = flt(
375 self.doc.get("taxes")[i - 1].grand_total_for_current_item + current_tax_amount
376 )
Nabin Hait3237c752015-02-17 11:11:11 +0530377
378 # set precision in the last item iteration
379 if n == len(self.doc.get("items")) - 1:
380 self.round_off_totals(tax)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530381 self._set_in_company_currency(tax, ["tax_amount", "tax_amount_after_discount_amount"])
Deepesh Gargb6705882021-04-14 11:20:27 +0530382
383 self.round_off_base_values(tax)
Nabin Haitcd951342017-07-31 18:07:45 +0530384 self.set_cumulative_total(i, tax)
385
Deepesh Gargb6705882021-04-14 11:20:27 +0530386 self._set_in_company_currency(tax, ["total"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530387
Nabin Hait3237c752015-02-17 11:11:11 +0530388 # adjust Discount Amount loss in last tax iteration
Ankush Menat494bd9e2022-03-28 18:52:46 +0530389 if (
390 i == (len(self.doc.get("taxes")) - 1)
391 and self.discount_amount_applied
392 and self.doc.discount_amount
393 and self.doc.apply_discount_on == "Grand Total"
394 and not rounding_adjustment_computed
395 ):
396 self.doc.rounding_adjustment = flt(
397 self.doc.grand_total - flt(self.doc.discount_amount) - tax.total,
398 self.doc.precision("rounding_adjustment"),
399 )
Anand Doshiec5ec602015-03-05 19:31:23 +0530400
Nabin Haitcd951342017-07-31 18:07:45 +0530401 def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
402 # if just for valuation, do not add the tax amount in total
403 # if tax/charges is for deduction, multiply by -1
404 if getattr(tax, "category", None):
405 tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
Ankush Menat494bd9e2022-03-28 18:52:46 +0530406 if self.doc.doctype in [
407 "Purchase Order",
408 "Purchase Invoice",
409 "Purchase Receipt",
410 "Supplier Quotation",
411 ]:
Rushabh Mehta30dc9a12017-11-17 14:31:09 +0530412 tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
Nabin Haitcd951342017-07-31 18:07:45 +0530413 return tax_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530414
Nabin Haitcd951342017-07-31 18:07:45 +0530415 def set_cumulative_total(self, row_idx, tax):
416 tax_amount = tax.tax_amount_after_discount_amount
417 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax_amount, tax)
418
419 if row_idx == 0:
420 tax.total = flt(self.doc.net_total + tax_amount, tax.precision("total"))
421 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530422 tax.total = flt(self.doc.get("taxes")[row_idx - 1].total + tax_amount, tax.precision("total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530423
424 def get_current_tax_amount(self, item, tax, item_tax_map):
425 tax_rate = self._get_tax_rate(tax, item_tax_map)
426 current_tax_amount = 0.0
427
428 if tax.charge_type == "Actual":
429 # distribute the tax amount proportionally to each item row
Nabin Haite7679702015-02-20 14:40:35 +0530430 actual = flt(tax.tax_amount, tax.precision("tax_amount"))
Ankush Menat494bd9e2022-03-28 18:52:46 +0530431 current_tax_amount = (
432 item.net_amount * actual / self.doc.net_total if self.doc.net_total else 0.0
433 )
Nabin Haite7679702015-02-20 14:40:35 +0530434
Nabin Hait3237c752015-02-17 11:11:11 +0530435 elif tax.charge_type == "On Net Total":
Nabin Haite7679702015-02-20 14:40:35 +0530436 current_tax_amount = (tax_rate / 100.0) * item.net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530437 elif tax.charge_type == "On Previous Row Amount":
Ankush Menat494bd9e2022-03-28 18:52:46 +0530438 current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[
439 cint(tax.row_id) - 1
440 ].tax_amount_for_current_item
Nabin Hait3237c752015-02-17 11:11:11 +0530441 elif tax.charge_type == "On Previous Row Total":
Ankush Menat494bd9e2022-03-28 18:52:46 +0530442 current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[
443 cint(tax.row_id) - 1
444 ].grand_total_for_current_item
Himanshu Mishra35b26272018-11-13 11:13:04 +0530445 elif tax.charge_type == "On Item Quantity":
Nabin Hait19ea7212020-08-11 20:34:57 +0530446 current_tax_amount = tax_rate * item.qty
Nabin Hait3237c752015-02-17 11:11:11 +0530447
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530448 if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530449 self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530450
451 return current_tax_amount
452
Nabin Haite7679702015-02-20 14:40:35 +0530453 def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
454 # store tax breakup for each item
455 key = item.item_code or item.item_name
Ankush Menat494bd9e2022-03-28 18:52:46 +0530456 item_wise_tax_amount = current_tax_amount * self.doc.conversion_rate
Nabin Haite7679702015-02-20 14:40:35 +0530457 if tax.item_wise_tax_detail.get(key):
458 item_wise_tax_amount += tax.item_wise_tax_detail[key][1]
459
Ankush Menat494bd9e2022-03-28 18:52:46 +0530460 tax.item_wise_tax_detail[key] = [tax_rate, flt(item_wise_tax_amount)]
Nabin Haite7679702015-02-20 14:40:35 +0530461
Nabin Hait3237c752015-02-17 11:11:11 +0530462 def round_off_totals(self, tax):
Deepesh Gargb6705882021-04-14 11:20:27 +0530463 if tax.account_head in frappe.flags.round_off_applicable_accounts:
464 tax.tax_amount = round(tax.tax_amount, 0)
465 tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
466
Nabin Haite7679702015-02-20 14:40:35 +0530467 tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
Ankush Menat494bd9e2022-03-28 18:52:46 +0530468 tax.tax_amount_after_discount_amount = flt(
469 tax.tax_amount_after_discount_amount, tax.precision("tax_amount")
470 )
Nabin Haitce245122015-02-22 20:14:49 +0530471
Deepesh Gargb6705882021-04-14 11:20:27 +0530472 def round_off_base_values(self, tax):
473 # Round off to nearest integer based on regional settings
474 if tax.account_head in frappe.flags.round_off_applicable_accounts:
475 tax.base_tax_amount = round(tax.base_tax_amount, 0)
476 tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
477
Nabin Haita1bf43b2015-03-17 10:50:47 +0530478 def manipulate_grand_total_for_inclusive_tax(self):
479 # if fully inclusive taxes and diff
Ankush Menat98917802021-06-11 18:40:22 +0530480 if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")):
Nabin Haita1bf43b2015-03-17 10:50:47 +0530481 last_tax = self.doc.get("taxes")[-1]
Ankush Menat494bd9e2022-03-28 18:52:46 +0530482 non_inclusive_tax_amount = sum(
483 flt(d.tax_amount_after_discount_amount)
484 for d in self.doc.get("taxes")
485 if not d.included_in_print_rate
486 )
Nabin Haitf32fc232019-12-25 13:59:24 +0530487
Ankush Menat494bd9e2022-03-28 18:52:46 +0530488 diff = (
489 self.doc.total + non_inclusive_tax_amount - flt(last_tax.total, last_tax.precision("total"))
490 )
Nabin Haitf32fc232019-12-25 13:59:24 +0530491
492 # If discount amount applied, deduct the discount amount
493 # because self.doc.total is always without discount, but last_tax.total is after discount
494 if self.discount_amount_applied and self.doc.discount_amount:
495 diff -= flt(self.doc.discount_amount)
496
497 diff = flt(diff, self.doc.precision("rounding_adjustment"))
498
Ankush Menat494bd9e2022-03-28 18:52:46 +0530499 if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")):
Nabin Haitf32fc232019-12-25 13:59:24 +0530500 self.doc.rounding_adjustment = diff
Nabin Hait3237c752015-02-17 11:11:11 +0530501
502 def calculate_totals(self):
Subin Tom75a76e62021-10-29 16:45:04 +0530503 if self.doc.get("taxes"):
504 self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment)
505 else:
506 self.doc.grand_total = flt(self.doc.net_total)
Nabin Hait3237c752015-02-17 11:11:11 +0530507
Subin Tom75a76e62021-10-29 16:45:04 +0530508 if self.doc.get("taxes"):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530509 self.doc.total_taxes_and_charges = flt(
510 self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
511 self.doc.precision("total_taxes_and_charges"),
512 )
Subin Tom75a76e62021-10-29 16:45:04 +0530513 else:
514 self.doc.total_taxes_and_charges = 0.0
Anand Doshiec5ec602015-03-05 19:31:23 +0530515
Nabin Hait2e4de832017-09-19 14:53:16 +0530516 self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530517
Ankush Menat494bd9e2022-03-28 18:52:46 +0530518 if self.doc.doctype in [
519 "Quotation",
520 "Sales Order",
521 "Delivery Note",
522 "Sales Invoice",
523 "POS Invoice",
524 ]:
525 self.doc.base_grand_total = (
526 flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total"))
527 if self.doc.total_taxes_and_charges
528 else self.doc.base_net_total
529 )
Nabin Hait3237c752015-02-17 11:11:11 +0530530 else:
Anand Doshiec5ec602015-03-05 19:31:23 +0530531 self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0
Nabin Hait3237c752015-02-17 11:11:11 +0530532 for tax in self.doc.get("taxes"):
533 if tax.category in ["Valuation and Total", "Total"]:
534 if tax.add_deduct_tax == "Add":
Nabin Haitdb53a782015-07-31 16:53:13 +0530535 self.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530536 else:
Nabin Haitdb53a782015-07-31 16:53:13 +0530537 self.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530538
Nabin Haite7679702015-02-20 14:40:35 +0530539 self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530540
Ankush Menat494bd9e2022-03-28 18:52:46 +0530541 self.doc.base_grand_total = (
542 flt(self.doc.grand_total * self.doc.conversion_rate)
543 if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted)
Nabin Haite7679702015-02-20 14:40:35 +0530544 else self.doc.base_net_total
Ankush Menat494bd9e2022-03-28 18:52:46 +0530545 )
Nabin Hait3237c752015-02-17 11:11:11 +0530546
Ankush Menat494bd9e2022-03-28 18:52:46 +0530547 self._set_in_company_currency(
548 self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"]
549 )
Nabin Hait3237c752015-02-17 11:11:11 +0530550
Nabin Haite7679702015-02-20 14:40:35 +0530551 self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530552
Nabin Hait2e4de832017-09-19 14:53:16 +0530553 self.set_rounded_total()
554
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +0530555 def calculate_total_net_weight(self):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530556 if self.doc.meta.get_field("total_net_weight"):
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +0530557 self.doc.total_net_weight = 0.0
558 for d in self.doc.items:
559 if d.total_weight:
560 self.doc.total_net_weight += d.total_weight
561
Nabin Hait2e4de832017-09-19 14:53:16 +0530562 def set_rounded_total(self):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530563 if self.doc.get("is_consolidated") and self.doc.get("rounding_adjustment"):
Saqib Ansari17445c72022-03-07 18:01:07 +0530564 return
Nabin Hait877e1bb2017-11-17 12:27:43 +0530565
Saqib Ansari17445c72022-03-07 18:01:07 +0530566 if self.doc.meta.get_field("rounded_total"):
567 if self.doc.is_rounded_total_disabled():
568 self.doc.rounded_total = self.doc.base_rounded_total = 0
569 return
Nabin Hait2e4de832017-09-19 14:53:16 +0530570
Ankush Menat494bd9e2022-03-28 18:52:46 +0530571 self.doc.rounded_total = round_based_on_smallest_currency_fraction(
572 self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
573 )
Nabin Hait877e1bb2017-11-17 12:27:43 +0530574
Ankush Menat494bd9e2022-03-28 18:52:46 +0530575 # if print_in_rate is set, we would have already calculated rounding adjustment
576 self.doc.rounding_adjustment += flt(
577 self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment")
578 )
Saqib Ansari17445c72022-03-07 18:01:07 +0530579
580 self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530581
Nabin Hait3237c752015-02-17 11:11:11 +0530582 def _cleanup(self):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530583 if not self.doc.get("is_consolidated"):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530584 for tax in self.doc.get("taxes"):
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530585 if not tax.get("dont_recompute_tax"):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530586 tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(",", ":"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530587
Nabin Hait3769d872015-12-18 13:12:02 +0530588 def set_discount_amount(self):
Nabin Haite0405102016-10-13 12:14:32 +0530589 if self.doc.additional_discount_percentage:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530590 self.doc.discount_amount = flt(
591 flt(self.doc.get(scrub(self.doc.apply_discount_on)))
592 * self.doc.additional_discount_percentage
593 / 100,
594 self.doc.precision("discount_amount"),
595 )
Nabin Hait3237c752015-02-17 11:11:11 +0530596
597 def apply_discount_amount(self):
598 if self.doc.discount_amount:
Nabin Hait37b047d2015-02-23 16:01:33 +0530599 if not self.doc.apply_discount_on:
600 frappe.throw(_("Please select Apply Discount On"))
601
Deepesh Garg3b159662022-08-21 17:51:05 +0530602 self.doc.base_discount_amount = flt(
603 self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
604 )
605
Deepesh Garge54ec4b2022-07-03 11:02:21 +0530606 if self.doc.apply_discount_on == "Grand Total" and self.doc.get(
607 "is_cash_or_non_trade_discount"
608 ):
Deepesh Garg169ff5a2022-06-19 21:18:12 +0530609 self.discount_amount_applied = True
610 return
611
Nabin Haite7679702015-02-20 14:40:35 +0530612 total_for_discount_amount = self.get_total_for_discount_amount()
Nabin Hait25bd84d2015-03-04 15:06:56 +0530613 taxes = self.doc.get("taxes")
614 net_total = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530615
Nabin Haite7679702015-02-20 14:40:35 +0530616 if total_for_discount_amount:
Nabin Hait3237c752015-02-17 11:11:11 +0530617 # calculate item amount after Discount Amount
Nabin Hait25bd84d2015-03-04 15:06:56 +0530618 for i, item in enumerate(self.doc.get("items")):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530619 distributed_amount = (
620 flt(self.doc.discount_amount) * item.net_amount / total_for_discount_amount
621 )
Anand Doshiec5ec602015-03-05 19:31:23 +0530622
Nabin Haite7679702015-02-20 14:40:35 +0530623 item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
Nabin Hait25bd84d2015-03-04 15:06:56 +0530624 net_total += item.net_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530625
Nabin Hait25bd84d2015-03-04 15:06:56 +0530626 # discount amount rounding loss adjustment if no taxes
Ankush Menat494bd9e2022-03-28 18:52:46 +0530627 if (
628 self.doc.apply_discount_on == "Net Total"
629 or not taxes
630 or total_for_discount_amount == self.doc.net_total
631 ) and i == len(self.doc.get("items")) - 1:
632 discount_amount_loss = flt(
633 self.doc.net_total - net_total - self.doc.discount_amount, self.doc.precision("net_total")
634 )
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530635
Ankush Menat494bd9e2022-03-28 18:52:46 +0530636 item.net_amount = flt(item.net_amount + discount_amount_loss, item.precision("net_amount"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530637
Nabin Hait51e980d2015-10-10 18:10:05 +0530638 item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) if item.qty else 0
Anand Doshiec5ec602015-03-05 19:31:23 +0530639
Nabin Haite7679702015-02-20 14:40:35 +0530640 self._set_in_company_currency(item, ["net_rate", "net_amount"])
Nabin Hait3237c752015-02-17 11:11:11 +0530641
642 self.discount_amount_applied = True
643 self._calculate()
644 else:
645 self.doc.base_discount_amount = 0
646
Nabin Haite7679702015-02-20 14:40:35 +0530647 def get_total_for_discount_amount(self):
Nabin Haitde9c8a92015-02-23 01:06:00 +0530648 if self.doc.apply_discount_on == "Net Total":
649 return self.doc.net_total
Nabin Haite7679702015-02-20 14:40:35 +0530650 else:
651 actual_taxes_dict = {}
Nabin Hait3237c752015-02-17 11:11:11 +0530652
Nabin Haite7679702015-02-20 14:40:35 +0530653 for tax in self.doc.get("taxes"):
Nabin Hait19ea7212020-08-11 20:34:57 +0530654 if tax.charge_type in ["Actual", "On Item Quantity"]:
Nabin Haitaf9bdfe2017-12-12 18:50:05 +0530655 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
656 actual_taxes_dict.setdefault(tax.idx, tax_amount)
Nabin Haite7679702015-02-20 14:40:35 +0530657 elif tax.row_id in actual_taxes_dict:
658 actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * flt(tax.rate) / 100
659 actual_taxes_dict.setdefault(tax.idx, actual_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530660
Ankush Menat494bd9e2022-03-28 18:52:46 +0530661 return flt(
662 self.doc.grand_total - sum(actual_taxes_dict.values()), self.doc.precision("grand_total")
663 )
Nabin Hait3237c752015-02-17 11:11:11 +0530664
Nabin Hait7b19b9e2015-02-24 09:42:24 +0530665 def calculate_total_advance(self):
666 if self.doc.docstatus < 2:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530667 total_allocated_amount = sum(
668 flt(adv.allocated_amount, adv.precision("allocated_amount"))
669 for adv in self.doc.get("advances")
670 )
Nabin Hait3237c752015-02-17 11:11:11 +0530671
Nabin Haite7679702015-02-20 14:40:35 +0530672 self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530673
Faris Ansari6041f5c2018-02-08 13:33:52 +0530674 grand_total = self.doc.rounded_total or self.doc.grand_total
675
Nabin Hait289ffb72016-02-08 11:06:55 +0530676 if self.doc.party_account_currency == self.doc.currency:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530677 invoice_total = flt(
678 grand_total - flt(self.doc.write_off_amount), self.doc.precision("grand_total")
679 )
Nabin Hait8d8cba72017-04-03 17:26:22 +0530680 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530681 base_write_off_amount = flt(
682 flt(self.doc.write_off_amount) * self.doc.conversion_rate,
683 self.doc.precision("base_write_off_amount"),
684 )
685 invoice_total = (
686 flt(grand_total * self.doc.conversion_rate, self.doc.precision("grand_total"))
687 - base_write_off_amount
688 )
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530689
Nabin Haitadc09232016-02-09 10:31:11 +0530690 if invoice_total > 0 and self.doc.total_advance > invoice_total:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530691 frappe.throw(
692 _("Advance amount cannot be greater than {0} {1}").format(
693 self.doc.party_account_currency, invoice_total
694 )
695 )
Nabin Hait3237c752015-02-17 11:11:11 +0530696
Rushabh Mehta8bb6e532015-02-18 20:22:59 +0530697 if self.doc.docstatus == 0:
Ankush Menat3821a972022-03-28 20:14:19 +0530698 if self.doc.get("write_off_outstanding_amount_automatically"):
Deepesh Garg19b1b1f2022-03-25 18:02:14 +0530699 self.doc.write_off_amount = 0
700
Nabin Hait3237c752015-02-17 11:11:11 +0530701 self.calculate_outstanding_amount()
Deepesh Gargf57f4af2022-03-24 12:31:37 +0530702 self.calculate_write_off_amount()
Nabin Hait3237c752015-02-17 11:11:11 +0530703
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530704 def is_internal_invoice(self):
705 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530706 Checks if its an internal transfer invoice
707 and decides if to calculate any out standing amount or not
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530708 """
709
Ankush Menat494bd9e2022-03-28 18:52:46 +0530710 if self.doc.doctype in ("Sales Invoice", "Purchase Invoice") and self.doc.is_internal_transfer():
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530711 return True
712
713 return False
714
Nabin Hait3237c752015-02-17 11:11:11 +0530715 def calculate_outstanding_amount(self):
716 # NOTE:
717 # write_off_amount is only for POS Invoice
718 # total_advance is only for non POS Invoice
Rohit Waghchaureefb5bf22016-08-25 02:09:53 +0530719 if self.doc.doctype == "Sales Invoice":
720 self.calculate_paid_amount()
721
Ankush Menat494bd9e2022-03-28 18:52:46 +0530722 if (
723 self.doc.is_return
724 and self.doc.return_against
725 and not self.doc.get("is_pos")
726 or self.is_internal_invoice()
727 ):
728 return
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530729
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530730 self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
Ankush Menat494bd9e2022-03-28 18:52:46 +0530731 self._set_in_company_currency(self.doc, ["write_off_amount"])
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530732
Nabin Hait877e1bb2017-11-17 12:27:43 +0530733 if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
734 grand_total = self.doc.rounded_total or self.doc.grand_total
Deepesh Garg9d3a5c32022-01-06 18:58:49 +0530735 base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
736
Nabin Hait877e1bb2017-11-17 12:27:43 +0530737 if self.doc.party_account_currency == self.doc.currency:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530738 total_amount_to_pay = flt(
739 grand_total - self.doc.total_advance - flt(self.doc.write_off_amount),
740 self.doc.precision("grand_total"),
741 )
Nabin Hait877e1bb2017-11-17 12:27:43 +0530742 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530743 total_amount_to_pay = flt(
744 flt(base_grand_total, self.doc.precision("base_grand_total"))
745 - self.doc.total_advance
746 - flt(self.doc.base_write_off_amount),
747 self.doc.precision("base_grand_total"),
748 )
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530749
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530750 self.doc.round_floats_in(self.doc, ["paid_amount"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530751 change_amount = 0
752
Ankush Menat494bd9e2022-03-28 18:52:46 +0530753 if self.doc.doctype == "Sales Invoice" and not self.doc.get("is_return"):
Nabin Hait877e1bb2017-11-17 12:27:43 +0530754 self.calculate_change_amount()
Ankush Menat494bd9e2022-03-28 18:52:46 +0530755 change_amount = (
756 self.doc.change_amount
757 if self.doc.party_account_currency == self.doc.currency
758 else self.doc.base_change_amount
759 )
Nabin Hait877e1bb2017-11-17 12:27:43 +0530760
Ankush Menat494bd9e2022-03-28 18:52:46 +0530761 paid_amount = (
762 self.doc.paid_amount
763 if self.doc.party_account_currency == self.doc.currency
764 else self.doc.base_paid_amount
765 )
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530766
Ankush Menat494bd9e2022-03-28 18:52:46 +0530767 self.doc.outstanding_amount = flt(
768 total_amount_to_pay - flt(paid_amount) + flt(change_amount),
769 self.doc.precision("outstanding_amount"),
770 )
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530771
Saqib Ansarie8a7a542022-03-12 19:20:48 +0530772 if (
Ankush Menat494bd9e2022-03-28 18:52:46 +0530773 self.doc.doctype == "Sales Invoice"
774 and self.doc.get("is_pos")
775 and self.doc.get("is_return")
776 and not self.doc.get("is_consolidated")
Saqib Ansarie8a7a542022-03-12 19:20:48 +0530777 ):
Subin Tom7d627df2021-08-23 11:05:07 +0530778 self.set_total_amount_to_default_mop(total_amount_to_pay)
779 self.calculate_paid_amount()
Deepesh Garg0ebace52020-02-25 13:21:16 +0530780
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530781 def calculate_paid_amount(self):
Manas Solankida486ee2018-07-06 12:36:57 +0530782
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530783 paid_amount = base_paid_amount = 0.0
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530784
785 if self.doc.is_pos:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530786 for payment in self.doc.get("payments"):
Ayush Shuklae9cf1ab2017-05-25 14:14:55 +0530787 payment.amount = flt(payment.amount)
788 payment.base_amount = payment.amount * flt(self.doc.conversion_rate)
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530789 paid_amount += payment.amount
790 base_paid_amount += payment.base_amount
rohitwaghchaure73456ac2017-05-16 11:29:57 +0530791 elif not self.doc.is_return:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530792 self.doc.set("payments", [])
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530793
Manas Solankida486ee2018-07-06 12:36:57 +0530794 if self.doc.redeem_loyalty_points and self.doc.loyalty_amount:
795 base_paid_amount += self.doc.loyalty_amount
Ankush Menat494bd9e2022-03-28 18:52:46 +0530796 paid_amount += self.doc.loyalty_amount / flt(self.doc.conversion_rate)
Manas Solankida486ee2018-07-06 12:36:57 +0530797
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530798 self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount"))
799 self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount"))
800
Nabin Hait3bb1a422016-08-02 16:41:10 +0530801 def calculate_change_amount(self):
802 self.doc.change_amount = 0.0
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530803 self.doc.base_change_amount = 0.0
Saqib Ansaric2b83a02022-02-08 17:07:51 +0530804 grand_total = self.doc.rounded_total or self.doc.grand_total
805 base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
Nabin Hait877e1bb2017-11-17 12:27:43 +0530806
Ankush Menat494bd9e2022-03-28 18:52:46 +0530807 if (
808 self.doc.doctype == "Sales Invoice"
809 and self.doc.paid_amount > grand_total
810 and not self.doc.is_return
811 and any(d.type == "Cash" for d in self.doc.payments)
812 ):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530813 self.doc.change_amount = flt(
Ankush Menat3821a972022-03-28 20:14:19 +0530814 self.doc.paid_amount - grand_total, self.doc.precision("change_amount")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530815 )
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530816
Ankush Menat494bd9e2022-03-28 18:52:46 +0530817 self.doc.base_change_amount = flt(
Ankush Menat3821a972022-03-28 20:14:19 +0530818 self.doc.base_paid_amount - base_grand_total, self.doc.precision("base_change_amount")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530819 )
mbauskar36b51892016-01-18 16:31:10 +0530820
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530821 def calculate_write_off_amount(self):
Ankush Menat3821a972022-03-28 20:14:19 +0530822 if self.doc.get("write_off_outstanding_amount_automatically"):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530823 self.doc.write_off_amount = flt(
Ankush Menat3821a972022-03-28 20:14:19 +0530824 self.doc.outstanding_amount, self.doc.precision("write_off_amount")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530825 )
826 self.doc.base_write_off_amount = flt(
827 self.doc.write_off_amount * self.doc.conversion_rate,
828 self.doc.precision("base_write_off_amount"),
829 )
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530830
Deepesh Gargf57f4af2022-03-24 12:31:37 +0530831 self.calculate_outstanding_amount()
832
mbauskar36b51892016-01-18 16:31:10 +0530833 def calculate_margin(self, item):
Makarand Bauskar0e4c5c92017-05-11 11:40:02 +0530834 rate_with_margin = 0.0
Shreya Shahbe690ef2017-11-14 17:22:41 +0530835 base_rate_with_margin = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530836 if item.price_list_rate:
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530837 if item.pricing_rules and not self.doc.ignore_pricing_rule:
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530838 has_margin = False
marination733fd5f2020-08-26 18:23:12 +0530839 for d in get_applied_pricing_rules(item.pricing_rules):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530840 pricing_rule = frappe.get_cached_doc("Pricing Rule", d)
Shreya Shahf718b0c2018-02-20 11:26:46 +0530841
Ankush Menat494bd9e2022-03-28 18:52:46 +0530842 if pricing_rule.margin_rate_or_amount and (
843 (
844 pricing_rule.currency == self.doc.currency
845 and pricing_rule.margin_type in ["Amount", "Percentage"]
846 )
847 or pricing_rule.margin_type == "Percentage"
848 ):
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530849 item.margin_type = pricing_rule.margin_type
850 item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530851 has_margin = True
852
853 if not has_margin:
854 item.margin_type = None
855 item.margin_rate_or_amount = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530856
Rohit Waghchaure01faa9c2021-06-21 00:59:02 +0530857 if not item.pricing_rules and flt(item.rate) > flt(item.price_list_rate):
858 item.margin_type = "Amount"
Ankush Menat494bd9e2022-03-28 18:52:46 +0530859 item.margin_rate_or_amount = flt(
860 item.rate - item.price_list_rate, item.precision("margin_rate_or_amount")
861 )
Rohit Waghchaure01faa9c2021-06-21 00:59:02 +0530862 item.rate_with_margin = item.rate
863
864 elif item.margin_type and item.margin_rate_or_amount:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530865 margin_value = (
866 item.margin_rate_or_amount
867 if item.margin_type == "Amount"
868 else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100
869 )
Makarand Bauskar0e4c5c92017-05-11 11:40:02 +0530870 rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
Shreya Shahbe690ef2017-11-14 17:22:41 +0530871 base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
mbauskar36b51892016-01-18 16:31:10 +0530872
Shreya Shahbe690ef2017-11-14 17:22:41 +0530873 return rate_with_margin, base_rate_with_margin
Nabin Hait852cb642017-07-05 12:58:19 +0530874
875 def set_item_wise_tax_breakup(self):
Nabin Hait9c421612017-07-20 13:32:01 +0530876 self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530877
Subin Tom7d627df2021-08-23 11:05:07 +0530878 def set_total_amount_to_default_mop(self, total_amount_to_pay):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530879 default_mode_of_payment = frappe.db.get_value(
880 "POS Payment Method",
881 {"parent": self.doc.pos_profile, "default": 1},
882 ["mode_of_payment"],
883 as_dict=1,
884 )
Deepesh Garg0ebace52020-02-25 13:21:16 +0530885
Deepesh Garg0ebace52020-02-25 13:21:16 +0530886 if default_mode_of_payment:
Afshanb6148342021-08-10 21:33:58 +0530887 self.doc.payments = []
Ankush Menat494bd9e2022-03-28 18:52:46 +0530888 self.doc.append(
889 "payments",
890 {
891 "mode_of_payment": default_mode_of_payment.mode_of_payment,
892 "amount": total_amount_to_pay,
893 "default": 1,
894 },
895 )
896
Deepesh Garg0ebace52020-02-25 13:21:16 +0530897
Nabin Hait9c421612017-07-20 13:32:01 +0530898def get_itemised_tax_breakup_html(doc):
899 if not doc.taxes:
900 return
901 frappe.flags.company = doc.company
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530902
Nabin Hait9c421612017-07-20 13:32:01 +0530903 # get headers
Nabin Haitcaab5822017-08-24 16:22:28 +0530904 tax_accounts = []
905 for tax in doc.taxes:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530906 if getattr(tax, "category", None) and tax.category == "Valuation":
Nabin Haitcaab5822017-08-24 16:22:28 +0530907 continue
rohitwaghchaure4e17fae2017-12-12 14:40:52 +0530908 if tax.description not in tax_accounts:
Nabin Haitcaab5822017-08-24 16:22:28 +0530909 tax_accounts.append(tax.description)
910
Nabin Hait9c421612017-07-20 13:32:01 +0530911 headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530912
Nabin Hait9c421612017-07-20 13:32:01 +0530913 # get tax breakup data
914 itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc)
Nabin Haitcaab5822017-08-24 16:22:28 +0530915
916 get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes"))
917
rohitwaghchaured4526682017-12-28 14:20:13 +0530918 update_itemised_tax_data(doc)
Nabin Hait9c421612017-07-20 13:32:01 +0530919 frappe.flags.company = None
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530920
Nabin Hait9c421612017-07-20 13:32:01 +0530921 return frappe.render_template(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530922 "templates/includes/itemised_tax_breakup.html",
923 dict(
Nabin Hait9c421612017-07-20 13:32:01 +0530924 headers=headers,
925 itemised_tax=itemised_tax,
926 itemised_taxable_amount=itemised_taxable_amount,
927 tax_accounts=tax_accounts,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530928 doc=doc,
929 ),
Nabin Hait9c421612017-07-20 13:32:01 +0530930 )
Nabin Hait852cb642017-07-05 12:58:19 +0530931
Ankush Menat494bd9e2022-03-28 18:52:46 +0530932
Deepesh Garg6a5ef262021-02-19 14:30:23 +0530933@frappe.whitelist()
934def get_round_off_applicable_accounts(company, account_list):
935 account_list = get_regional_round_off_accounts(company, account_list)
936
937 return account_list
938
Ankush Menat494bd9e2022-03-28 18:52:46 +0530939
Deepesh Garg6a5ef262021-02-19 14:30:23 +0530940@erpnext.allow_regional
941def get_regional_round_off_accounts(company, account_list):
942 pass
rohitwaghchaured4526682017-12-28 14:20:13 +0530943
Ankush Menat494bd9e2022-03-28 18:52:46 +0530944
rohitwaghchaured4526682017-12-28 14:20:13 +0530945@erpnext.allow_regional
946def update_itemised_tax_data(doc):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530947 # Don't delete this method, used for localization
rohitwaghchaured4526682017-12-28 14:20:13 +0530948 pass
949
Ankush Menat494bd9e2022-03-28 18:52:46 +0530950
Nabin Haitb962fc12017-07-17 18:02:31 +0530951@erpnext.allow_regional
952def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
953 return [_("Item"), _("Taxable Amount")] + tax_accounts
954
Ankush Menat494bd9e2022-03-28 18:52:46 +0530955
Nabin Haitb962fc12017-07-17 18:02:31 +0530956@erpnext.allow_regional
957def get_itemised_tax_breakup_data(doc):
958 itemised_tax = get_itemised_tax(doc.taxes)
959
960 itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
961
962 return itemised_tax, itemised_taxable_amount
963
Ankush Menat494bd9e2022-03-28 18:52:46 +0530964
Nabin Hait34c551d2019-07-03 10:34:31 +0530965def get_itemised_tax(taxes, with_tax_account=False):
Nabin Haitb962fc12017-07-17 18:02:31 +0530966 itemised_tax = {}
967 for tax in taxes:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530968 if getattr(tax, "category", None) and tax.category == "Valuation":
Nabin Haitcaab5822017-08-24 16:22:28 +0530969 continue
970
Nabin Haitb962fc12017-07-17 18:02:31 +0530971 item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
Nabin Hait2e4de832017-09-19 14:53:16 +0530972 if item_tax_map:
973 for item_code, tax_data in item_tax_map.items():
974 itemised_tax.setdefault(item_code, frappe._dict())
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530975
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530976 tax_rate = 0.0
977 tax_amount = 0.0
978
Nabin Hait2e4de832017-09-19 14:53:16 +0530979 if isinstance(tax_data, list):
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530980 tax_rate = flt(tax_data[0])
981 tax_amount = flt(tax_data[1])
Nabin Hait2e4de832017-09-19 14:53:16 +0530982 else:
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530983 tax_rate = flt(tax_data)
984
Ankush Menat494bd9e2022-03-28 18:52:46 +0530985 itemised_tax[item_code][tax.description] = frappe._dict(
986 dict(tax_rate=tax_rate, tax_amount=tax_amount)
987 )
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530988
Nabin Hait34c551d2019-07-03 10:34:31 +0530989 if with_tax_account:
990 itemised_tax[item_code][tax.description].tax_account = tax.account_head
991
Nabin Haitb962fc12017-07-17 18:02:31 +0530992 return itemised_tax
993
Ankush Menat494bd9e2022-03-28 18:52:46 +0530994
Nabin Haitb962fc12017-07-17 18:02:31 +0530995def get_itemised_taxable_amount(items):
996 itemised_taxable_amount = frappe._dict()
997 for item in items:
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530998 item_code = item.item_code or item.item_name
Nabin Haitb962fc12017-07-17 18:02:31 +0530999 itemised_taxable_amount.setdefault(item_code, 0)
1000 itemised_taxable_amount[item_code] += item.net_amount
1001
Nabin Haitcaab5822017-08-24 16:22:28 +05301002 return itemised_taxable_amount
1003
Ankush Menat494bd9e2022-03-28 18:52:46 +05301004
Nabin Haitcaab5822017-08-24 16:22:28 +05301005def get_rounded_tax_amount(itemised_tax, precision):
1006 # Rounding based on tax_amount precision
1007 for taxes in itemised_tax.values():
1008 for tax_account in taxes:
Himanshu Mishra35b26272018-11-13 11:13:04 +05301009 taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
Deepesh Gargbfc17e42020-12-25 18:34:39 +05301010
Ankush Menat494bd9e2022-03-28 18:52:46 +05301011
Deepesh Gargbfc17e42020-12-25 18:34:39 +05301012class init_landed_taxes_and_totals(object):
1013 def __init__(self, doc):
1014 self.doc = doc
Ankush Menat494bd9e2022-03-28 18:52:46 +05301015 self.tax_field = "taxes" if self.doc.doctype == "Landed Cost Voucher" else "additional_costs"
Deepesh Gargbfc17e42020-12-25 18:34:39 +05301016 self.set_account_currency()
1017 self.set_exchange_rate()
1018 self.set_amounts_in_company_currency()
1019
1020 def set_account_currency(self):
1021 company_currency = erpnext.get_company_currency(self.doc.company)
1022 for d in self.doc.get(self.tax_field):
1023 if not d.account_currency:
Ankush Menat494bd9e2022-03-28 18:52:46 +05301024 account_currency = frappe.db.get_value("Account", d.expense_account, "account_currency")
Deepesh Gargbfc17e42020-12-25 18:34:39 +05301025 d.account_currency = account_currency or company_currency
1026
1027 def set_exchange_rate(self):
1028 company_currency = erpnext.get_company_currency(self.doc.company)
1029 for d in self.doc.get(self.tax_field):
1030 if d.account_currency == company_currency:
1031 d.exchange_rate = 1
Deepesh Garg22f5ff82021-03-17 10:56:52 +05301032 elif not d.exchange_rate:
Ankush Menat494bd9e2022-03-28 18:52:46 +05301033 d.exchange_rate = get_exchange_rate(
1034 self.doc.posting_date,
1035 account=d.expense_account,
1036 account_currency=d.account_currency,
1037 company=self.doc.company,
1038 )
Deepesh Gargbfc17e42020-12-25 18:34:39 +05301039
1040 if not d.exchange_rate:
1041 frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
1042
1043 def set_amounts_in_company_currency(self):
1044 for d in self.doc.get(self.tax_field):
1045 d.amount = flt(d.amount, d.precision("amount"))
Walstan Baptista37b826b2021-04-03 19:48:46 +05301046 d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))