blob: 987fd31260fd141fc20496bdd1cc9ed32c626c1e [file] [log] [blame]
Anand Doshi885e0742015-03-03 14:55:30 +05301# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
Nabin Hait3237c752015-02-17 11:11:11 +05302# License: GNU General Public License v3. See license.txt
3
Chillar Anand915b3432021-09-02 16:44:59 +05304
Nabin Hait3237c752015-02-17 11:11:11 +05305import json
Chillar Anand915b3432021-09-02 16:44:59 +05306
7import frappe
Nabin Hait3769d872015-12-18 13:12:02 +05308from frappe import _, scrub
Nabin Haitb962fc12017-07-17 18:02:31 +05309from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
Chillar Anand915b3432021-09-02 16:44:59 +053010
11import erpnext
Deepesh Gargbfc17e42020-12-25 18:34:39 +053012from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
Chillar Anand915b3432021-09-02 16:44:59 +053013from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
14from erpnext.controllers.accounts_controller import (
15 validate_conversion_rate,
16 validate_inclusive_tax,
17 validate_taxes_and_charges,
18)
19from erpnext.stock.get_item_details import _get_item_tax_template
20
Nabin Hait3237c752015-02-17 11:11:11 +053021
Nabin Haitfe81da22015-02-18 12:23:18 +053022class calculate_taxes_and_totals(object):
Nabin Hait3237c752015-02-17 11:11:11 +053023 def __init__(self, doc):
24 self.doc = doc
Deepesh Garg6a5ef262021-02-19 14:30:23 +053025 frappe.flags.round_off_applicable_accounts = []
26 get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
Nabin Haitfe81da22015-02-18 12:23:18 +053027 self.calculate()
28
Nabin Hait3237c752015-02-17 11:11:11 +053029 def calculate(self):
Nabin Haitb315acb2019-07-12 14:27:19 +053030 if not len(self.doc.get("items")):
31 return
32
Nabin Hait3237c752015-02-17 11:11:11 +053033 self.discount_amount_applied = False
34 self._calculate()
35
36 if self.doc.meta.get_field("discount_amount"):
Nabin Hait3769d872015-12-18 13:12:02 +053037 self.set_discount_amount()
Nabin Hait3237c752015-02-17 11:11:11 +053038 self.apply_discount_amount()
39
Nabin Haitbd00e812015-02-17 12:50:51 +053040 if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
Nabin Hait3237c752015-02-17 11:11:11 +053041 self.calculate_total_advance()
Vishal Dhayaguded42242d2017-11-29 16:09:59 +053042
Nabin Hait852cb642017-07-05 12:58:19 +053043 if self.doc.meta.get_field("other_charges_calculation"):
44 self.set_item_wise_tax_breakup()
Nabin Hait3237c752015-02-17 11:11:11 +053045
46 def _calculate(self):
Faris Ansarieae2dda2018-05-02 12:19:30 +053047 self.validate_conversion_rate()
Nabin Haite7679702015-02-20 14:40:35 +053048 self.calculate_item_values()
Deepesh Gargef0d26c2020-01-06 15:34:15 +053049 self.validate_item_tax_template()
Nabin Haite7679702015-02-20 14:40:35 +053050 self.initialize_taxes()
51 self.determine_exclusive_rate()
52 self.calculate_net_total()
Subin Toma8e2c022021-11-16 19:06:49 +053053 self.calculate_shipping_charges()
Nabin Haite7679702015-02-20 14:40:35 +053054 self.calculate_taxes()
Nabin Haita1bf43b2015-03-17 10:50:47 +053055 self.manipulate_grand_total_for_inclusive_tax()
Nabin Haite7679702015-02-20 14:40:35 +053056 self.calculate_totals()
57 self._cleanup()
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +053058 self.calculate_total_net_weight()
Nabin Haite7679702015-02-20 14:40:35 +053059
Deepesh Gargef0d26c2020-01-06 15:34:15 +053060 def validate_item_tax_template(self):
61 for item in self.doc.get('items'):
62 if item.item_code and item.get('item_tax_template'):
63 item_doc = frappe.get_cached_doc("Item", item.item_code)
64 args = {
Deepesh Garg8a7e2832021-06-04 22:53:26 +053065 'net_rate': item.net_rate or item.rate,
Deepesh Gargef0d26c2020-01-06 15:34:15 +053066 'tax_category': self.doc.get('tax_category'),
67 'posting_date': self.doc.get('posting_date'),
68 'bill_date': self.doc.get('bill_date'),
mohammadahmad1990728bf0e2020-06-18 12:21:42 +050069 'transaction_date': self.doc.get('transaction_date'),
70 'company': self.doc.get('company')
Deepesh Gargef0d26c2020-01-06 15:34:15 +053071 }
72
73 item_group = item_doc.item_group
74 item_group_taxes = []
75
76 while item_group:
77 item_group_doc = frappe.get_cached_doc('Item Group', item_group)
78 item_group_taxes += item_group_doc.taxes or []
79 item_group = item_group_doc.parent_item_group
80
81 item_taxes = item_doc.taxes or []
82
83 if not item_group_taxes and (not item_taxes):
84 # No validation if no taxes in item or item group
85 continue
86
87 taxes = _get_item_tax_template(args, item_taxes + item_group_taxes, for_validate=True)
88
Deepesh Garg18be7672021-06-06 13:25:34 +053089 if taxes:
90 if item.item_tax_template not in taxes:
91 item.item_tax_template = taxes[0]
92 frappe.msgprint(_("Row {0}: Item Tax template updated as per validity and rate applied").format(
93 item.idx, frappe.bold(item.item_code)
94 ))
Deepesh Gargef0d26c2020-01-06 15:34:15 +053095
Nabin Haite7679702015-02-20 14:40:35 +053096 def validate_conversion_rate(self):
Nabin Hait3237c752015-02-17 11:11:11 +053097 # validate conversion rate
Rushabh Mehtacc8b2b22017-03-31 12:44:29 +053098 company_currency = erpnext.get_company_currency(self.doc.company)
Nabin Hait3237c752015-02-17 11:11:11 +053099 if not self.doc.currency or self.doc.currency == company_currency:
100 self.doc.currency = company_currency
101 self.doc.conversion_rate = 1.0
102 else:
103 validate_conversion_rate(self.doc.currency, self.doc.conversion_rate,
104 self.doc.meta.get_label("conversion_rate"), self.doc.company)
105
106 self.doc.conversion_rate = flt(self.doc.conversion_rate)
107
Nabin Hait3237c752015-02-17 11:11:11 +0530108 def calculate_item_values(self):
109 if not self.discount_amount_applied:
110 for item in self.doc.get("items"):
111 self.doc.round_floats_in(item)
112
113 if item.discount_percentage == 100:
114 item.rate = 0.0
Nabin Hait593242f2019-04-05 19:35:02 +0530115 elif item.price_list_rate:
116 if not item.rate or (item.pricing_rules and item.discount_percentage > 0):
117 item.rate = flt(item.price_list_rate *
118 (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
119 item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
120 elif item.discount_amount and item.pricing_rules:
121 item.rate = item.price_list_rate - item.discount_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530122
Anupam Kumared42afc2021-03-15 11:11:28 +0530123 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 +0530124 item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
Nabin Hait64bfdd92019-04-23 13:37:19 +0530125 if flt(item.rate_with_margin) > 0:
126 item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
Nabin Hait10c61372021-04-13 15:46:01 +0530127
Walstan Baptista37b826b2021-04-03 19:48:46 +0530128 if item.discount_amount and not item.discount_percentage:
Nabin Hait10c61372021-04-13 15:46:01 +0530129 item.rate = item.rate_with_margin - item.discount_amount
Walstan Baptista37b826b2021-04-03 19:48:46 +0530130 else:
131 item.discount_amount = item.rate_with_margin - item.rate
Nabin Hait10c61372021-04-13 15:46:01 +0530132
Nabin Hait64bfdd92019-04-23 13:37:19 +0530133 elif flt(item.price_list_rate) > 0:
134 item.discount_amount = item.price_list_rate - item.rate
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530135 elif flt(item.price_list_rate) > 0 and not item.discount_amount:
136 item.discount_amount = item.price_list_rate - item.rate
mbauskara52472c2016-03-05 15:10:25 +0530137
Nabin Haite7679702015-02-20 14:40:35 +0530138 item.net_rate = item.rate
Deepesh Gargb65c7612019-07-31 15:58:01 +0530139
deepeshgarg0078bf19ce2019-08-03 13:40:37 +0530140 if not item.qty and self.doc.get("is_return"):
Deepesh Gargb65c7612019-07-31 15:58:01 +0530141 item.amount = flt(-1 * item.rate, item.precision("amount"))
142 else:
143 item.amount = flt(item.rate * item.qty, item.precision("amount"))
144
Nabin Haite7679702015-02-20 14:40:35 +0530145 item.net_amount = item.amount
Nabin Hait3237c752015-02-17 11:11:11 +0530146
Nabin Haite7679702015-02-20 14:40:35 +0530147 self._set_in_company_currency(item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"])
Nabin Hait3237c752015-02-17 11:11:11 +0530148
Nabin Haite7679702015-02-20 14:40:35 +0530149 item.item_tax_amount = 0.0
150
151 def _set_in_company_currency(self, doc, fields):
Nabin Hait3237c752015-02-17 11:11:11 +0530152 """set values in base currency"""
Nabin Haite7679702015-02-20 14:40:35 +0530153 for f in fields:
154 val = flt(flt(doc.get(f), doc.precision(f)) * self.doc.conversion_rate, doc.precision("base_" + f))
155 doc.set("base_" + f, val)
Nabin Hait3237c752015-02-17 11:11:11 +0530156
157 def initialize_taxes(self):
158 for tax in self.doc.get("taxes"):
Nabin Hait86cd4cc2015-02-28 19:11:51 +0530159 if not self.discount_amount_applied:
160 validate_taxes_and_charges(tax)
161 validate_inclusive_tax(tax, self.doc)
Nabin Hait613d0812015-02-23 11:58:15 +0530162
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530163 if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530164 tax.item_wise_tax_detail = {}
165
Nabin Hait3237c752015-02-17 11:11:11 +0530166 tax_fields = ["total", "tax_amount_after_discount_amount",
167 "tax_amount_for_current_item", "grand_total_for_current_item",
168 "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
169
Nabin Haitde9c8a92015-02-23 01:06:00 +0530170 if tax.charge_type != "Actual" and \
171 not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
172 tax_fields.append("tax_amount")
Nabin Hait3237c752015-02-17 11:11:11 +0530173
174 for fieldname in tax_fields:
175 tax.set(fieldname, 0.0)
176
Nabin Hait3237c752015-02-17 11:11:11 +0530177 self.doc.round_floats_in(tax)
178
Nabin Hait3237c752015-02-17 11:11:11 +0530179 def determine_exclusive_rate(self):
Ankush Menat8fe5feb2021-11-04 19:48:32 +0530180 if not any(cint(tax.included_in_print_rate) for tax in self.doc.get("taxes")):
Nabin Hait37b047d2015-02-23 16:01:33 +0530181 return
Nabin Hait3237c752015-02-17 11:11:11 +0530182
183 for item in self.doc.get("items"):
184 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
185 cumulated_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530186 total_inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530187 for i, tax in enumerate(self.doc.get("taxes")):
Nabin Hait19ea7212020-08-11 20:34:57 +0530188 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 +0530189
190 if i==0:
191 tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
192 else:
193 tax.grand_total_fraction_for_current_item = \
194 self.doc.get("taxes")[i-1].grand_total_fraction_for_current_item \
195 + tax.tax_fraction_for_current_item
196
197 cumulated_tax_fraction += tax.tax_fraction_for_current_item
Nabin Haite2ee4552020-08-12 20:58:03 +0530198 total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
Nabin Hait3237c752015-02-17 11:11:11 +0530199
Nabin Hait19ea7212020-08-11 20:34:57 +0530200 if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty):
201 amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
202
203 item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
Nabin Haite7679702015-02-20 14:40:35 +0530204 item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
Nabin Hait2e4de832017-09-19 14:53:16 +0530205 item.discount_percentage = flt(item.discount_percentage,
206 item.precision("discount_percentage"))
Nabin Hait3237c752015-02-17 11:11:11 +0530207
Nabin Haite7679702015-02-20 14:40:35 +0530208 self._set_in_company_currency(item, ["net_rate", "net_amount"])
209
Nabin Hait3237c752015-02-17 11:11:11 +0530210 def _load_item_tax_rate(self, item_tax_rate):
211 return json.loads(item_tax_rate) if item_tax_rate else {}
212
213 def get_current_tax_fraction(self, tax, item_tax_map):
214 """
215 Get tax fraction for calculating tax exclusive amount
216 from tax inclusive amount
217 """
218 current_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530219 inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530220
221 if cint(tax.included_in_print_rate):
222 tax_rate = self._get_tax_rate(tax, item_tax_map)
223
224 if tax.charge_type == "On Net Total":
225 current_tax_fraction = tax_rate / 100.0
226
227 elif tax.charge_type == "On Previous Row Amount":
228 current_tax_fraction = (tax_rate / 100.0) * \
229 self.doc.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
230
231 elif tax.charge_type == "On Previous Row Total":
232 current_tax_fraction = (tax_rate / 100.0) * \
233 self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
marination733fd5f2020-08-26 18:23:12 +0530234
Nabin Hait19ea7212020-08-11 20:34:57 +0530235 elif tax.charge_type == "On Item Quantity":
236 inclusive_tax_amount_per_qty = flt(tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530237
Nabin Hait19ea7212020-08-11 20:34:57 +0530238 if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
239 current_tax_fraction *= -1.0
240 inclusive_tax_amount_per_qty *= -1.0
241
242 return current_tax_fraction, inclusive_tax_amount_per_qty
Nabin Hait3237c752015-02-17 11:11:11 +0530243
244 def _get_tax_rate(self, tax, item_tax_map):
Achilles Rasquinha87dab142018-03-08 14:21:48 +0530245 if tax.account_head in item_tax_map:
Nabin Hait3237c752015-02-17 11:11:11 +0530246 return flt(item_tax_map.get(tax.account_head), self.doc.precision("rate", tax))
247 else:
248 return tax.rate
249
250 def calculate_net_total(self):
Shreya Shahe3290382018-05-28 11:49:08 +0530251 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 +0530252
Nabin Hait3237c752015-02-17 11:11:11 +0530253 for item in self.doc.get("items"):
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530254 self.doc.total += item.amount
Shreya Shahe3290382018-05-28 11:49:08 +0530255 self.doc.total_qty += item.qty
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530256 self.doc.base_total += item.base_amount
Nabin Haite7679702015-02-20 14:40:35 +0530257 self.doc.net_total += item.net_amount
258 self.doc.base_net_total += item.base_net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530259
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530260 self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530261
Subin Toma8e2c022021-11-16 19:06:49 +0530262 def calculate_shipping_charges(self):
Subin Tomaf1fce02021-11-10 16:49:12 +0530263 if hasattr(self.doc, "shipping_rule") and self.doc.shipping_rule:
Subin Tom18ae03d2021-11-10 15:57:41 +0530264 shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule)
265 shipping_rule.apply(self.doc)
266
Nabin Hait3237c752015-02-17 11:11:11 +0530267 def calculate_taxes(self):
Subin Tom75a76e62021-10-29 16:45:04 +0530268 if not self.doc.get('is_consolidated'):
269 self.doc.rounding_adjustment = 0
270
Nabin Hait3237c752015-02-17 11:11:11 +0530271 # maintain actual tax rate based on idx
Nabin Haite7679702015-02-20 14:40:35 +0530272 actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
Nabin Hait3237c752015-02-17 11:11:11 +0530273 for tax in self.doc.get("taxes") if tax.charge_type == "Actual"])
274
275 for n, item in enumerate(self.doc.get("items")):
276 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530277 for i, tax in enumerate(self.doc.get("taxes")):
278 # tax_amount represents the amount of tax for the current step
279 current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map)
280
281 # Adjust divisional loss to the last item
282 if tax.charge_type == "Actual":
283 actual_tax_dict[tax.idx] -= current_tax_amount
284 if n == len(self.doc.get("items")) - 1:
285 current_tax_amount += actual_tax_dict[tax.idx]
286
Nabin Hait2b019ed2015-02-22 23:03:07 +0530287 # accumulate tax amount into tax.tax_amount
Nabin Haitde9c8a92015-02-23 01:06:00 +0530288 if tax.charge_type != "Actual" and \
289 not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
290 tax.tax_amount += current_tax_amount
Nabin Hait2b019ed2015-02-22 23:03:07 +0530291
Nabin Hait3237c752015-02-17 11:11:11 +0530292 # store tax_amount for current item as it will be used for
293 # charge type = 'On Previous Row Amount'
294 tax.tax_amount_for_current_item = current_tax_amount
295
Nabin Hait2b019ed2015-02-22 23:03:07 +0530296 # set tax after discount
Nabin Hait3237c752015-02-17 11:11:11 +0530297 tax.tax_amount_after_discount_amount += current_tax_amount
298
Nabin Haitcd951342017-07-31 18:07:45 +0530299 current_tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(current_tax_amount, tax)
Nabin Hait3237c752015-02-17 11:11:11 +0530300
Nabin Hait3237c752015-02-17 11:11:11 +0530301 # note: grand_total_for_current_item contains the contribution of
302 # item's amount, previously applied tax and the current tax on that item
303 if i==0:
Nabin Haitcd951342017-07-31 18:07:45 +0530304 tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530305 else:
306 tax.grand_total_for_current_item = \
Nabin Haitcd951342017-07-31 18:07:45 +0530307 flt(self.doc.get("taxes")[i-1].grand_total_for_current_item + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530308
309 # set precision in the last item iteration
310 if n == len(self.doc.get("items")) - 1:
311 self.round_off_totals(tax)
Deepesh Gargb6705882021-04-14 11:20:27 +0530312 self._set_in_company_currency(tax,
313 ["tax_amount", "tax_amount_after_discount_amount"])
314
315 self.round_off_base_values(tax)
Nabin Haitcd951342017-07-31 18:07:45 +0530316 self.set_cumulative_total(i, tax)
317
Deepesh Gargb6705882021-04-14 11:20:27 +0530318 self._set_in_company_currency(tax, ["total"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530319
Nabin Hait3237c752015-02-17 11:11:11 +0530320 # adjust Discount Amount loss in last tax iteration
Nabin Haitde9c8a92015-02-23 01:06:00 +0530321 if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
Subin Tom75a76e62021-10-29 16:45:04 +0530322 and self.doc.discount_amount \
323 and self.doc.apply_discount_on == "Grand Total" \
324 and not self.doc.get('is_consolidated'):
Nabin Hait2e4de832017-09-19 14:53:16 +0530325 self.doc.rounding_adjustment = flt(self.doc.grand_total
326 - flt(self.doc.discount_amount) - tax.total,
327 self.doc.precision("rounding_adjustment"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530328
Nabin Haitcd951342017-07-31 18:07:45 +0530329 def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
330 # if just for valuation, do not add the tax amount in total
331 # if tax/charges is for deduction, multiply by -1
332 if getattr(tax, "category", None):
333 tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
Rushabh Mehta30dc9a12017-11-17 14:31:09 +0530334 if self.doc.doctype in ["Purchase Order", "Purchase Invoice", "Purchase Receipt", "Supplier Quotation"]:
335 tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
Nabin Haitcd951342017-07-31 18:07:45 +0530336 return tax_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530337
Nabin Haitcd951342017-07-31 18:07:45 +0530338 def set_cumulative_total(self, row_idx, tax):
339 tax_amount = tax.tax_amount_after_discount_amount
340 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax_amount, tax)
341
342 if row_idx == 0:
343 tax.total = flt(self.doc.net_total + tax_amount, tax.precision("total"))
344 else:
345 tax.total = flt(self.doc.get("taxes")[row_idx-1].total + tax_amount, tax.precision("total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530346
347 def get_current_tax_amount(self, item, tax, item_tax_map):
348 tax_rate = self._get_tax_rate(tax, item_tax_map)
349 current_tax_amount = 0.0
350
351 if tax.charge_type == "Actual":
352 # distribute the tax amount proportionally to each item row
Nabin Haite7679702015-02-20 14:40:35 +0530353 actual = flt(tax.tax_amount, tax.precision("tax_amount"))
354 current_tax_amount = item.net_amount*actual / self.doc.net_total if self.doc.net_total else 0.0
355
Nabin Hait3237c752015-02-17 11:11:11 +0530356 elif tax.charge_type == "On Net Total":
Nabin Haite7679702015-02-20 14:40:35 +0530357 current_tax_amount = (tax_rate / 100.0) * item.net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530358 elif tax.charge_type == "On Previous Row Amount":
359 current_tax_amount = (tax_rate / 100.0) * \
360 self.doc.get("taxes")[cint(tax.row_id) - 1].tax_amount_for_current_item
361 elif tax.charge_type == "On Previous Row Total":
362 current_tax_amount = (tax_rate / 100.0) * \
363 self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
Himanshu Mishra35b26272018-11-13 11:13:04 +0530364 elif tax.charge_type == "On Item Quantity":
Nabin Hait19ea7212020-08-11 20:34:57 +0530365 current_tax_amount = tax_rate * item.qty
Nabin Hait3237c752015-02-17 11:11:11 +0530366
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530367 if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530368 self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530369
370 return current_tax_amount
371
Nabin Haite7679702015-02-20 14:40:35 +0530372 def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
373 # store tax breakup for each item
374 key = item.item_code or item.item_name
375 item_wise_tax_amount = current_tax_amount*self.doc.conversion_rate
376 if tax.item_wise_tax_detail.get(key):
377 item_wise_tax_amount += tax.item_wise_tax_detail[key][1]
378
Nabin Haitcaab5822017-08-24 16:22:28 +0530379 tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
Nabin Haite7679702015-02-20 14:40:35 +0530380
Nabin Hait3237c752015-02-17 11:11:11 +0530381 def round_off_totals(self, tax):
Deepesh Gargb6705882021-04-14 11:20:27 +0530382 if tax.account_head in frappe.flags.round_off_applicable_accounts:
383 tax.tax_amount = round(tax.tax_amount, 0)
384 tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
385
Nabin Haite7679702015-02-20 14:40:35 +0530386 tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530387 tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
Nabin Haitcd951342017-07-31 18:07:45 +0530388 tax.precision("tax_amount"))
Nabin Haitce245122015-02-22 20:14:49 +0530389
Deepesh Gargb6705882021-04-14 11:20:27 +0530390 def round_off_base_values(self, tax):
391 # Round off to nearest integer based on regional settings
392 if tax.account_head in frappe.flags.round_off_applicable_accounts:
393 tax.base_tax_amount = round(tax.base_tax_amount, 0)
394 tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
395
Nabin Haita1bf43b2015-03-17 10:50:47 +0530396 def manipulate_grand_total_for_inclusive_tax(self):
397 # if fully inclusive taxes and diff
Ankush Menat98917802021-06-11 18:40:22 +0530398 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 +0530399 last_tax = self.doc.get("taxes")[-1]
Ankush Menat98917802021-06-11 18:40:22 +0530400 non_inclusive_tax_amount = sum(flt(d.tax_amount_after_discount_amount)
401 for d in self.doc.get("taxes") if not d.included_in_print_rate)
Nabin Haitf32fc232019-12-25 13:59:24 +0530402
Nabin Hait2e4de832017-09-19 14:53:16 +0530403 diff = self.doc.total + non_inclusive_tax_amount \
404 - flt(last_tax.total, last_tax.precision("total"))
Nabin Haitf32fc232019-12-25 13:59:24 +0530405
406 # If discount amount applied, deduct the discount amount
407 # because self.doc.total is always without discount, but last_tax.total is after discount
408 if self.discount_amount_applied and self.doc.discount_amount:
409 diff -= flt(self.doc.discount_amount)
410
411 diff = flt(diff, self.doc.precision("rounding_adjustment"))
412
Nabin Hait2e4de832017-09-19 14:53:16 +0530413 if diff and abs(diff) <= (5.0 / 10**last_tax.precision("tax_amount")):
Nabin Haitf32fc232019-12-25 13:59:24 +0530414 self.doc.rounding_adjustment = diff
Nabin Hait3237c752015-02-17 11:11:11 +0530415
416 def calculate_totals(self):
Subin Tom75a76e62021-10-29 16:45:04 +0530417 if self.doc.get("taxes"):
418 self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment)
419 else:
420 self.doc.grand_total = flt(self.doc.net_total)
Nabin Hait3237c752015-02-17 11:11:11 +0530421
Subin Tom75a76e62021-10-29 16:45:04 +0530422 if self.doc.get("taxes"):
423 self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total
Nabin Hait2e4de832017-09-19 14:53:16 +0530424 - flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges"))
Subin Tom75a76e62021-10-29 16:45:04 +0530425 else:
426 self.doc.total_taxes_and_charges = 0.0
Anand Doshiec5ec602015-03-05 19:31:23 +0530427
Nabin Hait2e4de832017-09-19 14:53:16 +0530428 self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530429
Saqiba6f98d42020-07-23 18:51:26 +0530430 if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]:
Anurag Mishra8a06b8f2019-07-17 14:55:16 +0530431 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 +0530432 if self.doc.total_taxes_and_charges else self.doc.base_net_total
Nabin Hait3237c752015-02-17 11:11:11 +0530433 else:
Anand Doshiec5ec602015-03-05 19:31:23 +0530434 self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0
Nabin Hait3237c752015-02-17 11:11:11 +0530435 for tax in self.doc.get("taxes"):
436 if tax.category in ["Valuation and Total", "Total"]:
437 if tax.add_deduct_tax == "Add":
Nabin Haitdb53a782015-07-31 16:53:13 +0530438 self.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530439 else:
Nabin Haitdb53a782015-07-31 16:53:13 +0530440 self.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530441
Nabin Haite7679702015-02-20 14:40:35 +0530442 self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530443
Nabin Haite7679702015-02-20 14:40:35 +0530444 self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
445 if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
446 else self.doc.base_net_total
Nabin Hait3237c752015-02-17 11:11:11 +0530447
Nabin Hait2e4de832017-09-19 14:53:16 +0530448 self._set_in_company_currency(self.doc,
449 ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530450
Nabin Haite7679702015-02-20 14:40:35 +0530451 self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530452
Nabin Hait2e4de832017-09-19 14:53:16 +0530453 self.set_rounded_total()
454
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +0530455 def calculate_total_net_weight(self):
456 if self.doc.meta.get_field('total_net_weight'):
457 self.doc.total_net_weight = 0.0
458 for d in self.doc.items:
459 if d.total_weight:
460 self.doc.total_net_weight += d.total_weight
461
Nabin Hait2e4de832017-09-19 14:53:16 +0530462 def set_rounded_total(self):
Subin Tom75a76e62021-10-29 16:45:04 +0530463 if not self.doc.get('is_consolidated'):
464 if self.doc.meta.get_field("rounded_total"):
465 if self.doc.is_rounded_total_disabled():
466 self.doc.rounded_total = self.doc.base_rounded_total = 0
467 return
Nabin Hait877e1bb2017-11-17 12:27:43 +0530468
Subin Tom75a76e62021-10-29 16:45:04 +0530469 self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
470 self.doc.currency, self.doc.precision("rounded_total"))
Nabin Hait2e4de832017-09-19 14:53:16 +0530471
Subin Tom75a76e62021-10-29 16:45:04 +0530472 #if print_in_rate is set, we would have already calculated rounding adjustment
473 self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
474 self.doc.precision("rounding_adjustment"))
Nabin Hait877e1bb2017-11-17 12:27:43 +0530475
Subin Tom75a76e62021-10-29 16:45:04 +0530476 self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530477
Nabin Hait3237c752015-02-17 11:11:11 +0530478 def _cleanup(self):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530479 if not self.doc.get('is_consolidated'):
480 for tax in self.doc.get("taxes"):
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530481 if not tax.get("dont_recompute_tax"):
482 tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530483
Nabin Hait3769d872015-12-18 13:12:02 +0530484 def set_discount_amount(self):
Nabin Haite0405102016-10-13 12:14:32 +0530485 if self.doc.additional_discount_percentage:
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530486 self.doc.discount_amount = flt(flt(self.doc.get(scrub(self.doc.apply_discount_on)))
Nabin Hait3769d872015-12-18 13:12:02 +0530487 * self.doc.additional_discount_percentage / 100, self.doc.precision("discount_amount"))
Nabin Hait3237c752015-02-17 11:11:11 +0530488
489 def apply_discount_amount(self):
490 if self.doc.discount_amount:
Nabin Hait37b047d2015-02-23 16:01:33 +0530491 if not self.doc.apply_discount_on:
492 frappe.throw(_("Please select Apply Discount On"))
493
Nabin Hait3237c752015-02-17 11:11:11 +0530494 self.doc.base_discount_amount = flt(self.doc.discount_amount * self.doc.conversion_rate,
495 self.doc.precision("base_discount_amount"))
496
Nabin Haite7679702015-02-20 14:40:35 +0530497 total_for_discount_amount = self.get_total_for_discount_amount()
Nabin Hait25bd84d2015-03-04 15:06:56 +0530498 taxes = self.doc.get("taxes")
499 net_total = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530500
Nabin Haite7679702015-02-20 14:40:35 +0530501 if total_for_discount_amount:
Nabin Hait3237c752015-02-17 11:11:11 +0530502 # calculate item amount after Discount Amount
Nabin Hait25bd84d2015-03-04 15:06:56 +0530503 for i, item in enumerate(self.doc.get("items")):
504 distributed_amount = flt(self.doc.discount_amount) * \
505 item.net_amount / total_for_discount_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530506
Nabin Haite7679702015-02-20 14:40:35 +0530507 item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
Nabin Hait25bd84d2015-03-04 15:06:56 +0530508 net_total += item.net_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530509
Nabin Hait25bd84d2015-03-04 15:06:56 +0530510 # discount amount rounding loss adjustment if no taxes
Nabin Hait4d587342019-05-30 15:50:46 +0530511 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 +0530512 and i == len(self.doc.get("items")) - 1:
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530513 discount_amount_loss = flt(self.doc.net_total - net_total - self.doc.discount_amount,
Nabin Hait25bd84d2015-03-04 15:06:56 +0530514 self.doc.precision("net_total"))
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530515
Anand Doshiec5ec602015-03-05 19:31:23 +0530516 item.net_amount = flt(item.net_amount + discount_amount_loss,
Nabin Hait25bd84d2015-03-04 15:06:56 +0530517 item.precision("net_amount"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530518
Nabin Hait51e980d2015-10-10 18:10:05 +0530519 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 +0530520
Nabin Haite7679702015-02-20 14:40:35 +0530521 self._set_in_company_currency(item, ["net_rate", "net_amount"])
Nabin Hait3237c752015-02-17 11:11:11 +0530522
523 self.discount_amount_applied = True
524 self._calculate()
525 else:
526 self.doc.base_discount_amount = 0
527
Nabin Haite7679702015-02-20 14:40:35 +0530528 def get_total_for_discount_amount(self):
Nabin Haitde9c8a92015-02-23 01:06:00 +0530529 if self.doc.apply_discount_on == "Net Total":
530 return self.doc.net_total
Nabin Haite7679702015-02-20 14:40:35 +0530531 else:
532 actual_taxes_dict = {}
Nabin Hait3237c752015-02-17 11:11:11 +0530533
Nabin Haite7679702015-02-20 14:40:35 +0530534 for tax in self.doc.get("taxes"):
Nabin Hait19ea7212020-08-11 20:34:57 +0530535 if tax.charge_type in ["Actual", "On Item Quantity"]:
Nabin Haitaf9bdfe2017-12-12 18:50:05 +0530536 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
537 actual_taxes_dict.setdefault(tax.idx, tax_amount)
Nabin Haite7679702015-02-20 14:40:35 +0530538 elif tax.row_id in actual_taxes_dict:
539 actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * flt(tax.rate) / 100
540 actual_taxes_dict.setdefault(tax.idx, actual_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530541
Nabin Hait877e1bb2017-11-17 12:27:43 +0530542 return flt(self.doc.grand_total - sum(actual_taxes_dict.values()),
543 self.doc.precision("grand_total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530544
545
Nabin Hait7b19b9e2015-02-24 09:42:24 +0530546 def calculate_total_advance(self):
547 if self.doc.docstatus < 2:
Ankush Menat98917802021-06-11 18:40:22 +0530548 total_allocated_amount = sum(flt(adv.allocated_amount, adv.precision("allocated_amount"))
549 for adv in self.doc.get("advances"))
Nabin Hait3237c752015-02-17 11:11:11 +0530550
Nabin Haite7679702015-02-20 14:40:35 +0530551 self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530552
Faris Ansari6041f5c2018-02-08 13:33:52 +0530553 grand_total = self.doc.rounded_total or self.doc.grand_total
554
Nabin Hait289ffb72016-02-08 11:06:55 +0530555 if self.doc.party_account_currency == self.doc.currency:
Faris Ansari6041f5c2018-02-08 13:33:52 +0530556 invoice_total = flt(grand_total - flt(self.doc.write_off_amount),
Nabin Hait289ffb72016-02-08 11:06:55 +0530557 self.doc.precision("grand_total"))
Nabin Hait8d8cba72017-04-03 17:26:22 +0530558 else:
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530559 base_write_off_amount = flt(flt(self.doc.write_off_amount) * self.doc.conversion_rate,
Nabin Hait8d8cba72017-04-03 17:26:22 +0530560 self.doc.precision("base_write_off_amount"))
Faris Ansari6041f5c2018-02-08 13:33:52 +0530561 invoice_total = flt(grand_total * self.doc.conversion_rate,
Nabin Hait8d8cba72017-04-03 17:26:22 +0530562 self.doc.precision("grand_total")) - base_write_off_amount
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530563
Nabin Haitadc09232016-02-09 10:31:11 +0530564 if invoice_total > 0 and self.doc.total_advance > invoice_total:
Nabin Hait289ffb72016-02-08 11:06:55 +0530565 frappe.throw(_("Advance amount cannot be greater than {0} {1}")
566 .format(self.doc.party_account_currency, invoice_total))
Nabin Hait3237c752015-02-17 11:11:11 +0530567
Rushabh Mehta8bb6e532015-02-18 20:22:59 +0530568 if self.doc.docstatus == 0:
Nabin Hait3237c752015-02-17 11:11:11 +0530569 self.calculate_outstanding_amount()
570
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530571 def is_internal_invoice(self):
572 """
573 Checks if its an internal transfer invoice
574 and decides if to calculate any out standing amount or not
575 """
576
577 if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer():
578 return True
579
580 return False
581
Nabin Hait3237c752015-02-17 11:11:11 +0530582 def calculate_outstanding_amount(self):
583 # NOTE:
584 # write_off_amount is only for POS Invoice
585 # total_advance is only for non POS Invoice
Rohit Waghchaureefb5bf22016-08-25 02:09:53 +0530586 if self.doc.doctype == "Sales Invoice":
587 self.calculate_paid_amount()
588
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530589 if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \
590 self.is_internal_invoice(): return
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530591
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530592 self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530593 self._set_in_company_currency(self.doc, ['write_off_amount'])
594
Nabin Hait877e1bb2017-11-17 12:27:43 +0530595 if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
596 grand_total = self.doc.rounded_total or self.doc.grand_total
Deepesh Garg9d3a5c32022-01-06 18:58:49 +0530597 base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
598
Nabin Hait877e1bb2017-11-17 12:27:43 +0530599 if self.doc.party_account_currency == self.doc.currency:
Manas Solankida486ee2018-07-06 12:36:57 +0530600 total_amount_to_pay = flt(grand_total - self.doc.total_advance
Nabin Hait877e1bb2017-11-17 12:27:43 +0530601 - flt(self.doc.write_off_amount), self.doc.precision("grand_total"))
602 else:
Deepesh Garg9d3a5c32022-01-06 18:58:49 +0530603 total_amount_to_pay = flt(flt(base_grand_total, self.doc.precision("base_grand_total")) - self.doc.total_advance
604 - flt(self.doc.base_write_off_amount), self.doc.precision("base_grand_total"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530605
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530606 self.doc.round_floats_in(self.doc, ["paid_amount"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530607 change_amount = 0
608
Deepesh Garg0ebace52020-02-25 13:21:16 +0530609 if self.doc.doctype == "Sales Invoice" and not self.doc.get('is_return'):
Nabin Hait877e1bb2017-11-17 12:27:43 +0530610 self.calculate_write_off_amount()
611 self.calculate_change_amount()
612 change_amount = self.doc.change_amount \
613 if self.doc.party_account_currency == self.doc.currency else self.doc.base_change_amount
614
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530615 paid_amount = self.doc.paid_amount \
616 if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530617
Nabin Hait877e1bb2017-11-17 12:27:43 +0530618 self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount),
619 self.doc.precision("outstanding_amount"))
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530620
Deepesh Garg0ebace52020-02-25 13:21:16 +0530621 if self.doc.doctype == 'Sales Invoice' and self.doc.get('is_pos') and self.doc.get('is_return'):
Subin Tom7d627df2021-08-23 11:05:07 +0530622 self.set_total_amount_to_default_mop(total_amount_to_pay)
623 self.calculate_paid_amount()
Deepesh Garg0ebace52020-02-25 13:21:16 +0530624
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530625 def calculate_paid_amount(self):
Manas Solankida486ee2018-07-06 12:36:57 +0530626
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530627 paid_amount = base_paid_amount = 0.0
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530628
629 if self.doc.is_pos:
630 for payment in self.doc.get('payments'):
Ayush Shuklae9cf1ab2017-05-25 14:14:55 +0530631 payment.amount = flt(payment.amount)
632 payment.base_amount = payment.amount * flt(self.doc.conversion_rate)
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530633 paid_amount += payment.amount
634 base_paid_amount += payment.base_amount
rohitwaghchaure73456ac2017-05-16 11:29:57 +0530635 elif not self.doc.is_return:
636 self.doc.set('payments', [])
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530637
Manas Solankida486ee2018-07-06 12:36:57 +0530638 if self.doc.redeem_loyalty_points and self.doc.loyalty_amount:
639 base_paid_amount += self.doc.loyalty_amount
640 paid_amount += (self.doc.loyalty_amount / flt(self.doc.conversion_rate))
641
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530642 self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount"))
643 self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount"))
644
Nabin Hait3bb1a422016-08-02 16:41:10 +0530645 def calculate_change_amount(self):
646 self.doc.change_amount = 0.0
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530647 self.doc.base_change_amount = 0.0
Nabin Hait877e1bb2017-11-17 12:27:43 +0530648
649 if self.doc.doctype == "Sales Invoice" \
650 and self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \
Ankush Menat98917802021-06-11 18:40:22 +0530651 and any(d.type == "Cash" for d in self.doc.payments):
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530652 grand_total = self.doc.rounded_total or self.doc.grand_total
653 base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530654
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530655 self.doc.change_amount = flt(self.doc.paid_amount - grand_total +
Nabin Hait3bb1a422016-08-02 16:41:10 +0530656 self.doc.write_off_amount, self.doc.precision("change_amount"))
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530657
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530658 self.doc.base_change_amount = flt(self.doc.base_paid_amount - base_grand_total +
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530659 self.doc.base_write_off_amount, self.doc.precision("base_change_amount"))
mbauskar36b51892016-01-18 16:31:10 +0530660
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530661 def calculate_write_off_amount(self):
Rohit Waghchaurebaef2622016-08-05 15:41:36 +0530662 if flt(self.doc.change_amount) > 0:
Nabin Hait877e1bb2017-11-17 12:27:43 +0530663 self.doc.write_off_amount = flt(self.doc.grand_total - self.doc.paid_amount
664 + self.doc.change_amount, self.doc.precision("write_off_amount"))
Rohit Waghchaurebaef2622016-08-05 15:41:36 +0530665 self.doc.base_write_off_amount = flt(self.doc.write_off_amount * self.doc.conversion_rate,
666 self.doc.precision("base_write_off_amount"))
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530667
mbauskar36b51892016-01-18 16:31:10 +0530668 def calculate_margin(self, item):
Makarand Bauskar0e4c5c92017-05-11 11:40:02 +0530669 rate_with_margin = 0.0
Shreya Shahbe690ef2017-11-14 17:22:41 +0530670 base_rate_with_margin = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530671 if item.price_list_rate:
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530672 if item.pricing_rules and not self.doc.ignore_pricing_rule:
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530673 has_margin = False
marination733fd5f2020-08-26 18:23:12 +0530674 for d in get_applied_pricing_rules(item.pricing_rules):
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530675 pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
Shreya Shahf718b0c2018-02-20 11:26:46 +0530676
Rohit Waghchaured4d639c2021-02-01 22:58:22 +0530677 if pricing_rule.margin_rate_or_amount and ((pricing_rule.currency == self.doc.currency and
678 pricing_rule.margin_type in ['Amount', 'Percentage']) or pricing_rule.margin_type == 'Percentage'):
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530679 item.margin_type = pricing_rule.margin_type
680 item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530681 has_margin = True
682
683 if not has_margin:
684 item.margin_type = None
685 item.margin_rate_or_amount = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530686
Rohit Waghchaure01faa9c2021-06-21 00:59:02 +0530687 if not item.pricing_rules and flt(item.rate) > flt(item.price_list_rate):
688 item.margin_type = "Amount"
689 item.margin_rate_or_amount = flt(item.rate - item.price_list_rate,
690 item.precision("margin_rate_or_amount"))
691 item.rate_with_margin = item.rate
692
693 elif item.margin_type and item.margin_rate_or_amount:
mbauskara52472c2016-03-05 15:10:25 +0530694 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 +0530695 rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
Shreya Shahbe690ef2017-11-14 17:22:41 +0530696 base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
mbauskar36b51892016-01-18 16:31:10 +0530697
Shreya Shahbe690ef2017-11-14 17:22:41 +0530698 return rate_with_margin, base_rate_with_margin
Nabin Hait852cb642017-07-05 12:58:19 +0530699
700 def set_item_wise_tax_breakup(self):
Nabin Hait9c421612017-07-20 13:32:01 +0530701 self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530702
Subin Tom7d627df2021-08-23 11:05:07 +0530703 def set_total_amount_to_default_mop(self, total_amount_to_pay):
Saqiba6f98d42020-07-23 18:51:26 +0530704 default_mode_of_payment = frappe.db.get_value('POS Payment Method',
705 {'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
Deepesh Garg0ebace52020-02-25 13:21:16 +0530706
Deepesh Garg0ebace52020-02-25 13:21:16 +0530707 if default_mode_of_payment:
Afshanb6148342021-08-10 21:33:58 +0530708 self.doc.payments = []
Deepesh Garg0ebace52020-02-25 13:21:16 +0530709 self.doc.append('payments', {
710 'mode_of_payment': default_mode_of_payment.mode_of_payment,
Rucha Mahabaledee5302020-12-04 12:13:26 +0530711 'amount': total_amount_to_pay,
712 'default': 1
Ankush Menatb147b852021-09-01 16:45:57 +0530713 })
Deepesh Garg0ebace52020-02-25 13:21:16 +0530714
Nabin Hait9c421612017-07-20 13:32:01 +0530715def get_itemised_tax_breakup_html(doc):
716 if not doc.taxes:
717 return
718 frappe.flags.company = doc.company
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530719
Nabin Hait9c421612017-07-20 13:32:01 +0530720 # get headers
Nabin Haitcaab5822017-08-24 16:22:28 +0530721 tax_accounts = []
722 for tax in doc.taxes:
723 if getattr(tax, "category", None) and tax.category=="Valuation":
724 continue
rohitwaghchaure4e17fae2017-12-12 14:40:52 +0530725 if tax.description not in tax_accounts:
Nabin Haitcaab5822017-08-24 16:22:28 +0530726 tax_accounts.append(tax.description)
727
Nabin Hait9c421612017-07-20 13:32:01 +0530728 headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530729
Nabin Hait9c421612017-07-20 13:32:01 +0530730 # get tax breakup data
731 itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc)
Nabin Haitcaab5822017-08-24 16:22:28 +0530732
733 get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes"))
734
rohitwaghchaured4526682017-12-28 14:20:13 +0530735 update_itemised_tax_data(doc)
Nabin Hait9c421612017-07-20 13:32:01 +0530736 frappe.flags.company = None
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530737
Nabin Hait9c421612017-07-20 13:32:01 +0530738 return frappe.render_template(
739 "templates/includes/itemised_tax_breakup.html", dict(
740 headers=headers,
741 itemised_tax=itemised_tax,
742 itemised_taxable_amount=itemised_taxable_amount,
743 tax_accounts=tax_accounts,
Saqib Ansari10a6a2d2020-04-13 16:40:13 +0530744 doc=doc
Nabin Haitb962fc12017-07-17 18:02:31 +0530745 )
Nabin Hait9c421612017-07-20 13:32:01 +0530746 )
Nabin Hait852cb642017-07-05 12:58:19 +0530747
Deepesh Garg6a5ef262021-02-19 14:30:23 +0530748@frappe.whitelist()
749def get_round_off_applicable_accounts(company, account_list):
750 account_list = get_regional_round_off_accounts(company, account_list)
751
752 return account_list
753
754@erpnext.allow_regional
755def get_regional_round_off_accounts(company, account_list):
756 pass
rohitwaghchaured4526682017-12-28 14:20:13 +0530757
758@erpnext.allow_regional
759def update_itemised_tax_data(doc):
760 #Don't delete this method, used for localization
761 pass
762
Nabin Haitb962fc12017-07-17 18:02:31 +0530763@erpnext.allow_regional
764def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
765 return [_("Item"), _("Taxable Amount")] + tax_accounts
766
767@erpnext.allow_regional
768def get_itemised_tax_breakup_data(doc):
769 itemised_tax = get_itemised_tax(doc.taxes)
770
771 itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
772
773 return itemised_tax, itemised_taxable_amount
774
Nabin Hait34c551d2019-07-03 10:34:31 +0530775def get_itemised_tax(taxes, with_tax_account=False):
Nabin Haitb962fc12017-07-17 18:02:31 +0530776 itemised_tax = {}
777 for tax in taxes:
Nabin Haitcaab5822017-08-24 16:22:28 +0530778 if getattr(tax, "category", None) and tax.category=="Valuation":
779 continue
780
Nabin Haitb962fc12017-07-17 18:02:31 +0530781 item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
Nabin Hait2e4de832017-09-19 14:53:16 +0530782 if item_tax_map:
783 for item_code, tax_data in item_tax_map.items():
784 itemised_tax.setdefault(item_code, frappe._dict())
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530785
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530786 tax_rate = 0.0
787 tax_amount = 0.0
788
Nabin Hait2e4de832017-09-19 14:53:16 +0530789 if isinstance(tax_data, list):
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530790 tax_rate = flt(tax_data[0])
791 tax_amount = flt(tax_data[1])
Nabin Hait2e4de832017-09-19 14:53:16 +0530792 else:
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530793 tax_rate = flt(tax_data)
794
795 itemised_tax[item_code][tax.description] = frappe._dict(dict(
796 tax_rate = tax_rate,
797 tax_amount = tax_amount
798 ))
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530799
Nabin Hait34c551d2019-07-03 10:34:31 +0530800 if with_tax_account:
801 itemised_tax[item_code][tax.description].tax_account = tax.account_head
802
Nabin Haitb962fc12017-07-17 18:02:31 +0530803 return itemised_tax
804
805def get_itemised_taxable_amount(items):
806 itemised_taxable_amount = frappe._dict()
807 for item in items:
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530808 item_code = item.item_code or item.item_name
Nabin Haitb962fc12017-07-17 18:02:31 +0530809 itemised_taxable_amount.setdefault(item_code, 0)
810 itemised_taxable_amount[item_code] += item.net_amount
811
Nabin Haitcaab5822017-08-24 16:22:28 +0530812 return itemised_taxable_amount
813
814def get_rounded_tax_amount(itemised_tax, precision):
815 # Rounding based on tax_amount precision
816 for taxes in itemised_tax.values():
817 for tax_account in taxes:
Himanshu Mishra35b26272018-11-13 11:13:04 +0530818 taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530819
820class init_landed_taxes_and_totals(object):
821 def __init__(self, doc):
822 self.doc = doc
823 self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
824 self.set_account_currency()
825 self.set_exchange_rate()
826 self.set_amounts_in_company_currency()
827
828 def set_account_currency(self):
829 company_currency = erpnext.get_company_currency(self.doc.company)
830 for d in self.doc.get(self.tax_field):
831 if not d.account_currency:
832 account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
833 d.account_currency = account_currency or company_currency
834
835 def set_exchange_rate(self):
836 company_currency = erpnext.get_company_currency(self.doc.company)
837 for d in self.doc.get(self.tax_field):
838 if d.account_currency == company_currency:
839 d.exchange_rate = 1
Deepesh Garg22f5ff82021-03-17 10:56:52 +0530840 elif not d.exchange_rate:
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530841 d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
842 account_currency=d.account_currency, company=self.doc.company)
843
844 if not d.exchange_rate:
845 frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
846
847 def set_amounts_in_company_currency(self):
848 for d in self.doc.get(self.tax_field):
849 d.amount = flt(d.amount, d.precision("amount"))
Walstan Baptista37b826b2021-04-03 19:48:46 +0530850 d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))