blob: 667edab06a4d2b7de39298bd661741aa805d8b6d [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
261 def calculate_taxes(self):
Subin Tom75a76e62021-10-29 16:45:04 +0530262 if not self.doc.get('is_consolidated'):
263 self.doc.rounding_adjustment = 0
264
Nabin Hait3237c752015-02-17 11:11:11 +0530265 # maintain actual tax rate based on idx
Nabin Haite7679702015-02-20 14:40:35 +0530266 actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
Nabin Hait3237c752015-02-17 11:11:11 +0530267 for tax in self.doc.get("taxes") if tax.charge_type == "Actual"])
268
269 for n, item in enumerate(self.doc.get("items")):
270 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530271 for i, tax in enumerate(self.doc.get("taxes")):
272 # tax_amount represents the amount of tax for the current step
273 current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map)
274
275 # Adjust divisional loss to the last item
276 if tax.charge_type == "Actual":
277 actual_tax_dict[tax.idx] -= current_tax_amount
278 if n == len(self.doc.get("items")) - 1:
279 current_tax_amount += actual_tax_dict[tax.idx]
280
Nabin Hait2b019ed2015-02-22 23:03:07 +0530281 # accumulate tax amount into tax.tax_amount
Nabin Haitde9c8a92015-02-23 01:06:00 +0530282 if tax.charge_type != "Actual" and \
283 not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
284 tax.tax_amount += current_tax_amount
Nabin Hait2b019ed2015-02-22 23:03:07 +0530285
Nabin Hait3237c752015-02-17 11:11:11 +0530286 # store tax_amount for current item as it will be used for
287 # charge type = 'On Previous Row Amount'
288 tax.tax_amount_for_current_item = current_tax_amount
289
Nabin Hait2b019ed2015-02-22 23:03:07 +0530290 # set tax after discount
Nabin Hait3237c752015-02-17 11:11:11 +0530291 tax.tax_amount_after_discount_amount += current_tax_amount
292
Nabin Haitcd951342017-07-31 18:07:45 +0530293 current_tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(current_tax_amount, tax)
Nabin Hait3237c752015-02-17 11:11:11 +0530294
Nabin Hait3237c752015-02-17 11:11:11 +0530295 # note: grand_total_for_current_item contains the contribution of
296 # item's amount, previously applied tax and the current tax on that item
297 if i==0:
Nabin Haitcd951342017-07-31 18:07:45 +0530298 tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530299 else:
300 tax.grand_total_for_current_item = \
Nabin Haitcd951342017-07-31 18:07:45 +0530301 flt(self.doc.get("taxes")[i-1].grand_total_for_current_item + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530302
303 # set precision in the last item iteration
304 if n == len(self.doc.get("items")) - 1:
305 self.round_off_totals(tax)
Deepesh Gargb6705882021-04-14 11:20:27 +0530306 self._set_in_company_currency(tax,
307 ["tax_amount", "tax_amount_after_discount_amount"])
308
309 self.round_off_base_values(tax)
Nabin Haitcd951342017-07-31 18:07:45 +0530310 self.set_cumulative_total(i, tax)
311
Deepesh Gargb6705882021-04-14 11:20:27 +0530312 self._set_in_company_currency(tax, ["total"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530313
Nabin Hait3237c752015-02-17 11:11:11 +0530314 # adjust Discount Amount loss in last tax iteration
Nabin Haitde9c8a92015-02-23 01:06:00 +0530315 if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
Subin Tom75a76e62021-10-29 16:45:04 +0530316 and self.doc.discount_amount \
317 and self.doc.apply_discount_on == "Grand Total" \
318 and not self.doc.get('is_consolidated'):
Nabin Hait2e4de832017-09-19 14:53:16 +0530319 self.doc.rounding_adjustment = flt(self.doc.grand_total
320 - flt(self.doc.discount_amount) - tax.total,
321 self.doc.precision("rounding_adjustment"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530322
Nabin Haitcd951342017-07-31 18:07:45 +0530323 def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
324 # if just for valuation, do not add the tax amount in total
325 # if tax/charges is for deduction, multiply by -1
326 if getattr(tax, "category", None):
327 tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
Rushabh Mehta30dc9a12017-11-17 14:31:09 +0530328 if self.doc.doctype in ["Purchase Order", "Purchase Invoice", "Purchase Receipt", "Supplier Quotation"]:
329 tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
Nabin Haitcd951342017-07-31 18:07:45 +0530330 return tax_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530331
Nabin Haitcd951342017-07-31 18:07:45 +0530332 def set_cumulative_total(self, row_idx, tax):
333 tax_amount = tax.tax_amount_after_discount_amount
334 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax_amount, tax)
335
336 if row_idx == 0:
337 tax.total = flt(self.doc.net_total + tax_amount, tax.precision("total"))
338 else:
339 tax.total = flt(self.doc.get("taxes")[row_idx-1].total + tax_amount, tax.precision("total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530340
341 def get_current_tax_amount(self, item, tax, item_tax_map):
342 tax_rate = self._get_tax_rate(tax, item_tax_map)
343 current_tax_amount = 0.0
344
345 if tax.charge_type == "Actual":
346 # distribute the tax amount proportionally to each item row
Nabin Haite7679702015-02-20 14:40:35 +0530347 actual = flt(tax.tax_amount, tax.precision("tax_amount"))
348 current_tax_amount = item.net_amount*actual / self.doc.net_total if self.doc.net_total else 0.0
349
Nabin Hait3237c752015-02-17 11:11:11 +0530350 elif tax.charge_type == "On Net Total":
Nabin Haite7679702015-02-20 14:40:35 +0530351 current_tax_amount = (tax_rate / 100.0) * item.net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530352 elif tax.charge_type == "On Previous Row Amount":
353 current_tax_amount = (tax_rate / 100.0) * \
354 self.doc.get("taxes")[cint(tax.row_id) - 1].tax_amount_for_current_item
355 elif tax.charge_type == "On Previous Row Total":
356 current_tax_amount = (tax_rate / 100.0) * \
357 self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
Himanshu Mishra35b26272018-11-13 11:13:04 +0530358 elif tax.charge_type == "On Item Quantity":
Nabin Hait19ea7212020-08-11 20:34:57 +0530359 current_tax_amount = tax_rate * item.qty
Nabin Hait3237c752015-02-17 11:11:11 +0530360
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530361 if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530362 self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530363
364 return current_tax_amount
365
Nabin Haite7679702015-02-20 14:40:35 +0530366 def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
367 # store tax breakup for each item
368 key = item.item_code or item.item_name
369 item_wise_tax_amount = current_tax_amount*self.doc.conversion_rate
370 if tax.item_wise_tax_detail.get(key):
371 item_wise_tax_amount += tax.item_wise_tax_detail[key][1]
372
Nabin Haitcaab5822017-08-24 16:22:28 +0530373 tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
Nabin Haite7679702015-02-20 14:40:35 +0530374
Nabin Hait3237c752015-02-17 11:11:11 +0530375 def round_off_totals(self, tax):
Deepesh Gargb6705882021-04-14 11:20:27 +0530376 if tax.account_head in frappe.flags.round_off_applicable_accounts:
377 tax.tax_amount = round(tax.tax_amount, 0)
378 tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
379
Nabin Haite7679702015-02-20 14:40:35 +0530380 tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530381 tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
Nabin Haitcd951342017-07-31 18:07:45 +0530382 tax.precision("tax_amount"))
Nabin Haitce245122015-02-22 20:14:49 +0530383
Deepesh Gargb6705882021-04-14 11:20:27 +0530384 def round_off_base_values(self, tax):
385 # Round off to nearest integer based on regional settings
386 if tax.account_head in frappe.flags.round_off_applicable_accounts:
387 tax.base_tax_amount = round(tax.base_tax_amount, 0)
388 tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
389
Nabin Haita1bf43b2015-03-17 10:50:47 +0530390 def manipulate_grand_total_for_inclusive_tax(self):
391 # if fully inclusive taxes and diff
Ankush Menat98917802021-06-11 18:40:22 +0530392 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 +0530393 last_tax = self.doc.get("taxes")[-1]
Ankush Menat98917802021-06-11 18:40:22 +0530394 non_inclusive_tax_amount = sum(flt(d.tax_amount_after_discount_amount)
395 for d in self.doc.get("taxes") if not d.included_in_print_rate)
Nabin Haitf32fc232019-12-25 13:59:24 +0530396
Nabin Hait2e4de832017-09-19 14:53:16 +0530397 diff = self.doc.total + non_inclusive_tax_amount \
398 - flt(last_tax.total, last_tax.precision("total"))
Nabin Haitf32fc232019-12-25 13:59:24 +0530399
400 # If discount amount applied, deduct the discount amount
401 # because self.doc.total is always without discount, but last_tax.total is after discount
402 if self.discount_amount_applied and self.doc.discount_amount:
403 diff -= flt(self.doc.discount_amount)
404
405 diff = flt(diff, self.doc.precision("rounding_adjustment"))
406
Nabin Hait2e4de832017-09-19 14:53:16 +0530407 if diff and abs(diff) <= (5.0 / 10**last_tax.precision("tax_amount")):
Nabin Haitf32fc232019-12-25 13:59:24 +0530408 self.doc.rounding_adjustment = diff
Nabin Hait3237c752015-02-17 11:11:11 +0530409
410 def calculate_totals(self):
Subin Tom75a76e62021-10-29 16:45:04 +0530411 if self.doc.get("taxes"):
412 self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment)
413 else:
414 self.doc.grand_total = flt(self.doc.net_total)
Nabin Hait3237c752015-02-17 11:11:11 +0530415
Subin Tom75a76e62021-10-29 16:45:04 +0530416 if self.doc.get("taxes"):
417 self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total
Nabin Hait2e4de832017-09-19 14:53:16 +0530418 - flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges"))
Subin Tom75a76e62021-10-29 16:45:04 +0530419 else:
420 self.doc.total_taxes_and_charges = 0.0
Anand Doshiec5ec602015-03-05 19:31:23 +0530421
Nabin Hait2e4de832017-09-19 14:53:16 +0530422 self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530423
Saqiba6f98d42020-07-23 18:51:26 +0530424 if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]:
Anurag Mishra8a06b8f2019-07-17 14:55:16 +0530425 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 +0530426 if self.doc.total_taxes_and_charges else self.doc.base_net_total
Nabin Hait3237c752015-02-17 11:11:11 +0530427 else:
Anand Doshiec5ec602015-03-05 19:31:23 +0530428 self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0
Nabin Hait3237c752015-02-17 11:11:11 +0530429 for tax in self.doc.get("taxes"):
430 if tax.category in ["Valuation and Total", "Total"]:
431 if tax.add_deduct_tax == "Add":
Nabin Haitdb53a782015-07-31 16:53:13 +0530432 self.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530433 else:
Nabin Haitdb53a782015-07-31 16:53:13 +0530434 self.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530435
Nabin Haite7679702015-02-20 14:40:35 +0530436 self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530437
Nabin Haite7679702015-02-20 14:40:35 +0530438 self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
439 if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
440 else self.doc.base_net_total
Nabin Hait3237c752015-02-17 11:11:11 +0530441
Nabin Hait2e4de832017-09-19 14:53:16 +0530442 self._set_in_company_currency(self.doc,
443 ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530444
Nabin Haite7679702015-02-20 14:40:35 +0530445 self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530446
Nabin Hait2e4de832017-09-19 14:53:16 +0530447 self.set_rounded_total()
448
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +0530449 def calculate_total_net_weight(self):
450 if self.doc.meta.get_field('total_net_weight'):
451 self.doc.total_net_weight = 0.0
452 for d in self.doc.items:
453 if d.total_weight:
454 self.doc.total_net_weight += d.total_weight
455
Nabin Hait2e4de832017-09-19 14:53:16 +0530456 def set_rounded_total(self):
Subin Tom75a76e62021-10-29 16:45:04 +0530457 if not self.doc.get('is_consolidated'):
458 if self.doc.meta.get_field("rounded_total"):
459 if self.doc.is_rounded_total_disabled():
460 self.doc.rounded_total = self.doc.base_rounded_total = 0
461 return
Nabin Hait877e1bb2017-11-17 12:27:43 +0530462
Subin Tom75a76e62021-10-29 16:45:04 +0530463 self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
464 self.doc.currency, self.doc.precision("rounded_total"))
Nabin Hait2e4de832017-09-19 14:53:16 +0530465
Subin Tom75a76e62021-10-29 16:45:04 +0530466 #if print_in_rate is set, we would have already calculated rounding adjustment
467 self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
468 self.doc.precision("rounding_adjustment"))
Nabin Hait877e1bb2017-11-17 12:27:43 +0530469
Subin Tom75a76e62021-10-29 16:45:04 +0530470 self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530471
Nabin Hait3237c752015-02-17 11:11:11 +0530472 def _cleanup(self):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530473 if not self.doc.get('is_consolidated'):
474 for tax in self.doc.get("taxes"):
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530475 if not tax.get("dont_recompute_tax"):
476 tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530477
Nabin Hait3769d872015-12-18 13:12:02 +0530478 def set_discount_amount(self):
Nabin Haite0405102016-10-13 12:14:32 +0530479 if self.doc.additional_discount_percentage:
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530480 self.doc.discount_amount = flt(flt(self.doc.get(scrub(self.doc.apply_discount_on)))
Nabin Hait3769d872015-12-18 13:12:02 +0530481 * self.doc.additional_discount_percentage / 100, self.doc.precision("discount_amount"))
Nabin Hait3237c752015-02-17 11:11:11 +0530482
483 def apply_discount_amount(self):
484 if self.doc.discount_amount:
Nabin Hait37b047d2015-02-23 16:01:33 +0530485 if not self.doc.apply_discount_on:
486 frappe.throw(_("Please select Apply Discount On"))
487
Nabin Hait3237c752015-02-17 11:11:11 +0530488 self.doc.base_discount_amount = flt(self.doc.discount_amount * self.doc.conversion_rate,
489 self.doc.precision("base_discount_amount"))
490
Nabin Haite7679702015-02-20 14:40:35 +0530491 total_for_discount_amount = self.get_total_for_discount_amount()
Nabin Hait25bd84d2015-03-04 15:06:56 +0530492 taxes = self.doc.get("taxes")
493 net_total = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530494
Nabin Haite7679702015-02-20 14:40:35 +0530495 if total_for_discount_amount:
Nabin Hait3237c752015-02-17 11:11:11 +0530496 # calculate item amount after Discount Amount
Nabin Hait25bd84d2015-03-04 15:06:56 +0530497 for i, item in enumerate(self.doc.get("items")):
498 distributed_amount = flt(self.doc.discount_amount) * \
499 item.net_amount / total_for_discount_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530500
Nabin Haite7679702015-02-20 14:40:35 +0530501 item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
Nabin Hait25bd84d2015-03-04 15:06:56 +0530502 net_total += item.net_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530503
Nabin Hait25bd84d2015-03-04 15:06:56 +0530504 # discount amount rounding loss adjustment if no taxes
Nabin Hait4d587342019-05-30 15:50:46 +0530505 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 +0530506 and i == len(self.doc.get("items")) - 1:
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530507 discount_amount_loss = flt(self.doc.net_total - net_total - self.doc.discount_amount,
Nabin Hait25bd84d2015-03-04 15:06:56 +0530508 self.doc.precision("net_total"))
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530509
Anand Doshiec5ec602015-03-05 19:31:23 +0530510 item.net_amount = flt(item.net_amount + discount_amount_loss,
Nabin Hait25bd84d2015-03-04 15:06:56 +0530511 item.precision("net_amount"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530512
Nabin Hait51e980d2015-10-10 18:10:05 +0530513 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 +0530514
Nabin Haite7679702015-02-20 14:40:35 +0530515 self._set_in_company_currency(item, ["net_rate", "net_amount"])
Nabin Hait3237c752015-02-17 11:11:11 +0530516
517 self.discount_amount_applied = True
518 self._calculate()
519 else:
520 self.doc.base_discount_amount = 0
521
Nabin Haite7679702015-02-20 14:40:35 +0530522 def get_total_for_discount_amount(self):
Nabin Haitde9c8a92015-02-23 01:06:00 +0530523 if self.doc.apply_discount_on == "Net Total":
524 return self.doc.net_total
Nabin Haite7679702015-02-20 14:40:35 +0530525 else:
526 actual_taxes_dict = {}
Nabin Hait3237c752015-02-17 11:11:11 +0530527
Nabin Haite7679702015-02-20 14:40:35 +0530528 for tax in self.doc.get("taxes"):
Nabin Hait19ea7212020-08-11 20:34:57 +0530529 if tax.charge_type in ["Actual", "On Item Quantity"]:
Nabin Haitaf9bdfe2017-12-12 18:50:05 +0530530 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
531 actual_taxes_dict.setdefault(tax.idx, tax_amount)
Nabin Haite7679702015-02-20 14:40:35 +0530532 elif tax.row_id in actual_taxes_dict:
533 actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * flt(tax.rate) / 100
534 actual_taxes_dict.setdefault(tax.idx, actual_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530535
Nabin Hait877e1bb2017-11-17 12:27:43 +0530536 return flt(self.doc.grand_total - sum(actual_taxes_dict.values()),
537 self.doc.precision("grand_total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530538
539
Nabin Hait7b19b9e2015-02-24 09:42:24 +0530540 def calculate_total_advance(self):
541 if self.doc.docstatus < 2:
Ankush Menat98917802021-06-11 18:40:22 +0530542 total_allocated_amount = sum(flt(adv.allocated_amount, adv.precision("allocated_amount"))
543 for adv in self.doc.get("advances"))
Nabin Hait3237c752015-02-17 11:11:11 +0530544
Nabin Haite7679702015-02-20 14:40:35 +0530545 self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530546
Faris Ansari6041f5c2018-02-08 13:33:52 +0530547 grand_total = self.doc.rounded_total or self.doc.grand_total
548
Nabin Hait289ffb72016-02-08 11:06:55 +0530549 if self.doc.party_account_currency == self.doc.currency:
Faris Ansari6041f5c2018-02-08 13:33:52 +0530550 invoice_total = flt(grand_total - flt(self.doc.write_off_amount),
Nabin Hait289ffb72016-02-08 11:06:55 +0530551 self.doc.precision("grand_total"))
Nabin Hait8d8cba72017-04-03 17:26:22 +0530552 else:
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530553 base_write_off_amount = flt(flt(self.doc.write_off_amount) * self.doc.conversion_rate,
Nabin Hait8d8cba72017-04-03 17:26:22 +0530554 self.doc.precision("base_write_off_amount"))
Faris Ansari6041f5c2018-02-08 13:33:52 +0530555 invoice_total = flt(grand_total * self.doc.conversion_rate,
Nabin Hait8d8cba72017-04-03 17:26:22 +0530556 self.doc.precision("grand_total")) - base_write_off_amount
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530557
Nabin Haitadc09232016-02-09 10:31:11 +0530558 if invoice_total > 0 and self.doc.total_advance > invoice_total:
Nabin Hait289ffb72016-02-08 11:06:55 +0530559 frappe.throw(_("Advance amount cannot be greater than {0} {1}")
560 .format(self.doc.party_account_currency, invoice_total))
Nabin Hait3237c752015-02-17 11:11:11 +0530561
Rushabh Mehta8bb6e532015-02-18 20:22:59 +0530562 if self.doc.docstatus == 0:
Nabin Hait3237c752015-02-17 11:11:11 +0530563 self.calculate_outstanding_amount()
564
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530565 def is_internal_invoice(self):
566 """
567 Checks if its an internal transfer invoice
568 and decides if to calculate any out standing amount or not
569 """
570
571 if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer():
572 return True
573
574 return False
575
Nabin Hait3237c752015-02-17 11:11:11 +0530576 def calculate_outstanding_amount(self):
577 # NOTE:
578 # write_off_amount is only for POS Invoice
579 # total_advance is only for non POS Invoice
Rohit Waghchaureefb5bf22016-08-25 02:09:53 +0530580 if self.doc.doctype == "Sales Invoice":
581 self.calculate_paid_amount()
582
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530583 if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \
584 self.is_internal_invoice(): return
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530585
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530586 self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530587 self._set_in_company_currency(self.doc, ['write_off_amount'])
588
Nabin Hait877e1bb2017-11-17 12:27:43 +0530589 if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
590 grand_total = self.doc.rounded_total or self.doc.grand_total
591 if self.doc.party_account_currency == self.doc.currency:
Manas Solankida486ee2018-07-06 12:36:57 +0530592 total_amount_to_pay = flt(grand_total - self.doc.total_advance
Nabin Hait877e1bb2017-11-17 12:27:43 +0530593 - flt(self.doc.write_off_amount), self.doc.precision("grand_total"))
594 else:
595 total_amount_to_pay = flt(flt(grand_total *
596 self.doc.conversion_rate, self.doc.precision("grand_total")) - self.doc.total_advance
597 - flt(self.doc.base_write_off_amount), self.doc.precision("grand_total"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530598
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530599 self.doc.round_floats_in(self.doc, ["paid_amount"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530600 change_amount = 0
601
Deepesh Garg0ebace52020-02-25 13:21:16 +0530602 if self.doc.doctype == "Sales Invoice" and not self.doc.get('is_return'):
Nabin Hait877e1bb2017-11-17 12:27:43 +0530603 self.calculate_write_off_amount()
604 self.calculate_change_amount()
605 change_amount = self.doc.change_amount \
606 if self.doc.party_account_currency == self.doc.currency else self.doc.base_change_amount
607
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530608 paid_amount = self.doc.paid_amount \
609 if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530610
Nabin Hait877e1bb2017-11-17 12:27:43 +0530611 self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount),
612 self.doc.precision("outstanding_amount"))
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530613
Deepesh Garg0ebace52020-02-25 13:21:16 +0530614 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 +0530615 self.set_total_amount_to_default_mop(total_amount_to_pay)
616 self.calculate_paid_amount()
Deepesh Garg0ebace52020-02-25 13:21:16 +0530617
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530618 def calculate_paid_amount(self):
Manas Solankida486ee2018-07-06 12:36:57 +0530619
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530620 paid_amount = base_paid_amount = 0.0
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530621
622 if self.doc.is_pos:
623 for payment in self.doc.get('payments'):
Ayush Shuklae9cf1ab2017-05-25 14:14:55 +0530624 payment.amount = flt(payment.amount)
625 payment.base_amount = payment.amount * flt(self.doc.conversion_rate)
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530626 paid_amount += payment.amount
627 base_paid_amount += payment.base_amount
rohitwaghchaure73456ac2017-05-16 11:29:57 +0530628 elif not self.doc.is_return:
629 self.doc.set('payments', [])
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530630
Manas Solankida486ee2018-07-06 12:36:57 +0530631 if self.doc.redeem_loyalty_points and self.doc.loyalty_amount:
632 base_paid_amount += self.doc.loyalty_amount
633 paid_amount += (self.doc.loyalty_amount / flt(self.doc.conversion_rate))
634
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530635 self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount"))
636 self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount"))
637
Nabin Hait3bb1a422016-08-02 16:41:10 +0530638 def calculate_change_amount(self):
639 self.doc.change_amount = 0.0
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530640 self.doc.base_change_amount = 0.0
Nabin Hait877e1bb2017-11-17 12:27:43 +0530641
642 if self.doc.doctype == "Sales Invoice" \
643 and self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \
Ankush Menat98917802021-06-11 18:40:22 +0530644 and any(d.type == "Cash" for d in self.doc.payments):
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530645 grand_total = self.doc.rounded_total or self.doc.grand_total
646 base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530647
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530648 self.doc.change_amount = flt(self.doc.paid_amount - grand_total +
Nabin Hait3bb1a422016-08-02 16:41:10 +0530649 self.doc.write_off_amount, self.doc.precision("change_amount"))
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530650
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530651 self.doc.base_change_amount = flt(self.doc.base_paid_amount - base_grand_total +
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530652 self.doc.base_write_off_amount, self.doc.precision("base_change_amount"))
mbauskar36b51892016-01-18 16:31:10 +0530653
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530654 def calculate_write_off_amount(self):
Rohit Waghchaurebaef2622016-08-05 15:41:36 +0530655 if flt(self.doc.change_amount) > 0:
Nabin Hait877e1bb2017-11-17 12:27:43 +0530656 self.doc.write_off_amount = flt(self.doc.grand_total - self.doc.paid_amount
657 + self.doc.change_amount, self.doc.precision("write_off_amount"))
Rohit Waghchaurebaef2622016-08-05 15:41:36 +0530658 self.doc.base_write_off_amount = flt(self.doc.write_off_amount * self.doc.conversion_rate,
659 self.doc.precision("base_write_off_amount"))
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530660
mbauskar36b51892016-01-18 16:31:10 +0530661 def calculate_margin(self, item):
Makarand Bauskar0e4c5c92017-05-11 11:40:02 +0530662 rate_with_margin = 0.0
Shreya Shahbe690ef2017-11-14 17:22:41 +0530663 base_rate_with_margin = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530664 if item.price_list_rate:
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530665 if item.pricing_rules and not self.doc.ignore_pricing_rule:
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530666 has_margin = False
marination733fd5f2020-08-26 18:23:12 +0530667 for d in get_applied_pricing_rules(item.pricing_rules):
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530668 pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
Shreya Shahf718b0c2018-02-20 11:26:46 +0530669
Rohit Waghchaured4d639c2021-02-01 22:58:22 +0530670 if pricing_rule.margin_rate_or_amount and ((pricing_rule.currency == self.doc.currency and
671 pricing_rule.margin_type in ['Amount', 'Percentage']) or pricing_rule.margin_type == 'Percentage'):
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530672 item.margin_type = pricing_rule.margin_type
673 item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530674 has_margin = True
675
676 if not has_margin:
677 item.margin_type = None
678 item.margin_rate_or_amount = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530679
Rohit Waghchaure01faa9c2021-06-21 00:59:02 +0530680 if not item.pricing_rules and flt(item.rate) > flt(item.price_list_rate):
681 item.margin_type = "Amount"
682 item.margin_rate_or_amount = flt(item.rate - item.price_list_rate,
683 item.precision("margin_rate_or_amount"))
684 item.rate_with_margin = item.rate
685
686 elif item.margin_type and item.margin_rate_or_amount:
mbauskara52472c2016-03-05 15:10:25 +0530687 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 +0530688 rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
Shreya Shahbe690ef2017-11-14 17:22:41 +0530689 base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
mbauskar36b51892016-01-18 16:31:10 +0530690
Shreya Shahbe690ef2017-11-14 17:22:41 +0530691 return rate_with_margin, base_rate_with_margin
Nabin Hait852cb642017-07-05 12:58:19 +0530692
693 def set_item_wise_tax_breakup(self):
Nabin Hait9c421612017-07-20 13:32:01 +0530694 self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530695
Subin Tom7d627df2021-08-23 11:05:07 +0530696 def set_total_amount_to_default_mop(self, total_amount_to_pay):
Saqiba6f98d42020-07-23 18:51:26 +0530697 default_mode_of_payment = frappe.db.get_value('POS Payment Method',
698 {'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
Deepesh Garg0ebace52020-02-25 13:21:16 +0530699
Deepesh Garg0ebace52020-02-25 13:21:16 +0530700 if default_mode_of_payment:
Afshanb6148342021-08-10 21:33:58 +0530701 self.doc.payments = []
Deepesh Garg0ebace52020-02-25 13:21:16 +0530702 self.doc.append('payments', {
703 'mode_of_payment': default_mode_of_payment.mode_of_payment,
Rucha Mahabaledee5302020-12-04 12:13:26 +0530704 'amount': total_amount_to_pay,
705 'default': 1
Ankush Menatb147b852021-09-01 16:45:57 +0530706 })
Deepesh Garg0ebace52020-02-25 13:21:16 +0530707
Nabin Hait9c421612017-07-20 13:32:01 +0530708def get_itemised_tax_breakup_html(doc):
709 if not doc.taxes:
710 return
711 frappe.flags.company = doc.company
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530712
Nabin Hait9c421612017-07-20 13:32:01 +0530713 # get headers
Nabin Haitcaab5822017-08-24 16:22:28 +0530714 tax_accounts = []
715 for tax in doc.taxes:
716 if getattr(tax, "category", None) and tax.category=="Valuation":
717 continue
rohitwaghchaure4e17fae2017-12-12 14:40:52 +0530718 if tax.description not in tax_accounts:
Nabin Haitcaab5822017-08-24 16:22:28 +0530719 tax_accounts.append(tax.description)
720
Nabin Hait9c421612017-07-20 13:32:01 +0530721 headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530722
Nabin Hait9c421612017-07-20 13:32:01 +0530723 # get tax breakup data
724 itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc)
Nabin Haitcaab5822017-08-24 16:22:28 +0530725
726 get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes"))
727
rohitwaghchaured4526682017-12-28 14:20:13 +0530728 update_itemised_tax_data(doc)
Nabin Hait9c421612017-07-20 13:32:01 +0530729 frappe.flags.company = None
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530730
Nabin Hait9c421612017-07-20 13:32:01 +0530731 return frappe.render_template(
732 "templates/includes/itemised_tax_breakup.html", dict(
733 headers=headers,
734 itemised_tax=itemised_tax,
735 itemised_taxable_amount=itemised_taxable_amount,
736 tax_accounts=tax_accounts,
Saqib Ansari10a6a2d2020-04-13 16:40:13 +0530737 doc=doc
Nabin Haitb962fc12017-07-17 18:02:31 +0530738 )
Nabin Hait9c421612017-07-20 13:32:01 +0530739 )
Nabin Hait852cb642017-07-05 12:58:19 +0530740
Deepesh Garg6a5ef262021-02-19 14:30:23 +0530741@frappe.whitelist()
742def get_round_off_applicable_accounts(company, account_list):
743 account_list = get_regional_round_off_accounts(company, account_list)
744
745 return account_list
746
747@erpnext.allow_regional
748def get_regional_round_off_accounts(company, account_list):
749 pass
rohitwaghchaured4526682017-12-28 14:20:13 +0530750
751@erpnext.allow_regional
752def update_itemised_tax_data(doc):
753 #Don't delete this method, used for localization
754 pass
755
Nabin Haitb962fc12017-07-17 18:02:31 +0530756@erpnext.allow_regional
757def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
758 return [_("Item"), _("Taxable Amount")] + tax_accounts
759
760@erpnext.allow_regional
761def get_itemised_tax_breakup_data(doc):
762 itemised_tax = get_itemised_tax(doc.taxes)
763
764 itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
765
766 return itemised_tax, itemised_taxable_amount
767
Nabin Hait34c551d2019-07-03 10:34:31 +0530768def get_itemised_tax(taxes, with_tax_account=False):
Nabin Haitb962fc12017-07-17 18:02:31 +0530769 itemised_tax = {}
770 for tax in taxes:
Nabin Haitcaab5822017-08-24 16:22:28 +0530771 if getattr(tax, "category", None) and tax.category=="Valuation":
772 continue
773
Nabin Haitb962fc12017-07-17 18:02:31 +0530774 item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
Nabin Hait2e4de832017-09-19 14:53:16 +0530775 if item_tax_map:
776 for item_code, tax_data in item_tax_map.items():
777 itemised_tax.setdefault(item_code, frappe._dict())
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530778
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530779 tax_rate = 0.0
780 tax_amount = 0.0
781
Nabin Hait2e4de832017-09-19 14:53:16 +0530782 if isinstance(tax_data, list):
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530783 tax_rate = flt(tax_data[0])
784 tax_amount = flt(tax_data[1])
Nabin Hait2e4de832017-09-19 14:53:16 +0530785 else:
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530786 tax_rate = flt(tax_data)
787
788 itemised_tax[item_code][tax.description] = frappe._dict(dict(
789 tax_rate = tax_rate,
790 tax_amount = tax_amount
791 ))
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530792
Nabin Hait34c551d2019-07-03 10:34:31 +0530793 if with_tax_account:
794 itemised_tax[item_code][tax.description].tax_account = tax.account_head
795
Nabin Haitb962fc12017-07-17 18:02:31 +0530796 return itemised_tax
797
798def get_itemised_taxable_amount(items):
799 itemised_taxable_amount = frappe._dict()
800 for item in items:
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530801 item_code = item.item_code or item.item_name
Nabin Haitb962fc12017-07-17 18:02:31 +0530802 itemised_taxable_amount.setdefault(item_code, 0)
803 itemised_taxable_amount[item_code] += item.net_amount
804
Nabin Haitcaab5822017-08-24 16:22:28 +0530805 return itemised_taxable_amount
806
807def get_rounded_tax_amount(itemised_tax, precision):
808 # Rounding based on tax_amount precision
809 for taxes in itemised_tax.values():
810 for tax_account in taxes:
Himanshu Mishra35b26272018-11-13 11:13:04 +0530811 taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530812
813class init_landed_taxes_and_totals(object):
814 def __init__(self, doc):
815 self.doc = doc
816 self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
817 self.set_account_currency()
818 self.set_exchange_rate()
819 self.set_amounts_in_company_currency()
820
821 def set_account_currency(self):
822 company_currency = erpnext.get_company_currency(self.doc.company)
823 for d in self.doc.get(self.tax_field):
824 if not d.account_currency:
825 account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
826 d.account_currency = account_currency or company_currency
827
828 def set_exchange_rate(self):
829 company_currency = erpnext.get_company_currency(self.doc.company)
830 for d in self.doc.get(self.tax_field):
831 if d.account_currency == company_currency:
832 d.exchange_rate = 1
Deepesh Garg22f5ff82021-03-17 10:56:52 +0530833 elif not d.exchange_rate:
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530834 d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
835 account_currency=d.account_currency, company=self.doc.company)
836
837 if not d.exchange_rate:
838 frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
839
840 def set_amounts_in_company_currency(self):
841 for d in self.doc.get(self.tax_field):
842 d.amount = flt(d.amount, d.precision("amount"))
Walstan Baptista37b826b2021-04-03 19:48:46 +0530843 d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))