blob: 5f73c55836b8fe3c2510feaab5b0350a74bc6fb4 [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
4from __future__ import unicode_literals
5import json
Rushabh Mehtacc8b2b22017-03-31 12:44:29 +05306import frappe, erpnext
Nabin Hait3769d872015-12-18 13:12:02 +05307from frappe import _, scrub
Nabin Haitb962fc12017-07-17 18:02:31 +05308from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
Nabin Hait613d0812015-02-23 11:58:15 +05309from erpnext.controllers.accounts_controller import validate_conversion_rate, \
10 validate_taxes_and_charges, validate_inclusive_tax
Deepesh Gargef0d26c2020-01-06 15:34:15 +053011from erpnext.stock.get_item_details import _get_item_tax_template
marination733fd5f2020-08-26 18:23:12 +053012from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
Deepesh Gargbfc17e42020-12-25 18:34:39 +053013from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
Nabin Hait3237c752015-02-17 11:11:11 +053014
Nabin Haitfe81da22015-02-18 12:23:18 +053015class calculate_taxes_and_totals(object):
Nabin Hait3237c752015-02-17 11:11:11 +053016 def __init__(self, doc):
17 self.doc = doc
Deepesh Garg6a5ef262021-02-19 14:30:23 +053018 frappe.flags.round_off_applicable_accounts = []
19 get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
Nabin Haitfe81da22015-02-18 12:23:18 +053020 self.calculate()
21
Nabin Hait3237c752015-02-17 11:11:11 +053022 def calculate(self):
Nabin Haitb315acb2019-07-12 14:27:19 +053023 if not len(self.doc.get("items")):
24 return
25
Nabin Hait3237c752015-02-17 11:11:11 +053026 self.discount_amount_applied = False
27 self._calculate()
28
29 if self.doc.meta.get_field("discount_amount"):
Nabin Hait3769d872015-12-18 13:12:02 +053030 self.set_discount_amount()
Nabin Hait3237c752015-02-17 11:11:11 +053031 self.apply_discount_amount()
32
Nabin Haitbd00e812015-02-17 12:50:51 +053033 if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
Nabin Hait3237c752015-02-17 11:11:11 +053034 self.calculate_total_advance()
Vishal Dhayaguded42242d2017-11-29 16:09:59 +053035
Nabin Hait852cb642017-07-05 12:58:19 +053036 if self.doc.meta.get_field("other_charges_calculation"):
37 self.set_item_wise_tax_breakup()
Nabin Hait3237c752015-02-17 11:11:11 +053038
39 def _calculate(self):
Faris Ansarieae2dda2018-05-02 12:19:30 +053040 self.validate_conversion_rate()
Nabin Haite7679702015-02-20 14:40:35 +053041 self.calculate_item_values()
Deepesh Gargef0d26c2020-01-06 15:34:15 +053042 self.validate_item_tax_template()
Nabin Haite7679702015-02-20 14:40:35 +053043 self.initialize_taxes()
44 self.determine_exclusive_rate()
45 self.calculate_net_total()
46 self.calculate_taxes()
Nabin Haita1bf43b2015-03-17 10:50:47 +053047 self.manipulate_grand_total_for_inclusive_tax()
Nabin Haite7679702015-02-20 14:40:35 +053048 self.calculate_totals()
49 self._cleanup()
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +053050 self.calculate_total_net_weight()
Nabin Haite7679702015-02-20 14:40:35 +053051
Deepesh Gargef0d26c2020-01-06 15:34:15 +053052 def validate_item_tax_template(self):
53 for item in self.doc.get('items'):
54 if item.item_code and item.get('item_tax_template'):
55 item_doc = frappe.get_cached_doc("Item", item.item_code)
56 args = {
57 'tax_category': self.doc.get('tax_category'),
58 'posting_date': self.doc.get('posting_date'),
59 'bill_date': self.doc.get('bill_date'),
mohammadahmad1990728bf0e2020-06-18 12:21:42 +050060 'transaction_date': self.doc.get('transaction_date'),
61 'company': self.doc.get('company')
Deepesh Gargef0d26c2020-01-06 15:34:15 +053062 }
63
64 item_group = item_doc.item_group
65 item_group_taxes = []
66
67 while item_group:
68 item_group_doc = frappe.get_cached_doc('Item Group', item_group)
69 item_group_taxes += item_group_doc.taxes or []
70 item_group = item_group_doc.parent_item_group
71
72 item_taxes = item_doc.taxes or []
73
74 if not item_group_taxes and (not item_taxes):
75 # No validation if no taxes in item or item group
76 continue
77
78 taxes = _get_item_tax_template(args, item_taxes + item_group_taxes, for_validate=True)
79
80 if item.item_tax_template not in taxes:
81 frappe.throw(_("Row {0}: Invalid Item Tax Template for item {1}").format(
82 item.idx, frappe.bold(item.item_code)
83 ))
84
Nabin Haite7679702015-02-20 14:40:35 +053085 def validate_conversion_rate(self):
Nabin Hait3237c752015-02-17 11:11:11 +053086 # validate conversion rate
Rushabh Mehtacc8b2b22017-03-31 12:44:29 +053087 company_currency = erpnext.get_company_currency(self.doc.company)
Nabin Hait3237c752015-02-17 11:11:11 +053088 if not self.doc.currency or self.doc.currency == company_currency:
89 self.doc.currency = company_currency
90 self.doc.conversion_rate = 1.0
91 else:
92 validate_conversion_rate(self.doc.currency, self.doc.conversion_rate,
93 self.doc.meta.get_label("conversion_rate"), self.doc.company)
94
95 self.doc.conversion_rate = flt(self.doc.conversion_rate)
96
Nabin Hait3237c752015-02-17 11:11:11 +053097 def calculate_item_values(self):
98 if not self.discount_amount_applied:
99 for item in self.doc.get("items"):
100 self.doc.round_floats_in(item)
101
102 if item.discount_percentage == 100:
103 item.rate = 0.0
Nabin Hait593242f2019-04-05 19:35:02 +0530104 elif item.price_list_rate:
105 if not item.rate or (item.pricing_rules and item.discount_percentage > 0):
106 item.rate = flt(item.price_list_rate *
107 (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
108 item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
109 elif item.discount_amount and item.pricing_rules:
110 item.rate = item.price_list_rate - item.discount_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530111
Anupam Kumared42afc2021-03-15 11:11:28 +0530112 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 +0530113 item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
Nabin Hait64bfdd92019-04-23 13:37:19 +0530114 if flt(item.rate_with_margin) > 0:
115 item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
Walstan Baptista37b826b2021-04-03 19:48:46 +0530116 if item.discount_amount and not item.discount_percentage:
Rohit Waghchaureff70e612021-03-06 22:08:08 +0530117 item.rate -= item.discount_amount
Walstan Baptista37b826b2021-04-03 19:48:46 +0530118 else:
119 item.discount_amount = item.rate_with_margin - item.rate
Nabin Hait64bfdd92019-04-23 13:37:19 +0530120 elif flt(item.price_list_rate) > 0:
121 item.discount_amount = item.price_list_rate - item.rate
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530122 elif flt(item.price_list_rate) > 0 and not item.discount_amount:
123 item.discount_amount = item.price_list_rate - item.rate
mbauskara52472c2016-03-05 15:10:25 +0530124
Nabin Haite7679702015-02-20 14:40:35 +0530125 item.net_rate = item.rate
Deepesh Gargb65c7612019-07-31 15:58:01 +0530126
deepeshgarg0078bf19ce2019-08-03 13:40:37 +0530127 if not item.qty and self.doc.get("is_return"):
Deepesh Gargb65c7612019-07-31 15:58:01 +0530128 item.amount = flt(-1 * item.rate, item.precision("amount"))
129 else:
130 item.amount = flt(item.rate * item.qty, item.precision("amount"))
131
Nabin Haite7679702015-02-20 14:40:35 +0530132 item.net_amount = item.amount
Nabin Hait3237c752015-02-17 11:11:11 +0530133
Nabin Haite7679702015-02-20 14:40:35 +0530134 self._set_in_company_currency(item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"])
Nabin Hait3237c752015-02-17 11:11:11 +0530135
Nabin Haite7679702015-02-20 14:40:35 +0530136 item.item_tax_amount = 0.0
137
138 def _set_in_company_currency(self, doc, fields):
Nabin Hait3237c752015-02-17 11:11:11 +0530139 """set values in base currency"""
Nabin Haite7679702015-02-20 14:40:35 +0530140 for f in fields:
141 val = flt(flt(doc.get(f), doc.precision(f)) * self.doc.conversion_rate, doc.precision("base_" + f))
142 doc.set("base_" + f, val)
Nabin Hait3237c752015-02-17 11:11:11 +0530143
144 def initialize_taxes(self):
145 for tax in self.doc.get("taxes"):
Nabin Hait86cd4cc2015-02-28 19:11:51 +0530146 if not self.discount_amount_applied:
147 validate_taxes_and_charges(tax)
148 validate_inclusive_tax(tax, self.doc)
Nabin Hait613d0812015-02-23 11:58:15 +0530149
Nabin Hait3237c752015-02-17 11:11:11 +0530150 tax.item_wise_tax_detail = {}
151 tax_fields = ["total", "tax_amount_after_discount_amount",
152 "tax_amount_for_current_item", "grand_total_for_current_item",
153 "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
154
Nabin Haitde9c8a92015-02-23 01:06:00 +0530155 if tax.charge_type != "Actual" and \
156 not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
157 tax_fields.append("tax_amount")
Nabin Hait3237c752015-02-17 11:11:11 +0530158
159 for fieldname in tax_fields:
160 tax.set(fieldname, 0.0)
161
Nabin Hait3237c752015-02-17 11:11:11 +0530162 self.doc.round_floats_in(tax)
163
Nabin Hait3237c752015-02-17 11:11:11 +0530164 def determine_exclusive_rate(self):
Nabin Hait37b047d2015-02-23 16:01:33 +0530165 if not any((cint(tax.included_in_print_rate) for tax in self.doc.get("taxes"))):
166 return
Nabin Hait3237c752015-02-17 11:11:11 +0530167
168 for item in self.doc.get("items"):
169 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
170 cumulated_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530171 total_inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530172 for i, tax in enumerate(self.doc.get("taxes")):
Nabin Hait19ea7212020-08-11 20:34:57 +0530173 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 +0530174
175 if i==0:
176 tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
177 else:
178 tax.grand_total_fraction_for_current_item = \
179 self.doc.get("taxes")[i-1].grand_total_fraction_for_current_item \
180 + tax.tax_fraction_for_current_item
181
182 cumulated_tax_fraction += tax.tax_fraction_for_current_item
Nabin Haite2ee4552020-08-12 20:58:03 +0530183 total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
Nabin Hait3237c752015-02-17 11:11:11 +0530184
Nabin Hait19ea7212020-08-11 20:34:57 +0530185 if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty):
186 amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
187
188 item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
Nabin Haite7679702015-02-20 14:40:35 +0530189 item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
Nabin Hait2e4de832017-09-19 14:53:16 +0530190 item.discount_percentage = flt(item.discount_percentage,
191 item.precision("discount_percentage"))
Nabin Hait3237c752015-02-17 11:11:11 +0530192
Nabin Haite7679702015-02-20 14:40:35 +0530193 self._set_in_company_currency(item, ["net_rate", "net_amount"])
194
Nabin Hait3237c752015-02-17 11:11:11 +0530195 def _load_item_tax_rate(self, item_tax_rate):
196 return json.loads(item_tax_rate) if item_tax_rate else {}
197
198 def get_current_tax_fraction(self, tax, item_tax_map):
199 """
200 Get tax fraction for calculating tax exclusive amount
201 from tax inclusive amount
202 """
203 current_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530204 inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530205
206 if cint(tax.included_in_print_rate):
207 tax_rate = self._get_tax_rate(tax, item_tax_map)
208
209 if tax.charge_type == "On Net Total":
210 current_tax_fraction = tax_rate / 100.0
211
212 elif tax.charge_type == "On Previous Row Amount":
213 current_tax_fraction = (tax_rate / 100.0) * \
214 self.doc.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
215
216 elif tax.charge_type == "On Previous Row Total":
217 current_tax_fraction = (tax_rate / 100.0) * \
218 self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
marination733fd5f2020-08-26 18:23:12 +0530219
Nabin Hait19ea7212020-08-11 20:34:57 +0530220 elif tax.charge_type == "On Item Quantity":
221 inclusive_tax_amount_per_qty = flt(tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530222
Nabin Hait19ea7212020-08-11 20:34:57 +0530223 if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
224 current_tax_fraction *= -1.0
225 inclusive_tax_amount_per_qty *= -1.0
226
227 return current_tax_fraction, inclusive_tax_amount_per_qty
Nabin Hait3237c752015-02-17 11:11:11 +0530228
229 def _get_tax_rate(self, tax, item_tax_map):
Achilles Rasquinha87dab142018-03-08 14:21:48 +0530230 if tax.account_head in item_tax_map:
Nabin Hait3237c752015-02-17 11:11:11 +0530231 return flt(item_tax_map.get(tax.account_head), self.doc.precision("rate", tax))
232 else:
233 return tax.rate
234
235 def calculate_net_total(self):
Shreya Shahe3290382018-05-28 11:49:08 +0530236 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 +0530237
Nabin Hait3237c752015-02-17 11:11:11 +0530238 for item in self.doc.get("items"):
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530239 self.doc.total += item.amount
Shreya Shahe3290382018-05-28 11:49:08 +0530240 self.doc.total_qty += item.qty
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530241 self.doc.base_total += item.base_amount
Nabin Haite7679702015-02-20 14:40:35 +0530242 self.doc.net_total += item.net_amount
243 self.doc.base_net_total += item.base_net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530244
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530245 self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530246
247 def calculate_taxes(self):
Nabin Hait2e4de832017-09-19 14:53:16 +0530248 self.doc.rounding_adjustment = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530249 # maintain actual tax rate based on idx
Nabin Haite7679702015-02-20 14:40:35 +0530250 actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
Nabin Hait3237c752015-02-17 11:11:11 +0530251 for tax in self.doc.get("taxes") if tax.charge_type == "Actual"])
252
253 for n, item in enumerate(self.doc.get("items")):
254 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530255 for i, tax in enumerate(self.doc.get("taxes")):
256 # tax_amount represents the amount of tax for the current step
257 current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map)
258
259 # Adjust divisional loss to the last item
260 if tax.charge_type == "Actual":
261 actual_tax_dict[tax.idx] -= current_tax_amount
262 if n == len(self.doc.get("items")) - 1:
263 current_tax_amount += actual_tax_dict[tax.idx]
264
Nabin Hait2b019ed2015-02-22 23:03:07 +0530265 # accumulate tax amount into tax.tax_amount
Nabin Haitde9c8a92015-02-23 01:06:00 +0530266 if tax.charge_type != "Actual" and \
267 not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
268 tax.tax_amount += current_tax_amount
Nabin Hait2b019ed2015-02-22 23:03:07 +0530269
Nabin Hait3237c752015-02-17 11:11:11 +0530270 # store tax_amount for current item as it will be used for
271 # charge type = 'On Previous Row Amount'
272 tax.tax_amount_for_current_item = current_tax_amount
273
Nabin Hait2b019ed2015-02-22 23:03:07 +0530274 # set tax after discount
Nabin Hait3237c752015-02-17 11:11:11 +0530275 tax.tax_amount_after_discount_amount += current_tax_amount
276
Nabin Haitcd951342017-07-31 18:07:45 +0530277 current_tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(current_tax_amount, tax)
Nabin Hait3237c752015-02-17 11:11:11 +0530278
Nabin Hait3237c752015-02-17 11:11:11 +0530279 # note: grand_total_for_current_item contains the contribution of
280 # item's amount, previously applied tax and the current tax on that item
281 if i==0:
Nabin Haitcd951342017-07-31 18:07:45 +0530282 tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530283 else:
284 tax.grand_total_for_current_item = \
Nabin Haitcd951342017-07-31 18:07:45 +0530285 flt(self.doc.get("taxes")[i-1].grand_total_for_current_item + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530286
287 # set precision in the last item iteration
288 if n == len(self.doc.get("items")) - 1:
289 self.round_off_totals(tax)
Nabin Haitcd951342017-07-31 18:07:45 +0530290 self.set_cumulative_total(i, tax)
291
292 self._set_in_company_currency(tax,
293 ["total", "tax_amount", "tax_amount_after_discount_amount"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530294
Nabin Hait3237c752015-02-17 11:11:11 +0530295 # adjust Discount Amount loss in last tax iteration
Nabin Haitde9c8a92015-02-23 01:06:00 +0530296 if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
Nabin Haitdb53a782015-07-31 16:53:13 +0530297 and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total":
Nabin Hait2e4de832017-09-19 14:53:16 +0530298 self.doc.rounding_adjustment = flt(self.doc.grand_total
299 - flt(self.doc.discount_amount) - tax.total,
300 self.doc.precision("rounding_adjustment"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530301
Nabin Haitcd951342017-07-31 18:07:45 +0530302 def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
303 # if just for valuation, do not add the tax amount in total
304 # if tax/charges is for deduction, multiply by -1
305 if getattr(tax, "category", None):
306 tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
Rushabh Mehta30dc9a12017-11-17 14:31:09 +0530307 if self.doc.doctype in ["Purchase Order", "Purchase Invoice", "Purchase Receipt", "Supplier Quotation"]:
308 tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
Nabin Haitcd951342017-07-31 18:07:45 +0530309 return tax_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530310
Nabin Haitcd951342017-07-31 18:07:45 +0530311 def set_cumulative_total(self, row_idx, tax):
312 tax_amount = tax.tax_amount_after_discount_amount
313 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax_amount, tax)
314
315 if row_idx == 0:
316 tax.total = flt(self.doc.net_total + tax_amount, tax.precision("total"))
317 else:
318 tax.total = flt(self.doc.get("taxes")[row_idx-1].total + tax_amount, tax.precision("total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530319
320 def get_current_tax_amount(self, item, tax, item_tax_map):
321 tax_rate = self._get_tax_rate(tax, item_tax_map)
322 current_tax_amount = 0.0
323
324 if tax.charge_type == "Actual":
325 # distribute the tax amount proportionally to each item row
Nabin Haite7679702015-02-20 14:40:35 +0530326 actual = flt(tax.tax_amount, tax.precision("tax_amount"))
327 current_tax_amount = item.net_amount*actual / self.doc.net_total if self.doc.net_total else 0.0
328
Nabin Hait3237c752015-02-17 11:11:11 +0530329 elif tax.charge_type == "On Net Total":
Nabin Haite7679702015-02-20 14:40:35 +0530330 current_tax_amount = (tax_rate / 100.0) * item.net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530331 elif tax.charge_type == "On Previous Row Amount":
332 current_tax_amount = (tax_rate / 100.0) * \
333 self.doc.get("taxes")[cint(tax.row_id) - 1].tax_amount_for_current_item
334 elif tax.charge_type == "On Previous Row Total":
335 current_tax_amount = (tax_rate / 100.0) * \
336 self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
Himanshu Mishra35b26272018-11-13 11:13:04 +0530337 elif tax.charge_type == "On Item Quantity":
Nabin Hait19ea7212020-08-11 20:34:57 +0530338 current_tax_amount = tax_rate * item.qty
Nabin Hait3237c752015-02-17 11:11:11 +0530339
Deepesh Garg6a5ef262021-02-19 14:30:23 +0530340 current_tax_amount = self.get_final_current_tax_amount(tax, current_tax_amount)
Nabin Haite7679702015-02-20 14:40:35 +0530341 self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530342
343 return current_tax_amount
344
Deepesh Garg6a5ef262021-02-19 14:30:23 +0530345 def get_final_current_tax_amount(self, tax, current_tax_amount):
346 # Some countries need individual tax components to be rounded
347 # Handeled via regional doctypess
348 if tax.account_head in frappe.flags.round_off_applicable_accounts:
349 current_tax_amount = round(current_tax_amount, 0)
350 return current_tax_amount
351
Nabin Haite7679702015-02-20 14:40:35 +0530352 def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
353 # store tax breakup for each item
354 key = item.item_code or item.item_name
355 item_wise_tax_amount = current_tax_amount*self.doc.conversion_rate
356 if tax.item_wise_tax_detail.get(key):
357 item_wise_tax_amount += tax.item_wise_tax_detail[key][1]
358
Nabin Haitcaab5822017-08-24 16:22:28 +0530359 tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
Nabin Haite7679702015-02-20 14:40:35 +0530360
Nabin Hait3237c752015-02-17 11:11:11 +0530361 def round_off_totals(self, tax):
Nabin Haite7679702015-02-20 14:40:35 +0530362 tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530363 tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
Nabin Haitcd951342017-07-31 18:07:45 +0530364 tax.precision("tax_amount"))
Nabin Haitce245122015-02-22 20:14:49 +0530365
Nabin Haita1bf43b2015-03-17 10:50:47 +0530366 def manipulate_grand_total_for_inclusive_tax(self):
367 # if fully inclusive taxes and diff
Nabin Hait2e4de832017-09-19 14:53:16 +0530368 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 +0530369 last_tax = self.doc.get("taxes")[-1]
Nabin Hait2e4de832017-09-19 14:53:16 +0530370 non_inclusive_tax_amount = sum([flt(d.tax_amount_after_discount_amount)
371 for d in self.doc.get("taxes") if not d.included_in_print_rate])
Nabin Haitf32fc232019-12-25 13:59:24 +0530372
Nabin Hait2e4de832017-09-19 14:53:16 +0530373 diff = self.doc.total + non_inclusive_tax_amount \
374 - flt(last_tax.total, last_tax.precision("total"))
Nabin Haitf32fc232019-12-25 13:59:24 +0530375
376 # If discount amount applied, deduct the discount amount
377 # because self.doc.total is always without discount, but last_tax.total is after discount
378 if self.discount_amount_applied and self.doc.discount_amount:
379 diff -= flt(self.doc.discount_amount)
380
381 diff = flt(diff, self.doc.precision("rounding_adjustment"))
382
Nabin Hait2e4de832017-09-19 14:53:16 +0530383 if diff and abs(diff) <= (5.0 / 10**last_tax.precision("tax_amount")):
Nabin Haitf32fc232019-12-25 13:59:24 +0530384 self.doc.rounding_adjustment = diff
Nabin Hait3237c752015-02-17 11:11:11 +0530385
386 def calculate_totals(self):
Nabin Hait2e4de832017-09-19 14:53:16 +0530387 self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) \
388 if self.doc.get("taxes") else flt(self.doc.net_total)
Nabin Hait3237c752015-02-17 11:11:11 +0530389
Nabin Hait2e4de832017-09-19 14:53:16 +0530390 self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total
391 - flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530392
Nabin Hait2e4de832017-09-19 14:53:16 +0530393 self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530394
Saqiba6f98d42020-07-23 18:51:26 +0530395 if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]:
Anurag Mishra8a06b8f2019-07-17 14:55:16 +0530396 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 +0530397 if self.doc.total_taxes_and_charges else self.doc.base_net_total
Nabin Hait3237c752015-02-17 11:11:11 +0530398 else:
Anand Doshiec5ec602015-03-05 19:31:23 +0530399 self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0
Nabin Hait3237c752015-02-17 11:11:11 +0530400 for tax in self.doc.get("taxes"):
401 if tax.category in ["Valuation and Total", "Total"]:
402 if tax.add_deduct_tax == "Add":
Nabin Haitdb53a782015-07-31 16:53:13 +0530403 self.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530404 else:
Nabin Haitdb53a782015-07-31 16:53:13 +0530405 self.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530406
Nabin Haite7679702015-02-20 14:40:35 +0530407 self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530408
Nabin Haite7679702015-02-20 14:40:35 +0530409 self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
410 if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
411 else self.doc.base_net_total
Nabin Hait3237c752015-02-17 11:11:11 +0530412
Nabin Hait2e4de832017-09-19 14:53:16 +0530413 self._set_in_company_currency(self.doc,
414 ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530415
Nabin Haite7679702015-02-20 14:40:35 +0530416 self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530417
Nabin Hait2e4de832017-09-19 14:53:16 +0530418 self.set_rounded_total()
419
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +0530420 def calculate_total_net_weight(self):
421 if self.doc.meta.get_field('total_net_weight'):
422 self.doc.total_net_weight = 0.0
423 for d in self.doc.items:
424 if d.total_weight:
425 self.doc.total_net_weight += d.total_weight
426
Nabin Hait2e4de832017-09-19 14:53:16 +0530427 def set_rounded_total(self):
Nabin Hait3237c752015-02-17 11:11:11 +0530428 if self.doc.meta.get_field("rounded_total"):
Nabin Hait877e1bb2017-11-17 12:27:43 +0530429 if self.doc.is_rounded_total_disabled():
430 self.doc.rounded_total = self.doc.base_rounded_total = 0
431 return
432
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530433 self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
Nabin Haitfb0b24a2016-01-20 14:46:26 +0530434 self.doc.currency, self.doc.precision("rounded_total"))
Nabin Hait2e4de832017-09-19 14:53:16 +0530435
Nabin Hait877e1bb2017-11-17 12:27:43 +0530436 #if print_in_rate is set, we would have already calculated rounding adjustment
437 self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
438 self.doc.precision("rounding_adjustment"))
439
Nabin Hait02ac9012017-11-22 16:12:20 +0530440 self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530441
Nabin Hait3237c752015-02-17 11:11:11 +0530442 def _cleanup(self):
443 for tax in self.doc.get("taxes"):
444 tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530445
Nabin Hait3769d872015-12-18 13:12:02 +0530446 def set_discount_amount(self):
Nabin Haite0405102016-10-13 12:14:32 +0530447 if self.doc.additional_discount_percentage:
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530448 self.doc.discount_amount = flt(flt(self.doc.get(scrub(self.doc.apply_discount_on)))
Nabin Hait3769d872015-12-18 13:12:02 +0530449 * self.doc.additional_discount_percentage / 100, self.doc.precision("discount_amount"))
Nabin Hait3237c752015-02-17 11:11:11 +0530450
451 def apply_discount_amount(self):
452 if self.doc.discount_amount:
Nabin Hait37b047d2015-02-23 16:01:33 +0530453 if not self.doc.apply_discount_on:
454 frappe.throw(_("Please select Apply Discount On"))
455
Nabin Hait3237c752015-02-17 11:11:11 +0530456 self.doc.base_discount_amount = flt(self.doc.discount_amount * self.doc.conversion_rate,
457 self.doc.precision("base_discount_amount"))
458
Nabin Haite7679702015-02-20 14:40:35 +0530459 total_for_discount_amount = self.get_total_for_discount_amount()
Nabin Hait25bd84d2015-03-04 15:06:56 +0530460 taxes = self.doc.get("taxes")
461 net_total = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530462
Nabin Haite7679702015-02-20 14:40:35 +0530463 if total_for_discount_amount:
Nabin Hait3237c752015-02-17 11:11:11 +0530464 # calculate item amount after Discount Amount
Nabin Hait25bd84d2015-03-04 15:06:56 +0530465 for i, item in enumerate(self.doc.get("items")):
466 distributed_amount = flt(self.doc.discount_amount) * \
467 item.net_amount / total_for_discount_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530468
Nabin Haite7679702015-02-20 14:40:35 +0530469 item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
Nabin Hait25bd84d2015-03-04 15:06:56 +0530470 net_total += item.net_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530471
Nabin Hait25bd84d2015-03-04 15:06:56 +0530472 # discount amount rounding loss adjustment if no taxes
Nabin Hait4d587342019-05-30 15:50:46 +0530473 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 +0530474 and i == len(self.doc.get("items")) - 1:
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530475 discount_amount_loss = flt(self.doc.net_total - net_total - self.doc.discount_amount,
Nabin Hait25bd84d2015-03-04 15:06:56 +0530476 self.doc.precision("net_total"))
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530477
Anand Doshiec5ec602015-03-05 19:31:23 +0530478 item.net_amount = flt(item.net_amount + discount_amount_loss,
Nabin Hait25bd84d2015-03-04 15:06:56 +0530479 item.precision("net_amount"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530480
Nabin Hait51e980d2015-10-10 18:10:05 +0530481 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 +0530482
Nabin Haite7679702015-02-20 14:40:35 +0530483 self._set_in_company_currency(item, ["net_rate", "net_amount"])
Nabin Hait3237c752015-02-17 11:11:11 +0530484
485 self.discount_amount_applied = True
486 self._calculate()
487 else:
488 self.doc.base_discount_amount = 0
489
Nabin Haite7679702015-02-20 14:40:35 +0530490 def get_total_for_discount_amount(self):
Nabin Haitde9c8a92015-02-23 01:06:00 +0530491 if self.doc.apply_discount_on == "Net Total":
492 return self.doc.net_total
Nabin Haite7679702015-02-20 14:40:35 +0530493 else:
494 actual_taxes_dict = {}
Nabin Hait3237c752015-02-17 11:11:11 +0530495
Nabin Haite7679702015-02-20 14:40:35 +0530496 for tax in self.doc.get("taxes"):
Nabin Hait19ea7212020-08-11 20:34:57 +0530497 if tax.charge_type in ["Actual", "On Item Quantity"]:
Nabin Haitaf9bdfe2017-12-12 18:50:05 +0530498 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
499 actual_taxes_dict.setdefault(tax.idx, tax_amount)
Nabin Haite7679702015-02-20 14:40:35 +0530500 elif tax.row_id in actual_taxes_dict:
501 actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * flt(tax.rate) / 100
502 actual_taxes_dict.setdefault(tax.idx, actual_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530503
Nabin Hait877e1bb2017-11-17 12:27:43 +0530504 return flt(self.doc.grand_total - sum(actual_taxes_dict.values()),
505 self.doc.precision("grand_total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530506
507
Nabin Hait7b19b9e2015-02-24 09:42:24 +0530508 def calculate_total_advance(self):
509 if self.doc.docstatus < 2:
Nabin Haite7679702015-02-20 14:40:35 +0530510 total_allocated_amount = sum([flt(adv.allocated_amount, adv.precision("allocated_amount"))
Nabin Hait3237c752015-02-17 11:11:11 +0530511 for adv in self.doc.get("advances")])
512
Nabin Haite7679702015-02-20 14:40:35 +0530513 self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530514
Faris Ansari6041f5c2018-02-08 13:33:52 +0530515 grand_total = self.doc.rounded_total or self.doc.grand_total
516
Nabin Hait289ffb72016-02-08 11:06:55 +0530517 if self.doc.party_account_currency == self.doc.currency:
Faris Ansari6041f5c2018-02-08 13:33:52 +0530518 invoice_total = flt(grand_total - flt(self.doc.write_off_amount),
Nabin Hait289ffb72016-02-08 11:06:55 +0530519 self.doc.precision("grand_total"))
Nabin Hait8d8cba72017-04-03 17:26:22 +0530520 else:
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530521 base_write_off_amount = flt(flt(self.doc.write_off_amount) * self.doc.conversion_rate,
Nabin Hait8d8cba72017-04-03 17:26:22 +0530522 self.doc.precision("base_write_off_amount"))
Faris Ansari6041f5c2018-02-08 13:33:52 +0530523 invoice_total = flt(grand_total * self.doc.conversion_rate,
Nabin Hait8d8cba72017-04-03 17:26:22 +0530524 self.doc.precision("grand_total")) - base_write_off_amount
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530525
Nabin Haitadc09232016-02-09 10:31:11 +0530526 if invoice_total > 0 and self.doc.total_advance > invoice_total:
Nabin Hait289ffb72016-02-08 11:06:55 +0530527 frappe.throw(_("Advance amount cannot be greater than {0} {1}")
528 .format(self.doc.party_account_currency, invoice_total))
Nabin Hait3237c752015-02-17 11:11:11 +0530529
Rushabh Mehta8bb6e532015-02-18 20:22:59 +0530530 if self.doc.docstatus == 0:
Nabin Hait3237c752015-02-17 11:11:11 +0530531 self.calculate_outstanding_amount()
532
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530533 def is_internal_invoice(self):
534 """
535 Checks if its an internal transfer invoice
536 and decides if to calculate any out standing amount or not
537 """
538
539 if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer():
540 return True
541
542 return False
543
Nabin Hait3237c752015-02-17 11:11:11 +0530544 def calculate_outstanding_amount(self):
545 # NOTE:
546 # write_off_amount is only for POS Invoice
547 # total_advance is only for non POS Invoice
Rohit Waghchaureefb5bf22016-08-25 02:09:53 +0530548 if self.doc.doctype == "Sales Invoice":
549 self.calculate_paid_amount()
550
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530551 if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \
552 self.is_internal_invoice(): return
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530553
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530554 self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530555 self._set_in_company_currency(self.doc, ['write_off_amount'])
556
Nabin Hait877e1bb2017-11-17 12:27:43 +0530557 if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
558 grand_total = self.doc.rounded_total or self.doc.grand_total
559 if self.doc.party_account_currency == self.doc.currency:
Manas Solankida486ee2018-07-06 12:36:57 +0530560 total_amount_to_pay = flt(grand_total - self.doc.total_advance
Nabin Hait877e1bb2017-11-17 12:27:43 +0530561 - flt(self.doc.write_off_amount), self.doc.precision("grand_total"))
562 else:
563 total_amount_to_pay = flt(flt(grand_total *
564 self.doc.conversion_rate, self.doc.precision("grand_total")) - self.doc.total_advance
565 - flt(self.doc.base_write_off_amount), self.doc.precision("grand_total"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530566
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530567 self.doc.round_floats_in(self.doc, ["paid_amount"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530568 change_amount = 0
569
Deepesh Garg0ebace52020-02-25 13:21:16 +0530570 if self.doc.doctype == "Sales Invoice" and not self.doc.get('is_return'):
Nabin Hait877e1bb2017-11-17 12:27:43 +0530571 self.calculate_write_off_amount()
572 self.calculate_change_amount()
573 change_amount = self.doc.change_amount \
574 if self.doc.party_account_currency == self.doc.currency else self.doc.base_change_amount
575
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530576 paid_amount = self.doc.paid_amount \
577 if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530578
Nabin Hait877e1bb2017-11-17 12:27:43 +0530579 self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount),
580 self.doc.precision("outstanding_amount"))
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530581
Deepesh Garg0ebace52020-02-25 13:21:16 +0530582 if self.doc.doctype == 'Sales Invoice' and self.doc.get('is_pos') and self.doc.get('is_return'):
583 self.update_paid_amount_for_return(total_amount_to_pay)
584
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530585 def calculate_paid_amount(self):
Manas Solankida486ee2018-07-06 12:36:57 +0530586
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530587 paid_amount = base_paid_amount = 0.0
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530588
589 if self.doc.is_pos:
590 for payment in self.doc.get('payments'):
Ayush Shuklae9cf1ab2017-05-25 14:14:55 +0530591 payment.amount = flt(payment.amount)
592 payment.base_amount = payment.amount * flt(self.doc.conversion_rate)
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530593 paid_amount += payment.amount
594 base_paid_amount += payment.base_amount
rohitwaghchaure73456ac2017-05-16 11:29:57 +0530595 elif not self.doc.is_return:
596 self.doc.set('payments', [])
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530597
Manas Solankida486ee2018-07-06 12:36:57 +0530598 if self.doc.redeem_loyalty_points and self.doc.loyalty_amount:
599 base_paid_amount += self.doc.loyalty_amount
600 paid_amount += (self.doc.loyalty_amount / flt(self.doc.conversion_rate))
601
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530602 self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount"))
603 self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount"))
604
Nabin Hait3bb1a422016-08-02 16:41:10 +0530605 def calculate_change_amount(self):
606 self.doc.change_amount = 0.0
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530607 self.doc.base_change_amount = 0.0
Nabin Hait877e1bb2017-11-17 12:27:43 +0530608
609 if self.doc.doctype == "Sales Invoice" \
610 and self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530611 and any([d.type == "Cash" for d in self.doc.payments]):
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530612 grand_total = self.doc.rounded_total or self.doc.grand_total
613 base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530614
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530615 self.doc.change_amount = flt(self.doc.paid_amount - grand_total +
Nabin Hait3bb1a422016-08-02 16:41:10 +0530616 self.doc.write_off_amount, self.doc.precision("change_amount"))
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530617
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530618 self.doc.base_change_amount = flt(self.doc.base_paid_amount - base_grand_total +
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530619 self.doc.base_write_off_amount, self.doc.precision("base_change_amount"))
mbauskar36b51892016-01-18 16:31:10 +0530620
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530621 def calculate_write_off_amount(self):
Rohit Waghchaurebaef2622016-08-05 15:41:36 +0530622 if flt(self.doc.change_amount) > 0:
Nabin Hait877e1bb2017-11-17 12:27:43 +0530623 self.doc.write_off_amount = flt(self.doc.grand_total - self.doc.paid_amount
624 + self.doc.change_amount, self.doc.precision("write_off_amount"))
Rohit Waghchaurebaef2622016-08-05 15:41:36 +0530625 self.doc.base_write_off_amount = flt(self.doc.write_off_amount * self.doc.conversion_rate,
626 self.doc.precision("base_write_off_amount"))
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530627
mbauskar36b51892016-01-18 16:31:10 +0530628 def calculate_margin(self, item):
Makarand Bauskar0e4c5c92017-05-11 11:40:02 +0530629 rate_with_margin = 0.0
Shreya Shahbe690ef2017-11-14 17:22:41 +0530630 base_rate_with_margin = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530631 if item.price_list_rate:
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530632 if item.pricing_rules and not self.doc.ignore_pricing_rule:
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530633 has_margin = False
marination733fd5f2020-08-26 18:23:12 +0530634 for d in get_applied_pricing_rules(item.pricing_rules):
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530635 pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
Shreya Shahf718b0c2018-02-20 11:26:46 +0530636
Rohit Waghchaured4d639c2021-02-01 22:58:22 +0530637 if pricing_rule.margin_rate_or_amount and ((pricing_rule.currency == self.doc.currency and
638 pricing_rule.margin_type in ['Amount', 'Percentage']) or pricing_rule.margin_type == 'Percentage'):
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530639 item.margin_type = pricing_rule.margin_type
640 item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530641 has_margin = True
642
643 if not has_margin:
644 item.margin_type = None
645 item.margin_rate_or_amount = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530646
mbauskara52472c2016-03-05 15:10:25 +0530647 if item.margin_type and item.margin_rate_or_amount:
648 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 +0530649 rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
Shreya Shahbe690ef2017-11-14 17:22:41 +0530650 base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
mbauskar36b51892016-01-18 16:31:10 +0530651
Shreya Shahbe690ef2017-11-14 17:22:41 +0530652 return rate_with_margin, base_rate_with_margin
Nabin Hait852cb642017-07-05 12:58:19 +0530653
654 def set_item_wise_tax_breakup(self):
Nabin Hait9c421612017-07-20 13:32:01 +0530655 self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530656
Deepesh Garg0ebace52020-02-25 13:21:16 +0530657 def update_paid_amount_for_return(self, total_amount_to_pay):
Saqiba6f98d42020-07-23 18:51:26 +0530658 default_mode_of_payment = frappe.db.get_value('POS Payment Method',
659 {'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
Deepesh Garg0ebace52020-02-25 13:21:16 +0530660
661 self.doc.payments = []
662
663 if default_mode_of_payment:
664 self.doc.append('payments', {
665 'mode_of_payment': default_mode_of_payment.mode_of_payment,
Rucha Mahabaledee5302020-12-04 12:13:26 +0530666 'amount': total_amount_to_pay,
667 'default': 1
Deepesh Garg0ebace52020-02-25 13:21:16 +0530668 })
669 else:
670 self.doc.is_pos = 0
671 self.doc.pos_profile = ''
672
673 self.calculate_paid_amount()
674
675
Nabin Hait9c421612017-07-20 13:32:01 +0530676def get_itemised_tax_breakup_html(doc):
677 if not doc.taxes:
678 return
679 frappe.flags.company = doc.company
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530680
Nabin Hait9c421612017-07-20 13:32:01 +0530681 # get headers
Nabin Haitcaab5822017-08-24 16:22:28 +0530682 tax_accounts = []
683 for tax in doc.taxes:
684 if getattr(tax, "category", None) and tax.category=="Valuation":
685 continue
rohitwaghchaure4e17fae2017-12-12 14:40:52 +0530686 if tax.description not in tax_accounts:
Nabin Haitcaab5822017-08-24 16:22:28 +0530687 tax_accounts.append(tax.description)
688
Nabin Hait9c421612017-07-20 13:32:01 +0530689 headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530690
Nabin Hait9c421612017-07-20 13:32:01 +0530691 # get tax breakup data
692 itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc)
Nabin Haitcaab5822017-08-24 16:22:28 +0530693
694 get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes"))
695
rohitwaghchaured4526682017-12-28 14:20:13 +0530696 update_itemised_tax_data(doc)
Nabin Hait9c421612017-07-20 13:32:01 +0530697 frappe.flags.company = None
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530698
Nabin Hait9c421612017-07-20 13:32:01 +0530699 return frappe.render_template(
700 "templates/includes/itemised_tax_breakup.html", dict(
701 headers=headers,
702 itemised_tax=itemised_tax,
703 itemised_taxable_amount=itemised_taxable_amount,
704 tax_accounts=tax_accounts,
Saqib Ansari10a6a2d2020-04-13 16:40:13 +0530705 doc=doc
Nabin Haitb962fc12017-07-17 18:02:31 +0530706 )
Nabin Hait9c421612017-07-20 13:32:01 +0530707 )
Nabin Hait852cb642017-07-05 12:58:19 +0530708
Deepesh Garg6a5ef262021-02-19 14:30:23 +0530709@frappe.whitelist()
710def get_round_off_applicable_accounts(company, account_list):
711 account_list = get_regional_round_off_accounts(company, account_list)
712
713 return account_list
714
715@erpnext.allow_regional
716def get_regional_round_off_accounts(company, account_list):
717 pass
rohitwaghchaured4526682017-12-28 14:20:13 +0530718
719@erpnext.allow_regional
720def update_itemised_tax_data(doc):
721 #Don't delete this method, used for localization
722 pass
723
Nabin Haitb962fc12017-07-17 18:02:31 +0530724@erpnext.allow_regional
725def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
726 return [_("Item"), _("Taxable Amount")] + tax_accounts
727
728@erpnext.allow_regional
729def get_itemised_tax_breakup_data(doc):
730 itemised_tax = get_itemised_tax(doc.taxes)
731
732 itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
733
734 return itemised_tax, itemised_taxable_amount
735
Nabin Hait34c551d2019-07-03 10:34:31 +0530736def get_itemised_tax(taxes, with_tax_account=False):
Nabin Haitb962fc12017-07-17 18:02:31 +0530737 itemised_tax = {}
738 for tax in taxes:
Nabin Haitcaab5822017-08-24 16:22:28 +0530739 if getattr(tax, "category", None) and tax.category=="Valuation":
740 continue
741
Nabin Haitb962fc12017-07-17 18:02:31 +0530742 item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
Nabin Hait2e4de832017-09-19 14:53:16 +0530743 if item_tax_map:
744 for item_code, tax_data in item_tax_map.items():
745 itemised_tax.setdefault(item_code, frappe._dict())
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530746
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530747 tax_rate = 0.0
748 tax_amount = 0.0
749
Nabin Hait2e4de832017-09-19 14:53:16 +0530750 if isinstance(tax_data, list):
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530751 tax_rate = flt(tax_data[0])
752 tax_amount = flt(tax_data[1])
Nabin Hait2e4de832017-09-19 14:53:16 +0530753 else:
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530754 tax_rate = flt(tax_data)
755
756 itemised_tax[item_code][tax.description] = frappe._dict(dict(
757 tax_rate = tax_rate,
758 tax_amount = tax_amount
759 ))
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530760
Nabin Hait34c551d2019-07-03 10:34:31 +0530761 if with_tax_account:
762 itemised_tax[item_code][tax.description].tax_account = tax.account_head
763
Nabin Haitb962fc12017-07-17 18:02:31 +0530764 return itemised_tax
765
766def get_itemised_taxable_amount(items):
767 itemised_taxable_amount = frappe._dict()
768 for item in items:
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530769 item_code = item.item_code or item.item_name
Nabin Haitb962fc12017-07-17 18:02:31 +0530770 itemised_taxable_amount.setdefault(item_code, 0)
771 itemised_taxable_amount[item_code] += item.net_amount
772
Nabin Haitcaab5822017-08-24 16:22:28 +0530773 return itemised_taxable_amount
774
775def get_rounded_tax_amount(itemised_tax, precision):
776 # Rounding based on tax_amount precision
777 for taxes in itemised_tax.values():
778 for tax_account in taxes:
Himanshu Mishra35b26272018-11-13 11:13:04 +0530779 taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530780
781class init_landed_taxes_and_totals(object):
782 def __init__(self, doc):
783 self.doc = doc
784 self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
785 self.set_account_currency()
786 self.set_exchange_rate()
787 self.set_amounts_in_company_currency()
788
789 def set_account_currency(self):
790 company_currency = erpnext.get_company_currency(self.doc.company)
791 for d in self.doc.get(self.tax_field):
792 if not d.account_currency:
793 account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
794 d.account_currency = account_currency or company_currency
795
796 def set_exchange_rate(self):
797 company_currency = erpnext.get_company_currency(self.doc.company)
798 for d in self.doc.get(self.tax_field):
799 if d.account_currency == company_currency:
800 d.exchange_rate = 1
Deepesh Garg22f5ff82021-03-17 10:56:52 +0530801 elif not d.exchange_rate:
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530802 d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
803 account_currency=d.account_currency, company=self.doc.company)
804
805 if not d.exchange_rate:
806 frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
807
808 def set_amounts_in_company_currency(self):
809 for d in self.doc.get(self.tax_field):
810 d.amount = flt(d.amount, d.precision("amount"))
Walstan Baptista37b826b2021-04-03 19:48:46 +0530811 d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))