blob: 7653a5dfdc4967dfea4977d1acfd6e4e9861a447 [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
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530150 if not self.doc.get('is_consolidated'):
151 tax.item_wise_tax_detail = {}
152
Nabin Hait3237c752015-02-17 11:11:11 +0530153 tax_fields = ["total", "tax_amount_after_discount_amount",
154 "tax_amount_for_current_item", "grand_total_for_current_item",
155 "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
156
Nabin Haitde9c8a92015-02-23 01:06:00 +0530157 if tax.charge_type != "Actual" and \
158 not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
159 tax_fields.append("tax_amount")
Nabin Hait3237c752015-02-17 11:11:11 +0530160
161 for fieldname in tax_fields:
162 tax.set(fieldname, 0.0)
163
Nabin Hait3237c752015-02-17 11:11:11 +0530164 self.doc.round_floats_in(tax)
165
Nabin Hait3237c752015-02-17 11:11:11 +0530166 def determine_exclusive_rate(self):
Nabin Hait37b047d2015-02-23 16:01:33 +0530167 if not any((cint(tax.included_in_print_rate) for tax in self.doc.get("taxes"))):
168 return
Nabin Hait3237c752015-02-17 11:11:11 +0530169
170 for item in self.doc.get("items"):
171 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
172 cumulated_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530173 total_inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530174 for i, tax in enumerate(self.doc.get("taxes")):
Nabin Hait19ea7212020-08-11 20:34:57 +0530175 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 +0530176
177 if i==0:
178 tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
179 else:
180 tax.grand_total_fraction_for_current_item = \
181 self.doc.get("taxes")[i-1].grand_total_fraction_for_current_item \
182 + tax.tax_fraction_for_current_item
183
184 cumulated_tax_fraction += tax.tax_fraction_for_current_item
Nabin Haite2ee4552020-08-12 20:58:03 +0530185 total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
Nabin Hait3237c752015-02-17 11:11:11 +0530186
Nabin Hait19ea7212020-08-11 20:34:57 +0530187 if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty):
188 amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
189
190 item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
Nabin Haite7679702015-02-20 14:40:35 +0530191 item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
Nabin Hait2e4de832017-09-19 14:53:16 +0530192 item.discount_percentage = flt(item.discount_percentage,
193 item.precision("discount_percentage"))
Nabin Hait3237c752015-02-17 11:11:11 +0530194
Nabin Haite7679702015-02-20 14:40:35 +0530195 self._set_in_company_currency(item, ["net_rate", "net_amount"])
196
Nabin Hait3237c752015-02-17 11:11:11 +0530197 def _load_item_tax_rate(self, item_tax_rate):
198 return json.loads(item_tax_rate) if item_tax_rate else {}
199
200 def get_current_tax_fraction(self, tax, item_tax_map):
201 """
202 Get tax fraction for calculating tax exclusive amount
203 from tax inclusive amount
204 """
205 current_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530206 inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530207
208 if cint(tax.included_in_print_rate):
209 tax_rate = self._get_tax_rate(tax, item_tax_map)
210
211 if tax.charge_type == "On Net Total":
212 current_tax_fraction = tax_rate / 100.0
213
214 elif tax.charge_type == "On Previous Row Amount":
215 current_tax_fraction = (tax_rate / 100.0) * \
216 self.doc.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
217
218 elif tax.charge_type == "On Previous Row Total":
219 current_tax_fraction = (tax_rate / 100.0) * \
220 self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
marination733fd5f2020-08-26 18:23:12 +0530221
Nabin Hait19ea7212020-08-11 20:34:57 +0530222 elif tax.charge_type == "On Item Quantity":
223 inclusive_tax_amount_per_qty = flt(tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530224
Nabin Hait19ea7212020-08-11 20:34:57 +0530225 if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
226 current_tax_fraction *= -1.0
227 inclusive_tax_amount_per_qty *= -1.0
228
229 return current_tax_fraction, inclusive_tax_amount_per_qty
Nabin Hait3237c752015-02-17 11:11:11 +0530230
231 def _get_tax_rate(self, tax, item_tax_map):
Achilles Rasquinha87dab142018-03-08 14:21:48 +0530232 if tax.account_head in item_tax_map:
Nabin Hait3237c752015-02-17 11:11:11 +0530233 return flt(item_tax_map.get(tax.account_head), self.doc.precision("rate", tax))
234 else:
235 return tax.rate
236
237 def calculate_net_total(self):
Shreya Shahe3290382018-05-28 11:49:08 +0530238 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 +0530239
Nabin Hait3237c752015-02-17 11:11:11 +0530240 for item in self.doc.get("items"):
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530241 self.doc.total += item.amount
Shreya Shahe3290382018-05-28 11:49:08 +0530242 self.doc.total_qty += item.qty
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530243 self.doc.base_total += item.base_amount
Nabin Haite7679702015-02-20 14:40:35 +0530244 self.doc.net_total += item.net_amount
245 self.doc.base_net_total += item.base_net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530246
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530247 self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530248
249 def calculate_taxes(self):
Nabin Hait2e4de832017-09-19 14:53:16 +0530250 self.doc.rounding_adjustment = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530251 # maintain actual tax rate based on idx
Nabin Haite7679702015-02-20 14:40:35 +0530252 actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
Nabin Hait3237c752015-02-17 11:11:11 +0530253 for tax in self.doc.get("taxes") if tax.charge_type == "Actual"])
254
255 for n, item in enumerate(self.doc.get("items")):
256 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530257 for i, tax in enumerate(self.doc.get("taxes")):
258 # tax_amount represents the amount of tax for the current step
259 current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map)
260
261 # Adjust divisional loss to the last item
262 if tax.charge_type == "Actual":
263 actual_tax_dict[tax.idx] -= current_tax_amount
264 if n == len(self.doc.get("items")) - 1:
265 current_tax_amount += actual_tax_dict[tax.idx]
266
Nabin Hait2b019ed2015-02-22 23:03:07 +0530267 # accumulate tax amount into tax.tax_amount
Nabin Haitde9c8a92015-02-23 01:06:00 +0530268 if tax.charge_type != "Actual" and \
269 not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
270 tax.tax_amount += current_tax_amount
Nabin Hait2b019ed2015-02-22 23:03:07 +0530271
Nabin Hait3237c752015-02-17 11:11:11 +0530272 # store tax_amount for current item as it will be used for
273 # charge type = 'On Previous Row Amount'
274 tax.tax_amount_for_current_item = current_tax_amount
275
Nabin Hait2b019ed2015-02-22 23:03:07 +0530276 # set tax after discount
Nabin Hait3237c752015-02-17 11:11:11 +0530277 tax.tax_amount_after_discount_amount += current_tax_amount
278
Nabin Haitcd951342017-07-31 18:07:45 +0530279 current_tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(current_tax_amount, tax)
Nabin Hait3237c752015-02-17 11:11:11 +0530280
Nabin Hait3237c752015-02-17 11:11:11 +0530281 # note: grand_total_for_current_item contains the contribution of
282 # item's amount, previously applied tax and the current tax on that item
283 if i==0:
Nabin Haitcd951342017-07-31 18:07:45 +0530284 tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530285 else:
286 tax.grand_total_for_current_item = \
Nabin Haitcd951342017-07-31 18:07:45 +0530287 flt(self.doc.get("taxes")[i-1].grand_total_for_current_item + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530288
289 # set precision in the last item iteration
290 if n == len(self.doc.get("items")) - 1:
291 self.round_off_totals(tax)
Nabin Haitcd951342017-07-31 18:07:45 +0530292 self.set_cumulative_total(i, tax)
293
294 self._set_in_company_currency(tax,
295 ["total", "tax_amount", "tax_amount_after_discount_amount"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530296
Nabin Hait3237c752015-02-17 11:11:11 +0530297 # adjust Discount Amount loss in last tax iteration
Nabin Haitde9c8a92015-02-23 01:06:00 +0530298 if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
Nabin Haitdb53a782015-07-31 16:53:13 +0530299 and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total":
Nabin Hait2e4de832017-09-19 14:53:16 +0530300 self.doc.rounding_adjustment = flt(self.doc.grand_total
301 - flt(self.doc.discount_amount) - tax.total,
302 self.doc.precision("rounding_adjustment"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530303
Nabin Haitcd951342017-07-31 18:07:45 +0530304 def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
305 # if just for valuation, do not add the tax amount in total
306 # if tax/charges is for deduction, multiply by -1
307 if getattr(tax, "category", None):
308 tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
Rushabh Mehta30dc9a12017-11-17 14:31:09 +0530309 if self.doc.doctype in ["Purchase Order", "Purchase Invoice", "Purchase Receipt", "Supplier Quotation"]:
310 tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
Nabin Haitcd951342017-07-31 18:07:45 +0530311 return tax_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530312
Nabin Haitcd951342017-07-31 18:07:45 +0530313 def set_cumulative_total(self, row_idx, tax):
314 tax_amount = tax.tax_amount_after_discount_amount
315 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax_amount, tax)
316
317 if row_idx == 0:
318 tax.total = flt(self.doc.net_total + tax_amount, tax.precision("total"))
319 else:
320 tax.total = flt(self.doc.get("taxes")[row_idx-1].total + tax_amount, tax.precision("total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530321
322 def get_current_tax_amount(self, item, tax, item_tax_map):
323 tax_rate = self._get_tax_rate(tax, item_tax_map)
324 current_tax_amount = 0.0
325
326 if tax.charge_type == "Actual":
327 # distribute the tax amount proportionally to each item row
Nabin Haite7679702015-02-20 14:40:35 +0530328 actual = flt(tax.tax_amount, tax.precision("tax_amount"))
329 current_tax_amount = item.net_amount*actual / self.doc.net_total if self.doc.net_total else 0.0
330
Nabin Hait3237c752015-02-17 11:11:11 +0530331 elif tax.charge_type == "On Net Total":
Nabin Haite7679702015-02-20 14:40:35 +0530332 current_tax_amount = (tax_rate / 100.0) * item.net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530333 elif tax.charge_type == "On Previous Row Amount":
334 current_tax_amount = (tax_rate / 100.0) * \
335 self.doc.get("taxes")[cint(tax.row_id) - 1].tax_amount_for_current_item
336 elif tax.charge_type == "On Previous Row Total":
337 current_tax_amount = (tax_rate / 100.0) * \
338 self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
Himanshu Mishra35b26272018-11-13 11:13:04 +0530339 elif tax.charge_type == "On Item Quantity":
Nabin Hait19ea7212020-08-11 20:34:57 +0530340 current_tax_amount = tax_rate * item.qty
Nabin Hait3237c752015-02-17 11:11:11 +0530341
Deepesh Garg6a5ef262021-02-19 14:30:23 +0530342 current_tax_amount = self.get_final_current_tax_amount(tax, current_tax_amount)
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530343
344 if not self.doc.get("is_consolidated"):
345 self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530346
347 return current_tax_amount
348
Deepesh Garg6a5ef262021-02-19 14:30:23 +0530349 def get_final_current_tax_amount(self, tax, current_tax_amount):
350 # Some countries need individual tax components to be rounded
351 # Handeled via regional doctypess
352 if tax.account_head in frappe.flags.round_off_applicable_accounts:
353 current_tax_amount = round(current_tax_amount, 0)
354 return current_tax_amount
355
Nabin Haite7679702015-02-20 14:40:35 +0530356 def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
357 # store tax breakup for each item
358 key = item.item_code or item.item_name
359 item_wise_tax_amount = current_tax_amount*self.doc.conversion_rate
360 if tax.item_wise_tax_detail.get(key):
361 item_wise_tax_amount += tax.item_wise_tax_detail[key][1]
362
Nabin Haitcaab5822017-08-24 16:22:28 +0530363 tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
Nabin Haite7679702015-02-20 14:40:35 +0530364
Nabin Hait3237c752015-02-17 11:11:11 +0530365 def round_off_totals(self, tax):
Nabin Haite7679702015-02-20 14:40:35 +0530366 tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530367 tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
Nabin Haitcd951342017-07-31 18:07:45 +0530368 tax.precision("tax_amount"))
Nabin Haitce245122015-02-22 20:14:49 +0530369
Nabin Haita1bf43b2015-03-17 10:50:47 +0530370 def manipulate_grand_total_for_inclusive_tax(self):
371 # if fully inclusive taxes and diff
Nabin Hait2e4de832017-09-19 14:53:16 +0530372 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 +0530373 last_tax = self.doc.get("taxes")[-1]
Nabin Hait2e4de832017-09-19 14:53:16 +0530374 non_inclusive_tax_amount = sum([flt(d.tax_amount_after_discount_amount)
375 for d in self.doc.get("taxes") if not d.included_in_print_rate])
Nabin Haitf32fc232019-12-25 13:59:24 +0530376
Nabin Hait2e4de832017-09-19 14:53:16 +0530377 diff = self.doc.total + non_inclusive_tax_amount \
378 - flt(last_tax.total, last_tax.precision("total"))
Nabin Haitf32fc232019-12-25 13:59:24 +0530379
380 # If discount amount applied, deduct the discount amount
381 # because self.doc.total is always without discount, but last_tax.total is after discount
382 if self.discount_amount_applied and self.doc.discount_amount:
383 diff -= flt(self.doc.discount_amount)
384
385 diff = flt(diff, self.doc.precision("rounding_adjustment"))
386
Nabin Hait2e4de832017-09-19 14:53:16 +0530387 if diff and abs(diff) <= (5.0 / 10**last_tax.precision("tax_amount")):
Nabin Haitf32fc232019-12-25 13:59:24 +0530388 self.doc.rounding_adjustment = diff
Nabin Hait3237c752015-02-17 11:11:11 +0530389
390 def calculate_totals(self):
Nabin Hait2e4de832017-09-19 14:53:16 +0530391 self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) \
392 if self.doc.get("taxes") else flt(self.doc.net_total)
Nabin Hait3237c752015-02-17 11:11:11 +0530393
Nabin Hait2e4de832017-09-19 14:53:16 +0530394 self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total
395 - flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530396
Nabin Hait2e4de832017-09-19 14:53:16 +0530397 self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530398
Saqiba6f98d42020-07-23 18:51:26 +0530399 if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]:
Anurag Mishra8a06b8f2019-07-17 14:55:16 +0530400 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 +0530401 if self.doc.total_taxes_and_charges else self.doc.base_net_total
Nabin Hait3237c752015-02-17 11:11:11 +0530402 else:
Anand Doshiec5ec602015-03-05 19:31:23 +0530403 self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0
Nabin Hait3237c752015-02-17 11:11:11 +0530404 for tax in self.doc.get("taxes"):
405 if tax.category in ["Valuation and Total", "Total"]:
406 if tax.add_deduct_tax == "Add":
Nabin Haitdb53a782015-07-31 16:53:13 +0530407 self.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530408 else:
Nabin Haitdb53a782015-07-31 16:53:13 +0530409 self.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530410
Nabin Haite7679702015-02-20 14:40:35 +0530411 self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530412
Nabin Haite7679702015-02-20 14:40:35 +0530413 self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
414 if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
415 else self.doc.base_net_total
Nabin Hait3237c752015-02-17 11:11:11 +0530416
Nabin Hait2e4de832017-09-19 14:53:16 +0530417 self._set_in_company_currency(self.doc,
418 ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530419
Nabin Haite7679702015-02-20 14:40:35 +0530420 self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530421
Nabin Hait2e4de832017-09-19 14:53:16 +0530422 self.set_rounded_total()
423
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +0530424 def calculate_total_net_weight(self):
425 if self.doc.meta.get_field('total_net_weight'):
426 self.doc.total_net_weight = 0.0
427 for d in self.doc.items:
428 if d.total_weight:
429 self.doc.total_net_weight += d.total_weight
430
Nabin Hait2e4de832017-09-19 14:53:16 +0530431 def set_rounded_total(self):
Nabin Hait3237c752015-02-17 11:11:11 +0530432 if self.doc.meta.get_field("rounded_total"):
Nabin Hait877e1bb2017-11-17 12:27:43 +0530433 if self.doc.is_rounded_total_disabled():
434 self.doc.rounded_total = self.doc.base_rounded_total = 0
435 return
436
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530437 self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
Nabin Haitfb0b24a2016-01-20 14:46:26 +0530438 self.doc.currency, self.doc.precision("rounded_total"))
Nabin Hait2e4de832017-09-19 14:53:16 +0530439
Nabin Hait877e1bb2017-11-17 12:27:43 +0530440 #if print_in_rate is set, we would have already calculated rounding adjustment
441 self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
442 self.doc.precision("rounding_adjustment"))
443
Nabin Hait02ac9012017-11-22 16:12:20 +0530444 self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530445
Nabin Hait3237c752015-02-17 11:11:11 +0530446 def _cleanup(self):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530447 if not self.doc.get('is_consolidated'):
448 for tax in self.doc.get("taxes"):
449 tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530450
Nabin Hait3769d872015-12-18 13:12:02 +0530451 def set_discount_amount(self):
Nabin Haite0405102016-10-13 12:14:32 +0530452 if self.doc.additional_discount_percentage:
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530453 self.doc.discount_amount = flt(flt(self.doc.get(scrub(self.doc.apply_discount_on)))
Nabin Hait3769d872015-12-18 13:12:02 +0530454 * self.doc.additional_discount_percentage / 100, self.doc.precision("discount_amount"))
Nabin Hait3237c752015-02-17 11:11:11 +0530455
456 def apply_discount_amount(self):
457 if self.doc.discount_amount:
Nabin Hait37b047d2015-02-23 16:01:33 +0530458 if not self.doc.apply_discount_on:
459 frappe.throw(_("Please select Apply Discount On"))
460
Nabin Hait3237c752015-02-17 11:11:11 +0530461 self.doc.base_discount_amount = flt(self.doc.discount_amount * self.doc.conversion_rate,
462 self.doc.precision("base_discount_amount"))
463
Nabin Haite7679702015-02-20 14:40:35 +0530464 total_for_discount_amount = self.get_total_for_discount_amount()
Nabin Hait25bd84d2015-03-04 15:06:56 +0530465 taxes = self.doc.get("taxes")
466 net_total = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530467
Nabin Haite7679702015-02-20 14:40:35 +0530468 if total_for_discount_amount:
Nabin Hait3237c752015-02-17 11:11:11 +0530469 # calculate item amount after Discount Amount
Nabin Hait25bd84d2015-03-04 15:06:56 +0530470 for i, item in enumerate(self.doc.get("items")):
471 distributed_amount = flt(self.doc.discount_amount) * \
472 item.net_amount / total_for_discount_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530473
Nabin Haite7679702015-02-20 14:40:35 +0530474 item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
Nabin Hait25bd84d2015-03-04 15:06:56 +0530475 net_total += item.net_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530476
Nabin Hait25bd84d2015-03-04 15:06:56 +0530477 # discount amount rounding loss adjustment if no taxes
Nabin Hait4d587342019-05-30 15:50:46 +0530478 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 +0530479 and i == len(self.doc.get("items")) - 1:
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530480 discount_amount_loss = flt(self.doc.net_total - net_total - self.doc.discount_amount,
Nabin Hait25bd84d2015-03-04 15:06:56 +0530481 self.doc.precision("net_total"))
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530482
Anand Doshiec5ec602015-03-05 19:31:23 +0530483 item.net_amount = flt(item.net_amount + discount_amount_loss,
Nabin Hait25bd84d2015-03-04 15:06:56 +0530484 item.precision("net_amount"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530485
Nabin Hait51e980d2015-10-10 18:10:05 +0530486 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 +0530487
Nabin Haite7679702015-02-20 14:40:35 +0530488 self._set_in_company_currency(item, ["net_rate", "net_amount"])
Nabin Hait3237c752015-02-17 11:11:11 +0530489
490 self.discount_amount_applied = True
491 self._calculate()
492 else:
493 self.doc.base_discount_amount = 0
494
Nabin Haite7679702015-02-20 14:40:35 +0530495 def get_total_for_discount_amount(self):
Nabin Haitde9c8a92015-02-23 01:06:00 +0530496 if self.doc.apply_discount_on == "Net Total":
497 return self.doc.net_total
Nabin Haite7679702015-02-20 14:40:35 +0530498 else:
499 actual_taxes_dict = {}
Nabin Hait3237c752015-02-17 11:11:11 +0530500
Nabin Haite7679702015-02-20 14:40:35 +0530501 for tax in self.doc.get("taxes"):
Nabin Hait19ea7212020-08-11 20:34:57 +0530502 if tax.charge_type in ["Actual", "On Item Quantity"]:
Nabin Haitaf9bdfe2017-12-12 18:50:05 +0530503 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
504 actual_taxes_dict.setdefault(tax.idx, tax_amount)
Nabin Haite7679702015-02-20 14:40:35 +0530505 elif tax.row_id in actual_taxes_dict:
506 actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * flt(tax.rate) / 100
507 actual_taxes_dict.setdefault(tax.idx, actual_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530508
Nabin Hait877e1bb2017-11-17 12:27:43 +0530509 return flt(self.doc.grand_total - sum(actual_taxes_dict.values()),
510 self.doc.precision("grand_total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530511
512
Nabin Hait7b19b9e2015-02-24 09:42:24 +0530513 def calculate_total_advance(self):
514 if self.doc.docstatus < 2:
Nabin Haite7679702015-02-20 14:40:35 +0530515 total_allocated_amount = sum([flt(adv.allocated_amount, adv.precision("allocated_amount"))
Nabin Hait3237c752015-02-17 11:11:11 +0530516 for adv in self.doc.get("advances")])
517
Nabin Haite7679702015-02-20 14:40:35 +0530518 self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530519
Faris Ansari6041f5c2018-02-08 13:33:52 +0530520 grand_total = self.doc.rounded_total or self.doc.grand_total
521
Nabin Hait289ffb72016-02-08 11:06:55 +0530522 if self.doc.party_account_currency == self.doc.currency:
Faris Ansari6041f5c2018-02-08 13:33:52 +0530523 invoice_total = flt(grand_total - flt(self.doc.write_off_amount),
Nabin Hait289ffb72016-02-08 11:06:55 +0530524 self.doc.precision("grand_total"))
Nabin Hait8d8cba72017-04-03 17:26:22 +0530525 else:
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530526 base_write_off_amount = flt(flt(self.doc.write_off_amount) * self.doc.conversion_rate,
Nabin Hait8d8cba72017-04-03 17:26:22 +0530527 self.doc.precision("base_write_off_amount"))
Faris Ansari6041f5c2018-02-08 13:33:52 +0530528 invoice_total = flt(grand_total * self.doc.conversion_rate,
Nabin Hait8d8cba72017-04-03 17:26:22 +0530529 self.doc.precision("grand_total")) - base_write_off_amount
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530530
Nabin Haitadc09232016-02-09 10:31:11 +0530531 if invoice_total > 0 and self.doc.total_advance > invoice_total:
Nabin Hait289ffb72016-02-08 11:06:55 +0530532 frappe.throw(_("Advance amount cannot be greater than {0} {1}")
533 .format(self.doc.party_account_currency, invoice_total))
Nabin Hait3237c752015-02-17 11:11:11 +0530534
Rushabh Mehta8bb6e532015-02-18 20:22:59 +0530535 if self.doc.docstatus == 0:
Nabin Hait3237c752015-02-17 11:11:11 +0530536 self.calculate_outstanding_amount()
537
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530538 def is_internal_invoice(self):
539 """
540 Checks if its an internal transfer invoice
541 and decides if to calculate any out standing amount or not
542 """
543
544 if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer():
545 return True
546
547 return False
548
Nabin Hait3237c752015-02-17 11:11:11 +0530549 def calculate_outstanding_amount(self):
550 # NOTE:
551 # write_off_amount is only for POS Invoice
552 # total_advance is only for non POS Invoice
Rohit Waghchaureefb5bf22016-08-25 02:09:53 +0530553 if self.doc.doctype == "Sales Invoice":
554 self.calculate_paid_amount()
555
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530556 if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \
557 self.is_internal_invoice(): return
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530558
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530559 self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530560 self._set_in_company_currency(self.doc, ['write_off_amount'])
561
Nabin Hait877e1bb2017-11-17 12:27:43 +0530562 if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
563 grand_total = self.doc.rounded_total or self.doc.grand_total
564 if self.doc.party_account_currency == self.doc.currency:
Manas Solankida486ee2018-07-06 12:36:57 +0530565 total_amount_to_pay = flt(grand_total - self.doc.total_advance
Nabin Hait877e1bb2017-11-17 12:27:43 +0530566 - flt(self.doc.write_off_amount), self.doc.precision("grand_total"))
567 else:
568 total_amount_to_pay = flt(flt(grand_total *
569 self.doc.conversion_rate, self.doc.precision("grand_total")) - self.doc.total_advance
570 - flt(self.doc.base_write_off_amount), self.doc.precision("grand_total"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530571
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530572 self.doc.round_floats_in(self.doc, ["paid_amount"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530573 change_amount = 0
574
Deepesh Garg0ebace52020-02-25 13:21:16 +0530575 if self.doc.doctype == "Sales Invoice" and not self.doc.get('is_return'):
Nabin Hait877e1bb2017-11-17 12:27:43 +0530576 self.calculate_write_off_amount()
577 self.calculate_change_amount()
578 change_amount = self.doc.change_amount \
579 if self.doc.party_account_currency == self.doc.currency else self.doc.base_change_amount
580
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530581 paid_amount = self.doc.paid_amount \
582 if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530583
Nabin Hait877e1bb2017-11-17 12:27:43 +0530584 self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount),
585 self.doc.precision("outstanding_amount"))
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530586
Deepesh Garg0ebace52020-02-25 13:21:16 +0530587 if self.doc.doctype == 'Sales Invoice' and self.doc.get('is_pos') and self.doc.get('is_return'):
588 self.update_paid_amount_for_return(total_amount_to_pay)
589
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530590 def calculate_paid_amount(self):
Manas Solankida486ee2018-07-06 12:36:57 +0530591
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530592 paid_amount = base_paid_amount = 0.0
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530593
594 if self.doc.is_pos:
595 for payment in self.doc.get('payments'):
Ayush Shuklae9cf1ab2017-05-25 14:14:55 +0530596 payment.amount = flt(payment.amount)
597 payment.base_amount = payment.amount * flt(self.doc.conversion_rate)
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530598 paid_amount += payment.amount
599 base_paid_amount += payment.base_amount
rohitwaghchaure73456ac2017-05-16 11:29:57 +0530600 elif not self.doc.is_return:
601 self.doc.set('payments', [])
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530602
Manas Solankida486ee2018-07-06 12:36:57 +0530603 if self.doc.redeem_loyalty_points and self.doc.loyalty_amount:
604 base_paid_amount += self.doc.loyalty_amount
605 paid_amount += (self.doc.loyalty_amount / flt(self.doc.conversion_rate))
606
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530607 self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount"))
608 self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount"))
609
Nabin Hait3bb1a422016-08-02 16:41:10 +0530610 def calculate_change_amount(self):
611 self.doc.change_amount = 0.0
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530612 self.doc.base_change_amount = 0.0
Nabin Hait877e1bb2017-11-17 12:27:43 +0530613
614 if self.doc.doctype == "Sales Invoice" \
615 and self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530616 and any([d.type == "Cash" for d in self.doc.payments]):
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530617 grand_total = self.doc.rounded_total or self.doc.grand_total
618 base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530619
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530620 self.doc.change_amount = flt(self.doc.paid_amount - grand_total +
Nabin Hait3bb1a422016-08-02 16:41:10 +0530621 self.doc.write_off_amount, self.doc.precision("change_amount"))
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530622
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530623 self.doc.base_change_amount = flt(self.doc.base_paid_amount - base_grand_total +
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530624 self.doc.base_write_off_amount, self.doc.precision("base_change_amount"))
mbauskar36b51892016-01-18 16:31:10 +0530625
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530626 def calculate_write_off_amount(self):
Rohit Waghchaurebaef2622016-08-05 15:41:36 +0530627 if flt(self.doc.change_amount) > 0:
Nabin Hait877e1bb2017-11-17 12:27:43 +0530628 self.doc.write_off_amount = flt(self.doc.grand_total - self.doc.paid_amount
629 + self.doc.change_amount, self.doc.precision("write_off_amount"))
Rohit Waghchaurebaef2622016-08-05 15:41:36 +0530630 self.doc.base_write_off_amount = flt(self.doc.write_off_amount * self.doc.conversion_rate,
631 self.doc.precision("base_write_off_amount"))
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530632
mbauskar36b51892016-01-18 16:31:10 +0530633 def calculate_margin(self, item):
Makarand Bauskar0e4c5c92017-05-11 11:40:02 +0530634 rate_with_margin = 0.0
Shreya Shahbe690ef2017-11-14 17:22:41 +0530635 base_rate_with_margin = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530636 if item.price_list_rate:
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530637 if item.pricing_rules and not self.doc.ignore_pricing_rule:
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530638 has_margin = False
marination733fd5f2020-08-26 18:23:12 +0530639 for d in get_applied_pricing_rules(item.pricing_rules):
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530640 pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
Shreya Shahf718b0c2018-02-20 11:26:46 +0530641
Rohit Waghchaured4d639c2021-02-01 22:58:22 +0530642 if pricing_rule.margin_rate_or_amount and ((pricing_rule.currency == self.doc.currency and
643 pricing_rule.margin_type in ['Amount', 'Percentage']) or pricing_rule.margin_type == 'Percentage'):
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530644 item.margin_type = pricing_rule.margin_type
645 item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530646 has_margin = True
647
648 if not has_margin:
649 item.margin_type = None
650 item.margin_rate_or_amount = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530651
mbauskara52472c2016-03-05 15:10:25 +0530652 if item.margin_type and item.margin_rate_or_amount:
653 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 +0530654 rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
Shreya Shahbe690ef2017-11-14 17:22:41 +0530655 base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
mbauskar36b51892016-01-18 16:31:10 +0530656
Shreya Shahbe690ef2017-11-14 17:22:41 +0530657 return rate_with_margin, base_rate_with_margin
Nabin Hait852cb642017-07-05 12:58:19 +0530658
659 def set_item_wise_tax_breakup(self):
Nabin Hait9c421612017-07-20 13:32:01 +0530660 self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530661
Deepesh Garg0ebace52020-02-25 13:21:16 +0530662 def update_paid_amount_for_return(self, total_amount_to_pay):
Saqiba6f98d42020-07-23 18:51:26 +0530663 default_mode_of_payment = frappe.db.get_value('POS Payment Method',
664 {'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
Deepesh Garg0ebace52020-02-25 13:21:16 +0530665
666 self.doc.payments = []
667
668 if default_mode_of_payment:
669 self.doc.append('payments', {
670 'mode_of_payment': default_mode_of_payment.mode_of_payment,
Rucha Mahabaledee5302020-12-04 12:13:26 +0530671 'amount': total_amount_to_pay,
672 'default': 1
Deepesh Garg0ebace52020-02-25 13:21:16 +0530673 })
674 else:
675 self.doc.is_pos = 0
676 self.doc.pos_profile = ''
677
678 self.calculate_paid_amount()
679
680
Nabin Hait9c421612017-07-20 13:32:01 +0530681def get_itemised_tax_breakup_html(doc):
682 if not doc.taxes:
683 return
684 frappe.flags.company = doc.company
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530685
Nabin Hait9c421612017-07-20 13:32:01 +0530686 # get headers
Nabin Haitcaab5822017-08-24 16:22:28 +0530687 tax_accounts = []
688 for tax in doc.taxes:
689 if getattr(tax, "category", None) and tax.category=="Valuation":
690 continue
rohitwaghchaure4e17fae2017-12-12 14:40:52 +0530691 if tax.description not in tax_accounts:
Nabin Haitcaab5822017-08-24 16:22:28 +0530692 tax_accounts.append(tax.description)
693
Nabin Hait9c421612017-07-20 13:32:01 +0530694 headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530695
Nabin Hait9c421612017-07-20 13:32:01 +0530696 # get tax breakup data
697 itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc)
Nabin Haitcaab5822017-08-24 16:22:28 +0530698
699 get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes"))
700
rohitwaghchaured4526682017-12-28 14:20:13 +0530701 update_itemised_tax_data(doc)
Nabin Hait9c421612017-07-20 13:32:01 +0530702 frappe.flags.company = None
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530703
Nabin Hait9c421612017-07-20 13:32:01 +0530704 return frappe.render_template(
705 "templates/includes/itemised_tax_breakup.html", dict(
706 headers=headers,
707 itemised_tax=itemised_tax,
708 itemised_taxable_amount=itemised_taxable_amount,
709 tax_accounts=tax_accounts,
Saqib Ansari10a6a2d2020-04-13 16:40:13 +0530710 doc=doc
Nabin Haitb962fc12017-07-17 18:02:31 +0530711 )
Nabin Hait9c421612017-07-20 13:32:01 +0530712 )
Nabin Hait852cb642017-07-05 12:58:19 +0530713
Deepesh Garg6a5ef262021-02-19 14:30:23 +0530714@frappe.whitelist()
715def get_round_off_applicable_accounts(company, account_list):
716 account_list = get_regional_round_off_accounts(company, account_list)
717
718 return account_list
719
720@erpnext.allow_regional
721def get_regional_round_off_accounts(company, account_list):
722 pass
rohitwaghchaured4526682017-12-28 14:20:13 +0530723
724@erpnext.allow_regional
725def update_itemised_tax_data(doc):
726 #Don't delete this method, used for localization
727 pass
728
Nabin Haitb962fc12017-07-17 18:02:31 +0530729@erpnext.allow_regional
730def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
731 return [_("Item"), _("Taxable Amount")] + tax_accounts
732
733@erpnext.allow_regional
734def get_itemised_tax_breakup_data(doc):
735 itemised_tax = get_itemised_tax(doc.taxes)
736
737 itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
738
739 return itemised_tax, itemised_taxable_amount
740
Nabin Hait34c551d2019-07-03 10:34:31 +0530741def get_itemised_tax(taxes, with_tax_account=False):
Nabin Haitb962fc12017-07-17 18:02:31 +0530742 itemised_tax = {}
743 for tax in taxes:
Nabin Haitcaab5822017-08-24 16:22:28 +0530744 if getattr(tax, "category", None) and tax.category=="Valuation":
745 continue
746
Nabin Haitb962fc12017-07-17 18:02:31 +0530747 item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
Nabin Hait2e4de832017-09-19 14:53:16 +0530748 if item_tax_map:
749 for item_code, tax_data in item_tax_map.items():
750 itemised_tax.setdefault(item_code, frappe._dict())
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530751
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530752 tax_rate = 0.0
753 tax_amount = 0.0
754
Nabin Hait2e4de832017-09-19 14:53:16 +0530755 if isinstance(tax_data, list):
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530756 tax_rate = flt(tax_data[0])
757 tax_amount = flt(tax_data[1])
Nabin Hait2e4de832017-09-19 14:53:16 +0530758 else:
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530759 tax_rate = flt(tax_data)
760
761 itemised_tax[item_code][tax.description] = frappe._dict(dict(
762 tax_rate = tax_rate,
763 tax_amount = tax_amount
764 ))
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530765
Nabin Hait34c551d2019-07-03 10:34:31 +0530766 if with_tax_account:
767 itemised_tax[item_code][tax.description].tax_account = tax.account_head
768
Nabin Haitb962fc12017-07-17 18:02:31 +0530769 return itemised_tax
770
771def get_itemised_taxable_amount(items):
772 itemised_taxable_amount = frappe._dict()
773 for item in items:
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530774 item_code = item.item_code or item.item_name
Nabin Haitb962fc12017-07-17 18:02:31 +0530775 itemised_taxable_amount.setdefault(item_code, 0)
776 itemised_taxable_amount[item_code] += item.net_amount
777
Nabin Haitcaab5822017-08-24 16:22:28 +0530778 return itemised_taxable_amount
779
780def get_rounded_tax_amount(itemised_tax, precision):
781 # Rounding based on tax_amount precision
782 for taxes in itemised_tax.values():
783 for tax_account in taxes:
Himanshu Mishra35b26272018-11-13 11:13:04 +0530784 taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530785
786class init_landed_taxes_and_totals(object):
787 def __init__(self, doc):
788 self.doc = doc
789 self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
790 self.set_account_currency()
791 self.set_exchange_rate()
792 self.set_amounts_in_company_currency()
793
794 def set_account_currency(self):
795 company_currency = erpnext.get_company_currency(self.doc.company)
796 for d in self.doc.get(self.tax_field):
797 if not d.account_currency:
798 account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
799 d.account_currency = account_currency or company_currency
800
801 def set_exchange_rate(self):
802 company_currency = erpnext.get_company_currency(self.doc.company)
803 for d in self.doc.get(self.tax_field):
804 if d.account_currency == company_currency:
805 d.exchange_rate = 1
Deepesh Garg22f5ff82021-03-17 10:56:52 +0530806 elif not d.exchange_rate:
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530807 d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
808 account_currency=d.account_currency, company=self.doc.company)
809
810 if not d.exchange_rate:
811 frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
812
813 def set_amounts_in_company_currency(self):
814 for d in self.doc.get(self.tax_field):
815 d.amount = flt(d.amount, d.precision("amount"))
Walstan Baptista37b826b2021-04-03 19:48:46 +0530816 d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))