blob: 075e3e38fa5f69261b506f2fe43c16a3fd807a45 [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"))
Saqib Ansari04ea42c2021-12-22 13:23:42 +0530142 elif not item.qty and self.doc.get("is_debit_note"):
143 item.amount = flt(item.rate, item.precision("amount"))
Deepesh Gargb65c7612019-07-31 15:58:01 +0530144 else:
145 item.amount = flt(item.rate * item.qty, item.precision("amount"))
146
Nabin Haite7679702015-02-20 14:40:35 +0530147 item.net_amount = item.amount
Nabin Hait3237c752015-02-17 11:11:11 +0530148
Nabin Haite7679702015-02-20 14:40:35 +0530149 self._set_in_company_currency(item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"])
Nabin Hait3237c752015-02-17 11:11:11 +0530150
Nabin Haite7679702015-02-20 14:40:35 +0530151 item.item_tax_amount = 0.0
152
153 def _set_in_company_currency(self, doc, fields):
Nabin Hait3237c752015-02-17 11:11:11 +0530154 """set values in base currency"""
Nabin Haite7679702015-02-20 14:40:35 +0530155 for f in fields:
156 val = flt(flt(doc.get(f), doc.precision(f)) * self.doc.conversion_rate, doc.precision("base_" + f))
157 doc.set("base_" + f, val)
Nabin Hait3237c752015-02-17 11:11:11 +0530158
159 def initialize_taxes(self):
160 for tax in self.doc.get("taxes"):
Nabin Hait86cd4cc2015-02-28 19:11:51 +0530161 if not self.discount_amount_applied:
162 validate_taxes_and_charges(tax)
163 validate_inclusive_tax(tax, self.doc)
Nabin Hait613d0812015-02-23 11:58:15 +0530164
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530165 if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530166 tax.item_wise_tax_detail = {}
167
Nabin Hait3237c752015-02-17 11:11:11 +0530168 tax_fields = ["total", "tax_amount_after_discount_amount",
169 "tax_amount_for_current_item", "grand_total_for_current_item",
170 "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
171
Nabin Haitde9c8a92015-02-23 01:06:00 +0530172 if tax.charge_type != "Actual" and \
173 not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
174 tax_fields.append("tax_amount")
Nabin Hait3237c752015-02-17 11:11:11 +0530175
176 for fieldname in tax_fields:
177 tax.set(fieldname, 0.0)
178
Nabin Hait3237c752015-02-17 11:11:11 +0530179 self.doc.round_floats_in(tax)
180
Nabin Hait3237c752015-02-17 11:11:11 +0530181 def determine_exclusive_rate(self):
Ankush Menat8fe5feb2021-11-04 19:48:32 +0530182 if not any(cint(tax.included_in_print_rate) for tax in self.doc.get("taxes")):
Nabin Hait37b047d2015-02-23 16:01:33 +0530183 return
Nabin Hait3237c752015-02-17 11:11:11 +0530184
185 for item in self.doc.get("items"):
186 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
187 cumulated_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530188 total_inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530189 for i, tax in enumerate(self.doc.get("taxes")):
Nabin Hait19ea7212020-08-11 20:34:57 +0530190 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 +0530191
192 if i==0:
193 tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
194 else:
195 tax.grand_total_fraction_for_current_item = \
196 self.doc.get("taxes")[i-1].grand_total_fraction_for_current_item \
197 + tax.tax_fraction_for_current_item
198
199 cumulated_tax_fraction += tax.tax_fraction_for_current_item
Nabin Haite2ee4552020-08-12 20:58:03 +0530200 total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
Nabin Hait3237c752015-02-17 11:11:11 +0530201
Nabin Hait19ea7212020-08-11 20:34:57 +0530202 if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty):
203 amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
204
205 item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
Nabin Haite7679702015-02-20 14:40:35 +0530206 item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
Nabin Hait2e4de832017-09-19 14:53:16 +0530207 item.discount_percentage = flt(item.discount_percentage,
208 item.precision("discount_percentage"))
Nabin Hait3237c752015-02-17 11:11:11 +0530209
Nabin Haite7679702015-02-20 14:40:35 +0530210 self._set_in_company_currency(item, ["net_rate", "net_amount"])
211
Nabin Hait3237c752015-02-17 11:11:11 +0530212 def _load_item_tax_rate(self, item_tax_rate):
213 return json.loads(item_tax_rate) if item_tax_rate else {}
214
215 def get_current_tax_fraction(self, tax, item_tax_map):
216 """
217 Get tax fraction for calculating tax exclusive amount
218 from tax inclusive amount
219 """
220 current_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530221 inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530222
223 if cint(tax.included_in_print_rate):
224 tax_rate = self._get_tax_rate(tax, item_tax_map)
225
226 if tax.charge_type == "On Net Total":
227 current_tax_fraction = tax_rate / 100.0
228
229 elif tax.charge_type == "On Previous Row Amount":
230 current_tax_fraction = (tax_rate / 100.0) * \
231 self.doc.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
232
233 elif tax.charge_type == "On Previous Row Total":
234 current_tax_fraction = (tax_rate / 100.0) * \
235 self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
marination733fd5f2020-08-26 18:23:12 +0530236
Nabin Hait19ea7212020-08-11 20:34:57 +0530237 elif tax.charge_type == "On Item Quantity":
238 inclusive_tax_amount_per_qty = flt(tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530239
Nabin Hait19ea7212020-08-11 20:34:57 +0530240 if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
241 current_tax_fraction *= -1.0
242 inclusive_tax_amount_per_qty *= -1.0
243
244 return current_tax_fraction, inclusive_tax_amount_per_qty
Nabin Hait3237c752015-02-17 11:11:11 +0530245
246 def _get_tax_rate(self, tax, item_tax_map):
Achilles Rasquinha87dab142018-03-08 14:21:48 +0530247 if tax.account_head in item_tax_map:
Nabin Hait3237c752015-02-17 11:11:11 +0530248 return flt(item_tax_map.get(tax.account_head), self.doc.precision("rate", tax))
249 else:
250 return tax.rate
251
252 def calculate_net_total(self):
Shreya Shahe3290382018-05-28 11:49:08 +0530253 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 +0530254
Nabin Hait3237c752015-02-17 11:11:11 +0530255 for item in self.doc.get("items"):
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530256 self.doc.total += item.amount
Shreya Shahe3290382018-05-28 11:49:08 +0530257 self.doc.total_qty += item.qty
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530258 self.doc.base_total += item.base_amount
Nabin Haite7679702015-02-20 14:40:35 +0530259 self.doc.net_total += item.net_amount
260 self.doc.base_net_total += item.base_net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530261
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530262 self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530263
Subin Toma8e2c022021-11-16 19:06:49 +0530264 def calculate_shipping_charges(self):
Subin Tomaf1fce02021-11-10 16:49:12 +0530265 if hasattr(self.doc, "shipping_rule") and self.doc.shipping_rule:
Subin Tom18ae03d2021-11-10 15:57:41 +0530266 shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule)
267 shipping_rule.apply(self.doc)
268
Nabin Hait3237c752015-02-17 11:11:11 +0530269 def calculate_taxes(self):
Subin Tom75a76e62021-10-29 16:45:04 +0530270 if not self.doc.get('is_consolidated'):
271 self.doc.rounding_adjustment = 0
272
Nabin Hait3237c752015-02-17 11:11:11 +0530273 # maintain actual tax rate based on idx
Nabin Haite7679702015-02-20 14:40:35 +0530274 actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
Nabin Hait3237c752015-02-17 11:11:11 +0530275 for tax in self.doc.get("taxes") if tax.charge_type == "Actual"])
276
277 for n, item in enumerate(self.doc.get("items")):
278 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530279 for i, tax in enumerate(self.doc.get("taxes")):
280 # tax_amount represents the amount of tax for the current step
281 current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map)
282
283 # Adjust divisional loss to the last item
284 if tax.charge_type == "Actual":
285 actual_tax_dict[tax.idx] -= current_tax_amount
286 if n == len(self.doc.get("items")) - 1:
287 current_tax_amount += actual_tax_dict[tax.idx]
288
Nabin Hait2b019ed2015-02-22 23:03:07 +0530289 # accumulate tax amount into tax.tax_amount
Nabin Haitde9c8a92015-02-23 01:06:00 +0530290 if tax.charge_type != "Actual" and \
291 not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
292 tax.tax_amount += current_tax_amount
Nabin Hait2b019ed2015-02-22 23:03:07 +0530293
Nabin Hait3237c752015-02-17 11:11:11 +0530294 # store tax_amount for current item as it will be used for
295 # charge type = 'On Previous Row Amount'
296 tax.tax_amount_for_current_item = current_tax_amount
297
Nabin Hait2b019ed2015-02-22 23:03:07 +0530298 # set tax after discount
Nabin Hait3237c752015-02-17 11:11:11 +0530299 tax.tax_amount_after_discount_amount += current_tax_amount
300
Nabin Haitcd951342017-07-31 18:07:45 +0530301 current_tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(current_tax_amount, tax)
Nabin Hait3237c752015-02-17 11:11:11 +0530302
Nabin Hait3237c752015-02-17 11:11:11 +0530303 # note: grand_total_for_current_item contains the contribution of
304 # item's amount, previously applied tax and the current tax on that item
305 if i==0:
Nabin Haitcd951342017-07-31 18:07:45 +0530306 tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530307 else:
308 tax.grand_total_for_current_item = \
Nabin Haitcd951342017-07-31 18:07:45 +0530309 flt(self.doc.get("taxes")[i-1].grand_total_for_current_item + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530310
311 # set precision in the last item iteration
312 if n == len(self.doc.get("items")) - 1:
313 self.round_off_totals(tax)
Deepesh Gargb6705882021-04-14 11:20:27 +0530314 self._set_in_company_currency(tax,
315 ["tax_amount", "tax_amount_after_discount_amount"])
316
317 self.round_off_base_values(tax)
Nabin Haitcd951342017-07-31 18:07:45 +0530318 self.set_cumulative_total(i, tax)
319
Deepesh Gargb6705882021-04-14 11:20:27 +0530320 self._set_in_company_currency(tax, ["total"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530321
Nabin Hait3237c752015-02-17 11:11:11 +0530322 # adjust Discount Amount loss in last tax iteration
Nabin Haitde9c8a92015-02-23 01:06:00 +0530323 if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
Subin Tom75a76e62021-10-29 16:45:04 +0530324 and self.doc.discount_amount \
325 and self.doc.apply_discount_on == "Grand Total" \
326 and not self.doc.get('is_consolidated'):
Nabin Hait2e4de832017-09-19 14:53:16 +0530327 self.doc.rounding_adjustment = flt(self.doc.grand_total
328 - flt(self.doc.discount_amount) - tax.total,
329 self.doc.precision("rounding_adjustment"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530330
Nabin Haitcd951342017-07-31 18:07:45 +0530331 def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
332 # if just for valuation, do not add the tax amount in total
333 # if tax/charges is for deduction, multiply by -1
334 if getattr(tax, "category", None):
335 tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
Rushabh Mehta30dc9a12017-11-17 14:31:09 +0530336 if self.doc.doctype in ["Purchase Order", "Purchase Invoice", "Purchase Receipt", "Supplier Quotation"]:
337 tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
Nabin Haitcd951342017-07-31 18:07:45 +0530338 return tax_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530339
Nabin Haitcd951342017-07-31 18:07:45 +0530340 def set_cumulative_total(self, row_idx, tax):
341 tax_amount = tax.tax_amount_after_discount_amount
342 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax_amount, tax)
343
344 if row_idx == 0:
345 tax.total = flt(self.doc.net_total + tax_amount, tax.precision("total"))
346 else:
347 tax.total = flt(self.doc.get("taxes")[row_idx-1].total + tax_amount, tax.precision("total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530348
349 def get_current_tax_amount(self, item, tax, item_tax_map):
350 tax_rate = self._get_tax_rate(tax, item_tax_map)
351 current_tax_amount = 0.0
352
353 if tax.charge_type == "Actual":
354 # distribute the tax amount proportionally to each item row
Nabin Haite7679702015-02-20 14:40:35 +0530355 actual = flt(tax.tax_amount, tax.precision("tax_amount"))
356 current_tax_amount = item.net_amount*actual / self.doc.net_total if self.doc.net_total else 0.0
357
Nabin Hait3237c752015-02-17 11:11:11 +0530358 elif tax.charge_type == "On Net Total":
Nabin Haite7679702015-02-20 14:40:35 +0530359 current_tax_amount = (tax_rate / 100.0) * item.net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530360 elif tax.charge_type == "On Previous Row Amount":
361 current_tax_amount = (tax_rate / 100.0) * \
362 self.doc.get("taxes")[cint(tax.row_id) - 1].tax_amount_for_current_item
363 elif tax.charge_type == "On Previous Row Total":
364 current_tax_amount = (tax_rate / 100.0) * \
365 self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
Himanshu Mishra35b26272018-11-13 11:13:04 +0530366 elif tax.charge_type == "On Item Quantity":
Nabin Hait19ea7212020-08-11 20:34:57 +0530367 current_tax_amount = tax_rate * item.qty
Nabin Hait3237c752015-02-17 11:11:11 +0530368
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530369 if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530370 self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530371
372 return current_tax_amount
373
Nabin Haite7679702015-02-20 14:40:35 +0530374 def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
375 # store tax breakup for each item
376 key = item.item_code or item.item_name
377 item_wise_tax_amount = current_tax_amount*self.doc.conversion_rate
378 if tax.item_wise_tax_detail.get(key):
379 item_wise_tax_amount += tax.item_wise_tax_detail[key][1]
380
Nabin Haitcaab5822017-08-24 16:22:28 +0530381 tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
Nabin Haite7679702015-02-20 14:40:35 +0530382
Nabin Hait3237c752015-02-17 11:11:11 +0530383 def round_off_totals(self, tax):
Deepesh Gargb6705882021-04-14 11:20:27 +0530384 if tax.account_head in frappe.flags.round_off_applicable_accounts:
385 tax.tax_amount = round(tax.tax_amount, 0)
386 tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
387
Nabin Haite7679702015-02-20 14:40:35 +0530388 tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530389 tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
Nabin Haitcd951342017-07-31 18:07:45 +0530390 tax.precision("tax_amount"))
Nabin Haitce245122015-02-22 20:14:49 +0530391
Deepesh Gargb6705882021-04-14 11:20:27 +0530392 def round_off_base_values(self, tax):
393 # Round off to nearest integer based on regional settings
394 if tax.account_head in frappe.flags.round_off_applicable_accounts:
395 tax.base_tax_amount = round(tax.base_tax_amount, 0)
396 tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
397
Nabin Haita1bf43b2015-03-17 10:50:47 +0530398 def manipulate_grand_total_for_inclusive_tax(self):
399 # if fully inclusive taxes and diff
Ankush Menat98917802021-06-11 18:40:22 +0530400 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 +0530401 last_tax = self.doc.get("taxes")[-1]
Ankush Menat98917802021-06-11 18:40:22 +0530402 non_inclusive_tax_amount = sum(flt(d.tax_amount_after_discount_amount)
403 for d in self.doc.get("taxes") if not d.included_in_print_rate)
Nabin Haitf32fc232019-12-25 13:59:24 +0530404
Nabin Hait2e4de832017-09-19 14:53:16 +0530405 diff = self.doc.total + non_inclusive_tax_amount \
406 - flt(last_tax.total, last_tax.precision("total"))
Nabin Haitf32fc232019-12-25 13:59:24 +0530407
408 # If discount amount applied, deduct the discount amount
409 # because self.doc.total is always without discount, but last_tax.total is after discount
410 if self.discount_amount_applied and self.doc.discount_amount:
411 diff -= flt(self.doc.discount_amount)
412
413 diff = flt(diff, self.doc.precision("rounding_adjustment"))
414
Nabin Hait2e4de832017-09-19 14:53:16 +0530415 if diff and abs(diff) <= (5.0 / 10**last_tax.precision("tax_amount")):
Nabin Haitf32fc232019-12-25 13:59:24 +0530416 self.doc.rounding_adjustment = diff
Nabin Hait3237c752015-02-17 11:11:11 +0530417
418 def calculate_totals(self):
Subin Tom75a76e62021-10-29 16:45:04 +0530419 if self.doc.get("taxes"):
420 self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment)
421 else:
422 self.doc.grand_total = flt(self.doc.net_total)
Nabin Hait3237c752015-02-17 11:11:11 +0530423
Subin Tom75a76e62021-10-29 16:45:04 +0530424 if self.doc.get("taxes"):
425 self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total
Nabin Hait2e4de832017-09-19 14:53:16 +0530426 - flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges"))
Subin Tom75a76e62021-10-29 16:45:04 +0530427 else:
428 self.doc.total_taxes_and_charges = 0.0
Anand Doshiec5ec602015-03-05 19:31:23 +0530429
Nabin Hait2e4de832017-09-19 14:53:16 +0530430 self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530431
Saqiba6f98d42020-07-23 18:51:26 +0530432 if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]:
Anurag Mishra8a06b8f2019-07-17 14:55:16 +0530433 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 +0530434 if self.doc.total_taxes_and_charges else self.doc.base_net_total
Nabin Hait3237c752015-02-17 11:11:11 +0530435 else:
Anand Doshiec5ec602015-03-05 19:31:23 +0530436 self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0
Nabin Hait3237c752015-02-17 11:11:11 +0530437 for tax in self.doc.get("taxes"):
438 if tax.category in ["Valuation and Total", "Total"]:
439 if tax.add_deduct_tax == "Add":
Nabin Haitdb53a782015-07-31 16:53:13 +0530440 self.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530441 else:
Nabin Haitdb53a782015-07-31 16:53:13 +0530442 self.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530443
Nabin Haite7679702015-02-20 14:40:35 +0530444 self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530445
Nabin Haite7679702015-02-20 14:40:35 +0530446 self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
447 if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
448 else self.doc.base_net_total
Nabin Hait3237c752015-02-17 11:11:11 +0530449
Nabin Hait2e4de832017-09-19 14:53:16 +0530450 self._set_in_company_currency(self.doc,
451 ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530452
Nabin Haite7679702015-02-20 14:40:35 +0530453 self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530454
Nabin Hait2e4de832017-09-19 14:53:16 +0530455 self.set_rounded_total()
456
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +0530457 def calculate_total_net_weight(self):
458 if self.doc.meta.get_field('total_net_weight'):
459 self.doc.total_net_weight = 0.0
460 for d in self.doc.items:
461 if d.total_weight:
462 self.doc.total_net_weight += d.total_weight
463
Nabin Hait2e4de832017-09-19 14:53:16 +0530464 def set_rounded_total(self):
Subin Tom75a76e62021-10-29 16:45:04 +0530465 if not self.doc.get('is_consolidated'):
466 if self.doc.meta.get_field("rounded_total"):
467 if self.doc.is_rounded_total_disabled():
468 self.doc.rounded_total = self.doc.base_rounded_total = 0
469 return
Nabin Hait877e1bb2017-11-17 12:27:43 +0530470
Subin Tom75a76e62021-10-29 16:45:04 +0530471 self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
472 self.doc.currency, self.doc.precision("rounded_total"))
Nabin Hait2e4de832017-09-19 14:53:16 +0530473
Subin Tom75a76e62021-10-29 16:45:04 +0530474 #if print_in_rate is set, we would have already calculated rounding adjustment
475 self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
476 self.doc.precision("rounding_adjustment"))
Nabin Hait877e1bb2017-11-17 12:27:43 +0530477
Subin Tom75a76e62021-10-29 16:45:04 +0530478 self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530479
Nabin Hait3237c752015-02-17 11:11:11 +0530480 def _cleanup(self):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530481 if not self.doc.get('is_consolidated'):
482 for tax in self.doc.get("taxes"):
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530483 if not tax.get("dont_recompute_tax"):
484 tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530485
Nabin Hait3769d872015-12-18 13:12:02 +0530486 def set_discount_amount(self):
Nabin Haite0405102016-10-13 12:14:32 +0530487 if self.doc.additional_discount_percentage:
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530488 self.doc.discount_amount = flt(flt(self.doc.get(scrub(self.doc.apply_discount_on)))
Nabin Hait3769d872015-12-18 13:12:02 +0530489 * self.doc.additional_discount_percentage / 100, self.doc.precision("discount_amount"))
Nabin Hait3237c752015-02-17 11:11:11 +0530490
491 def apply_discount_amount(self):
492 if self.doc.discount_amount:
Nabin Hait37b047d2015-02-23 16:01:33 +0530493 if not self.doc.apply_discount_on:
494 frappe.throw(_("Please select Apply Discount On"))
495
Nabin Hait3237c752015-02-17 11:11:11 +0530496 self.doc.base_discount_amount = flt(self.doc.discount_amount * self.doc.conversion_rate,
497 self.doc.precision("base_discount_amount"))
498
Nabin Haite7679702015-02-20 14:40:35 +0530499 total_for_discount_amount = self.get_total_for_discount_amount()
Nabin Hait25bd84d2015-03-04 15:06:56 +0530500 taxes = self.doc.get("taxes")
501 net_total = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530502
Nabin Haite7679702015-02-20 14:40:35 +0530503 if total_for_discount_amount:
Nabin Hait3237c752015-02-17 11:11:11 +0530504 # calculate item amount after Discount Amount
Nabin Hait25bd84d2015-03-04 15:06:56 +0530505 for i, item in enumerate(self.doc.get("items")):
506 distributed_amount = flt(self.doc.discount_amount) * \
507 item.net_amount / total_for_discount_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530508
Nabin Haite7679702015-02-20 14:40:35 +0530509 item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
Nabin Hait25bd84d2015-03-04 15:06:56 +0530510 net_total += item.net_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530511
Nabin Hait25bd84d2015-03-04 15:06:56 +0530512 # discount amount rounding loss adjustment if no taxes
Nabin Hait4d587342019-05-30 15:50:46 +0530513 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 +0530514 and i == len(self.doc.get("items")) - 1:
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530515 discount_amount_loss = flt(self.doc.net_total - net_total - self.doc.discount_amount,
Nabin Hait25bd84d2015-03-04 15:06:56 +0530516 self.doc.precision("net_total"))
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530517
Anand Doshiec5ec602015-03-05 19:31:23 +0530518 item.net_amount = flt(item.net_amount + discount_amount_loss,
Nabin Hait25bd84d2015-03-04 15:06:56 +0530519 item.precision("net_amount"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530520
Nabin Hait51e980d2015-10-10 18:10:05 +0530521 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 +0530522
Nabin Haite7679702015-02-20 14:40:35 +0530523 self._set_in_company_currency(item, ["net_rate", "net_amount"])
Nabin Hait3237c752015-02-17 11:11:11 +0530524
525 self.discount_amount_applied = True
526 self._calculate()
527 else:
528 self.doc.base_discount_amount = 0
529
Nabin Haite7679702015-02-20 14:40:35 +0530530 def get_total_for_discount_amount(self):
Nabin Haitde9c8a92015-02-23 01:06:00 +0530531 if self.doc.apply_discount_on == "Net Total":
532 return self.doc.net_total
Nabin Haite7679702015-02-20 14:40:35 +0530533 else:
534 actual_taxes_dict = {}
Nabin Hait3237c752015-02-17 11:11:11 +0530535
Nabin Haite7679702015-02-20 14:40:35 +0530536 for tax in self.doc.get("taxes"):
Nabin Hait19ea7212020-08-11 20:34:57 +0530537 if tax.charge_type in ["Actual", "On Item Quantity"]:
Nabin Haitaf9bdfe2017-12-12 18:50:05 +0530538 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
539 actual_taxes_dict.setdefault(tax.idx, tax_amount)
Nabin Haite7679702015-02-20 14:40:35 +0530540 elif tax.row_id in actual_taxes_dict:
541 actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * flt(tax.rate) / 100
542 actual_taxes_dict.setdefault(tax.idx, actual_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530543
Nabin Hait877e1bb2017-11-17 12:27:43 +0530544 return flt(self.doc.grand_total - sum(actual_taxes_dict.values()),
545 self.doc.precision("grand_total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530546
547
Nabin Hait7b19b9e2015-02-24 09:42:24 +0530548 def calculate_total_advance(self):
549 if self.doc.docstatus < 2:
Ankush Menat98917802021-06-11 18:40:22 +0530550 total_allocated_amount = sum(flt(adv.allocated_amount, adv.precision("allocated_amount"))
551 for adv in self.doc.get("advances"))
Nabin Hait3237c752015-02-17 11:11:11 +0530552
Nabin Haite7679702015-02-20 14:40:35 +0530553 self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530554
Faris Ansari6041f5c2018-02-08 13:33:52 +0530555 grand_total = self.doc.rounded_total or self.doc.grand_total
556
Nabin Hait289ffb72016-02-08 11:06:55 +0530557 if self.doc.party_account_currency == self.doc.currency:
Faris Ansari6041f5c2018-02-08 13:33:52 +0530558 invoice_total = flt(grand_total - flt(self.doc.write_off_amount),
Nabin Hait289ffb72016-02-08 11:06:55 +0530559 self.doc.precision("grand_total"))
Nabin Hait8d8cba72017-04-03 17:26:22 +0530560 else:
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530561 base_write_off_amount = flt(flt(self.doc.write_off_amount) * self.doc.conversion_rate,
Nabin Hait8d8cba72017-04-03 17:26:22 +0530562 self.doc.precision("base_write_off_amount"))
Faris Ansari6041f5c2018-02-08 13:33:52 +0530563 invoice_total = flt(grand_total * self.doc.conversion_rate,
Nabin Hait8d8cba72017-04-03 17:26:22 +0530564 self.doc.precision("grand_total")) - base_write_off_amount
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530565
Nabin Haitadc09232016-02-09 10:31:11 +0530566 if invoice_total > 0 and self.doc.total_advance > invoice_total:
Nabin Hait289ffb72016-02-08 11:06:55 +0530567 frappe.throw(_("Advance amount cannot be greater than {0} {1}")
568 .format(self.doc.party_account_currency, invoice_total))
Nabin Hait3237c752015-02-17 11:11:11 +0530569
Rushabh Mehta8bb6e532015-02-18 20:22:59 +0530570 if self.doc.docstatus == 0:
Nabin Hait3237c752015-02-17 11:11:11 +0530571 self.calculate_outstanding_amount()
572
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530573 def is_internal_invoice(self):
574 """
575 Checks if its an internal transfer invoice
576 and decides if to calculate any out standing amount or not
577 """
578
579 if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer():
580 return True
581
582 return False
583
Nabin Hait3237c752015-02-17 11:11:11 +0530584 def calculate_outstanding_amount(self):
585 # NOTE:
586 # write_off_amount is only for POS Invoice
587 # total_advance is only for non POS Invoice
Rohit Waghchaureefb5bf22016-08-25 02:09:53 +0530588 if self.doc.doctype == "Sales Invoice":
589 self.calculate_paid_amount()
590
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530591 if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \
592 self.is_internal_invoice(): return
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530593
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530594 self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530595 self._set_in_company_currency(self.doc, ['write_off_amount'])
596
Nabin Hait877e1bb2017-11-17 12:27:43 +0530597 if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
598 grand_total = self.doc.rounded_total or self.doc.grand_total
Deepesh Garg9d3a5c32022-01-06 18:58:49 +0530599 base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
600
Nabin Hait877e1bb2017-11-17 12:27:43 +0530601 if self.doc.party_account_currency == self.doc.currency:
Manas Solankida486ee2018-07-06 12:36:57 +0530602 total_amount_to_pay = flt(grand_total - self.doc.total_advance
Nabin Hait877e1bb2017-11-17 12:27:43 +0530603 - flt(self.doc.write_off_amount), self.doc.precision("grand_total"))
604 else:
Deepesh Garg9d3a5c32022-01-06 18:58:49 +0530605 total_amount_to_pay = flt(flt(base_grand_total, self.doc.precision("base_grand_total")) - self.doc.total_advance
606 - flt(self.doc.base_write_off_amount), self.doc.precision("base_grand_total"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530607
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530608 self.doc.round_floats_in(self.doc, ["paid_amount"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530609 change_amount = 0
610
Deepesh Garg0ebace52020-02-25 13:21:16 +0530611 if self.doc.doctype == "Sales Invoice" and not self.doc.get('is_return'):
Nabin Hait877e1bb2017-11-17 12:27:43 +0530612 self.calculate_write_off_amount()
613 self.calculate_change_amount()
614 change_amount = self.doc.change_amount \
615 if self.doc.party_account_currency == self.doc.currency else self.doc.base_change_amount
616
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530617 paid_amount = self.doc.paid_amount \
618 if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530619
Nabin Hait877e1bb2017-11-17 12:27:43 +0530620 self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount),
621 self.doc.precision("outstanding_amount"))
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530622
Deepesh Garg0ebace52020-02-25 13:21:16 +0530623 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 +0530624 self.set_total_amount_to_default_mop(total_amount_to_pay)
625 self.calculate_paid_amount()
Deepesh Garg0ebace52020-02-25 13:21:16 +0530626
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530627 def calculate_paid_amount(self):
Manas Solankida486ee2018-07-06 12:36:57 +0530628
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530629 paid_amount = base_paid_amount = 0.0
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530630
631 if self.doc.is_pos:
632 for payment in self.doc.get('payments'):
Ayush Shuklae9cf1ab2017-05-25 14:14:55 +0530633 payment.amount = flt(payment.amount)
634 payment.base_amount = payment.amount * flt(self.doc.conversion_rate)
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530635 paid_amount += payment.amount
636 base_paid_amount += payment.base_amount
rohitwaghchaure73456ac2017-05-16 11:29:57 +0530637 elif not self.doc.is_return:
638 self.doc.set('payments', [])
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530639
Manas Solankida486ee2018-07-06 12:36:57 +0530640 if self.doc.redeem_loyalty_points and self.doc.loyalty_amount:
641 base_paid_amount += self.doc.loyalty_amount
642 paid_amount += (self.doc.loyalty_amount / flt(self.doc.conversion_rate))
643
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530644 self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount"))
645 self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount"))
646
Nabin Hait3bb1a422016-08-02 16:41:10 +0530647 def calculate_change_amount(self):
648 self.doc.change_amount = 0.0
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530649 self.doc.base_change_amount = 0.0
Nabin Hait877e1bb2017-11-17 12:27:43 +0530650
651 if self.doc.doctype == "Sales Invoice" \
652 and self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \
Ankush Menat98917802021-06-11 18:40:22 +0530653 and any(d.type == "Cash" for d in self.doc.payments):
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530654 grand_total = self.doc.rounded_total or self.doc.grand_total
655 base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530656
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530657 self.doc.change_amount = flt(self.doc.paid_amount - grand_total +
Nabin Hait3bb1a422016-08-02 16:41:10 +0530658 self.doc.write_off_amount, self.doc.precision("change_amount"))
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530659
Rohit Waghchauree8d22bb2018-02-05 18:13:29 +0530660 self.doc.base_change_amount = flt(self.doc.base_paid_amount - base_grand_total +
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530661 self.doc.base_write_off_amount, self.doc.precision("base_change_amount"))
mbauskar36b51892016-01-18 16:31:10 +0530662
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530663 def calculate_write_off_amount(self):
Rohit Waghchaurebaef2622016-08-05 15:41:36 +0530664 if flt(self.doc.change_amount) > 0:
Nabin Hait877e1bb2017-11-17 12:27:43 +0530665 self.doc.write_off_amount = flt(self.doc.grand_total - self.doc.paid_amount
666 + self.doc.change_amount, self.doc.precision("write_off_amount"))
Rohit Waghchaurebaef2622016-08-05 15:41:36 +0530667 self.doc.base_write_off_amount = flt(self.doc.write_off_amount * self.doc.conversion_rate,
668 self.doc.precision("base_write_off_amount"))
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530669
mbauskar36b51892016-01-18 16:31:10 +0530670 def calculate_margin(self, item):
Makarand Bauskar0e4c5c92017-05-11 11:40:02 +0530671 rate_with_margin = 0.0
Shreya Shahbe690ef2017-11-14 17:22:41 +0530672 base_rate_with_margin = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530673 if item.price_list_rate:
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530674 if item.pricing_rules and not self.doc.ignore_pricing_rule:
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530675 has_margin = False
marination733fd5f2020-08-26 18:23:12 +0530676 for d in get_applied_pricing_rules(item.pricing_rules):
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530677 pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
Shreya Shahf718b0c2018-02-20 11:26:46 +0530678
Rohit Waghchaured4d639c2021-02-01 22:58:22 +0530679 if pricing_rule.margin_rate_or_amount and ((pricing_rule.currency == self.doc.currency and
680 pricing_rule.margin_type in ['Amount', 'Percentage']) or pricing_rule.margin_type == 'Percentage'):
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530681 item.margin_type = pricing_rule.margin_type
682 item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530683 has_margin = True
684
685 if not has_margin:
686 item.margin_type = None
687 item.margin_rate_or_amount = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530688
Rohit Waghchaure01faa9c2021-06-21 00:59:02 +0530689 if not item.pricing_rules and flt(item.rate) > flt(item.price_list_rate):
690 item.margin_type = "Amount"
691 item.margin_rate_or_amount = flt(item.rate - item.price_list_rate,
692 item.precision("margin_rate_or_amount"))
693 item.rate_with_margin = item.rate
694
695 elif item.margin_type and item.margin_rate_or_amount:
mbauskara52472c2016-03-05 15:10:25 +0530696 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 +0530697 rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
Shreya Shahbe690ef2017-11-14 17:22:41 +0530698 base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
mbauskar36b51892016-01-18 16:31:10 +0530699
Shreya Shahbe690ef2017-11-14 17:22:41 +0530700 return rate_with_margin, base_rate_with_margin
Nabin Hait852cb642017-07-05 12:58:19 +0530701
702 def set_item_wise_tax_breakup(self):
Nabin Hait9c421612017-07-20 13:32:01 +0530703 self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530704
Subin Tom7d627df2021-08-23 11:05:07 +0530705 def set_total_amount_to_default_mop(self, total_amount_to_pay):
Saqiba6f98d42020-07-23 18:51:26 +0530706 default_mode_of_payment = frappe.db.get_value('POS Payment Method',
707 {'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
Deepesh Garg0ebace52020-02-25 13:21:16 +0530708
Deepesh Garg0ebace52020-02-25 13:21:16 +0530709 if default_mode_of_payment:
Afshanb6148342021-08-10 21:33:58 +0530710 self.doc.payments = []
Deepesh Garg0ebace52020-02-25 13:21:16 +0530711 self.doc.append('payments', {
712 'mode_of_payment': default_mode_of_payment.mode_of_payment,
Rucha Mahabaledee5302020-12-04 12:13:26 +0530713 'amount': total_amount_to_pay,
714 'default': 1
Ankush Menatb147b852021-09-01 16:45:57 +0530715 })
Deepesh Garg0ebace52020-02-25 13:21:16 +0530716
Nabin Hait9c421612017-07-20 13:32:01 +0530717def get_itemised_tax_breakup_html(doc):
718 if not doc.taxes:
719 return
720 frappe.flags.company = doc.company
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530721
Nabin Hait9c421612017-07-20 13:32:01 +0530722 # get headers
Nabin Haitcaab5822017-08-24 16:22:28 +0530723 tax_accounts = []
724 for tax in doc.taxes:
725 if getattr(tax, "category", None) and tax.category=="Valuation":
726 continue
rohitwaghchaure4e17fae2017-12-12 14:40:52 +0530727 if tax.description not in tax_accounts:
Nabin Haitcaab5822017-08-24 16:22:28 +0530728 tax_accounts.append(tax.description)
729
Nabin Hait9c421612017-07-20 13:32:01 +0530730 headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530731
Nabin Hait9c421612017-07-20 13:32:01 +0530732 # get tax breakup data
733 itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc)
Nabin Haitcaab5822017-08-24 16:22:28 +0530734
735 get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes"))
736
rohitwaghchaured4526682017-12-28 14:20:13 +0530737 update_itemised_tax_data(doc)
Nabin Hait9c421612017-07-20 13:32:01 +0530738 frappe.flags.company = None
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530739
Nabin Hait9c421612017-07-20 13:32:01 +0530740 return frappe.render_template(
741 "templates/includes/itemised_tax_breakup.html", dict(
742 headers=headers,
743 itemised_tax=itemised_tax,
744 itemised_taxable_amount=itemised_taxable_amount,
745 tax_accounts=tax_accounts,
Saqib Ansari10a6a2d2020-04-13 16:40:13 +0530746 doc=doc
Nabin Haitb962fc12017-07-17 18:02:31 +0530747 )
Nabin Hait9c421612017-07-20 13:32:01 +0530748 )
Nabin Hait852cb642017-07-05 12:58:19 +0530749
Deepesh Garg6a5ef262021-02-19 14:30:23 +0530750@frappe.whitelist()
751def get_round_off_applicable_accounts(company, account_list):
752 account_list = get_regional_round_off_accounts(company, account_list)
753
754 return account_list
755
756@erpnext.allow_regional
757def get_regional_round_off_accounts(company, account_list):
758 pass
rohitwaghchaured4526682017-12-28 14:20:13 +0530759
760@erpnext.allow_regional
761def update_itemised_tax_data(doc):
762 #Don't delete this method, used for localization
763 pass
764
Nabin Haitb962fc12017-07-17 18:02:31 +0530765@erpnext.allow_regional
766def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
767 return [_("Item"), _("Taxable Amount")] + tax_accounts
768
769@erpnext.allow_regional
770def get_itemised_tax_breakup_data(doc):
771 itemised_tax = get_itemised_tax(doc.taxes)
772
773 itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
774
775 return itemised_tax, itemised_taxable_amount
776
Nabin Hait34c551d2019-07-03 10:34:31 +0530777def get_itemised_tax(taxes, with_tax_account=False):
Nabin Haitb962fc12017-07-17 18:02:31 +0530778 itemised_tax = {}
779 for tax in taxes:
Nabin Haitcaab5822017-08-24 16:22:28 +0530780 if getattr(tax, "category", None) and tax.category=="Valuation":
781 continue
782
Nabin Haitb962fc12017-07-17 18:02:31 +0530783 item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
Nabin Hait2e4de832017-09-19 14:53:16 +0530784 if item_tax_map:
785 for item_code, tax_data in item_tax_map.items():
786 itemised_tax.setdefault(item_code, frappe._dict())
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530787
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530788 tax_rate = 0.0
789 tax_amount = 0.0
790
Nabin Hait2e4de832017-09-19 14:53:16 +0530791 if isinstance(tax_data, list):
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530792 tax_rate = flt(tax_data[0])
793 tax_amount = flt(tax_data[1])
Nabin Hait2e4de832017-09-19 14:53:16 +0530794 else:
Prateeksha Singhea7533f2018-06-11 13:31:33 +0530795 tax_rate = flt(tax_data)
796
797 itemised_tax[item_code][tax.description] = frappe._dict(dict(
798 tax_rate = tax_rate,
799 tax_amount = tax_amount
800 ))
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530801
Nabin Hait34c551d2019-07-03 10:34:31 +0530802 if with_tax_account:
803 itemised_tax[item_code][tax.description].tax_account = tax.account_head
804
Nabin Haitb962fc12017-07-17 18:02:31 +0530805 return itemised_tax
806
807def get_itemised_taxable_amount(items):
808 itemised_taxable_amount = frappe._dict()
809 for item in items:
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +0530810 item_code = item.item_code or item.item_name
Nabin Haitb962fc12017-07-17 18:02:31 +0530811 itemised_taxable_amount.setdefault(item_code, 0)
812 itemised_taxable_amount[item_code] += item.net_amount
813
Nabin Haitcaab5822017-08-24 16:22:28 +0530814 return itemised_taxable_amount
815
816def get_rounded_tax_amount(itemised_tax, precision):
817 # Rounding based on tax_amount precision
818 for taxes in itemised_tax.values():
819 for tax_account in taxes:
Himanshu Mishra35b26272018-11-13 11:13:04 +0530820 taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530821
822class init_landed_taxes_and_totals(object):
823 def __init__(self, doc):
824 self.doc = doc
825 self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
826 self.set_account_currency()
827 self.set_exchange_rate()
828 self.set_amounts_in_company_currency()
829
830 def set_account_currency(self):
831 company_currency = erpnext.get_company_currency(self.doc.company)
832 for d in self.doc.get(self.tax_field):
833 if not d.account_currency:
834 account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
835 d.account_currency = account_currency or company_currency
836
837 def set_exchange_rate(self):
838 company_currency = erpnext.get_company_currency(self.doc.company)
839 for d in self.doc.get(self.tax_field):
840 if d.account_currency == company_currency:
841 d.exchange_rate = 1
Deepesh Garg22f5ff82021-03-17 10:56:52 +0530842 elif not d.exchange_rate:
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530843 d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
844 account_currency=d.account_currency, company=self.doc.company)
845
846 if not d.exchange_rate:
847 frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
848
849 def set_amounts_in_company_currency(self):
850 for d in self.doc.get(self.tax_field):
851 d.amount = flt(d.amount, d.precision("amount"))
Walstan Baptista37b826b2021-04-03 19:48:46 +0530852 d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))