blob: e30a0a1967c3a1420448ac434c2f4f95ab4237f7 [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
Nabin Haitbd00e812015-02-17 12:50:51 +053040 if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
Nabin Hait3237c752015-02-17 11:11:11 +053041 self.calculate_total_advance()
Vishal Dhayaguded42242d2017-11-29 16:09:59 +053042
Nabin Hait852cb642017-07-05 12:58:19 +053043 if self.doc.meta.get_field("other_charges_calculation"):
44 self.set_item_wise_tax_breakup()
Nabin Hait3237c752015-02-17 11:11:11 +053045
46 def _calculate(self):
Faris Ansarieae2dda2018-05-02 12:19:30 +053047 self.validate_conversion_rate()
Nabin Haite7679702015-02-20 14:40:35 +053048 self.calculate_item_values()
Deepesh Gargef0d26c2020-01-06 15:34:15 +053049 self.validate_item_tax_template()
Nabin Haite7679702015-02-20 14:40:35 +053050 self.initialize_taxes()
51 self.determine_exclusive_rate()
52 self.calculate_net_total()
53 self.calculate_taxes()
Nabin Haita1bf43b2015-03-17 10:50:47 +053054 self.manipulate_grand_total_for_inclusive_tax()
Nabin Haite7679702015-02-20 14:40:35 +053055 self.calculate_totals()
56 self._cleanup()
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +053057 self.calculate_total_net_weight()
Nabin Haite7679702015-02-20 14:40:35 +053058
Deepesh Gargef0d26c2020-01-06 15:34:15 +053059 def validate_item_tax_template(self):
60 for item in self.doc.get('items'):
61 if item.item_code and item.get('item_tax_template'):
62 item_doc = frappe.get_cached_doc("Item", item.item_code)
63 args = {
Deepesh Garg8a7e2832021-06-04 22:53:26 +053064 'net_rate': item.net_rate or item.rate,
Deepesh Gargef0d26c2020-01-06 15:34:15 +053065 'tax_category': self.doc.get('tax_category'),
66 'posting_date': self.doc.get('posting_date'),
67 'bill_date': self.doc.get('bill_date'),
mohammadahmad1990728bf0e2020-06-18 12:21:42 +050068 'transaction_date': self.doc.get('transaction_date'),
69 'company': self.doc.get('company')
Deepesh Gargef0d26c2020-01-06 15:34:15 +053070 }
71
72 item_group = item_doc.item_group
73 item_group_taxes = []
74
75 while item_group:
76 item_group_doc = frappe.get_cached_doc('Item Group', item_group)
77 item_group_taxes += item_group_doc.taxes or []
78 item_group = item_group_doc.parent_item_group
79
80 item_taxes = item_doc.taxes or []
81
82 if not item_group_taxes and (not item_taxes):
83 # No validation if no taxes in item or item group
84 continue
85
86 taxes = _get_item_tax_template(args, item_taxes + item_group_taxes, for_validate=True)
87
Deepesh Garg18be7672021-06-06 13:25:34 +053088 if taxes:
89 if item.item_tax_template not in taxes:
90 item.item_tax_template = taxes[0]
91 frappe.msgprint(_("Row {0}: Item Tax template updated as per validity and rate applied").format(
92 item.idx, frappe.bold(item.item_code)
93 ))
Deepesh Gargef0d26c2020-01-06 15:34:15 +053094
Nabin Haite7679702015-02-20 14:40:35 +053095 def validate_conversion_rate(self):
Nabin Hait3237c752015-02-17 11:11:11 +053096 # validate conversion rate
Rushabh Mehtacc8b2b22017-03-31 12:44:29 +053097 company_currency = erpnext.get_company_currency(self.doc.company)
Nabin Hait3237c752015-02-17 11:11:11 +053098 if not self.doc.currency or self.doc.currency == company_currency:
99 self.doc.currency = company_currency
100 self.doc.conversion_rate = 1.0
101 else:
102 validate_conversion_rate(self.doc.currency, self.doc.conversion_rate,
103 self.doc.meta.get_label("conversion_rate"), self.doc.company)
104
105 self.doc.conversion_rate = flt(self.doc.conversion_rate)
106
Nabin Hait3237c752015-02-17 11:11:11 +0530107 def calculate_item_values(self):
108 if not self.discount_amount_applied:
109 for item in self.doc.get("items"):
110 self.doc.round_floats_in(item)
111
112 if item.discount_percentage == 100:
113 item.rate = 0.0
Nabin Hait593242f2019-04-05 19:35:02 +0530114 elif item.price_list_rate:
115 if not item.rate or (item.pricing_rules and item.discount_percentage > 0):
116 item.rate = flt(item.price_list_rate *
117 (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
118 item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
119 elif item.discount_amount and item.pricing_rules:
120 item.rate = item.price_list_rate - item.discount_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530121
Anupam Kumared42afc2021-03-15 11:11:28 +0530122 if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item', 'Purchase Invoice Item', 'Purchase Order Item', 'Purchase Receipt Item']:
Shreya Shahbe690ef2017-11-14 17:22:41 +0530123 item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
Nabin Hait64bfdd92019-04-23 13:37:19 +0530124 if flt(item.rate_with_margin) > 0:
125 item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
Nabin Hait10c61372021-04-13 15:46:01 +0530126
Walstan Baptista37b826b2021-04-03 19:48:46 +0530127 if item.discount_amount and not item.discount_percentage:
Nabin Hait10c61372021-04-13 15:46:01 +0530128 item.rate = item.rate_with_margin - item.discount_amount
Walstan Baptista37b826b2021-04-03 19:48:46 +0530129 else:
130 item.discount_amount = item.rate_with_margin - item.rate
Nabin Hait10c61372021-04-13 15:46:01 +0530131
Nabin Hait64bfdd92019-04-23 13:37:19 +0530132 elif flt(item.price_list_rate) > 0:
133 item.discount_amount = item.price_list_rate - item.rate
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530134 elif flt(item.price_list_rate) > 0 and not item.discount_amount:
135 item.discount_amount = item.price_list_rate - item.rate
mbauskara52472c2016-03-05 15:10:25 +0530136
Nabin Haite7679702015-02-20 14:40:35 +0530137 item.net_rate = item.rate
Deepesh Gargb65c7612019-07-31 15:58:01 +0530138
deepeshgarg0078bf19ce2019-08-03 13:40:37 +0530139 if not item.qty and self.doc.get("is_return"):
Deepesh Gargb65c7612019-07-31 15:58:01 +0530140 item.amount = flt(-1 * item.rate, item.precision("amount"))
141 else:
142 item.amount = flt(item.rate * item.qty, item.precision("amount"))
143
Nabin Haite7679702015-02-20 14:40:35 +0530144 item.net_amount = item.amount
Nabin Hait3237c752015-02-17 11:11:11 +0530145
Nabin Haite7679702015-02-20 14:40:35 +0530146 self._set_in_company_currency(item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"])
Nabin Hait3237c752015-02-17 11:11:11 +0530147
Nabin Haite7679702015-02-20 14:40:35 +0530148 item.item_tax_amount = 0.0
149
150 def _set_in_company_currency(self, doc, fields):
Nabin Hait3237c752015-02-17 11:11:11 +0530151 """set values in base currency"""
Nabin Haite7679702015-02-20 14:40:35 +0530152 for f in fields:
153 val = flt(flt(doc.get(f), doc.precision(f)) * self.doc.conversion_rate, doc.precision("base_" + f))
154 doc.set("base_" + f, val)
Nabin Hait3237c752015-02-17 11:11:11 +0530155
156 def initialize_taxes(self):
157 for tax in self.doc.get("taxes"):
Nabin Hait86cd4cc2015-02-28 19:11:51 +0530158 if not self.discount_amount_applied:
159 validate_taxes_and_charges(tax)
160 validate_inclusive_tax(tax, self.doc)
Nabin Hait613d0812015-02-23 11:58:15 +0530161
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530162 if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530163 tax.item_wise_tax_detail = {}
164
Nabin Hait3237c752015-02-17 11:11:11 +0530165 tax_fields = ["total", "tax_amount_after_discount_amount",
166 "tax_amount_for_current_item", "grand_total_for_current_item",
167 "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
168
Nabin Haitde9c8a92015-02-23 01:06:00 +0530169 if tax.charge_type != "Actual" and \
170 not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
171 tax_fields.append("tax_amount")
Nabin Hait3237c752015-02-17 11:11:11 +0530172
173 for fieldname in tax_fields:
174 tax.set(fieldname, 0.0)
175
Nabin Hait3237c752015-02-17 11:11:11 +0530176 self.doc.round_floats_in(tax)
177
Nabin Hait3237c752015-02-17 11:11:11 +0530178 def determine_exclusive_rate(self):
Ankush Menat8fe5feb2021-11-04 19:48:32 +0530179 if not any(cint(tax.included_in_print_rate) for tax in self.doc.get("taxes")):
Nabin Hait37b047d2015-02-23 16:01:33 +0530180 return
Nabin Hait3237c752015-02-17 11:11:11 +0530181
182 for item in self.doc.get("items"):
183 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
184 cumulated_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530185 total_inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530186 for i, tax in enumerate(self.doc.get("taxes")):
Nabin Hait19ea7212020-08-11 20:34:57 +0530187 tax.tax_fraction_for_current_item, inclusive_tax_amount_per_qty = self.get_current_tax_fraction(tax, item_tax_map)
Nabin Hait3237c752015-02-17 11:11:11 +0530188
189 if i==0:
190 tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
191 else:
192 tax.grand_total_fraction_for_current_item = \
193 self.doc.get("taxes")[i-1].grand_total_fraction_for_current_item \
194 + tax.tax_fraction_for_current_item
195
196 cumulated_tax_fraction += tax.tax_fraction_for_current_item
Nabin Haite2ee4552020-08-12 20:58:03 +0530197 total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
Nabin Hait3237c752015-02-17 11:11:11 +0530198
Nabin Hait19ea7212020-08-11 20:34:57 +0530199 if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty):
200 amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
201
202 item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
Nabin Haite7679702015-02-20 14:40:35 +0530203 item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
Nabin Hait2e4de832017-09-19 14:53:16 +0530204 item.discount_percentage = flt(item.discount_percentage,
205 item.precision("discount_percentage"))
Nabin Hait3237c752015-02-17 11:11:11 +0530206
Nabin Haite7679702015-02-20 14:40:35 +0530207 self._set_in_company_currency(item, ["net_rate", "net_amount"])
208
Nabin Hait3237c752015-02-17 11:11:11 +0530209 def _load_item_tax_rate(self, item_tax_rate):
210 return json.loads(item_tax_rate) if item_tax_rate else {}
211
212 def get_current_tax_fraction(self, tax, item_tax_map):
213 """
214 Get tax fraction for calculating tax exclusive amount
215 from tax inclusive amount
216 """
217 current_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530218 inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530219
220 if cint(tax.included_in_print_rate):
221 tax_rate = self._get_tax_rate(tax, item_tax_map)
222
223 if tax.charge_type == "On Net Total":
224 current_tax_fraction = tax_rate / 100.0
225
226 elif tax.charge_type == "On Previous Row Amount":
227 current_tax_fraction = (tax_rate / 100.0) * \
228 self.doc.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
229
230 elif tax.charge_type == "On Previous Row Total":
231 current_tax_fraction = (tax_rate / 100.0) * \
232 self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
marination733fd5f2020-08-26 18:23:12 +0530233
Nabin Hait19ea7212020-08-11 20:34:57 +0530234 elif tax.charge_type == "On Item Quantity":
235 inclusive_tax_amount_per_qty = flt(tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530236
Nabin Hait19ea7212020-08-11 20:34:57 +0530237 if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
238 current_tax_fraction *= -1.0
239 inclusive_tax_amount_per_qty *= -1.0
240
241 return current_tax_fraction, inclusive_tax_amount_per_qty
Nabin Hait3237c752015-02-17 11:11:11 +0530242
243 def _get_tax_rate(self, tax, item_tax_map):
Achilles Rasquinha87dab142018-03-08 14:21:48 +0530244 if tax.account_head in item_tax_map:
Nabin Hait3237c752015-02-17 11:11:11 +0530245 return flt(item_tax_map.get(tax.account_head), self.doc.precision("rate", tax))
246 else:
247 return tax.rate
248
249 def calculate_net_total(self):
Shreya Shahe3290382018-05-28 11:49:08 +0530250 self.doc.total_qty = self.doc.total = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
rohitwaghchaure3a595d02018-06-25 10:10:29 +0530251
Nabin Hait3237c752015-02-17 11:11:11 +0530252 for item in self.doc.get("items"):
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530253 self.doc.total += item.amount
Shreya Shahe3290382018-05-28 11:49:08 +0530254 self.doc.total_qty += item.qty
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530255 self.doc.base_total += item.base_amount
Nabin Haite7679702015-02-20 14:40:35 +0530256 self.doc.net_total += item.net_amount
257 self.doc.base_net_total += item.base_net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530258
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530259 self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530260
Subin Tomaf1fce02021-11-10 16:49:12 +0530261 if hasattr(self.doc, "shipping_rule") and self.doc.shipping_rule:
Subin Tom18ae03d2021-11-10 15:57:41 +0530262 shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule)
263 shipping_rule.apply(self.doc)
264
Nabin Hait3237c752015-02-17 11:11:11 +0530265 def calculate_taxes(self):
Subin Tom75a76e62021-10-29 16:45:04 +0530266 if not self.doc.get('is_consolidated'):
267 self.doc.rounding_adjustment = 0
268
Nabin Hait3237c752015-02-17 11:11:11 +0530269 # maintain actual tax rate based on idx
Nabin Haite7679702015-02-20 14:40:35 +0530270 actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
Nabin Hait3237c752015-02-17 11:11:11 +0530271 for tax in self.doc.get("taxes") if tax.charge_type == "Actual"])
272
273 for n, item in enumerate(self.doc.get("items")):
274 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530275 for i, tax in enumerate(self.doc.get("taxes")):
276 # tax_amount represents the amount of tax for the current step
277 current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map)
278
279 # Adjust divisional loss to the last item
280 if tax.charge_type == "Actual":
281 actual_tax_dict[tax.idx] -= current_tax_amount
282 if n == len(self.doc.get("items")) - 1:
283 current_tax_amount += actual_tax_dict[tax.idx]
284
Nabin Hait2b019ed2015-02-22 23:03:07 +0530285 # accumulate tax amount into tax.tax_amount
Nabin Haitde9c8a92015-02-23 01:06:00 +0530286 if tax.charge_type != "Actual" and \
287 not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
288 tax.tax_amount += current_tax_amount
Nabin Hait2b019ed2015-02-22 23:03:07 +0530289
Nabin Hait3237c752015-02-17 11:11:11 +0530290 # store tax_amount for current item as it will be used for
291 # charge type = 'On Previous Row Amount'
292 tax.tax_amount_for_current_item = current_tax_amount
293
Nabin Hait2b019ed2015-02-22 23:03:07 +0530294 # set tax after discount
Nabin Hait3237c752015-02-17 11:11:11 +0530295 tax.tax_amount_after_discount_amount += current_tax_amount
296
Nabin Haitcd951342017-07-31 18:07:45 +0530297 current_tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(current_tax_amount, tax)
Nabin Hait3237c752015-02-17 11:11:11 +0530298
Nabin Hait3237c752015-02-17 11:11:11 +0530299 # note: grand_total_for_current_item contains the contribution of
300 # item's amount, previously applied tax and the current tax on that item
301 if i==0:
Nabin Haitcd951342017-07-31 18:07:45 +0530302 tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530303 else:
304 tax.grand_total_for_current_item = \
Nabin Haitcd951342017-07-31 18:07:45 +0530305 flt(self.doc.get("taxes")[i-1].grand_total_for_current_item + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530306
307 # set precision in the last item iteration
308 if n == len(self.doc.get("items")) - 1:
309 self.round_off_totals(tax)
Deepesh Gargb6705882021-04-14 11:20:27 +0530310 self._set_in_company_currency(tax,
311 ["tax_amount", "tax_amount_after_discount_amount"])
312
313 self.round_off_base_values(tax)
Nabin Haitcd951342017-07-31 18:07:45 +0530314 self.set_cumulative_total(i, tax)
315
Deepesh Gargb6705882021-04-14 11:20:27 +0530316 self._set_in_company_currency(tax, ["total"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530317
Nabin Hait3237c752015-02-17 11:11:11 +0530318 # adjust Discount Amount loss in last tax iteration
Nabin Haitde9c8a92015-02-23 01:06:00 +0530319 if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
Subin Tom75a76e62021-10-29 16:45:04 +0530320 and self.doc.discount_amount \
321 and self.doc.apply_discount_on == "Grand Total" \
322 and not self.doc.get('is_consolidated'):
Nabin Hait2e4de832017-09-19 14:53:16 +0530323 self.doc.rounding_adjustment = flt(self.doc.grand_total
324 - flt(self.doc.discount_amount) - tax.total,
325 self.doc.precision("rounding_adjustment"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530326
Nabin Haitcd951342017-07-31 18:07:45 +0530327 def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
328 # if just for valuation, do not add the tax amount in total
329 # if tax/charges is for deduction, multiply by -1
330 if getattr(tax, "category", None):
331 tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
Rushabh Mehta30dc9a12017-11-17 14:31:09 +0530332 if self.doc.doctype in ["Purchase Order", "Purchase Invoice", "Purchase Receipt", "Supplier Quotation"]:
333 tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
Nabin Haitcd951342017-07-31 18:07:45 +0530334 return tax_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530335
Nabin Haitcd951342017-07-31 18:07:45 +0530336 def set_cumulative_total(self, row_idx, tax):
337 tax_amount = tax.tax_amount_after_discount_amount
338 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax_amount, tax)
339
340 if row_idx == 0:
341 tax.total = flt(self.doc.net_total + tax_amount, tax.precision("total"))
342 else:
343 tax.total = flt(self.doc.get("taxes")[row_idx-1].total + tax_amount, tax.precision("total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530344
345 def get_current_tax_amount(self, item, tax, item_tax_map):
346 tax_rate = self._get_tax_rate(tax, item_tax_map)
347 current_tax_amount = 0.0
348
349 if tax.charge_type == "Actual":
350 # distribute the tax amount proportionally to each item row
Nabin Haite7679702015-02-20 14:40:35 +0530351 actual = flt(tax.tax_amount, tax.precision("tax_amount"))
352 current_tax_amount = item.net_amount*actual / self.doc.net_total if self.doc.net_total else 0.0
353
Nabin Hait3237c752015-02-17 11:11:11 +0530354 elif tax.charge_type == "On Net Total":
Nabin Haite7679702015-02-20 14:40:35 +0530355 current_tax_amount = (tax_rate / 100.0) * item.net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530356 elif tax.charge_type == "On Previous Row Amount":
357 current_tax_amount = (tax_rate / 100.0) * \
358 self.doc.get("taxes")[cint(tax.row_id) - 1].tax_amount_for_current_item
359 elif tax.charge_type == "On Previous Row Total":
360 current_tax_amount = (tax_rate / 100.0) * \
361 self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
Himanshu Mishra35b26272018-11-13 11:13:04 +0530362 elif tax.charge_type == "On Item Quantity":
Nabin Hait19ea7212020-08-11 20:34:57 +0530363 current_tax_amount = tax_rate * item.qty
Nabin Hait3237c752015-02-17 11:11:11 +0530364
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530365 if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530366 self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530367
368 return current_tax_amount
369
Nabin Haite7679702015-02-20 14:40:35 +0530370 def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
371 # store tax breakup for each item
372 key = item.item_code or item.item_name
373 item_wise_tax_amount = current_tax_amount*self.doc.conversion_rate
374 if tax.item_wise_tax_detail.get(key):
375 item_wise_tax_amount += tax.item_wise_tax_detail[key][1]
376
Nabin Haitcaab5822017-08-24 16:22:28 +0530377 tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
Nabin Haite7679702015-02-20 14:40:35 +0530378
Nabin Hait3237c752015-02-17 11:11:11 +0530379 def round_off_totals(self, tax):
Deepesh Gargb6705882021-04-14 11:20:27 +0530380 if tax.account_head in frappe.flags.round_off_applicable_accounts:
381 tax.tax_amount = round(tax.tax_amount, 0)
382 tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
383
Nabin Haite7679702015-02-20 14:40:35 +0530384 tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530385 tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
Nabin Haitcd951342017-07-31 18:07:45 +0530386 tax.precision("tax_amount"))
Nabin Haitce245122015-02-22 20:14:49 +0530387
Deepesh Gargb6705882021-04-14 11:20:27 +0530388 def round_off_base_values(self, tax):
389 # Round off to nearest integer based on regional settings
390 if tax.account_head in frappe.flags.round_off_applicable_accounts:
391 tax.base_tax_amount = round(tax.base_tax_amount, 0)
392 tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
393
Nabin Haita1bf43b2015-03-17 10:50:47 +0530394 def manipulate_grand_total_for_inclusive_tax(self):
395 # if fully inclusive taxes and diff
Ankush Menat98917802021-06-11 18:40:22 +0530396 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 +0530397 last_tax = self.doc.get("taxes")[-1]
Ankush Menat98917802021-06-11 18:40:22 +0530398 non_inclusive_tax_amount = sum(flt(d.tax_amount_after_discount_amount)
399 for d in self.doc.get("taxes") if not d.included_in_print_rate)
Nabin Haitf32fc232019-12-25 13:59:24 +0530400
Nabin Hait2e4de832017-09-19 14:53:16 +0530401 diff = self.doc.total + non_inclusive_tax_amount \
402 - flt(last_tax.total, last_tax.precision("total"))
Nabin Haitf32fc232019-12-25 13:59:24 +0530403
404 # If discount amount applied, deduct the discount amount
405 # because self.doc.total is always without discount, but last_tax.total is after discount
406 if self.discount_amount_applied and self.doc.discount_amount:
407 diff -= flt(self.doc.discount_amount)
408
409 diff = flt(diff, self.doc.precision("rounding_adjustment"))
410
Nabin Hait2e4de832017-09-19 14:53:16 +0530411 if diff and abs(diff) <= (5.0 / 10**last_tax.precision("tax_amount")):
Nabin Haitf32fc232019-12-25 13:59:24 +0530412 self.doc.rounding_adjustment = diff
Nabin Hait3237c752015-02-17 11:11:11 +0530413
414 def calculate_totals(self):
Subin Tom75a76e62021-10-29 16:45:04 +0530415 if self.doc.get("taxes"):
416 self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment)
417 else:
418 self.doc.grand_total = flt(self.doc.net_total)
Nabin Hait3237c752015-02-17 11:11:11 +0530419
Subin Tom75a76e62021-10-29 16:45:04 +0530420 if self.doc.get("taxes"):
421 self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total
Nabin Hait2e4de832017-09-19 14:53:16 +0530422 - flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges"))
Subin Tom75a76e62021-10-29 16:45:04 +0530423 else:
424 self.doc.total_taxes_and_charges = 0.0
Anand Doshiec5ec602015-03-05 19:31:23 +0530425
Nabin Hait2e4de832017-09-19 14:53:16 +0530426 self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530427
Saqiba6f98d42020-07-23 18:51:26 +0530428 if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]:
Anurag Mishra8a06b8f2019-07-17 14:55:16 +0530429 self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total")) \
Nabin Haite7679702015-02-20 14:40:35 +0530430 if self.doc.total_taxes_and_charges else self.doc.base_net_total
Nabin Hait3237c752015-02-17 11:11:11 +0530431 else:
Anand Doshiec5ec602015-03-05 19:31:23 +0530432 self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0
Nabin Hait3237c752015-02-17 11:11:11 +0530433 for tax in self.doc.get("taxes"):
434 if tax.category in ["Valuation and Total", "Total"]:
435 if tax.add_deduct_tax == "Add":
Nabin Haitdb53a782015-07-31 16:53:13 +0530436 self.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530437 else:
Nabin Haitdb53a782015-07-31 16:53:13 +0530438 self.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530439
Nabin Haite7679702015-02-20 14:40:35 +0530440 self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530441
Nabin Haite7679702015-02-20 14:40:35 +0530442 self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
443 if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
444 else self.doc.base_net_total
Nabin Hait3237c752015-02-17 11:11:11 +0530445
Nabin Hait2e4de832017-09-19 14:53:16 +0530446 self._set_in_company_currency(self.doc,
447 ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530448
Nabin Haite7679702015-02-20 14:40:35 +0530449 self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530450
Nabin Hait2e4de832017-09-19 14:53:16 +0530451 self.set_rounded_total()
452
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +0530453 def calculate_total_net_weight(self):
454 if self.doc.meta.get_field('total_net_weight'):
455 self.doc.total_net_weight = 0.0
456 for d in self.doc.items:
457 if d.total_weight:
458 self.doc.total_net_weight += d.total_weight
459
Nabin Hait2e4de832017-09-19 14:53:16 +0530460 def set_rounded_total(self):
Subin Tom75a76e62021-10-29 16:45:04 +0530461 if not self.doc.get('is_consolidated'):
462 if self.doc.meta.get_field("rounded_total"):
463 if self.doc.is_rounded_total_disabled():
464 self.doc.rounded_total = self.doc.base_rounded_total = 0
465 return
Nabin Hait877e1bb2017-11-17 12:27:43 +0530466
Subin Tom75a76e62021-10-29 16:45:04 +0530467 self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
468 self.doc.currency, self.doc.precision("rounded_total"))
Nabin Hait2e4de832017-09-19 14:53:16 +0530469
Subin Tom75a76e62021-10-29 16:45:04 +0530470 #if print_in_rate is set, we would have already calculated rounding adjustment
471 self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
472 self.doc.precision("rounding_adjustment"))
Nabin Hait877e1bb2017-11-17 12:27:43 +0530473
Subin Tom75a76e62021-10-29 16:45:04 +0530474 self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530475
Nabin Hait3237c752015-02-17 11:11:11 +0530476 def _cleanup(self):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530477 if not self.doc.get('is_consolidated'):
478 for tax in self.doc.get("taxes"):
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530479 if not tax.get("dont_recompute_tax"):
480 tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530481
Nabin Hait3769d872015-12-18 13:12:02 +0530482 def set_discount_amount(self):
Nabin Haite0405102016-10-13 12:14:32 +0530483 if self.doc.additional_discount_percentage:
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530484 self.doc.discount_amount = flt(flt(self.doc.get(scrub(self.doc.apply_discount_on)))
Nabin Hait3769d872015-12-18 13:12:02 +0530485 * self.doc.additional_discount_percentage / 100, self.doc.precision("discount_amount"))
Nabin Hait3237c752015-02-17 11:11:11 +0530486
487 def apply_discount_amount(self):
488 if self.doc.discount_amount:
Nabin Hait37b047d2015-02-23 16:01:33 +0530489 if not self.doc.apply_discount_on:
490 frappe.throw(_("Please select Apply Discount On"))
491
Nabin Hait3237c752015-02-17 11:11:11 +0530492 self.doc.base_discount_amount = flt(self.doc.discount_amount * self.doc.conversion_rate,
493 self.doc.precision("base_discount_amount"))
494
Nabin Haite7679702015-02-20 14:40:35 +0530495 total_for_discount_amount = self.get_total_for_discount_amount()
Nabin Hait25bd84d2015-03-04 15:06:56 +0530496 taxes = self.doc.get("taxes")
497 net_total = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530498
Nabin Haite7679702015-02-20 14:40:35 +0530499 if total_for_discount_amount:
Nabin Hait3237c752015-02-17 11:11:11 +0530500 # calculate item amount after Discount Amount
Nabin Hait25bd84d2015-03-04 15:06:56 +0530501 for i, item in enumerate(self.doc.get("items")):
502 distributed_amount = flt(self.doc.discount_amount) * \
503 item.net_amount / total_for_discount_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530504
Nabin Haite7679702015-02-20 14:40:35 +0530505 item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
Nabin Hait25bd84d2015-03-04 15:06:56 +0530506 net_total += item.net_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530507
Nabin Hait25bd84d2015-03-04 15:06:56 +0530508 # discount amount rounding loss adjustment if no taxes
Nabin Hait4d587342019-05-30 15:50:46 +0530509 if (self.doc.apply_discount_on == "Net Total" or not taxes or total_for_discount_amount==self.doc.net_total) \
Nabin Hait25bd84d2015-03-04 15:06:56 +0530510 and i == len(self.doc.get("items")) - 1:
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530511 discount_amount_loss = flt(self.doc.net_total - net_total - self.doc.discount_amount,
Nabin Hait25bd84d2015-03-04 15:06:56 +0530512 self.doc.precision("net_total"))
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530513
Anand Doshiec5ec602015-03-05 19:31:23 +0530514 item.net_amount = flt(item.net_amount + discount_amount_loss,
Nabin Hait25bd84d2015-03-04 15:06:56 +0530515 item.precision("net_amount"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530516
Nabin Hait51e980d2015-10-10 18:10:05 +0530517 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 +0530518
Nabin Haite7679702015-02-20 14:40:35 +0530519 self._set_in_company_currency(item, ["net_rate", "net_amount"])
Nabin Hait3237c752015-02-17 11:11:11 +0530520
521 self.discount_amount_applied = True
522 self._calculate()
523 else:
524 self.doc.base_discount_amount = 0
525
Nabin Haite7679702015-02-20 14:40:35 +0530526 def get_total_for_discount_amount(self):
Nabin Haitde9c8a92015-02-23 01:06:00 +0530527 if self.doc.apply_discount_on == "Net Total":
528 return self.doc.net_total
Nabin Haite7679702015-02-20 14:40:35 +0530529 else:
530 actual_taxes_dict = {}
Nabin Hait3237c752015-02-17 11:11:11 +0530531
Nabin Haite7679702015-02-20 14:40:35 +0530532 for tax in self.doc.get("taxes"):
Nabin Hait19ea7212020-08-11 20:34:57 +0530533 if tax.charge_type in ["Actual", "On Item Quantity"]:
Nabin Haitaf9bdfe2017-12-12 18:50:05 +0530534 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
535 actual_taxes_dict.setdefault(tax.idx, tax_amount)
Nabin Haite7679702015-02-20 14:40:35 +0530536 elif tax.row_id in actual_taxes_dict:
537 actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * flt(tax.rate) / 100
538 actual_taxes_dict.setdefault(tax.idx, actual_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530539
Nabin Hait877e1bb2017-11-17 12:27:43 +0530540 return flt(self.doc.grand_total - sum(actual_taxes_dict.values()),
541 self.doc.precision("grand_total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530542
543
Nabin Hait7b19b9e2015-02-24 09:42:24 +0530544 def calculate_total_advance(self):
545 if self.doc.docstatus < 2:
Ankush Menat98917802021-06-11 18:40:22 +0530546 total_allocated_amount = sum(flt(adv.allocated_amount, adv.precision("allocated_amount"))
547 for adv in self.doc.get("advances"))
Nabin Hait3237c752015-02-17 11:11:11 +0530548
Nabin Haite7679702015-02-20 14:40:35 +0530549 self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530550
Faris Ansari6041f5c2018-02-08 13:33:52 +0530551 grand_total = self.doc.rounded_total or self.doc.grand_total
552
Nabin Hait289ffb72016-02-08 11:06:55 +0530553 if self.doc.party_account_currency == self.doc.currency:
Faris Ansari6041f5c2018-02-08 13:33:52 +0530554 invoice_total = flt(grand_total - flt(self.doc.write_off_amount),
Nabin Hait289ffb72016-02-08 11:06:55 +0530555 self.doc.precision("grand_total"))
Nabin Hait8d8cba72017-04-03 17:26:22 +0530556 else:
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530557 base_write_off_amount = flt(flt(self.doc.write_off_amount) * self.doc.conversion_rate,
Nabin Hait8d8cba72017-04-03 17:26:22 +0530558 self.doc.precision("base_write_off_amount"))
Faris Ansari6041f5c2018-02-08 13:33:52 +0530559 invoice_total = flt(grand_total * self.doc.conversion_rate,
Nabin Hait8d8cba72017-04-03 17:26:22 +0530560 self.doc.precision("grand_total")) - base_write_off_amount
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530561
Nabin Haitadc09232016-02-09 10:31:11 +0530562 if invoice_total > 0 and self.doc.total_advance > invoice_total:
Nabin Hait289ffb72016-02-08 11:06:55 +0530563 frappe.throw(_("Advance amount cannot be greater than {0} {1}")
564 .format(self.doc.party_account_currency, invoice_total))
Nabin Hait3237c752015-02-17 11:11:11 +0530565
Rushabh Mehta8bb6e532015-02-18 20:22:59 +0530566 if self.doc.docstatus == 0:
Nabin Hait3237c752015-02-17 11:11:11 +0530567 self.calculate_outstanding_amount()
568
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530569 def is_internal_invoice(self):
570 """
571 Checks if its an internal transfer invoice
572 and decides if to calculate any out standing amount or not
573 """
574
575 if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer():
576 return True
577
578 return False
579
Nabin Hait3237c752015-02-17 11:11:11 +0530580 def calculate_outstanding_amount(self):
581 # NOTE:
582 # write_off_amount is only for POS Invoice
583 # total_advance is only for non POS Invoice
Rohit Waghchaureefb5bf22016-08-25 02:09:53 +0530584 if self.doc.doctype == "Sales Invoice":
585 self.calculate_paid_amount()
586
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530587 if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \
588 self.is_internal_invoice(): return
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530589
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530590 self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530591 self._set_in_company_currency(self.doc, ['write_off_amount'])
592
Nabin Hait877e1bb2017-11-17 12:27:43 +0530593 if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
594 grand_total = self.doc.rounded_total or self.doc.grand_total
595 if self.doc.party_account_currency == self.doc.currency:
Manas Solankida486ee2018-07-06 12:36:57 +0530596 total_amount_to_pay = flt(grand_total - self.doc.total_advance
Nabin Hait877e1bb2017-11-17 12:27:43 +0530597 - flt(self.doc.write_off_amount), self.doc.precision("grand_total"))
598 else:
599 total_amount_to_pay = flt(flt(grand_total *
600 self.doc.conversion_rate, self.doc.precision("grand_total")) - self.doc.total_advance
601 - flt(self.doc.base_write_off_amount), self.doc.precision("grand_total"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530602
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530603 self.doc.round_floats_in(self.doc, ["paid_amount"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530604 change_amount = 0
605
Deepesh Garg0ebace52020-02-25 13:21:16 +0530606 if self.doc.doctype == "Sales Invoice" and not self.doc.get('is_return'):
Nabin Hait877e1bb2017-11-17 12:27:43 +0530607 self.calculate_write_off_amount()
608 self.calculate_change_amount()
609 change_amount = self.doc.change_amount \
610 if self.doc.party_account_currency == self.doc.currency else self.doc.base_change_amount
611
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530612 paid_amount = self.doc.paid_amount \
613 if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530614
Nabin Hait877e1bb2017-11-17 12:27:43 +0530615 self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount),
616 self.doc.precision("outstanding_amount"))
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530617
Deepesh Garg0ebace52020-02-25 13:21:16 +0530618 if self.doc.doctype == 'Sales Invoice' and self.doc.get('is_pos') and self.doc.get('is_return'):
Subin Tom7d627df2021-08-23 11:05:07 +0530619 self.set_total_amount_to_default_mop(total_amount_to_pay)
620 self.calculate_paid_amount()
Deepesh Garg0ebace52020-02-25 13:21:16 +0530621
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530622 def calculate_paid_amount(self):
Manas Solankida486ee2018-07-06 12:36:57 +0530623
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530624 paid_amount = base_paid_amount = 0.0
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530625
626 if self.doc.is_pos:
627 for payment in self.doc.get('payments'):
Ayush Shuklae9cf1ab2017-05-25 14:14:55 +0530628 payment.amount = flt(payment.amount)
629 payment.base_amount = payment.amount * flt(self.doc.conversion_rate)
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530630 paid_amount += payment.amount
631 base_paid_amount += payment.base_amount
rohitwaghchaure73456ac2017-05-16 11:29:57 +0530632 elif not self.doc.is_return:
633 self.doc.set('payments', [])
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530634
Manas Solankida486ee2018-07-06 12:36:57 +0530635 if self.doc.redeem_loyalty_points and self.doc.loyalty_amount:
636 base_paid_amount += self.doc.loyalty_amount
637 paid_amount += (self.doc.loyalty_amount / flt(self.doc.conversion_rate))
638
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530639 self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount"))
640 self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount"))
641
Nabin Hait3bb1a422016-08-02 16:41:10 +0530642 def calculate_change_amount(self):
643 self.doc.change_amount = 0.0
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530644 self.doc.base_change_amount = 0.0
Nabin Hait877e1bb2017-11-17 12:27:43 +0530645
646 if self.doc.doctype == "Sales Invoice" \
647 and self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \
Ankush Menat98917802021-06-11 18:40:22 +0530648 and any(d.type == "Cash" for d in self.doc.payments):
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530649 grand_total = self.doc.rounded_total or self.doc.grand_total
650 base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530651
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530652 self.doc.change_amount = flt(self.doc.paid_amount - grand_total +
Nabin Hait3bb1a422016-08-02 16:41:10 +0530653 self.doc.write_off_amount, self.doc.precision("change_amount"))
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530654
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530655 self.doc.base_change_amount = flt(self.doc.base_paid_amount - base_grand_total +
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530656 self.doc.base_write_off_amount, self.doc.precision("base_change_amount"))
mbauskar36b51892016-01-18 16:31:10 +0530657
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530658 def calculate_write_off_amount(self):
Rohit Waghchaurebaef2622016-08-05 15:41:36 +0530659 if flt(self.doc.change_amount) > 0:
Nabin Hait877e1bb2017-11-17 12:27:43 +0530660 self.doc.write_off_amount = flt(self.doc.grand_total - self.doc.paid_amount
661 + self.doc.change_amount, self.doc.precision("write_off_amount"))
Rohit Waghchaurebaef2622016-08-05 15:41:36 +0530662 self.doc.base_write_off_amount = flt(self.doc.write_off_amount * self.doc.conversion_rate,
663 self.doc.precision("base_write_off_amount"))
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530664
mbauskar36b51892016-01-18 16:31:10 +0530665 def calculate_margin(self, item):
Makarand Bauskar0e4c5c92017-05-11 11:40:02 +0530666 rate_with_margin = 0.0
Shreya Shahbe690ef2017-11-14 17:22:41 +0530667 base_rate_with_margin = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530668 if item.price_list_rate:
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530669 if item.pricing_rules and not self.doc.ignore_pricing_rule:
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530670 has_margin = False
marination733fd5f2020-08-26 18:23:12 +0530671 for d in get_applied_pricing_rules(item.pricing_rules):
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530672 pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
Shreya Shahf718b0c2018-02-20 11:26:46 +0530673
Rohit Waghchaured4d639c2021-02-01 22:58:22 +0530674 if pricing_rule.margin_rate_or_amount and ((pricing_rule.currency == self.doc.currency and
675 pricing_rule.margin_type in ['Amount', 'Percentage']) or pricing_rule.margin_type == 'Percentage'):
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530676 item.margin_type = pricing_rule.margin_type
677 item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530678 has_margin = True
679
680 if not has_margin:
681 item.margin_type = None
682 item.margin_rate_or_amount = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530683
Rohit Waghchaure01faa9c2021-06-21 00:59:02 +0530684 if not item.pricing_rules and flt(item.rate) > flt(item.price_list_rate):
685 item.margin_type = "Amount"
686 item.margin_rate_or_amount = flt(item.rate - item.price_list_rate,
687 item.precision("margin_rate_or_amount"))
688 item.rate_with_margin = item.rate
689
690 elif item.margin_type and item.margin_rate_or_amount:
mbauskara52472c2016-03-05 15:10:25 +0530691 margin_value = item.margin_rate_or_amount if item.margin_type == 'Amount' else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100
Makarand Bauskar0e4c5c92017-05-11 11:40:02 +0530692 rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
Shreya Shahbe690ef2017-11-14 17:22:41 +0530693 base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
mbauskar36b51892016-01-18 16:31:10 +0530694
Shreya Shahbe690ef2017-11-14 17:22:41 +0530695 return rate_with_margin, base_rate_with_margin
Nabin Hait852cb642017-07-05 12:58:19 +0530696
697 def set_item_wise_tax_breakup(self):
Nabin Hait9c421612017-07-20 13:32:01 +0530698 self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530699
Subin Tom7d627df2021-08-23 11:05:07 +0530700 def set_total_amount_to_default_mop(self, total_amount_to_pay):
Saqiba6f98d42020-07-23 18:51:26 +0530701 default_mode_of_payment = frappe.db.get_value('POS Payment Method',
702 {'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
Deepesh Garg0ebace52020-02-25 13:21:16 +0530703
Deepesh Garg0ebace52020-02-25 13:21:16 +0530704 if default_mode_of_payment:
Afshanb6148342021-08-10 21:33:58 +0530705 self.doc.payments = []
Deepesh Garg0ebace52020-02-25 13:21:16 +0530706 self.doc.append('payments', {
707 'mode_of_payment': default_mode_of_payment.mode_of_payment,
Rucha Mahabaledee5302020-12-04 12:13:26 +0530708 'amount': total_amount_to_pay,
709 'default': 1
Ankush Menatb147b852021-09-01 16:45:57 +0530710 })
Deepesh Garg0ebace52020-02-25 13:21:16 +0530711
Nabin Hait9c421612017-07-20 13:32:01 +0530712def get_itemised_tax_breakup_html(doc):
713 if not doc.taxes:
714 return
715 frappe.flags.company = doc.company
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530716
Nabin Hait9c421612017-07-20 13:32:01 +0530717 # get headers
Nabin Haitcaab5822017-08-24 16:22:28 +0530718 tax_accounts = []
719 for tax in doc.taxes:
720 if getattr(tax, "category", None) and tax.category=="Valuation":
721 continue
rohitwaghchaure4e17fae2017-12-12 14:40:52 +0530722 if tax.description not in tax_accounts:
Nabin Haitcaab5822017-08-24 16:22:28 +0530723 tax_accounts.append(tax.description)
724
Nabin Hait9c421612017-07-20 13:32:01 +0530725 headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530726
Nabin Hait9c421612017-07-20 13:32:01 +0530727 # get tax breakup data
728 itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc)
Nabin Haitcaab5822017-08-24 16:22:28 +0530729
730 get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes"))
731
rohitwaghchaured4526682017-12-28 14:20:13 +0530732 update_itemised_tax_data(doc)
Nabin Hait9c421612017-07-20 13:32:01 +0530733 frappe.flags.company = None
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530734
Nabin Hait9c421612017-07-20 13:32:01 +0530735 return frappe.render_template(
736 "templates/includes/itemised_tax_breakup.html", dict(
737 headers=headers,
738 itemised_tax=itemised_tax,
739 itemised_taxable_amount=itemised_taxable_amount,
740 tax_accounts=tax_accounts,
Saqib Ansari10a6a2d2020-04-13 16:40:13 +0530741 doc=doc
Nabin Haitb962fc12017-07-17 18:02:31 +0530742 )
Nabin Hait9c421612017-07-20 13:32:01 +0530743 )
Nabin Hait852cb642017-07-05 12:58:19 +0530744
Deepesh Garg6a5ef262021-02-19 14:30:23 +0530745@frappe.whitelist()
746def get_round_off_applicable_accounts(company, account_list):
747 account_list = get_regional_round_off_accounts(company, account_list)
748
749 return account_list
750
751@erpnext.allow_regional
752def get_regional_round_off_accounts(company, account_list):
753 pass
rohitwaghchaured4526682017-12-28 14:20:13 +0530754
755@erpnext.allow_regional
756def update_itemised_tax_data(doc):
757 #Don't delete this method, used for localization
758 pass
759
Nabin Haitb962fc12017-07-17 18:02:31 +0530760@erpnext.allow_regional
761def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
762 return [_("Item"), _("Taxable Amount")] + tax_accounts
763
764@erpnext.allow_regional
765def get_itemised_tax_breakup_data(doc):
766 itemised_tax = get_itemised_tax(doc.taxes)
767
768 itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
769
770 return itemised_tax, itemised_taxable_amount
771
Nabin Hait34c551d2019-07-03 10:34:31 +0530772def get_itemised_tax(taxes, with_tax_account=False):
Nabin Haitb962fc12017-07-17 18:02:31 +0530773 itemised_tax = {}
774 for tax in taxes:
Nabin Haitcaab5822017-08-24 16:22:28 +0530775 if getattr(tax, "category", None) and tax.category=="Valuation":
776 continue
777
Nabin Haitb962fc12017-07-17 18:02:31 +0530778 item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
Nabin Hait2e4de832017-09-19 14:53:16 +0530779 if item_tax_map:
780 for item_code, tax_data in item_tax_map.items():
781 itemised_tax.setdefault(item_code, frappe._dict())
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530782
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530783 tax_rate = 0.0
784 tax_amount = 0.0
785
Nabin Hait2e4de832017-09-19 14:53:16 +0530786 if isinstance(tax_data, list):
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530787 tax_rate = flt(tax_data[0])
788 tax_amount = flt(tax_data[1])
Nabin Hait2e4de832017-09-19 14:53:16 +0530789 else:
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530790 tax_rate = flt(tax_data)
791
792 itemised_tax[item_code][tax.description] = frappe._dict(dict(
793 tax_rate = tax_rate,
794 tax_amount = tax_amount
795 ))
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530796
Nabin Hait34c551d2019-07-03 10:34:31 +0530797 if with_tax_account:
798 itemised_tax[item_code][tax.description].tax_account = tax.account_head
799
Nabin Haitb962fc12017-07-17 18:02:31 +0530800 return itemised_tax
801
802def get_itemised_taxable_amount(items):
803 itemised_taxable_amount = frappe._dict()
804 for item in items:
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530805 item_code = item.item_code or item.item_name
Nabin Haitb962fc12017-07-17 18:02:31 +0530806 itemised_taxable_amount.setdefault(item_code, 0)
807 itemised_taxable_amount[item_code] += item.net_amount
808
Nabin Haitcaab5822017-08-24 16:22:28 +0530809 return itemised_taxable_amount
810
811def get_rounded_tax_amount(itemised_tax, precision):
812 # Rounding based on tax_amount precision
813 for taxes in itemised_tax.values():
814 for tax_account in taxes:
Himanshu Mishra35b26272018-11-13 11:13:04 +0530815 taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530816
817class init_landed_taxes_and_totals(object):
818 def __init__(self, doc):
819 self.doc = doc
820 self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
821 self.set_account_currency()
822 self.set_exchange_rate()
823 self.set_amounts_in_company_currency()
824
825 def set_account_currency(self):
826 company_currency = erpnext.get_company_currency(self.doc.company)
827 for d in self.doc.get(self.tax_field):
828 if not d.account_currency:
829 account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
830 d.account_currency = account_currency or company_currency
831
832 def set_exchange_rate(self):
833 company_currency = erpnext.get_company_currency(self.doc.company)
834 for d in self.doc.get(self.tax_field):
835 if d.account_currency == company_currency:
836 d.exchange_rate = 1
Deepesh Garg22f5ff82021-03-17 10:56:52 +0530837 elif not d.exchange_rate:
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530838 d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
839 account_currency=d.account_currency, company=self.doc.company)
840
841 if not d.exchange_rate:
842 frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
843
844 def set_amounts_in_company_currency(self):
845 for d in self.doc.get(self.tax_field):
846 d.amount = flt(d.amount, d.precision("amount"))
Walstan Baptista37b826b2021-04-03 19:48:46 +0530847 d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))