blob: 8cb1a0e8611df3250ac7f749c05eaec4d7d3256e [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
Raffael Meyer67cf7e12023-01-15 13:04:16 +01009from frappe.model.document import Document
Nabin Haitb962fc12017-07-17 18:02:31 +053010from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
Dany Robert50d56db2024-01-29 09:32:44 +053011from frappe.utils.deprecations import deprecated
Chillar Anand915b3432021-09-02 16:44:59 +053012
13import erpnext
Deepesh Gargbfc17e42020-12-25 18:34:39 +053014from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
Chillar Anand915b3432021-09-02 16:44:59 +053015from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
16from erpnext.controllers.accounts_controller import (
17 validate_conversion_rate,
18 validate_inclusive_tax,
19 validate_taxes_and_charges,
20)
21from erpnext.stock.get_item_details import _get_item_tax_template
Sagar Vora4205f562023-07-24 18:37:36 +053022from erpnext.utilities.regional import temporary_flag
Chillar Anand915b3432021-09-02 16:44:59 +053023
Nabin Hait3237c752015-02-17 11:11:11 +053024
Nabin Haitfe81da22015-02-18 12:23:18 +053025class calculate_taxes_and_totals(object):
Raffael Meyer67cf7e12023-01-15 13:04:16 +010026 def __init__(self, doc: Document):
Nabin Hait3237c752015-02-17 11:11:11 +053027 self.doc = doc
Deepesh Garg6a5ef262021-02-19 14:30:23 +053028 frappe.flags.round_off_applicable_accounts = []
Dany Robertdfb5b882023-08-23 04:01:00 +000029 frappe.flags.round_row_wise_tax = frappe.db.get_single_value(
30 "Accounts Settings", "round_row_wise_tax"
Dany Robert3ead2892023-08-22 14:41:07 +000031 )
marination91982d12023-01-24 18:03:53 +053032
33 self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items")
34
Deepesh Garg6a5ef262021-02-19 14:30:23 +053035 get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
Nabin Haitfe81da22015-02-18 12:23:18 +053036 self.calculate()
37
marination91982d12023-01-24 18:03:53 +053038 def filter_rows(self):
39 """Exclude rows, that do not fulfill the filter criteria, from totals computation."""
marinationcef7dfd2023-01-26 14:36:25 +053040 items = list(filter(lambda item: not item.get("is_alternative"), self.doc.get("items")))
marination91982d12023-01-24 18:03:53 +053041 return items
42
Nabin Hait3237c752015-02-17 11:11:11 +053043 def calculate(self):
marination91982d12023-01-24 18:03:53 +053044 if not len(self._items):
Nabin Haitb315acb2019-07-12 14:27:19 +053045 return
46
Nabin Hait3237c752015-02-17 11:11:11 +053047 self.discount_amount_applied = False
48 self._calculate()
49
50 if self.doc.meta.get_field("discount_amount"):
Nabin Hait3769d872015-12-18 13:12:02 +053051 self.set_discount_amount()
Nabin Hait3237c752015-02-17 11:11:11 +053052 self.apply_discount_amount()
53
Deepesh Garg3b159662022-08-21 17:51:05 +053054 # Update grand total as per cash and non trade discount
55 if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
56 self.doc.grand_total -= self.doc.discount_amount
57 self.doc.base_grand_total -= self.doc.base_discount_amount
Dany Robert3a487bd2023-11-18 13:32:04 +000058 self.doc.rounding_adjustment = self.doc.base_rounding_adjustment = 0.0
Deepesh Garg318da162022-08-29 14:18:39 +053059 self.set_rounded_total()
Deepesh Garg3b159662022-08-21 17:51:05 +053060
Deepesh Gargd596e0e2022-03-10 20:56:36 +053061 self.calculate_shipping_charges()
62
Nabin Haitbd00e812015-02-17 12:50:51 +053063 if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
Nabin Hait3237c752015-02-17 11:11:11 +053064 self.calculate_total_advance()
Vishal Dhayaguded42242d2017-11-29 16:09:59 +053065
Nabin Hait852cb642017-07-05 12:58:19 +053066 if self.doc.meta.get_field("other_charges_calculation"):
67 self.set_item_wise_tax_breakup()
Nabin Hait3237c752015-02-17 11:11:11 +053068
69 def _calculate(self):
Faris Ansarieae2dda2018-05-02 12:19:30 +053070 self.validate_conversion_rate()
Nabin Haite7679702015-02-20 14:40:35 +053071 self.calculate_item_values()
Deepesh Gargef0d26c2020-01-06 15:34:15 +053072 self.validate_item_tax_template()
Nabin Haite7679702015-02-20 14:40:35 +053073 self.initialize_taxes()
74 self.determine_exclusive_rate()
75 self.calculate_net_total()
niralisataparae758a752022-10-03 16:39:35 +053076 self.calculate_tax_withholding_net_total()
Nabin Haite7679702015-02-20 14:40:35 +053077 self.calculate_taxes()
Dany Robert50d56db2024-01-29 09:32:44 +053078 self.adjust_grand_total_for_inclusive_tax()
Nabin Haite7679702015-02-20 14:40:35 +053079 self.calculate_totals()
80 self._cleanup()
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +053081 self.calculate_total_net_weight()
Nabin Haite7679702015-02-20 14:40:35 +053082
niralisataparae758a752022-10-03 16:39:35 +053083 def calculate_tax_withholding_net_total(self):
84 if hasattr(self.doc, "tax_withholding_net_total"):
niralisataparae758a752022-10-03 16:39:35 +053085 sum_net_amount = 0
niralisatapara2ca0cf62022-11-02 12:19:51 +053086 sum_base_net_amount = 0
marination91982d12023-01-24 18:03:53 +053087 for item in self._items:
niralisataparae758a752022-10-03 16:39:35 +053088 if hasattr(item, "apply_tds") and item.apply_tds:
89 sum_net_amount += item.net_amount
niralisatapara2ca0cf62022-11-02 12:19:51 +053090 sum_base_net_amount += item.base_net_amount
91
niralisataparae758a752022-10-03 16:39:35 +053092 self.doc.tax_withholding_net_total = sum_net_amount
niralisatapara2ca0cf62022-11-02 12:19:51 +053093 self.doc.base_tax_withholding_net_total = sum_base_net_amount
niralisataparae758a752022-10-03 16:39:35 +053094
Deepesh Gargef0d26c2020-01-06 15:34:15 +053095 def validate_item_tax_template(self):
marination91982d12023-01-24 18:03:53 +053096 for item in self._items:
Ankush Menat494bd9e2022-03-28 18:52:46 +053097 if item.item_code and item.get("item_tax_template"):
Deepesh Gargef0d26c2020-01-06 15:34:15 +053098 item_doc = frappe.get_cached_doc("Item", item.item_code)
99 args = {
Ankush Menat494bd9e2022-03-28 18:52:46 +0530100 "net_rate": item.net_rate or item.rate,
101 "tax_category": self.doc.get("tax_category"),
102 "posting_date": self.doc.get("posting_date"),
103 "bill_date": self.doc.get("bill_date"),
104 "transaction_date": self.doc.get("transaction_date"),
105 "company": self.doc.get("company"),
Deepesh Gargef0d26c2020-01-06 15:34:15 +0530106 }
107
108 item_group = item_doc.item_group
109 item_group_taxes = []
110
111 while item_group:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530112 item_group_doc = frappe.get_cached_doc("Item Group", item_group)
Deepesh Gargef0d26c2020-01-06 15:34:15 +0530113 item_group_taxes += item_group_doc.taxes or []
114 item_group = item_group_doc.parent_item_group
115
116 item_taxes = item_doc.taxes or []
117
118 if not item_group_taxes and (not item_taxes):
119 # No validation if no taxes in item or item group
120 continue
121
122 taxes = _get_item_tax_template(args, item_taxes + item_group_taxes, for_validate=True)
123
Deepesh Garg18be7672021-06-06 13:25:34 +0530124 if taxes:
125 if item.item_tax_template not in taxes:
126 item.item_tax_template = taxes[0]
Ankush Menat494bd9e2022-03-28 18:52:46 +0530127 frappe.msgprint(
128 _("Row {0}: Item Tax template updated as per validity and rate applied").format(
129 item.idx, frappe.bold(item.item_code)
130 )
131 )
Deepesh Gargef0d26c2020-01-06 15:34:15 +0530132
Nabin Haite7679702015-02-20 14:40:35 +0530133 def validate_conversion_rate(self):
Nabin Hait3237c752015-02-17 11:11:11 +0530134 # validate conversion rate
Rushabh Mehtacc8b2b22017-03-31 12:44:29 +0530135 company_currency = erpnext.get_company_currency(self.doc.company)
Nabin Hait3237c752015-02-17 11:11:11 +0530136 if not self.doc.currency or self.doc.currency == company_currency:
137 self.doc.currency = company_currency
138 self.doc.conversion_rate = 1.0
139 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530140 validate_conversion_rate(
141 self.doc.currency,
142 self.doc.conversion_rate,
143 self.doc.meta.get_label("conversion_rate"),
144 self.doc.company,
145 )
Nabin Hait3237c752015-02-17 11:11:11 +0530146
147 self.doc.conversion_rate = flt(self.doc.conversion_rate)
148
Nabin Hait3237c752015-02-17 11:11:11 +0530149 def calculate_item_values(self):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530150 if self.doc.get("is_consolidated"):
Saqib Ansari0452d7d2022-02-08 11:26:23 +0530151 return
152
Nabin Hait3237c752015-02-17 11:11:11 +0530153 if not self.discount_amount_applied:
marination91982d12023-01-24 18:03:53 +0530154 for item in self._items:
Nabin Hait3237c752015-02-17 11:11:11 +0530155 self.doc.round_floats_in(item)
156
157 if item.discount_percentage == 100:
158 item.rate = 0.0
Nabin Hait593242f2019-04-05 19:35:02 +0530159 elif item.price_list_rate:
Deepesh Garga83a0a02022-03-25 12:28:55 +0530160 if not item.rate or (item.pricing_rules and item.discount_percentage > 0):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530161 item.rate = flt(
162 item.price_list_rate * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")
163 )
Deepesh Gargd95f8932022-03-01 23:09:59 +0530164
Deepesh Garga83a0a02022-03-25 12:28:55 +0530165 item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
Deepesh Gargd95f8932022-03-01 23:09:59 +0530166
Deepesh Garg97e102c2022-03-25 12:39:59 +0530167 elif item.discount_amount and item.pricing_rules:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530168 item.rate = item.price_list_rate - item.discount_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530169
Ankush Menat494bd9e2022-03-28 18:52:46 +0530170 if item.doctype in [
171 "Quotation Item",
172 "Sales Order Item",
173 "Delivery Note Item",
174 "Sales Invoice Item",
175 "POS Invoice Item",
176 "Purchase Invoice Item",
177 "Purchase Order Item",
178 "Purchase Receipt Item",
179 ]:
Shreya Shahbe690ef2017-11-14 17:22:41 +0530180 item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
Nabin Hait64bfdd92019-04-23 13:37:19 +0530181 if flt(item.rate_with_margin) > 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530182 item.rate = flt(
183 item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")
184 )
Nabin Hait10c61372021-04-13 15:46:01 +0530185
Walstan Baptista37b826b2021-04-03 19:48:46 +0530186 if item.discount_amount and not item.discount_percentage:
Nabin Hait10c61372021-04-13 15:46:01 +0530187 item.rate = item.rate_with_margin - item.discount_amount
Walstan Baptista37b826b2021-04-03 19:48:46 +0530188 else:
189 item.discount_amount = item.rate_with_margin - item.rate
Nabin Hait10c61372021-04-13 15:46:01 +0530190
Nabin Hait64bfdd92019-04-23 13:37:19 +0530191 elif flt(item.price_list_rate) > 0:
192 item.discount_amount = item.price_list_rate - item.rate
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530193 elif flt(item.price_list_rate) > 0 and not item.discount_amount:
194 item.discount_amount = item.price_list_rate - item.rate
mbauskara52472c2016-03-05 15:10:25 +0530195
Nabin Haite7679702015-02-20 14:40:35 +0530196 item.net_rate = item.rate
Deepesh Gargb65c7612019-07-31 15:58:01 +0530197
mergify[bot]10c666b2023-10-06 14:43:13 +0530198 if (
199 not item.qty and self.doc.get("is_return") and self.doc.get("doctype") != "Purchase Receipt"
200 ):
Deepesh Gargb65c7612019-07-31 15:58:01 +0530201 item.amount = flt(-1 * item.rate, item.precision("amount"))
Saqib Ansari04ea42c2021-12-22 13:23:42 +0530202 elif not item.qty and self.doc.get("is_debit_note"):
203 item.amount = flt(item.rate, item.precision("amount"))
Deepesh Gargb65c7612019-07-31 15:58:01 +0530204 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530205 item.amount = flt(item.rate * item.qty, item.precision("amount"))
Deepesh Gargb65c7612019-07-31 15:58:01 +0530206
Nabin Haite7679702015-02-20 14:40:35 +0530207 item.net_amount = item.amount
Nabin Hait3237c752015-02-17 11:11:11 +0530208
Ankush Menat494bd9e2022-03-28 18:52:46 +0530209 self._set_in_company_currency(
210 item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"]
211 )
Nabin Hait3237c752015-02-17 11:11:11 +0530212
Nabin Haite7679702015-02-20 14:40:35 +0530213 item.item_tax_amount = 0.0
214
215 def _set_in_company_currency(self, doc, fields):
Nabin Hait3237c752015-02-17 11:11:11 +0530216 """set values in base currency"""
Nabin Haite7679702015-02-20 14:40:35 +0530217 for f in fields:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530218 val = flt(
219 flt(doc.get(f), doc.precision(f)) * self.doc.conversion_rate, doc.precision("base_" + f)
220 )
Nabin Haite7679702015-02-20 14:40:35 +0530221 doc.set("base_" + f, val)
Nabin Hait3237c752015-02-17 11:11:11 +0530222
223 def initialize_taxes(self):
224 for tax in self.doc.get("taxes"):
Nabin Hait86cd4cc2015-02-28 19:11:51 +0530225 if not self.discount_amount_applied:
226 validate_taxes_and_charges(tax)
227 validate_inclusive_tax(tax, self.doc)
Nabin Hait613d0812015-02-23 11:58:15 +0530228
Ankush Menat494bd9e2022-03-28 18:52:46 +0530229 if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530230 tax.item_wise_tax_detail = {}
231
Ankush Menat494bd9e2022-03-28 18:52:46 +0530232 tax_fields = [
233 "total",
234 "tax_amount_after_discount_amount",
235 "tax_amount_for_current_item",
236 "grand_total_for_current_item",
237 "tax_fraction_for_current_item",
238 "grand_total_fraction_for_current_item",
239 ]
Nabin Hait3237c752015-02-17 11:11:11 +0530240
Ankush Menat494bd9e2022-03-28 18:52:46 +0530241 if tax.charge_type != "Actual" and not (
242 self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
243 ):
244 tax_fields.append("tax_amount")
Nabin Hait3237c752015-02-17 11:11:11 +0530245
246 for fieldname in tax_fields:
247 tax.set(fieldname, 0.0)
248
Nabin Hait3237c752015-02-17 11:11:11 +0530249 self.doc.round_floats_in(tax)
250
Nabin Hait3237c752015-02-17 11:11:11 +0530251 def determine_exclusive_rate(self):
Ankush Menat8fe5feb2021-11-04 19:48:32 +0530252 if not any(cint(tax.included_in_print_rate) for tax in self.doc.get("taxes")):
Nabin Hait37b047d2015-02-23 16:01:33 +0530253 return
Nabin Hait3237c752015-02-17 11:11:11 +0530254
marination91982d12023-01-24 18:03:53 +0530255 for item in self._items:
Nabin Hait3237c752015-02-17 11:11:11 +0530256 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
257 cumulated_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530258 total_inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530259 for i, tax in enumerate(self.doc.get("taxes")):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530260 (
261 tax.tax_fraction_for_current_item,
262 inclusive_tax_amount_per_qty,
263 ) = self.get_current_tax_fraction(tax, item_tax_map)
Nabin Hait3237c752015-02-17 11:11:11 +0530264
Ankush Menat494bd9e2022-03-28 18:52:46 +0530265 if i == 0:
Nabin Hait3237c752015-02-17 11:11:11 +0530266 tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
267 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530268 tax.grand_total_fraction_for_current_item = (
269 self.doc.get("taxes")[i - 1].grand_total_fraction_for_current_item
Nabin Hait3237c752015-02-17 11:11:11 +0530270 + tax.tax_fraction_for_current_item
Ankush Menat494bd9e2022-03-28 18:52:46 +0530271 )
Nabin Hait3237c752015-02-17 11:11:11 +0530272
273 cumulated_tax_fraction += tax.tax_fraction_for_current_item
Nabin Haite2ee4552020-08-12 20:58:03 +0530274 total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
Nabin Hait3237c752015-02-17 11:11:11 +0530275
Ankush Menat494bd9e2022-03-28 18:52:46 +0530276 if (
277 not self.discount_amount_applied
278 and item.qty
279 and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty)
280 ):
Nabin Hait19ea7212020-08-11 20:34:57 +0530281 amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
282
Dany Robert50d56db2024-01-29 09:32:44 +0530283 item.net_amount = flt(amount / (1 + cumulated_tax_fraction), item.precision("net_amount"))
Nabin Haite7679702015-02-20 14:40:35 +0530284 item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
Ankush Menat494bd9e2022-03-28 18:52:46 +0530285 item.discount_percentage = flt(item.discount_percentage, item.precision("discount_percentage"))
Nabin Hait3237c752015-02-17 11:11:11 +0530286
Nabin Haite7679702015-02-20 14:40:35 +0530287 self._set_in_company_currency(item, ["net_rate", "net_amount"])
288
Nabin Hait3237c752015-02-17 11:11:11 +0530289 def _load_item_tax_rate(self, item_tax_rate):
290 return json.loads(item_tax_rate) if item_tax_rate else {}
291
292 def get_current_tax_fraction(self, tax, item_tax_map):
293 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530294 Get tax fraction for calculating tax exclusive amount
295 from tax inclusive amount
Nabin Hait3237c752015-02-17 11:11:11 +0530296 """
297 current_tax_fraction = 0
Nabin Hait19ea7212020-08-11 20:34:57 +0530298 inclusive_tax_amount_per_qty = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530299
300 if cint(tax.included_in_print_rate):
301 tax_rate = self._get_tax_rate(tax, item_tax_map)
302
303 if tax.charge_type == "On Net Total":
304 current_tax_fraction = tax_rate / 100.0
305
306 elif tax.charge_type == "On Previous Row Amount":
Ankush Menat494bd9e2022-03-28 18:52:46 +0530307 current_tax_fraction = (tax_rate / 100.0) * self.doc.get("taxes")[
308 cint(tax.row_id) - 1
309 ].tax_fraction_for_current_item
Nabin Hait3237c752015-02-17 11:11:11 +0530310
311 elif tax.charge_type == "On Previous Row Total":
Ankush Menat494bd9e2022-03-28 18:52:46 +0530312 current_tax_fraction = (tax_rate / 100.0) * self.doc.get("taxes")[
313 cint(tax.row_id) - 1
314 ].grand_total_fraction_for_current_item
marination733fd5f2020-08-26 18:23:12 +0530315
Nabin Hait19ea7212020-08-11 20:34:57 +0530316 elif tax.charge_type == "On Item Quantity":
317 inclusive_tax_amount_per_qty = flt(tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530318
Nabin Hait19ea7212020-08-11 20:34:57 +0530319 if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
320 current_tax_fraction *= -1.0
321 inclusive_tax_amount_per_qty *= -1.0
322
323 return current_tax_fraction, inclusive_tax_amount_per_qty
Nabin Hait3237c752015-02-17 11:11:11 +0530324
325 def _get_tax_rate(self, tax, item_tax_map):
Achilles Rasquinha87dab142018-03-08 14:21:48 +0530326 if tax.account_head in item_tax_map:
Nabin Hait3237c752015-02-17 11:11:11 +0530327 return flt(item_tax_map.get(tax.account_head), self.doc.precision("rate", tax))
328 else:
329 return tax.rate
330
331 def calculate_net_total(self):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530332 self.doc.total_qty = (
333 self.doc.total
334 ) = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
rohitwaghchaure3a595d02018-06-25 10:10:29 +0530335
marination91982d12023-01-24 18:03:53 +0530336 for item in self._items:
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530337 self.doc.total += item.amount
Shreya Shahe3290382018-05-28 11:49:08 +0530338 self.doc.total_qty += item.qty
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530339 self.doc.base_total += item.base_amount
Nabin Haite7679702015-02-20 14:40:35 +0530340 self.doc.net_total += item.net_amount
341 self.doc.base_net_total += item.base_net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530342
Nabin Haitf0bc9b62015-02-23 01:40:01 +0530343 self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530344
Subin Toma8e2c022021-11-16 19:06:49 +0530345 def calculate_shipping_charges(self):
Deepesh Garg714fc082022-04-04 20:05:10 +0530346
347 # Do not apply shipping rule for POS
Deepesh Garg631545a2022-04-06 09:27:40 +0530348 if self.doc.get("is_pos"):
Deepesh Garg714fc082022-04-04 20:05:10 +0530349 return
350
Subin Tomaf1fce02021-11-10 16:49:12 +0530351 if hasattr(self.doc, "shipping_rule") and self.doc.shipping_rule:
Subin Tom18ae03d2021-11-10 15:57:41 +0530352 shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule)
353 shipping_rule.apply(self.doc)
354
Deepesh Gargd596e0e2022-03-10 20:56:36 +0530355 self._calculate()
356
Nabin Hait3237c752015-02-17 11:11:11 +0530357 def calculate_taxes(self):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530358 rounding_adjustment_computed = self.doc.get("is_consolidated") and self.doc.get(
359 "rounding_adjustment"
360 )
Saqib Ansari17445c72022-03-07 18:01:07 +0530361 if not rounding_adjustment_computed:
Subin Tom75a76e62021-10-29 16:45:04 +0530362 self.doc.rounding_adjustment = 0
363
Nabin Hait3237c752015-02-17 11:11:11 +0530364 # maintain actual tax rate based on idx
Ankush Menat494bd9e2022-03-28 18:52:46 +0530365 actual_tax_dict = dict(
366 [
367 [tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
368 for tax in self.doc.get("taxes")
369 if tax.charge_type == "Actual"
370 ]
371 )
Nabin Hait3237c752015-02-17 11:11:11 +0530372
marination91982d12023-01-24 18:03:53 +0530373 for n, item in enumerate(self._items):
Nabin Hait3237c752015-02-17 11:11:11 +0530374 item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
Nabin Hait3237c752015-02-17 11:11:11 +0530375 for i, tax in enumerate(self.doc.get("taxes")):
376 # tax_amount represents the amount of tax for the current step
377 current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map)
Dany Robert3ead2892023-08-22 14:41:07 +0000378 if frappe.flags.round_row_wise_tax:
379 current_tax_amount = flt(current_tax_amount, tax.precision("tax_amount"))
Nabin Hait3237c752015-02-17 11:11:11 +0530380
381 # Adjust divisional loss to the last item
382 if tax.charge_type == "Actual":
383 actual_tax_dict[tax.idx] -= current_tax_amount
marination91982d12023-01-24 18:03:53 +0530384 if n == len(self._items) - 1:
Nabin Hait3237c752015-02-17 11:11:11 +0530385 current_tax_amount += actual_tax_dict[tax.idx]
386
Nabin Hait2b019ed2015-02-22 23:03:07 +0530387 # accumulate tax amount into tax.tax_amount
Ankush Menat494bd9e2022-03-28 18:52:46 +0530388 if tax.charge_type != "Actual" and not (
389 self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
390 ):
391 tax.tax_amount += current_tax_amount
Nabin Hait2b019ed2015-02-22 23:03:07 +0530392
Nabin Hait3237c752015-02-17 11:11:11 +0530393 # store tax_amount for current item as it will be used for
394 # charge type = 'On Previous Row Amount'
395 tax.tax_amount_for_current_item = current_tax_amount
396
Nabin Hait2b019ed2015-02-22 23:03:07 +0530397 # set tax after discount
Nabin Hait3237c752015-02-17 11:11:11 +0530398 tax.tax_amount_after_discount_amount += current_tax_amount
399
Nabin Haitcd951342017-07-31 18:07:45 +0530400 current_tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(current_tax_amount, tax)
Nabin Hait3237c752015-02-17 11:11:11 +0530401
Nabin Hait3237c752015-02-17 11:11:11 +0530402 # note: grand_total_for_current_item contains the contribution of
403 # item's amount, previously applied tax and the current tax on that item
Ankush Menat494bd9e2022-03-28 18:52:46 +0530404 if i == 0:
Nabin Haitcd951342017-07-31 18:07:45 +0530405 tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530406 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530407 tax.grand_total_for_current_item = flt(
408 self.doc.get("taxes")[i - 1].grand_total_for_current_item + current_tax_amount
409 )
Nabin Hait3237c752015-02-17 11:11:11 +0530410
411 # set precision in the last item iteration
marination91982d12023-01-24 18:03:53 +0530412 if n == len(self._items) - 1:
Nabin Hait3237c752015-02-17 11:11:11 +0530413 self.round_off_totals(tax)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530414 self._set_in_company_currency(tax, ["tax_amount", "tax_amount_after_discount_amount"])
Deepesh Gargb6705882021-04-14 11:20:27 +0530415
416 self.round_off_base_values(tax)
Nabin Haitcd951342017-07-31 18:07:45 +0530417 self.set_cumulative_total(i, tax)
418
Deepesh Gargb6705882021-04-14 11:20:27 +0530419 self._set_in_company_currency(tax, ["total"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530420
Nabin Hait3237c752015-02-17 11:11:11 +0530421 # adjust Discount Amount loss in last tax iteration
Ankush Menat494bd9e2022-03-28 18:52:46 +0530422 if (
423 i == (len(self.doc.get("taxes")) - 1)
424 and self.discount_amount_applied
425 and self.doc.discount_amount
426 and self.doc.apply_discount_on == "Grand Total"
427 and not rounding_adjustment_computed
428 ):
429 self.doc.rounding_adjustment = flt(
430 self.doc.grand_total - flt(self.doc.discount_amount) - tax.total,
431 self.doc.precision("rounding_adjustment"),
432 )
Anand Doshiec5ec602015-03-05 19:31:23 +0530433
Nabin Haitcd951342017-07-31 18:07:45 +0530434 def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
435 # if just for valuation, do not add the tax amount in total
436 # if tax/charges is for deduction, multiply by -1
437 if getattr(tax, "category", None):
438 tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
Ankush Menat494bd9e2022-03-28 18:52:46 +0530439 if self.doc.doctype in [
440 "Purchase Order",
441 "Purchase Invoice",
442 "Purchase Receipt",
443 "Supplier Quotation",
444 ]:
Rushabh Mehta30dc9a12017-11-17 14:31:09 +0530445 tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
Nabin Haitcd951342017-07-31 18:07:45 +0530446 return tax_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530447
Nabin Haitcd951342017-07-31 18:07:45 +0530448 def set_cumulative_total(self, row_idx, tax):
449 tax_amount = tax.tax_amount_after_discount_amount
450 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax_amount, tax)
451
452 if row_idx == 0:
453 tax.total = flt(self.doc.net_total + tax_amount, tax.precision("total"))
454 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530455 tax.total = flt(self.doc.get("taxes")[row_idx - 1].total + tax_amount, tax.precision("total"))
Nabin Hait3237c752015-02-17 11:11:11 +0530456
457 def get_current_tax_amount(self, item, tax, item_tax_map):
458 tax_rate = self._get_tax_rate(tax, item_tax_map)
459 current_tax_amount = 0.0
460
461 if tax.charge_type == "Actual":
462 # distribute the tax amount proportionally to each item row
Nabin Haite7679702015-02-20 14:40:35 +0530463 actual = flt(tax.tax_amount, tax.precision("tax_amount"))
Ankush Menat494bd9e2022-03-28 18:52:46 +0530464 current_tax_amount = (
465 item.net_amount * actual / self.doc.net_total if self.doc.net_total else 0.0
466 )
Nabin Haite7679702015-02-20 14:40:35 +0530467
Nabin Hait3237c752015-02-17 11:11:11 +0530468 elif tax.charge_type == "On Net Total":
Nabin Haite7679702015-02-20 14:40:35 +0530469 current_tax_amount = (tax_rate / 100.0) * item.net_amount
Nabin Hait3237c752015-02-17 11:11:11 +0530470 elif tax.charge_type == "On Previous Row Amount":
Ankush Menat494bd9e2022-03-28 18:52:46 +0530471 current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[
472 cint(tax.row_id) - 1
473 ].tax_amount_for_current_item
Nabin Hait3237c752015-02-17 11:11:11 +0530474 elif tax.charge_type == "On Previous Row Total":
Ankush Menat494bd9e2022-03-28 18:52:46 +0530475 current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[
476 cint(tax.row_id) - 1
477 ].grand_total_for_current_item
Himanshu Mishra35b26272018-11-13 11:13:04 +0530478 elif tax.charge_type == "On Item Quantity":
Nabin Hait19ea7212020-08-11 20:34:57 +0530479 current_tax_amount = tax_rate * item.qty
Nabin Hait3237c752015-02-17 11:11:11 +0530480
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530481 if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530482 self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530483
484 return current_tax_amount
485
Nabin Haite7679702015-02-20 14:40:35 +0530486 def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
487 # store tax breakup for each item
488 key = item.item_code or item.item_name
Ankush Menat494bd9e2022-03-28 18:52:46 +0530489 item_wise_tax_amount = current_tax_amount * self.doc.conversion_rate
Dany Robert0ebcc2c2023-08-23 04:51:09 +0000490 if frappe.flags.round_row_wise_tax:
491 item_wise_tax_amount = flt(item_wise_tax_amount, tax.precision("tax_amount"))
492 if tax.item_wise_tax_detail.get(key):
493 item_wise_tax_amount += flt(tax.item_wise_tax_detail[key][1], tax.precision("tax_amount"))
Dany Robert9e1b2c92023-08-24 05:02:14 +0000494 tax.item_wise_tax_detail[key] = [
495 tax_rate,
496 flt(item_wise_tax_amount, tax.precision("tax_amount")),
497 ]
Dany Robert0ebcc2c2023-08-23 04:51:09 +0000498 else:
499 if tax.item_wise_tax_detail.get(key):
500 item_wise_tax_amount += tax.item_wise_tax_detail[key][1]
Nabin Haite7679702015-02-20 14:40:35 +0530501
Dany Robert9e1b2c92023-08-24 05:02:14 +0000502 tax.item_wise_tax_detail[key] = [tax_rate, flt(item_wise_tax_amount)]
Nabin Haite7679702015-02-20 14:40:35 +0530503
Nabin Hait3237c752015-02-17 11:11:11 +0530504 def round_off_totals(self, tax):
Deepesh Gargb6705882021-04-14 11:20:27 +0530505 if tax.account_head in frappe.flags.round_off_applicable_accounts:
506 tax.tax_amount = round(tax.tax_amount, 0)
507 tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
508
Nabin Haite7679702015-02-20 14:40:35 +0530509 tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
Ankush Menat494bd9e2022-03-28 18:52:46 +0530510 tax.tax_amount_after_discount_amount = flt(
511 tax.tax_amount_after_discount_amount, tax.precision("tax_amount")
512 )
Nabin Haitce245122015-02-22 20:14:49 +0530513
Deepesh Gargb6705882021-04-14 11:20:27 +0530514 def round_off_base_values(self, tax):
515 # Round off to nearest integer based on regional settings
516 if tax.account_head in frappe.flags.round_off_applicable_accounts:
517 tax.base_tax_amount = round(tax.base_tax_amount, 0)
518 tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
519
Dany Robert50d56db2024-01-29 09:32:44 +0530520 @deprecated
Nabin Haita1bf43b2015-03-17 10:50:47 +0530521 def manipulate_grand_total_for_inclusive_tax(self):
Dany Robert50d56db2024-01-29 09:32:44 +0530522 # for backward compatablility - if in case used by an external application
523 return self.adjust_grand_total_for_inclusive_tax()
524
525 def adjust_grand_total_for_inclusive_tax(self):
Nabin Haita1bf43b2015-03-17 10:50:47 +0530526 # if fully inclusive taxes and diff
Ankush Menat98917802021-06-11 18:40:22 +0530527 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 +0530528 last_tax = self.doc.get("taxes")[-1]
Ankush Menat494bd9e2022-03-28 18:52:46 +0530529 non_inclusive_tax_amount = sum(
530 flt(d.tax_amount_after_discount_amount)
531 for d in self.doc.get("taxes")
532 if not d.included_in_print_rate
533 )
Nabin Haitf32fc232019-12-25 13:59:24 +0530534
Ankush Menat494bd9e2022-03-28 18:52:46 +0530535 diff = (
536 self.doc.total + non_inclusive_tax_amount - flt(last_tax.total, last_tax.precision("total"))
537 )
Nabin Haitf32fc232019-12-25 13:59:24 +0530538
539 # If discount amount applied, deduct the discount amount
540 # because self.doc.total is always without discount, but last_tax.total is after discount
541 if self.discount_amount_applied and self.doc.discount_amount:
542 diff -= flt(self.doc.discount_amount)
543
544 diff = flt(diff, self.doc.precision("rounding_adjustment"))
545
Ankush Menat494bd9e2022-03-28 18:52:46 +0530546 if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")):
Dany Robert50d56db2024-01-29 09:32:44 +0530547 self.doc.grand_total_diff = diff
548 else:
549 self.doc.grand_total_diff = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530550
551 def calculate_totals(self):
Subin Tom75a76e62021-10-29 16:45:04 +0530552 if self.doc.get("taxes"):
Dany Robert50d56db2024-01-29 09:32:44 +0530553 self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(
554 self.doc.get("grand_total_diff")
555 )
Subin Tom75a76e62021-10-29 16:45:04 +0530556 else:
557 self.doc.grand_total = flt(self.doc.net_total)
Nabin Hait3237c752015-02-17 11:11:11 +0530558
Subin Tom75a76e62021-10-29 16:45:04 +0530559 if self.doc.get("taxes"):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530560 self.doc.total_taxes_and_charges = flt(
Dany Robert50d56db2024-01-29 09:32:44 +0530561 self.doc.grand_total - self.doc.net_total - flt(self.doc.get("grand_total_diff")),
Ankush Menat494bd9e2022-03-28 18:52:46 +0530562 self.doc.precision("total_taxes_and_charges"),
563 )
Subin Tom75a76e62021-10-29 16:45:04 +0530564 else:
565 self.doc.total_taxes_and_charges = 0.0
Anand Doshiec5ec602015-03-05 19:31:23 +0530566
Nabin Hait2e4de832017-09-19 14:53:16 +0530567 self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
Anand Doshiec5ec602015-03-05 19:31:23 +0530568
Ankush Menat494bd9e2022-03-28 18:52:46 +0530569 if self.doc.doctype in [
570 "Quotation",
571 "Sales Order",
572 "Delivery Note",
573 "Sales Invoice",
574 "POS Invoice",
575 ]:
576 self.doc.base_grand_total = (
577 flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total"))
578 if self.doc.total_taxes_and_charges
579 else self.doc.base_net_total
580 )
Nabin Hait3237c752015-02-17 11:11:11 +0530581 else:
Anand Doshiec5ec602015-03-05 19:31:23 +0530582 self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0
Nabin Hait3237c752015-02-17 11:11:11 +0530583 for tax in self.doc.get("taxes"):
584 if tax.category in ["Valuation and Total", "Total"]:
585 if tax.add_deduct_tax == "Add":
Nabin Haitdb53a782015-07-31 16:53:13 +0530586 self.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530587 else:
Nabin Haitdb53a782015-07-31 16:53:13 +0530588 self.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530589
Nabin Haite7679702015-02-20 14:40:35 +0530590 self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
Nabin Hait3237c752015-02-17 11:11:11 +0530591
Ankush Menat494bd9e2022-03-28 18:52:46 +0530592 self.doc.base_grand_total = (
593 flt(self.doc.grand_total * self.doc.conversion_rate)
594 if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted)
Nabin Haite7679702015-02-20 14:40:35 +0530595 else self.doc.base_net_total
Ankush Menat494bd9e2022-03-28 18:52:46 +0530596 )
Nabin Hait3237c752015-02-17 11:11:11 +0530597
Ankush Menat494bd9e2022-03-28 18:52:46 +0530598 self._set_in_company_currency(
599 self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"]
600 )
Nabin Hait3237c752015-02-17 11:11:11 +0530601
Nabin Haite7679702015-02-20 14:40:35 +0530602 self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
Nabin Hait3237c752015-02-17 11:11:11 +0530603
Nabin Hait2e4de832017-09-19 14:53:16 +0530604 self.set_rounded_total()
605
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +0530606 def calculate_total_net_weight(self):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530607 if self.doc.meta.get_field("total_net_weight"):
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +0530608 self.doc.total_net_weight = 0.0
marination91982d12023-01-24 18:03:53 +0530609 for d in self._items:
rohitwaghchaurea8fb2db2018-05-26 09:23:02 +0530610 if d.total_weight:
611 self.doc.total_net_weight += d.total_weight
612
Nabin Hait2e4de832017-09-19 14:53:16 +0530613 def set_rounded_total(self):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530614 if self.doc.get("is_consolidated") and self.doc.get("rounding_adjustment"):
Saqib Ansari17445c72022-03-07 18:01:07 +0530615 return
Nabin Hait877e1bb2017-11-17 12:27:43 +0530616
Saqib Ansari17445c72022-03-07 18:01:07 +0530617 if self.doc.meta.get_field("rounded_total"):
618 if self.doc.is_rounded_total_disabled():
619 self.doc.rounded_total = self.doc.base_rounded_total = 0
620 return
Nabin Hait2e4de832017-09-19 14:53:16 +0530621
Ankush Menat494bd9e2022-03-28 18:52:46 +0530622 self.doc.rounded_total = round_based_on_smallest_currency_fraction(
623 self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
624 )
Nabin Hait877e1bb2017-11-17 12:27:43 +0530625
Dany Robert50d56db2024-01-29 09:32:44 +0530626 # rounding adjustment should always be the difference vetween grand and rounded total
627 self.doc.rounding_adjustment = flt(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530628 self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment")
629 )
Saqib Ansari17445c72022-03-07 18:01:07 +0530630
631 self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530632
Nabin Hait3237c752015-02-17 11:11:11 +0530633 def _cleanup(self):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530634 if not self.doc.get("is_consolidated"):
Deepesh Garg5f3d7f52021-04-12 10:56:47 +0530635 for tax in self.doc.get("taxes"):
Ankush Menatdfdd1c62021-07-23 15:50:37 +0530636 if not tax.get("dont_recompute_tax"):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530637 tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(",", ":"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530638
Nabin Hait3769d872015-12-18 13:12:02 +0530639 def set_discount_amount(self):
Nabin Haite0405102016-10-13 12:14:32 +0530640 if self.doc.additional_discount_percentage:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530641 self.doc.discount_amount = flt(
642 flt(self.doc.get(scrub(self.doc.apply_discount_on)))
643 * self.doc.additional_discount_percentage
644 / 100,
645 self.doc.precision("discount_amount"),
646 )
Nabin Hait3237c752015-02-17 11:11:11 +0530647
648 def apply_discount_amount(self):
649 if self.doc.discount_amount:
Nabin Hait37b047d2015-02-23 16:01:33 +0530650 if not self.doc.apply_discount_on:
651 frappe.throw(_("Please select Apply Discount On"))
652
Deepesh Garg3b159662022-08-21 17:51:05 +0530653 self.doc.base_discount_amount = flt(
654 self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
655 )
656
Deepesh Garge54ec4b2022-07-03 11:02:21 +0530657 if self.doc.apply_discount_on == "Grand Total" and self.doc.get(
658 "is_cash_or_non_trade_discount"
659 ):
Deepesh Garg169ff5a2022-06-19 21:18:12 +0530660 self.discount_amount_applied = True
661 return
662
Nabin Haite7679702015-02-20 14:40:35 +0530663 total_for_discount_amount = self.get_total_for_discount_amount()
Nabin Hait25bd84d2015-03-04 15:06:56 +0530664 taxes = self.doc.get("taxes")
665 net_total = 0
Nabin Hait3237c752015-02-17 11:11:11 +0530666
Nabin Haite7679702015-02-20 14:40:35 +0530667 if total_for_discount_amount:
Nabin Hait3237c752015-02-17 11:11:11 +0530668 # calculate item amount after Discount Amount
marination91982d12023-01-24 18:03:53 +0530669 for i, item in enumerate(self._items):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530670 distributed_amount = (
671 flt(self.doc.discount_amount) * item.net_amount / total_for_discount_amount
672 )
Anand Doshiec5ec602015-03-05 19:31:23 +0530673
Nabin Haite7679702015-02-20 14:40:35 +0530674 item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
Nabin Hait25bd84d2015-03-04 15:06:56 +0530675 net_total += item.net_amount
Anand Doshiec5ec602015-03-05 19:31:23 +0530676
Nabin Hait25bd84d2015-03-04 15:06:56 +0530677 # discount amount rounding loss adjustment if no taxes
Ankush Menat494bd9e2022-03-28 18:52:46 +0530678 if (
679 self.doc.apply_discount_on == "Net Total"
680 or not taxes
681 or total_for_discount_amount == self.doc.net_total
marination91982d12023-01-24 18:03:53 +0530682 ) and i == len(self._items) - 1:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530683 discount_amount_loss = flt(
684 self.doc.net_total - net_total - self.doc.discount_amount, self.doc.precision("net_total")
685 )
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530686
Ankush Menat494bd9e2022-03-28 18:52:46 +0530687 item.net_amount = flt(item.net_amount + discount_amount_loss, item.precision("net_amount"))
Anand Doshiec5ec602015-03-05 19:31:23 +0530688
Nabin Hait51e980d2015-10-10 18:10:05 +0530689 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 +0530690
Nabin Haite7679702015-02-20 14:40:35 +0530691 self._set_in_company_currency(item, ["net_rate", "net_amount"])
Nabin Hait3237c752015-02-17 11:11:11 +0530692
693 self.discount_amount_applied = True
694 self._calculate()
695 else:
696 self.doc.base_discount_amount = 0
697
Nabin Haite7679702015-02-20 14:40:35 +0530698 def get_total_for_discount_amount(self):
Nabin Haitde9c8a92015-02-23 01:06:00 +0530699 if self.doc.apply_discount_on == "Net Total":
700 return self.doc.net_total
Nabin Haite7679702015-02-20 14:40:35 +0530701 else:
702 actual_taxes_dict = {}
Nabin Hait3237c752015-02-17 11:11:11 +0530703
Nabin Haite7679702015-02-20 14:40:35 +0530704 for tax in self.doc.get("taxes"):
Nabin Hait19ea7212020-08-11 20:34:57 +0530705 if tax.charge_type in ["Actual", "On Item Quantity"]:
Nabin Haitaf9bdfe2017-12-12 18:50:05 +0530706 tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
707 actual_taxes_dict.setdefault(tax.idx, tax_amount)
Nabin Haite7679702015-02-20 14:40:35 +0530708 elif tax.row_id in actual_taxes_dict:
709 actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * flt(tax.rate) / 100
710 actual_taxes_dict.setdefault(tax.idx, actual_tax_amount)
Nabin Hait3237c752015-02-17 11:11:11 +0530711
Ankush Menat494bd9e2022-03-28 18:52:46 +0530712 return flt(
713 self.doc.grand_total - sum(actual_taxes_dict.values()), self.doc.precision("grand_total")
714 )
Nabin Hait3237c752015-02-17 11:11:11 +0530715
Nabin Hait7b19b9e2015-02-24 09:42:24 +0530716 def calculate_total_advance(self):
Raffael Meyer67cf7e12023-01-15 13:04:16 +0100717 if not self.doc.docstatus.is_cancelled():
Ankush Menat494bd9e2022-03-28 18:52:46 +0530718 total_allocated_amount = sum(
719 flt(adv.allocated_amount, adv.precision("allocated_amount"))
720 for adv in self.doc.get("advances")
721 )
Nabin Hait3237c752015-02-17 11:11:11 +0530722
Nabin Haite7679702015-02-20 14:40:35 +0530723 self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530724
Faris Ansari6041f5c2018-02-08 13:33:52 +0530725 grand_total = self.doc.rounded_total or self.doc.grand_total
726
Nabin Hait289ffb72016-02-08 11:06:55 +0530727 if self.doc.party_account_currency == self.doc.currency:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530728 invoice_total = flt(
729 grand_total - flt(self.doc.write_off_amount), self.doc.precision("grand_total")
730 )
Nabin Hait8d8cba72017-04-03 17:26:22 +0530731 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530732 base_write_off_amount = flt(
733 flt(self.doc.write_off_amount) * self.doc.conversion_rate,
734 self.doc.precision("base_write_off_amount"),
735 )
736 invoice_total = (
737 flt(grand_total * self.doc.conversion_rate, self.doc.precision("grand_total"))
738 - base_write_off_amount
739 )
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530740
Nabin Haitadc09232016-02-09 10:31:11 +0530741 if invoice_total > 0 and self.doc.total_advance > invoice_total:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530742 frappe.throw(
743 _("Advance amount cannot be greater than {0} {1}").format(
744 self.doc.party_account_currency, invoice_total
745 )
746 )
Nabin Hait3237c752015-02-17 11:11:11 +0530747
Raffael Meyer67cf7e12023-01-15 13:04:16 +0100748 if self.doc.docstatus.is_draft():
Ankush Menat3821a972022-03-28 20:14:19 +0530749 if self.doc.get("write_off_outstanding_amount_automatically"):
Deepesh Garg19b1b1f2022-03-25 18:02:14 +0530750 self.doc.write_off_amount = 0
751
Nabin Hait3237c752015-02-17 11:11:11 +0530752 self.calculate_outstanding_amount()
Deepesh Gargf57f4af2022-03-24 12:31:37 +0530753 self.calculate_write_off_amount()
Nabin Hait3237c752015-02-17 11:11:11 +0530754
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530755 def is_internal_invoice(self):
756 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530757 Checks if its an internal transfer invoice
758 and decides if to calculate any out standing amount or not
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530759 """
760
Ankush Menat494bd9e2022-03-28 18:52:46 +0530761 if self.doc.doctype in ("Sales Invoice", "Purchase Invoice") and self.doc.is_internal_transfer():
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530762 return True
763
764 return False
765
Nabin Hait3237c752015-02-17 11:11:11 +0530766 def calculate_outstanding_amount(self):
767 # NOTE:
768 # write_off_amount is only for POS Invoice
769 # total_advance is only for non POS Invoice
Rohit Waghchaureefb5bf22016-08-25 02:09:53 +0530770 if self.doc.doctype == "Sales Invoice":
771 self.calculate_paid_amount()
772
Ankush Menat494bd9e2022-03-28 18:52:46 +0530773 if (
774 self.doc.is_return
775 and self.doc.return_against
776 and not self.doc.get("is_pos")
777 or self.is_internal_invoice()
778 ):
779 return
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530780
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530781 self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
Ankush Menat494bd9e2022-03-28 18:52:46 +0530782 self._set_in_company_currency(self.doc, ["write_off_amount"])
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530783
Nabin Hait877e1bb2017-11-17 12:27:43 +0530784 if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
785 grand_total = self.doc.rounded_total or self.doc.grand_total
Deepesh Garg9d3a5c32022-01-06 18:58:49 +0530786 base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
787
Nabin Hait877e1bb2017-11-17 12:27:43 +0530788 if self.doc.party_account_currency == self.doc.currency:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530789 total_amount_to_pay = flt(
790 grand_total - self.doc.total_advance - flt(self.doc.write_off_amount),
791 self.doc.precision("grand_total"),
792 )
Nabin Hait877e1bb2017-11-17 12:27:43 +0530793 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530794 total_amount_to_pay = flt(
795 flt(base_grand_total, self.doc.precision("base_grand_total"))
796 - self.doc.total_advance
797 - flt(self.doc.base_write_off_amount),
798 self.doc.precision("base_grand_total"),
799 )
Anand Doshi15f7b1e2016-04-04 15:03:28 +0530800
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530801 self.doc.round_floats_in(self.doc, ["paid_amount"])
Nabin Hait877e1bb2017-11-17 12:27:43 +0530802 change_amount = 0
803
Ankush Menat494bd9e2022-03-28 18:52:46 +0530804 if self.doc.doctype == "Sales Invoice" and not self.doc.get("is_return"):
Nabin Hait877e1bb2017-11-17 12:27:43 +0530805 self.calculate_change_amount()
Ankush Menat494bd9e2022-03-28 18:52:46 +0530806 change_amount = (
807 self.doc.change_amount
808 if self.doc.party_account_currency == self.doc.currency
809 else self.doc.base_change_amount
810 )
Nabin Hait877e1bb2017-11-17 12:27:43 +0530811
Ankush Menat494bd9e2022-03-28 18:52:46 +0530812 paid_amount = (
813 self.doc.paid_amount
814 if self.doc.party_account_currency == self.doc.currency
815 else self.doc.base_paid_amount
816 )
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530817
Ankush Menat494bd9e2022-03-28 18:52:46 +0530818 self.doc.outstanding_amount = flt(
819 total_amount_to_pay - flt(paid_amount) + flt(change_amount),
820 self.doc.precision("outstanding_amount"),
821 )
Rushabh Mehtac6bd7ad2016-12-21 17:30:29 +0530822
Saqib Ansarie8a7a542022-03-12 19:20:48 +0530823 if (
Ankush Menat494bd9e2022-03-28 18:52:46 +0530824 self.doc.doctype == "Sales Invoice"
825 and self.doc.get("is_pos")
Saqib Ansari33762db2022-08-10 14:17:28 +0530826 and self.doc.get("pos_profile")
827 and self.doc.get("is_consolidated")
828 ):
829 write_off_limit = flt(
830 frappe.db.get_value("POS Profile", self.doc.pos_profile, "write_off_limit")
831 )
832 if write_off_limit and abs(self.doc.outstanding_amount) <= write_off_limit:
833 self.doc.write_off_outstanding_amount_automatically = 1
834
835 if (
836 self.doc.doctype == "Sales Invoice"
837 and self.doc.get("is_pos")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530838 and self.doc.get("is_return")
839 and not self.doc.get("is_consolidated")
Saqib Ansarie8a7a542022-03-12 19:20:48 +0530840 ):
Subin Tom7d627df2021-08-23 11:05:07 +0530841 self.set_total_amount_to_default_mop(total_amount_to_pay)
842 self.calculate_paid_amount()
Deepesh Garg0ebace52020-02-25 13:21:16 +0530843
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530844 def calculate_paid_amount(self):
845 paid_amount = base_paid_amount = 0.0
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530846
847 if self.doc.is_pos:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530848 for payment in self.doc.get("payments"):
Ayush Shuklae9cf1ab2017-05-25 14:14:55 +0530849 payment.amount = flt(payment.amount)
850 payment.base_amount = payment.amount * flt(self.doc.conversion_rate)
Rohit Waghchauref58cad62017-01-17 12:11:57 +0530851 paid_amount += payment.amount
852 base_paid_amount += payment.base_amount
rohitwaghchaure73456ac2017-05-16 11:29:57 +0530853 elif not self.doc.is_return:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530854 self.doc.set("payments", [])
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530855
Manas Solankida486ee2018-07-06 12:36:57 +0530856 if self.doc.redeem_loyalty_points and self.doc.loyalty_amount:
857 base_paid_amount += self.doc.loyalty_amount
Ankush Menat494bd9e2022-03-28 18:52:46 +0530858 paid_amount += self.doc.loyalty_amount / flt(self.doc.conversion_rate)
Manas Solankida486ee2018-07-06 12:36:57 +0530859
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530860 self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount"))
861 self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount"))
862
Nabin Hait3bb1a422016-08-02 16:41:10 +0530863 def calculate_change_amount(self):
864 self.doc.change_amount = 0.0
Rohit Waghchaure609e2b42016-08-31 02:04:37 +0530865 self.doc.base_change_amount = 0.0
Saqib Ansaric2b83a02022-02-08 17:07:51 +0530866 grand_total = self.doc.rounded_total or self.doc.grand_total
867 base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
Nabin Hait877e1bb2017-11-17 12:27:43 +0530868
Ankush Menat494bd9e2022-03-28 18:52:46 +0530869 if (
870 self.doc.doctype == "Sales Invoice"
871 and self.doc.paid_amount > grand_total
872 and not self.doc.is_return
873 and any(d.type == "Cash" for d in self.doc.payments)
874 ):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530875 self.doc.change_amount = flt(
Ankush Menat3821a972022-03-28 20:14:19 +0530876 self.doc.paid_amount - grand_total, self.doc.precision("change_amount")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530877 )
Nabin Haitac3b2aa2017-05-30 15:35:01 +0530878
Ankush Menat494bd9e2022-03-28 18:52:46 +0530879 self.doc.base_change_amount = flt(
Ankush Menat3821a972022-03-28 20:14:19 +0530880 self.doc.base_paid_amount - base_grand_total, self.doc.precision("base_change_amount")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530881 )
mbauskar36b51892016-01-18 16:31:10 +0530882
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530883 def calculate_write_off_amount(self):
Ankush Menat3821a972022-03-28 20:14:19 +0530884 if self.doc.get("write_off_outstanding_amount_automatically"):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530885 self.doc.write_off_amount = flt(
Ankush Menat3821a972022-03-28 20:14:19 +0530886 self.doc.outstanding_amount, self.doc.precision("write_off_amount")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530887 )
888 self.doc.base_write_off_amount = flt(
889 self.doc.write_off_amount * self.doc.conversion_rate,
890 self.doc.precision("base_write_off_amount"),
891 )
Rohit Waghchaure7127a8f2016-08-04 14:56:15 +0530892
Deepesh Gargf57f4af2022-03-24 12:31:37 +0530893 self.calculate_outstanding_amount()
894
mbauskar36b51892016-01-18 16:31:10 +0530895 def calculate_margin(self, item):
Makarand Bauskar0e4c5c92017-05-11 11:40:02 +0530896 rate_with_margin = 0.0
Shreya Shahbe690ef2017-11-14 17:22:41 +0530897 base_rate_with_margin = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530898 if item.price_list_rate:
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530899 if item.pricing_rules and not self.doc.ignore_pricing_rule:
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530900 has_margin = False
marination733fd5f2020-08-26 18:23:12 +0530901 for d in get_applied_pricing_rules(item.pricing_rules):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530902 pricing_rule = frappe.get_cached_doc("Pricing Rule", d)
Shreya Shahf718b0c2018-02-20 11:26:46 +0530903
Ankush Menat494bd9e2022-03-28 18:52:46 +0530904 if pricing_rule.margin_rate_or_amount and (
905 (
906 pricing_rule.currency == self.doc.currency
907 and pricing_rule.margin_type in ["Amount", "Percentage"]
908 )
909 or pricing_rule.margin_type == "Percentage"
910 ):
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530911 item.margin_type = pricing_rule.margin_type
912 item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530913 has_margin = True
914
915 if not has_margin:
916 item.margin_type = None
917 item.margin_rate_or_amount = 0.0
mbauskar36b51892016-01-18 16:31:10 +0530918
Rohit Waghchaure01faa9c2021-06-21 00:59:02 +0530919 if not item.pricing_rules and flt(item.rate) > flt(item.price_list_rate):
920 item.margin_type = "Amount"
Ankush Menat494bd9e2022-03-28 18:52:46 +0530921 item.margin_rate_or_amount = flt(
922 item.rate - item.price_list_rate, item.precision("margin_rate_or_amount")
923 )
Rohit Waghchaure01faa9c2021-06-21 00:59:02 +0530924 item.rate_with_margin = item.rate
925
926 elif item.margin_type and item.margin_rate_or_amount:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530927 margin_value = (
928 item.margin_rate_or_amount
929 if item.margin_type == "Amount"
930 else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100
931 )
Makarand Bauskar0e4c5c92017-05-11 11:40:02 +0530932 rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
Shreya Shahbe690ef2017-11-14 17:22:41 +0530933 base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
mbauskar36b51892016-01-18 16:31:10 +0530934
Shreya Shahbe690ef2017-11-14 17:22:41 +0530935 return rate_with_margin, base_rate_with_margin
Nabin Hait852cb642017-07-05 12:58:19 +0530936
937 def set_item_wise_tax_breakup(self):
Nabin Hait9c421612017-07-20 13:32:01 +0530938 self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530939
Subin Tom7d627df2021-08-23 11:05:07 +0530940 def set_total_amount_to_default_mop(self, total_amount_to_pay):
Deepesh Garg06e8e282022-10-31 19:58:46 +0530941 total_paid_amount = 0
942 for payment in self.doc.get("payments"):
943 total_paid_amount += (
944 payment.amount if self.doc.party_account_currency == self.doc.currency else payment.base_amount
Ankush Menat494bd9e2022-03-28 18:52:46 +0530945 )
946
Deepesh Garg06e8e282022-10-31 19:58:46 +0530947 pending_amount = total_amount_to_pay - total_paid_amount
948
949 if pending_amount > 0:
950 default_mode_of_payment = frappe.db.get_value(
951 "POS Payment Method",
952 {"parent": self.doc.pos_profile, "default": 1},
953 ["mode_of_payment"],
954 as_dict=1,
955 )
956
957 if default_mode_of_payment:
958 self.doc.payments = []
959 self.doc.append(
960 "payments",
961 {
962 "mode_of_payment": default_mode_of_payment.mode_of_payment,
963 "amount": pending_amount,
964 "default": 1,
965 },
966 )
967
Deepesh Garg0ebace52020-02-25 13:21:16 +0530968
Nabin Hait9c421612017-07-20 13:32:01 +0530969def get_itemised_tax_breakup_html(doc):
970 if not doc.taxes:
971 return
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530972
Nabin Hait9c421612017-07-20 13:32:01 +0530973 # get headers
Nabin Haitcaab5822017-08-24 16:22:28 +0530974 tax_accounts = []
975 for tax in doc.taxes:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530976 if getattr(tax, "category", None) and tax.category == "Valuation":
Nabin Haitcaab5822017-08-24 16:22:28 +0530977 continue
rohitwaghchaure4e17fae2017-12-12 14:40:52 +0530978 if tax.description not in tax_accounts:
Nabin Haitcaab5822017-08-24 16:22:28 +0530979 tax_accounts.append(tax.description)
980
Sagar Vora4205f562023-07-24 18:37:36 +0530981 with temporary_flag("company", doc.company):
982 headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
Smit Vora1b8490d2023-07-24 20:20:19 +0530983 itemised_tax_data = get_itemised_tax_breakup_data(doc)
984 get_rounded_tax_amount(itemised_tax_data, doc.precision("tax_amount", "taxes"))
Sagar Vora4205f562023-07-24 18:37:36 +0530985 update_itemised_tax_data(doc)
Vishal Dhayaguded42242d2017-11-29 16:09:59 +0530986
Nabin Hait9c421612017-07-20 13:32:01 +0530987 return frappe.render_template(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530988 "templates/includes/itemised_tax_breakup.html",
989 dict(
Nabin Hait9c421612017-07-20 13:32:01 +0530990 headers=headers,
DaizyModib84deec2023-07-21 17:26:35 +0530991 itemised_tax_data=itemised_tax_data,
Nabin Hait9c421612017-07-20 13:32:01 +0530992 tax_accounts=tax_accounts,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530993 doc=doc,
994 ),
Nabin Hait9c421612017-07-20 13:32:01 +0530995 )
Nabin Hait852cb642017-07-05 12:58:19 +0530996
Ankush Menat494bd9e2022-03-28 18:52:46 +0530997
Deepesh Garg6a5ef262021-02-19 14:30:23 +0530998@frappe.whitelist()
999def get_round_off_applicable_accounts(company, account_list):
Sagar Vora17ef3c92023-04-04 17:49:50 +05301000 # required to set correct region
Sagar Vora4205f562023-07-24 18:37:36 +05301001 with temporary_flag("company", company):
1002 return get_regional_round_off_accounts(company, account_list)
Deepesh Garg6a5ef262021-02-19 14:30:23 +05301003
Ankush Menat494bd9e2022-03-28 18:52:46 +05301004
Deepesh Garg6a5ef262021-02-19 14:30:23 +05301005@erpnext.allow_regional
1006def get_regional_round_off_accounts(company, account_list):
1007 pass
rohitwaghchaured4526682017-12-28 14:20:13 +05301008
Ankush Menat494bd9e2022-03-28 18:52:46 +05301009
rohitwaghchaured4526682017-12-28 14:20:13 +05301010@erpnext.allow_regional
1011def update_itemised_tax_data(doc):
Ankush Menat494bd9e2022-03-28 18:52:46 +05301012 # Don't delete this method, used for localization
rohitwaghchaured4526682017-12-28 14:20:13 +05301013 pass
1014
Ankush Menat494bd9e2022-03-28 18:52:46 +05301015
Nabin Haitb962fc12017-07-17 18:02:31 +05301016@erpnext.allow_regional
1017def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
1018 return [_("Item"), _("Taxable Amount")] + tax_accounts
1019
Ankush Menat494bd9e2022-03-28 18:52:46 +05301020
Nabin Haitb962fc12017-07-17 18:02:31 +05301021@erpnext.allow_regional
DaizyModi653117c2023-07-21 17:52:54 +05301022def get_itemised_tax_breakup_data(doc):
DaizyModi6f376cf2023-07-24 18:02:42 +05301023 itemised_tax = get_itemised_tax(doc.taxes)
Nabin Haitb962fc12017-07-17 18:02:31 +05301024
1025 itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
1026
DaizyModib84deec2023-07-21 17:26:35 +05301027 itemised_tax_data = []
1028 for item_code, taxes in itemised_tax.items():
DaizyModi6f376cf2023-07-24 18:02:42 +05301029 itemised_tax_data.append(
1030 frappe._dict(
1031 {"item": item_code, "taxable_amount": itemised_taxable_amount.get(item_code), **taxes}
1032 )
1033 )
DaizyModib84deec2023-07-21 17:26:35 +05301034
1035 return itemised_tax_data
Nabin Haitb962fc12017-07-17 18:02:31 +05301036
Ankush Menat494bd9e2022-03-28 18:52:46 +05301037
Nabin Hait34c551d2019-07-03 10:34:31 +05301038def get_itemised_tax(taxes, with_tax_account=False):
Nabin Haitb962fc12017-07-17 18:02:31 +05301039 itemised_tax = {}
1040 for tax in taxes:
Ankush Menat494bd9e2022-03-28 18:52:46 +05301041 if getattr(tax, "category", None) and tax.category == "Valuation":
Nabin Haitcaab5822017-08-24 16:22:28 +05301042 continue
1043
Nabin Haitb962fc12017-07-17 18:02:31 +05301044 item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
Nabin Hait2e4de832017-09-19 14:53:16 +05301045 if item_tax_map:
1046 for item_code, tax_data in item_tax_map.items():
1047 itemised_tax.setdefault(item_code, frappe._dict())
Vishal Dhayaguded42242d2017-11-29 16:09:59 +05301048
Prateeksha Singhea7533f2018-06-11 13:31:33 +05301049 tax_rate = 0.0
1050 tax_amount = 0.0
1051
Nabin Hait2e4de832017-09-19 14:53:16 +05301052 if isinstance(tax_data, list):
Prateeksha Singhea7533f2018-06-11 13:31:33 +05301053 tax_rate = flt(tax_data[0])
1054 tax_amount = flt(tax_data[1])
Nabin Hait2e4de832017-09-19 14:53:16 +05301055 else:
Prateeksha Singhea7533f2018-06-11 13:31:33 +05301056 tax_rate = flt(tax_data)
1057
Ankush Menat494bd9e2022-03-28 18:52:46 +05301058 itemised_tax[item_code][tax.description] = frappe._dict(
1059 dict(tax_rate=tax_rate, tax_amount=tax_amount)
1060 )
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +05301061
Nabin Hait34c551d2019-07-03 10:34:31 +05301062 if with_tax_account:
1063 itemised_tax[item_code][tax.description].tax_account = tax.account_head
1064
Nabin Haitb962fc12017-07-17 18:02:31 +05301065 return itemised_tax
1066
Ankush Menat494bd9e2022-03-28 18:52:46 +05301067
Nabin Haitb962fc12017-07-17 18:02:31 +05301068def get_itemised_taxable_amount(items):
1069 itemised_taxable_amount = frappe._dict()
1070 for item in items:
Rohit Waghchaure296fbfe2017-07-10 13:03:29 +05301071 item_code = item.item_code or item.item_name
Nabin Haitb962fc12017-07-17 18:02:31 +05301072 itemised_taxable_amount.setdefault(item_code, 0)
1073 itemised_taxable_amount[item_code] += item.net_amount
1074
Nabin Haitcaab5822017-08-24 16:22:28 +05301075 return itemised_taxable_amount
1076
Ankush Menat494bd9e2022-03-28 18:52:46 +05301077
DaizyModi6f376cf2023-07-24 18:02:42 +05301078def get_rounded_tax_amount(itemised_tax, precision):
Nabin Haitcaab5822017-08-24 16:22:28 +05301079 # Rounding based on tax_amount precision
DaizyModi6f376cf2023-07-24 18:02:42 +05301080 for taxes in itemised_tax:
1081 for row in taxes.values():
1082 if isinstance(row, dict) and isinstance(row["tax_amount"], float):
1083 row["tax_amount"] = flt(row["tax_amount"], precision)
Deepesh Gargbfc17e42020-12-25 18:34:39 +05301084
Ankush Menat494bd9e2022-03-28 18:52:46 +05301085
Deepesh Gargbfc17e42020-12-25 18:34:39 +05301086class init_landed_taxes_and_totals(object):
1087 def __init__(self, doc):
1088 self.doc = doc
Ankush Menat494bd9e2022-03-28 18:52:46 +05301089 self.tax_field = "taxes" if self.doc.doctype == "Landed Cost Voucher" else "additional_costs"
Deepesh Gargbfc17e42020-12-25 18:34:39 +05301090 self.set_account_currency()
1091 self.set_exchange_rate()
1092 self.set_amounts_in_company_currency()
1093
1094 def set_account_currency(self):
1095 company_currency = erpnext.get_company_currency(self.doc.company)
1096 for d in self.doc.get(self.tax_field):
1097 if not d.account_currency:
Daizy Modi4efc9472022-11-07 09:21:03 +05301098 account_currency = frappe.get_cached_value("Account", d.expense_account, "account_currency")
Deepesh Gargbfc17e42020-12-25 18:34:39 +05301099 d.account_currency = account_currency or company_currency
1100
1101 def set_exchange_rate(self):
1102 company_currency = erpnext.get_company_currency(self.doc.company)
1103 for d in self.doc.get(self.tax_field):
1104 if d.account_currency == company_currency:
1105 d.exchange_rate = 1
Deepesh Garg22f5ff82021-03-17 10:56:52 +05301106 elif not d.exchange_rate:
Ankush Menat494bd9e2022-03-28 18:52:46 +05301107 d.exchange_rate = get_exchange_rate(
1108 self.doc.posting_date,
1109 account=d.expense_account,
1110 account_currency=d.account_currency,
1111 company=self.doc.company,
1112 )
Deepesh Gargbfc17e42020-12-25 18:34:39 +05301113
1114 if not d.exchange_rate:
1115 frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
1116
1117 def set_amounts_in_company_currency(self):
1118 for d in self.doc.get(self.tax_field):
1119 d.amount = flt(d.amount, d.precision("amount"))
niralisatapara12456f92022-11-03 10:46:30 +05301120 d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))