blob: 9fae49482ddeb097a7e4f0d9efe45b0ee44c40f9 [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"))
Nabin Hait10c61372021-04-13 15:46:01 +0530116
Walstan Baptista37b826b2021-04-03 19:48:46 +0530117 if item.discount_amount and not item.discount_percentage:
Nabin Hait10c61372021-04-13 15:46:01 +0530118 item.rate = item.rate_with_margin - item.discount_amount
Walstan Baptista37b826b2021-04-03 19:48:46 +0530119 else:
120 item.discount_amount = item.rate_with_margin - item.rate
Nabin Hait10c61372021-04-13 15:46:01 +0530121
Nabin Hait64bfdd92019-04-23 13:37:19 +0530122 elif flt(item.price_list_rate) > 0:
123 item.discount_amount = item.price_list_rate - item.rate
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530124 elif flt(item.price_list_rate) > 0 and not item.discount_amount:
125 item.discount_amount = item.price_list_rate - item.rate
mbauskara52472c2016-03-05 15:10:25 +0530126
Nabin Haite7679702015-02-20 14:40:35 +0530127 item.net_rate = item.rate
Deepesh Gargb65c7612019-07-31 15:58:01 +0530128
deepeshgarg0078bf19ce2019-08-03 13:40:37 +0530129 if not item.qty and self.doc.get("is_return"):
Deepesh Gargb65c7612019-07-31 15:58:01 +0530130 item.amount = flt(-1 * item.rate, item.precision("amount"))
131 else:
132 item.amount = flt(item.rate * item.qty, item.precision("amount"))
133
Nabin Haite7679702015-02-20 14:40:35 +0530134 item.net_amount = item.amount
Nabin Hait3237c752015-02-17 11:11:11 +0530135
Nabin Haite7679702015-02-20 14:40:35 +0530136 self._set_in_company_currency(item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"])
Nabin Hait3237c752015-02-17 11:11:11 +0530137
Nabin Haite7679702015-02-20 14:40:35 +0530138 item.item_tax_amount = 0.0
139
140 def _set_in_company_currency(self, doc, fields):
Nabin Hait3237c752015-02-17 11:11:11 +0530141 """set values in base currency"""
Nabin Haite7679702015-02-20 14:40:35 +0530142 for f in fields:
143 val = flt(flt(doc.get(f), doc.precision(f)) * self.doc.conversion_rate, doc.precision("base_" + f))
144 doc.set("base_" + f, val)
Nabin Hait3237c752015-02-17 11:11:11 +0530145
146 def initialize_taxes(self):
147 for tax in self.doc.get("taxes"):
Nabin Hait86cd4cc2015-02-28 19:11:51 +0530148 if not self.discount_amount_applied:
149 validate_taxes_and_charges(tax)
150 validate_inclusive_tax(tax, self.doc)
Nabin Hait613d0812015-02-23 11:58:15 +0530151
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530152 if not self.doc.get('is_consolidated'):
153 tax.item_wise_tax_detail = {}
154
Nabin Hait3237c752015-02-17 11:11:11 +0530155 tax_fields = ["total", "tax_amount_after_discount_amount",
156 "tax_amount_for_current_item", "grand_total_for_current_item",
157 "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
158
Nabin Haitde9c8a92015-02-23 01:06:00 +0530159 if tax.charge_type != "Actual" and \
160 not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
161 tax_fields.append("tax_amount")
Nabin Hait3237c752015-02-17 11:11:11 +0530162
163 for fieldname in tax_fields:
164 tax.set(fieldname, 0.0)
165
Nabin Hait3237c752015-02-17 11:11:11 +0530166 self.doc.round_floats_in(tax)
167
Nabin Hait3237c752015-02-17 11:11:11 +0530168 def determine_exclusive_rate(self):
Nabin Hait37b047d2015-02-23 16:01:33 +0530169 if not any((cint(tax.included_in_print_rate) for tax in self.doc.get("taxes"))):
170 return
Nabin Hait3237c752015-02-17 11:11:11 +0530171
172 for item in self.doc.get("items"):
173 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
174 cumulated_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530175 total_inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530176 for i, tax in enumerate(self.doc.get("taxes")):
Nabin Hait19ea7212020-08-11 20:34:57 +0530177 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 +0530178
179 if i==0:
180 tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
181 else:
182 tax.grand_total_fraction_for_current_item = \
183 self.doc.get("taxes")[i-1].grand_total_fraction_for_current_item \
184 + tax.tax_fraction_for_current_item
185
186 cumulated_tax_fraction += tax.tax_fraction_for_current_item
Nabin Haite2ee4552020-08-12 20:58:03 +0530187 total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
Nabin Hait3237c752015-02-17 11:11:11 +0530188
Nabin Hait19ea7212020-08-11 20:34:57 +0530189 if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty):
190 amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
191
192 item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
Nabin Haite7679702015-02-20 14:40:35 +0530193 item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
Nabin Hait2e4de832017-09-19 14:53:16 +0530194 item.discount_percentage = flt(item.discount_percentage,
195 item.precision("discount_percentage"))
Nabin Hait3237c752015-02-17 11:11:11 +0530196
Nabin Haite7679702015-02-20 14:40:35 +0530197 self._set_in_company_currency(item, ["net_rate", "net_amount"])
198
Nabin Hait3237c752015-02-17 11:11:11 +0530199 def _load_item_tax_rate(self, item_tax_rate):
200 return json.loads(item_tax_rate) if item_tax_rate else {}
201
202 def get_current_tax_fraction(self, tax, item_tax_map):
203 """
204 Get tax fraction for calculating tax exclusive amount
205 from tax inclusive amount
206 """
207 current_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530208 inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530209
210 if cint(tax.included_in_print_rate):
211 tax_rate = self._get_tax_rate(tax, item_tax_map)
212
213 if tax.charge_type == "On Net Total":
214 current_tax_fraction = tax_rate / 100.0
215
216 elif tax.charge_type == "On Previous Row Amount":
217 current_tax_fraction = (tax_rate / 100.0) * \
218 self.doc.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
219
220 elif tax.charge_type == "On Previous Row Total":
221 current_tax_fraction = (tax_rate / 100.0) * \
222 self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
marination733fd5f2020-08-26 18:23:12 +0530223
Nabin Hait19ea7212020-08-11 20:34:57 +0530224 elif tax.charge_type == "On Item Quantity":
225 inclusive_tax_amount_per_qty = flt(tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530226
Nabin Hait19ea7212020-08-11 20:34:57 +0530227 if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
228 current_tax_fraction *= -1.0
229 inclusive_tax_amount_per_qty *= -1.0
230
231 return current_tax_fraction, inclusive_tax_amount_per_qty
Nabin Hait3237c752015-02-17 11:11:11 +0530232
233 def _get_tax_rate(self, tax, item_tax_map):
Achilles Rasquinha87dab142018-03-08 14:21:48 +0530234 if tax.account_head in item_tax_map:
Nabin Hait3237c752015-02-17 11:11:11 +0530235 return flt(item_tax_map.get(tax.account_head), self.doc.precision("rate", tax))
236 else:
237 return tax.rate
238
239 def calculate_net_total(self):
Shreya Shahe3290382018-05-28 11:49:08 +0530240 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 +0530241
Nabin Hait3237c752015-02-17 11:11:11 +0530242 for item in self.doc.get("items"):
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530243 self.doc.total += item.amount
Shreya Shahe3290382018-05-28 11:49:08 +0530244 self.doc.total_qty += item.qty
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530245 self.doc.base_total += item.base_amount
Nabin Haite7679702015-02-20 14:40:35 +0530246 self.doc.net_total += item.net_amount
247 self.doc.base_net_total += item.base_net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530248
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530249 self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530250
251 def calculate_taxes(self):
Nabin Hait2e4de832017-09-19 14:53:16 +0530252 self.doc.rounding_adjustment = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530253 # maintain actual tax rate based on idx
Nabin Haite7679702015-02-20 14:40:35 +0530254 actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
Nabin Hait3237c752015-02-17 11:11:11 +0530255 for tax in self.doc.get("taxes") if tax.charge_type == "Actual"])
256
257 for n, item in enumerate(self.doc.get("items")):
258 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530259 for i, tax in enumerate(self.doc.get("taxes")):
260 # tax_amount represents the amount of tax for the current step
261 current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map)
262
263 # Adjust divisional loss to the last item
264 if tax.charge_type == "Actual":
265 actual_tax_dict[tax.idx] -= current_tax_amount
266 if n == len(self.doc.get("items")) - 1:
267 current_tax_amount += actual_tax_dict[tax.idx]
268
Nabin Hait2b019ed2015-02-22 23:03:07 +0530269 # accumulate tax amount into tax.tax_amount
Nabin Haitde9c8a92015-02-23 01:06:00 +0530270 if tax.charge_type != "Actual" and \
271 not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
272 tax.tax_amount += current_tax_amount
Nabin Hait2b019ed2015-02-22 23:03:07 +0530273
Nabin Hait3237c752015-02-17 11:11:11 +0530274 # store tax_amount for current item as it will be used for
275 # charge type = 'On Previous Row Amount'
276 tax.tax_amount_for_current_item = current_tax_amount
277
Nabin Hait2b019ed2015-02-22 23:03:07 +0530278 # set tax after discount
Nabin Hait3237c752015-02-17 11:11:11 +0530279 tax.tax_amount_after_discount_amount += current_tax_amount
280
Nabin Haitcd951342017-07-31 18:07:45 +0530281 current_tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(current_tax_amount, tax)
Nabin Hait3237c752015-02-17 11:11:11 +0530282
Nabin Hait3237c752015-02-17 11:11:11 +0530283 # note: grand_total_for_current_item contains the contribution of
284 # item's amount, previously applied tax and the current tax on that item
285 if i==0:
Nabin Haitcd951342017-07-31 18:07:45 +0530286 tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530287 else:
288 tax.grand_total_for_current_item = \
Nabin Haitcd951342017-07-31 18:07:45 +0530289 flt(self.doc.get("taxes")[i-1].grand_total_for_current_item + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530290
291 # set precision in the last item iteration
292 if n == len(self.doc.get("items")) - 1:
293 self.round_off_totals(tax)
Deepesh Gargb6705882021-04-14 11:20:27 +0530294 self._set_in_company_currency(tax,
295 ["tax_amount", "tax_amount_after_discount_amount"])
296
297 self.round_off_base_values(tax)
Nabin Haitcd951342017-07-31 18:07:45 +0530298 self.set_cumulative_total(i, tax)
299
Deepesh Gargb6705882021-04-14 11:20:27 +0530300 self._set_in_company_currency(tax, ["total"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530301
Nabin Hait3237c752015-02-17 11:11:11 +0530302 # adjust Discount Amount loss in last tax iteration
Nabin Haitde9c8a92015-02-23 01:06:00 +0530303 if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
Nabin Haitdb53a782015-07-31 16:53:13 +0530304 and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total":
Nabin Hait2e4de832017-09-19 14:53:16 +0530305 self.doc.rounding_adjustment = flt(self.doc.grand_total
306 - flt(self.doc.discount_amount) - tax.total,
307 self.doc.precision("rounding_adjustment"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530308
Nabin Haitcd951342017-07-31 18:07:45 +0530309 def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
310 # if just for valuation, do not add the tax amount in total
311 # if tax/charges is for deduction, multiply by -1
312 if getattr(tax, "category", None):
313 tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
Rushabh Mehta30dc9a12017-11-17 14:31:09 +0530314 if self.doc.doctype in ["Purchase Order", "Purchase Invoice", "Purchase Receipt", "Supplier Quotation"]:
315 tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
Nabin Haitcd951342017-07-31 18:07:45 +0530316 return tax_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530317
Nabin Haitcd951342017-07-31 18:07:45 +0530318 def set_cumulative_total(self, row_idx, tax):
319 tax_amount = tax.tax_amount_after_discount_amount
320 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax_amount, tax)
321
322 if row_idx == 0:
323 tax.total = flt(self.doc.net_total + tax_amount, tax.precision("total"))
324 else:
325 tax.total = flt(self.doc.get("taxes")[row_idx-1].total + tax_amount, tax.precision("total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530326
327 def get_current_tax_amount(self, item, tax, item_tax_map):
328 tax_rate = self._get_tax_rate(tax, item_tax_map)
329 current_tax_amount = 0.0
330
331 if tax.charge_type == "Actual":
332 # distribute the tax amount proportionally to each item row
Nabin Haite7679702015-02-20 14:40:35 +0530333 actual = flt(tax.tax_amount, tax.precision("tax_amount"))
334 current_tax_amount = item.net_amount*actual / self.doc.net_total if self.doc.net_total else 0.0
335
Nabin Hait3237c752015-02-17 11:11:11 +0530336 elif tax.charge_type == "On Net Total":
Nabin Haite7679702015-02-20 14:40:35 +0530337 current_tax_amount = (tax_rate / 100.0) * item.net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530338 elif tax.charge_type == "On Previous Row Amount":
339 current_tax_amount = (tax_rate / 100.0) * \
340 self.doc.get("taxes")[cint(tax.row_id) - 1].tax_amount_for_current_item
341 elif tax.charge_type == "On Previous Row Total":
342 current_tax_amount = (tax_rate / 100.0) * \
343 self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
Himanshu Mishra35b26272018-11-13 11:13:04 +0530344 elif tax.charge_type == "On Item Quantity":
Nabin Hait19ea7212020-08-11 20:34:57 +0530345 current_tax_amount = tax_rate * item.qty
Nabin Hait3237c752015-02-17 11:11:11 +0530346
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530347 if not self.doc.get("is_consolidated"):
348 self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530349
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):
Deepesh Gargb6705882021-04-14 11:20:27 +0530362 if tax.account_head in frappe.flags.round_off_applicable_accounts:
363 tax.tax_amount = round(tax.tax_amount, 0)
364 tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
365
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
Deepesh Gargb6705882021-04-14 11:20:27 +0530370 def round_off_base_values(self, tax):
371 # Round off to nearest integer based on regional settings
372 if tax.account_head in frappe.flags.round_off_applicable_accounts:
373 tax.base_tax_amount = round(tax.base_tax_amount, 0)
374 tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
375
Nabin Haita1bf43b2015-03-17 10:50:47 +0530376 def manipulate_grand_total_for_inclusive_tax(self):
377 # if fully inclusive taxes and diff
Nabin Hait2e4de832017-09-19 14:53:16 +0530378 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 +0530379 last_tax = self.doc.get("taxes")[-1]
Nabin Hait2e4de832017-09-19 14:53:16 +0530380 non_inclusive_tax_amount = sum([flt(d.tax_amount_after_discount_amount)
381 for d in self.doc.get("taxes") if not d.included_in_print_rate])
Nabin Haitf32fc232019-12-25 13:59:24 +0530382
Nabin Hait2e4de832017-09-19 14:53:16 +0530383 diff = self.doc.total + non_inclusive_tax_amount \
384 - flt(last_tax.total, last_tax.precision("total"))
Nabin Haitf32fc232019-12-25 13:59:24 +0530385
386 # If discount amount applied, deduct the discount amount
387 # because self.doc.total is always without discount, but last_tax.total is after discount
388 if self.discount_amount_applied and self.doc.discount_amount:
389 diff -= flt(self.doc.discount_amount)
390
391 diff = flt(diff, self.doc.precision("rounding_adjustment"))
392
Nabin Hait2e4de832017-09-19 14:53:16 +0530393 if diff and abs(diff) <= (5.0 / 10**last_tax.precision("tax_amount")):
Nabin Haitf32fc232019-12-25 13:59:24 +0530394 self.doc.rounding_adjustment = diff
Nabin Hait3237c752015-02-17 11:11:11 +0530395
396 def calculate_totals(self):
Nabin Hait2e4de832017-09-19 14:53:16 +0530397 self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) \
398 if self.doc.get("taxes") else flt(self.doc.net_total)
Nabin Hait3237c752015-02-17 11:11:11 +0530399
Nabin Hait2e4de832017-09-19 14:53:16 +0530400 self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total
401 - flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530402
Nabin Hait2e4de832017-09-19 14:53:16 +0530403 self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530404
Saqiba6f98d42020-07-23 18:51:26 +0530405 if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]:
Anurag Mishra8a06b8f2019-07-17 14:55:16 +0530406 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 +0530407 if self.doc.total_taxes_and_charges else self.doc.base_net_total
Nabin Hait3237c752015-02-17 11:11:11 +0530408 else:
Anand Doshiec5ec602015-03-05 19:31:23 +0530409 self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0
Nabin Hait3237c752015-02-17 11:11:11 +0530410 for tax in self.doc.get("taxes"):
411 if tax.category in ["Valuation and Total", "Total"]:
412 if tax.add_deduct_tax == "Add":
Nabin Haitdb53a782015-07-31 16:53:13 +0530413 self.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530414 else:
Nabin Haitdb53a782015-07-31 16:53:13 +0530415 self.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530416
Nabin Haite7679702015-02-20 14:40:35 +0530417 self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530418
Nabin Haite7679702015-02-20 14:40:35 +0530419 self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
420 if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
421 else self.doc.base_net_total
Nabin Hait3237c752015-02-17 11:11:11 +0530422
Nabin Hait2e4de832017-09-19 14:53:16 +0530423 self._set_in_company_currency(self.doc,
424 ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530425
Nabin Haite7679702015-02-20 14:40:35 +0530426 self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530427
Nabin Hait2e4de832017-09-19 14:53:16 +0530428 self.set_rounded_total()
429
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +0530430 def calculate_total_net_weight(self):
431 if self.doc.meta.get_field('total_net_weight'):
432 self.doc.total_net_weight = 0.0
433 for d in self.doc.items:
434 if d.total_weight:
435 self.doc.total_net_weight += d.total_weight
436
Nabin Hait2e4de832017-09-19 14:53:16 +0530437 def set_rounded_total(self):
Nabin Hait3237c752015-02-17 11:11:11 +0530438 if self.doc.meta.get_field("rounded_total"):
Nabin Hait877e1bb2017-11-17 12:27:43 +0530439 if self.doc.is_rounded_total_disabled():
440 self.doc.rounded_total = self.doc.base_rounded_total = 0
441 return
442
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530443 self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
Nabin Haitfb0b24a2016-01-20 14:46:26 +0530444 self.doc.currency, self.doc.precision("rounded_total"))
Nabin Hait2e4de832017-09-19 14:53:16 +0530445
Nabin Hait877e1bb2017-11-17 12:27:43 +0530446 #if print_in_rate is set, we would have already calculated rounding adjustment
447 self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
448 self.doc.precision("rounding_adjustment"))
449
Nabin Hait02ac9012017-11-22 16:12:20 +0530450 self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530451
Nabin Hait3237c752015-02-17 11:11:11 +0530452 def _cleanup(self):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530453 if not self.doc.get('is_consolidated'):
454 for tax in self.doc.get("taxes"):
455 tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530456
Nabin Hait3769d872015-12-18 13:12:02 +0530457 def set_discount_amount(self):
Nabin Haite0405102016-10-13 12:14:32 +0530458 if self.doc.additional_discount_percentage:
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530459 self.doc.discount_amount = flt(flt(self.doc.get(scrub(self.doc.apply_discount_on)))
Nabin Hait3769d872015-12-18 13:12:02 +0530460 * self.doc.additional_discount_percentage / 100, self.doc.precision("discount_amount"))
Nabin Hait3237c752015-02-17 11:11:11 +0530461
462 def apply_discount_amount(self):
463 if self.doc.discount_amount:
Nabin Hait37b047d2015-02-23 16:01:33 +0530464 if not self.doc.apply_discount_on:
465 frappe.throw(_("Please select Apply Discount On"))
466
Nabin Hait3237c752015-02-17 11:11:11 +0530467 self.doc.base_discount_amount = flt(self.doc.discount_amount * self.doc.conversion_rate,
468 self.doc.precision("base_discount_amount"))
469
Nabin Haite7679702015-02-20 14:40:35 +0530470 total_for_discount_amount = self.get_total_for_discount_amount()
Nabin Hait25bd84d2015-03-04 15:06:56 +0530471 taxes = self.doc.get("taxes")
472 net_total = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530473
Nabin Haite7679702015-02-20 14:40:35 +0530474 if total_for_discount_amount:
Nabin Hait3237c752015-02-17 11:11:11 +0530475 # calculate item amount after Discount Amount
Nabin Hait25bd84d2015-03-04 15:06:56 +0530476 for i, item in enumerate(self.doc.get("items")):
477 distributed_amount = flt(self.doc.discount_amount) * \
478 item.net_amount / total_for_discount_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530479
Nabin Haite7679702015-02-20 14:40:35 +0530480 item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
Nabin Hait25bd84d2015-03-04 15:06:56 +0530481 net_total += item.net_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530482
Nabin Hait25bd84d2015-03-04 15:06:56 +0530483 # discount amount rounding loss adjustment if no taxes
Nabin Hait4d587342019-05-30 15:50:46 +0530484 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 +0530485 and i == len(self.doc.get("items")) - 1:
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530486 discount_amount_loss = flt(self.doc.net_total - net_total - self.doc.discount_amount,
Nabin Hait25bd84d2015-03-04 15:06:56 +0530487 self.doc.precision("net_total"))
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530488
Anand Doshiec5ec602015-03-05 19:31:23 +0530489 item.net_amount = flt(item.net_amount + discount_amount_loss,
Nabin Hait25bd84d2015-03-04 15:06:56 +0530490 item.precision("net_amount"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530491
Nabin Hait51e980d2015-10-10 18:10:05 +0530492 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 +0530493
Nabin Haite7679702015-02-20 14:40:35 +0530494 self._set_in_company_currency(item, ["net_rate", "net_amount"])
Nabin Hait3237c752015-02-17 11:11:11 +0530495
496 self.discount_amount_applied = True
497 self._calculate()
498 else:
499 self.doc.base_discount_amount = 0
500
Nabin Haite7679702015-02-20 14:40:35 +0530501 def get_total_for_discount_amount(self):
Nabin Haitde9c8a92015-02-23 01:06:00 +0530502 if self.doc.apply_discount_on == "Net Total":
503 return self.doc.net_total
Nabin Haite7679702015-02-20 14:40:35 +0530504 else:
505 actual_taxes_dict = {}
Nabin Hait3237c752015-02-17 11:11:11 +0530506
Nabin Haite7679702015-02-20 14:40:35 +0530507 for tax in self.doc.get("taxes"):
Nabin Hait19ea7212020-08-11 20:34:57 +0530508 if tax.charge_type in ["Actual", "On Item Quantity"]:
Nabin Haitaf9bdfe2017-12-12 18:50:05 +0530509 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
510 actual_taxes_dict.setdefault(tax.idx, tax_amount)
Nabin Haite7679702015-02-20 14:40:35 +0530511 elif tax.row_id in actual_taxes_dict:
512 actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * flt(tax.rate) / 100
513 actual_taxes_dict.setdefault(tax.idx, actual_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530514
Nabin Hait877e1bb2017-11-17 12:27:43 +0530515 return flt(self.doc.grand_total - sum(actual_taxes_dict.values()),
516 self.doc.precision("grand_total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530517
518
Nabin Hait7b19b9e2015-02-24 09:42:24 +0530519 def calculate_total_advance(self):
520 if self.doc.docstatus < 2:
Nabin Haite7679702015-02-20 14:40:35 +0530521 total_allocated_amount = sum([flt(adv.allocated_amount, adv.precision("allocated_amount"))
Nabin Hait3237c752015-02-17 11:11:11 +0530522 for adv in self.doc.get("advances")])
523
Nabin Haite7679702015-02-20 14:40:35 +0530524 self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530525
Faris Ansari6041f5c2018-02-08 13:33:52 +0530526 grand_total = self.doc.rounded_total or self.doc.grand_total
527
Nabin Hait289ffb72016-02-08 11:06:55 +0530528 if self.doc.party_account_currency == self.doc.currency:
Faris Ansari6041f5c2018-02-08 13:33:52 +0530529 invoice_total = flt(grand_total - flt(self.doc.write_off_amount),
Nabin Hait289ffb72016-02-08 11:06:55 +0530530 self.doc.precision("grand_total"))
Nabin Hait8d8cba72017-04-03 17:26:22 +0530531 else:
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530532 base_write_off_amount = flt(flt(self.doc.write_off_amount) * self.doc.conversion_rate,
Nabin Hait8d8cba72017-04-03 17:26:22 +0530533 self.doc.precision("base_write_off_amount"))
Faris Ansari6041f5c2018-02-08 13:33:52 +0530534 invoice_total = flt(grand_total * self.doc.conversion_rate,
Nabin Hait8d8cba72017-04-03 17:26:22 +0530535 self.doc.precision("grand_total")) - base_write_off_amount
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530536
Nabin Haitadc09232016-02-09 10:31:11 +0530537 if invoice_total > 0 and self.doc.total_advance > invoice_total:
Nabin Hait289ffb72016-02-08 11:06:55 +0530538 frappe.throw(_("Advance amount cannot be greater than {0} {1}")
539 .format(self.doc.party_account_currency, invoice_total))
Nabin Hait3237c752015-02-17 11:11:11 +0530540
Rushabh Mehta8bb6e532015-02-18 20:22:59 +0530541 if self.doc.docstatus == 0:
Nabin Hait3237c752015-02-17 11:11:11 +0530542 self.calculate_outstanding_amount()
543
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530544 def is_internal_invoice(self):
545 """
546 Checks if its an internal transfer invoice
547 and decides if to calculate any out standing amount or not
548 """
549
550 if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer():
551 return True
552
553 return False
554
Nabin Hait3237c752015-02-17 11:11:11 +0530555 def calculate_outstanding_amount(self):
556 # NOTE:
557 # write_off_amount is only for POS Invoice
558 # total_advance is only for non POS Invoice
Rohit Waghchaureefb5bf22016-08-25 02:09:53 +0530559 if self.doc.doctype == "Sales Invoice":
560 self.calculate_paid_amount()
561
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530562 if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \
563 self.is_internal_invoice(): return
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530564
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530565 self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530566 self._set_in_company_currency(self.doc, ['write_off_amount'])
567
Nabin Hait877e1bb2017-11-17 12:27:43 +0530568 if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
569 grand_total = self.doc.rounded_total or self.doc.grand_total
570 if self.doc.party_account_currency == self.doc.currency:
Manas Solankida486ee2018-07-06 12:36:57 +0530571 total_amount_to_pay = flt(grand_total - self.doc.total_advance
Nabin Hait877e1bb2017-11-17 12:27:43 +0530572 - flt(self.doc.write_off_amount), self.doc.precision("grand_total"))
573 else:
574 total_amount_to_pay = flt(flt(grand_total *
575 self.doc.conversion_rate, self.doc.precision("grand_total")) - self.doc.total_advance
576 - flt(self.doc.base_write_off_amount), self.doc.precision("grand_total"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530577
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530578 self.doc.round_floats_in(self.doc, ["paid_amount"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530579 change_amount = 0
580
Deepesh Garg0ebace52020-02-25 13:21:16 +0530581 if self.doc.doctype == "Sales Invoice" and not self.doc.get('is_return'):
Nabin Hait877e1bb2017-11-17 12:27:43 +0530582 self.calculate_write_off_amount()
583 self.calculate_change_amount()
584 change_amount = self.doc.change_amount \
585 if self.doc.party_account_currency == self.doc.currency else self.doc.base_change_amount
586
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530587 paid_amount = self.doc.paid_amount \
588 if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530589
Nabin Hait877e1bb2017-11-17 12:27:43 +0530590 self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount),
591 self.doc.precision("outstanding_amount"))
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530592
Deepesh Garg0ebace52020-02-25 13:21:16 +0530593 if self.doc.doctype == 'Sales Invoice' and self.doc.get('is_pos') and self.doc.get('is_return'):
594 self.update_paid_amount_for_return(total_amount_to_pay)
595
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530596 def calculate_paid_amount(self):
Manas Solankida486ee2018-07-06 12:36:57 +0530597
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530598 paid_amount = base_paid_amount = 0.0
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530599
600 if self.doc.is_pos:
601 for payment in self.doc.get('payments'):
Ayush Shuklae9cf1ab2017-05-25 14:14:55 +0530602 payment.amount = flt(payment.amount)
603 payment.base_amount = payment.amount * flt(self.doc.conversion_rate)
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530604 paid_amount += payment.amount
605 base_paid_amount += payment.base_amount
rohitwaghchaure73456ac2017-05-16 11:29:57 +0530606 elif not self.doc.is_return:
607 self.doc.set('payments', [])
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530608
Manas Solankida486ee2018-07-06 12:36:57 +0530609 if self.doc.redeem_loyalty_points and self.doc.loyalty_amount:
610 base_paid_amount += self.doc.loyalty_amount
611 paid_amount += (self.doc.loyalty_amount / flt(self.doc.conversion_rate))
612
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530613 self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount"))
614 self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount"))
615
Nabin Hait3bb1a422016-08-02 16:41:10 +0530616 def calculate_change_amount(self):
617 self.doc.change_amount = 0.0
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530618 self.doc.base_change_amount = 0.0
Nabin Hait877e1bb2017-11-17 12:27:43 +0530619
620 if self.doc.doctype == "Sales Invoice" \
621 and self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530622 and any([d.type == "Cash" for d in self.doc.payments]):
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530623 grand_total = self.doc.rounded_total or self.doc.grand_total
624 base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530625
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530626 self.doc.change_amount = flt(self.doc.paid_amount - grand_total +
Nabin Hait3bb1a422016-08-02 16:41:10 +0530627 self.doc.write_off_amount, self.doc.precision("change_amount"))
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530628
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530629 self.doc.base_change_amount = flt(self.doc.base_paid_amount - base_grand_total +
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530630 self.doc.base_write_off_amount, self.doc.precision("base_change_amount"))
mbauskar36b51892016-01-18 16:31:10 +0530631
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530632 def calculate_write_off_amount(self):
Rohit Waghchaurebaef2622016-08-05 15:41:36 +0530633 if flt(self.doc.change_amount) > 0:
Nabin Hait877e1bb2017-11-17 12:27:43 +0530634 self.doc.write_off_amount = flt(self.doc.grand_total - self.doc.paid_amount
635 + self.doc.change_amount, self.doc.precision("write_off_amount"))
Rohit Waghchaurebaef2622016-08-05 15:41:36 +0530636 self.doc.base_write_off_amount = flt(self.doc.write_off_amount * self.doc.conversion_rate,
637 self.doc.precision("base_write_off_amount"))
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530638
mbauskar36b51892016-01-18 16:31:10 +0530639 def calculate_margin(self, item):
Makarand Bauskar0e4c5c92017-05-11 11:40:02 +0530640 rate_with_margin = 0.0
Shreya Shahbe690ef2017-11-14 17:22:41 +0530641 base_rate_with_margin = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530642 if item.price_list_rate:
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530643 if item.pricing_rules and not self.doc.ignore_pricing_rule:
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530644 has_margin = False
marination733fd5f2020-08-26 18:23:12 +0530645 for d in get_applied_pricing_rules(item.pricing_rules):
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530646 pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
Shreya Shahf718b0c2018-02-20 11:26:46 +0530647
Rohit Waghchaured4d639c2021-02-01 22:58:22 +0530648 if pricing_rule.margin_rate_or_amount and ((pricing_rule.currency == self.doc.currency and
649 pricing_rule.margin_type in ['Amount', 'Percentage']) or pricing_rule.margin_type == 'Percentage'):
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530650 item.margin_type = pricing_rule.margin_type
651 item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530652 has_margin = True
653
654 if not has_margin:
655 item.margin_type = None
656 item.margin_rate_or_amount = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530657
mbauskara52472c2016-03-05 15:10:25 +0530658 if item.margin_type and item.margin_rate_or_amount:
659 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 +0530660 rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
Shreya Shahbe690ef2017-11-14 17:22:41 +0530661 base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
mbauskar36b51892016-01-18 16:31:10 +0530662
Shreya Shahbe690ef2017-11-14 17:22:41 +0530663 return rate_with_margin, base_rate_with_margin
Nabin Hait852cb642017-07-05 12:58:19 +0530664
665 def set_item_wise_tax_breakup(self):
Nabin Hait9c421612017-07-20 13:32:01 +0530666 self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530667
Deepesh Garg0ebace52020-02-25 13:21:16 +0530668 def update_paid_amount_for_return(self, total_amount_to_pay):
Saqiba6f98d42020-07-23 18:51:26 +0530669 default_mode_of_payment = frappe.db.get_value('POS Payment Method',
670 {'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
Deepesh Garg0ebace52020-02-25 13:21:16 +0530671
672 self.doc.payments = []
673
674 if default_mode_of_payment:
675 self.doc.append('payments', {
676 'mode_of_payment': default_mode_of_payment.mode_of_payment,
Rucha Mahabaledee5302020-12-04 12:13:26 +0530677 'amount': total_amount_to_pay,
678 'default': 1
Deepesh Garg0ebace52020-02-25 13:21:16 +0530679 })
680 else:
681 self.doc.is_pos = 0
682 self.doc.pos_profile = ''
683
684 self.calculate_paid_amount()
685
686
Nabin Hait9c421612017-07-20 13:32:01 +0530687def get_itemised_tax_breakup_html(doc):
688 if not doc.taxes:
689 return
690 frappe.flags.company = doc.company
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530691
Nabin Hait9c421612017-07-20 13:32:01 +0530692 # get headers
Nabin Haitcaab5822017-08-24 16:22:28 +0530693 tax_accounts = []
694 for tax in doc.taxes:
695 if getattr(tax, "category", None) and tax.category=="Valuation":
696 continue
rohitwaghchaure4e17fae2017-12-12 14:40:52 +0530697 if tax.description not in tax_accounts:
Nabin Haitcaab5822017-08-24 16:22:28 +0530698 tax_accounts.append(tax.description)
699
Nabin Hait9c421612017-07-20 13:32:01 +0530700 headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530701
Nabin Hait9c421612017-07-20 13:32:01 +0530702 # get tax breakup data
703 itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc)
Nabin Haitcaab5822017-08-24 16:22:28 +0530704
705 get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes"))
706
rohitwaghchaured4526682017-12-28 14:20:13 +0530707 update_itemised_tax_data(doc)
Nabin Hait9c421612017-07-20 13:32:01 +0530708 frappe.flags.company = None
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530709
Nabin Hait9c421612017-07-20 13:32:01 +0530710 return frappe.render_template(
711 "templates/includes/itemised_tax_breakup.html", dict(
712 headers=headers,
713 itemised_tax=itemised_tax,
714 itemised_taxable_amount=itemised_taxable_amount,
715 tax_accounts=tax_accounts,
Saqib Ansari10a6a2d2020-04-13 16:40:13 +0530716 doc=doc
Nabin Haitb962fc12017-07-17 18:02:31 +0530717 )
Nabin Hait9c421612017-07-20 13:32:01 +0530718 )
Nabin Hait852cb642017-07-05 12:58:19 +0530719
Deepesh Garg6a5ef262021-02-19 14:30:23 +0530720@frappe.whitelist()
721def get_round_off_applicable_accounts(company, account_list):
722 account_list = get_regional_round_off_accounts(company, account_list)
723
724 return account_list
725
726@erpnext.allow_regional
727def get_regional_round_off_accounts(company, account_list):
728 pass
rohitwaghchaured4526682017-12-28 14:20:13 +0530729
730@erpnext.allow_regional
731def update_itemised_tax_data(doc):
732 #Don't delete this method, used for localization
733 pass
734
Nabin Haitb962fc12017-07-17 18:02:31 +0530735@erpnext.allow_regional
736def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
737 return [_("Item"), _("Taxable Amount")] + tax_accounts
738
739@erpnext.allow_regional
740def get_itemised_tax_breakup_data(doc):
741 itemised_tax = get_itemised_tax(doc.taxes)
742
743 itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
744
745 return itemised_tax, itemised_taxable_amount
746
Nabin Hait34c551d2019-07-03 10:34:31 +0530747def get_itemised_tax(taxes, with_tax_account=False):
Nabin Haitb962fc12017-07-17 18:02:31 +0530748 itemised_tax = {}
749 for tax in taxes:
Nabin Haitcaab5822017-08-24 16:22:28 +0530750 if getattr(tax, "category", None) and tax.category=="Valuation":
751 continue
752
Nabin Haitb962fc12017-07-17 18:02:31 +0530753 item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
Nabin Hait2e4de832017-09-19 14:53:16 +0530754 if item_tax_map:
755 for item_code, tax_data in item_tax_map.items():
756 itemised_tax.setdefault(item_code, frappe._dict())
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530757
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530758 tax_rate = 0.0
759 tax_amount = 0.0
760
Nabin Hait2e4de832017-09-19 14:53:16 +0530761 if isinstance(tax_data, list):
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530762 tax_rate = flt(tax_data[0])
763 tax_amount = flt(tax_data[1])
Nabin Hait2e4de832017-09-19 14:53:16 +0530764 else:
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530765 tax_rate = flt(tax_data)
766
767 itemised_tax[item_code][tax.description] = frappe._dict(dict(
768 tax_rate = tax_rate,
769 tax_amount = tax_amount
770 ))
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530771
Nabin Hait34c551d2019-07-03 10:34:31 +0530772 if with_tax_account:
773 itemised_tax[item_code][tax.description].tax_account = tax.account_head
774
Nabin Haitb962fc12017-07-17 18:02:31 +0530775 return itemised_tax
776
777def get_itemised_taxable_amount(items):
778 itemised_taxable_amount = frappe._dict()
779 for item in items:
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530780 item_code = item.item_code or item.item_name
Nabin Haitb962fc12017-07-17 18:02:31 +0530781 itemised_taxable_amount.setdefault(item_code, 0)
782 itemised_taxable_amount[item_code] += item.net_amount
783
Nabin Haitcaab5822017-08-24 16:22:28 +0530784 return itemised_taxable_amount
785
786def get_rounded_tax_amount(itemised_tax, precision):
787 # Rounding based on tax_amount precision
788 for taxes in itemised_tax.values():
789 for tax_account in taxes:
Himanshu Mishra35b26272018-11-13 11:13:04 +0530790 taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530791
792class init_landed_taxes_and_totals(object):
793 def __init__(self, doc):
794 self.doc = doc
795 self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
796 self.set_account_currency()
797 self.set_exchange_rate()
798 self.set_amounts_in_company_currency()
799
800 def set_account_currency(self):
801 company_currency = erpnext.get_company_currency(self.doc.company)
802 for d in self.doc.get(self.tax_field):
803 if not d.account_currency:
804 account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
805 d.account_currency = account_currency or company_currency
806
807 def set_exchange_rate(self):
808 company_currency = erpnext.get_company_currency(self.doc.company)
809 for d in self.doc.get(self.tax_field):
810 if d.account_currency == company_currency:
811 d.exchange_rate = 1
Deepesh Garg22f5ff82021-03-17 10:56:52 +0530812 elif not d.exchange_rate:
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530813 d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
814 account_currency=d.account_currency, company=self.doc.company)
815
816 if not d.exchange_rate:
817 frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
818
819 def set_amounts_in_company_currency(self):
820 for d in self.doc.get(self.tax_field):
821 d.amount = flt(d.amount, d.precision("amount"))
Walstan Baptista37b826b2021-04-03 19:48:46 +0530822 d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))