You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1093 lines
36 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # License: GNU General Public License v3. See license.txt
  3. import json
  4. import frappe
  5. from frappe import _, scrub
  6. from frappe.model.document import Document
  7. from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
  8. import erpnext
  9. from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
  10. from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
  11. from erpnext.controllers.accounts_controller import (
  12. validate_conversion_rate,
  13. validate_inclusive_tax,
  14. validate_taxes_and_charges,
  15. )
  16. from erpnext.stock.get_item_details import _get_item_tax_template
  17. class calculate_taxes_and_totals(object):
  18. def __init__(self, doc: Document):
  19. self.doc = doc
  20. frappe.flags.round_off_applicable_accounts = []
  21. self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items")
  22. get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
  23. self.calculate()
  24. def filter_rows(self):
  25. """Exclude rows, that do not fulfill the filter criteria, from totals computation."""
  26. items = list(filter(lambda item: not item.get("is_alternative"), self.doc.get("items")))
  27. return items
  28. def calculate(self):
  29. if not len(self._items):
  30. return
  31. self.discount_amount_applied = False
  32. self._calculate()
  33. if self.doc.meta.get_field("discount_amount"):
  34. self.set_discount_amount()
  35. self.apply_discount_amount()
  36. # Update grand total as per cash and non trade discount
  37. if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
  38. self.doc.grand_total -= self.doc.discount_amount
  39. self.doc.base_grand_total -= self.doc.base_discount_amount
  40. self.set_rounded_total()
  41. self.calculate_shipping_charges()
  42. if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
  43. self.calculate_total_advance()
  44. if self.doc.meta.get_field("other_charges_calculation"):
  45. self.set_item_wise_tax_breakup()
  46. def _calculate(self):
  47. self.validate_conversion_rate()
  48. self.calculate_item_values()
  49. self.validate_item_tax_template()
  50. self.initialize_taxes()
  51. self.determine_exclusive_rate()
  52. self.calculate_net_total()
  53. self.calculate_tax_withholding_net_total()
  54. self.calculate_taxes()
  55. self.manipulate_grand_total_for_inclusive_tax()
  56. self.calculate_totals()
  57. self._cleanup()
  58. self.calculate_total_net_weight()
  59. def calculate_tax_withholding_net_total(self):
  60. if hasattr(self.doc, "tax_withholding_net_total"):
  61. sum_net_amount = 0
  62. sum_base_net_amount = 0
  63. for item in self._items:
  64. if hasattr(item, "apply_tds") and item.apply_tds:
  65. sum_net_amount += item.net_amount
  66. sum_base_net_amount += item.base_net_amount
  67. self.doc.tax_withholding_net_total = sum_net_amount
  68. self.doc.base_tax_withholding_net_total = sum_base_net_amount
  69. def validate_item_tax_template(self):
  70. for item in self._items:
  71. if item.item_code and item.get("item_tax_template"):
  72. item_doc = frappe.get_cached_doc("Item", item.item_code)
  73. args = {
  74. "net_rate": item.net_rate or item.rate,
  75. "tax_category": self.doc.get("tax_category"),
  76. "posting_date": self.doc.get("posting_date"),
  77. "bill_date": self.doc.get("bill_date"),
  78. "transaction_date": self.doc.get("transaction_date"),
  79. "company": self.doc.get("company"),
  80. }
  81. item_group = item_doc.item_group
  82. item_group_taxes = []
  83. while item_group:
  84. item_group_doc = frappe.get_cached_doc("Item Group", item_group)
  85. item_group_taxes += item_group_doc.taxes or []
  86. item_group = item_group_doc.parent_item_group
  87. item_taxes = item_doc.taxes or []
  88. if not item_group_taxes and (not item_taxes):
  89. # No validation if no taxes in item or item group
  90. continue
  91. taxes = _get_item_tax_template(args, item_taxes + item_group_taxes, for_validate=True)
  92. if taxes:
  93. if item.item_tax_template not in taxes:
  94. item.item_tax_template = taxes[0]
  95. frappe.msgprint(
  96. _("Row {0}: Item Tax template updated as per validity and rate applied").format(
  97. item.idx, frappe.bold(item.item_code)
  98. )
  99. )
  100. def validate_conversion_rate(self):
  101. # validate conversion rate
  102. company_currency = erpnext.get_company_currency(self.doc.company)
  103. if not self.doc.currency or self.doc.currency == company_currency:
  104. self.doc.currency = company_currency
  105. self.doc.conversion_rate = 1.0
  106. else:
  107. validate_conversion_rate(
  108. self.doc.currency,
  109. self.doc.conversion_rate,
  110. self.doc.meta.get_label("conversion_rate"),
  111. self.doc.company,
  112. )
  113. self.doc.conversion_rate = flt(self.doc.conversion_rate)
  114. def calculate_item_values(self):
  115. if self.doc.get("is_consolidated"):
  116. return
  117. if not self.discount_amount_applied:
  118. for item in self._items:
  119. self.doc.round_floats_in(item)
  120. if item.discount_percentage == 100:
  121. item.rate = 0.0
  122. elif item.price_list_rate:
  123. if not item.rate or (item.pricing_rules and item.discount_percentage > 0):
  124. item.rate = flt(
  125. item.price_list_rate * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")
  126. )
  127. item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
  128. elif item.discount_amount and item.pricing_rules:
  129. item.rate = item.price_list_rate - item.discount_amount
  130. if item.doctype in [
  131. "Quotation Item",
  132. "Sales Order Item",
  133. "Delivery Note Item",
  134. "Sales Invoice Item",
  135. "POS Invoice Item",
  136. "Purchase Invoice Item",
  137. "Purchase Order Item",
  138. "Purchase Receipt Item",
  139. ]:
  140. item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
  141. if flt(item.rate_with_margin) > 0:
  142. item.rate = flt(
  143. item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")
  144. )
  145. if item.discount_amount and not item.discount_percentage:
  146. item.rate = item.rate_with_margin - item.discount_amount
  147. else:
  148. item.discount_amount = item.rate_with_margin - item.rate
  149. elif flt(item.price_list_rate) > 0:
  150. item.discount_amount = item.price_list_rate - item.rate
  151. elif flt(item.price_list_rate) > 0 and not item.discount_amount:
  152. item.discount_amount = item.price_list_rate - item.rate
  153. item.net_rate = item.rate
  154. if not item.qty and self.doc.get("is_return"):
  155. item.amount = flt(-1 * item.rate, item.precision("amount"))
  156. elif not item.qty and self.doc.get("is_debit_note"):
  157. item.amount = flt(item.rate, item.precision("amount"))
  158. else:
  159. item.amount = flt(item.rate * item.qty, item.precision("amount"))
  160. item.net_amount = item.amount
  161. self._set_in_company_currency(
  162. item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"]
  163. )
  164. item.item_tax_amount = 0.0
  165. def _set_in_company_currency(self, doc, fields):
  166. """set values in base currency"""
  167. for f in fields:
  168. val = flt(
  169. flt(doc.get(f), doc.precision(f)) * self.doc.conversion_rate, doc.precision("base_" + f)
  170. )
  171. doc.set("base_" + f, val)
  172. def initialize_taxes(self):
  173. for tax in self.doc.get("taxes"):
  174. if not self.discount_amount_applied:
  175. validate_taxes_and_charges(tax)
  176. validate_inclusive_tax(tax, self.doc)
  177. if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
  178. tax.item_wise_tax_detail = {}
  179. tax_fields = [
  180. "total",
  181. "tax_amount_after_discount_amount",
  182. "tax_amount_for_current_item",
  183. "grand_total_for_current_item",
  184. "tax_fraction_for_current_item",
  185. "grand_total_fraction_for_current_item",
  186. ]
  187. if tax.charge_type != "Actual" and not (
  188. self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
  189. ):
  190. tax_fields.append("tax_amount")
  191. for fieldname in tax_fields:
  192. tax.set(fieldname, 0.0)
  193. self.doc.round_floats_in(tax)
  194. def determine_exclusive_rate(self):
  195. if not any(cint(tax.included_in_print_rate) for tax in self.doc.get("taxes")):
  196. return
  197. for item in self._items:
  198. item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
  199. cumulated_tax_fraction = 0
  200. total_inclusive_tax_amount_per_qty = 0
  201. for i, tax in enumerate(self.doc.get("taxes")):
  202. (
  203. tax.tax_fraction_for_current_item,
  204. inclusive_tax_amount_per_qty,
  205. ) = self.get_current_tax_fraction(tax, item_tax_map)
  206. if i == 0:
  207. tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
  208. else:
  209. tax.grand_total_fraction_for_current_item = (
  210. self.doc.get("taxes")[i - 1].grand_total_fraction_for_current_item
  211. + tax.tax_fraction_for_current_item
  212. )
  213. cumulated_tax_fraction += tax.tax_fraction_for_current_item
  214. total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
  215. if (
  216. not self.discount_amount_applied
  217. and item.qty
  218. and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty)
  219. ):
  220. amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
  221. item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
  222. item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
  223. item.discount_percentage = flt(item.discount_percentage, item.precision("discount_percentage"))
  224. self._set_in_company_currency(item, ["net_rate", "net_amount"])
  225. def _load_item_tax_rate(self, item_tax_rate):
  226. return json.loads(item_tax_rate) if item_tax_rate else {}
  227. def get_current_tax_fraction(self, tax, item_tax_map):
  228. """
  229. Get tax fraction for calculating tax exclusive amount
  230. from tax inclusive amount
  231. """
  232. current_tax_fraction = 0
  233. inclusive_tax_amount_per_qty = 0
  234. if cint(tax.included_in_print_rate):
  235. tax_rate = self._get_tax_rate(tax, item_tax_map)
  236. if tax.charge_type == "On Net Total":
  237. current_tax_fraction = tax_rate / 100.0
  238. elif tax.charge_type == "On Previous Row Amount":
  239. current_tax_fraction = (tax_rate / 100.0) * self.doc.get("taxes")[
  240. cint(tax.row_id) - 1
  241. ].tax_fraction_for_current_item
  242. elif tax.charge_type == "On Previous Row Total":
  243. current_tax_fraction = (tax_rate / 100.0) * self.doc.get("taxes")[
  244. cint(tax.row_id) - 1
  245. ].grand_total_fraction_for_current_item
  246. elif tax.charge_type == "On Item Quantity":
  247. inclusive_tax_amount_per_qty = flt(tax_rate)
  248. if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
  249. current_tax_fraction *= -1.0
  250. inclusive_tax_amount_per_qty *= -1.0
  251. return current_tax_fraction, inclusive_tax_amount_per_qty
  252. def _get_tax_rate(self, tax, item_tax_map):
  253. if tax.account_head in item_tax_map:
  254. return flt(item_tax_map.get(tax.account_head), self.doc.precision("rate", tax))
  255. else:
  256. return tax.rate
  257. def calculate_net_total(self):
  258. self.doc.total_qty = (
  259. self.doc.total
  260. ) = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
  261. for item in self._items:
  262. self.doc.total += item.amount
  263. self.doc.total_qty += item.qty
  264. self.doc.base_total += item.base_amount
  265. self.doc.net_total += item.net_amount
  266. self.doc.base_net_total += item.base_net_amount
  267. self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"])
  268. def calculate_shipping_charges(self):
  269. # Do not apply shipping rule for POS
  270. if self.doc.get("is_pos"):
  271. return
  272. if hasattr(self.doc, "shipping_rule") and self.doc.shipping_rule:
  273. shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule)
  274. shipping_rule.apply(self.doc)
  275. self._calculate()
  276. def calculate_taxes(self):
  277. rounding_adjustment_computed = self.doc.get("is_consolidated") and self.doc.get(
  278. "rounding_adjustment"
  279. )
  280. if not rounding_adjustment_computed:
  281. self.doc.rounding_adjustment = 0
  282. # maintain actual tax rate based on idx
  283. actual_tax_dict = dict(
  284. [
  285. [tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
  286. for tax in self.doc.get("taxes")
  287. if tax.charge_type == "Actual"
  288. ]
  289. )
  290. for n, item in enumerate(self._items):
  291. item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
  292. for i, tax in enumerate(self.doc.get("taxes")):
  293. # tax_amount represents the amount of tax for the current step
  294. current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map)
  295. # Adjust divisional loss to the last item
  296. if tax.charge_type == "Actual":
  297. actual_tax_dict[tax.idx] -= current_tax_amount
  298. if n == len(self._items) - 1:
  299. current_tax_amount += actual_tax_dict[tax.idx]
  300. # accumulate tax amount into tax.tax_amount
  301. if tax.charge_type != "Actual" and not (
  302. self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
  303. ):
  304. tax.tax_amount += current_tax_amount
  305. # store tax_amount for current item as it will be used for
  306. # charge type = 'On Previous Row Amount'
  307. tax.tax_amount_for_current_item = current_tax_amount
  308. # set tax after discount
  309. tax.tax_amount_after_discount_amount += current_tax_amount
  310. current_tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(current_tax_amount, tax)
  311. # note: grand_total_for_current_item contains the contribution of
  312. # item's amount, previously applied tax and the current tax on that item
  313. if i == 0:
  314. tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
  315. else:
  316. tax.grand_total_for_current_item = flt(
  317. self.doc.get("taxes")[i - 1].grand_total_for_current_item + current_tax_amount
  318. )
  319. # set precision in the last item iteration
  320. if n == len(self._items) - 1:
  321. self.round_off_totals(tax)
  322. self._set_in_company_currency(tax, ["tax_amount", "tax_amount_after_discount_amount"])
  323. self.round_off_base_values(tax)
  324. self.set_cumulative_total(i, tax)
  325. self._set_in_company_currency(tax, ["total"])
  326. # adjust Discount Amount loss in last tax iteration
  327. if (
  328. i == (len(self.doc.get("taxes")) - 1)
  329. and self.discount_amount_applied
  330. and self.doc.discount_amount
  331. and self.doc.apply_discount_on == "Grand Total"
  332. and not rounding_adjustment_computed
  333. ):
  334. self.doc.rounding_adjustment = flt(
  335. self.doc.grand_total - flt(self.doc.discount_amount) - tax.total,
  336. self.doc.precision("rounding_adjustment"),
  337. )
  338. def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
  339. # if just for valuation, do not add the tax amount in total
  340. # if tax/charges is for deduction, multiply by -1
  341. if getattr(tax, "category", None):
  342. tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
  343. if self.doc.doctype in [
  344. "Purchase Order",
  345. "Purchase Invoice",
  346. "Purchase Receipt",
  347. "Supplier Quotation",
  348. ]:
  349. tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
  350. return tax_amount
  351. def set_cumulative_total(self, row_idx, tax):
  352. tax_amount = tax.tax_amount_after_discount_amount
  353. tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax_amount, tax)
  354. if row_idx == 0:
  355. tax.total = flt(self.doc.net_total + tax_amount, tax.precision("total"))
  356. else:
  357. tax.total = flt(self.doc.get("taxes")[row_idx - 1].total + tax_amount, tax.precision("total"))
  358. def get_current_tax_amount(self, item, tax, item_tax_map):
  359. tax_rate = self._get_tax_rate(tax, item_tax_map)
  360. current_tax_amount = 0.0
  361. if tax.charge_type == "Actual":
  362. # distribute the tax amount proportionally to each item row
  363. actual = flt(tax.tax_amount, tax.precision("tax_amount"))
  364. current_tax_amount = (
  365. item.net_amount * actual / self.doc.net_total if self.doc.net_total else 0.0
  366. )
  367. elif tax.charge_type == "On Net Total":
  368. current_tax_amount = (tax_rate / 100.0) * item.net_amount
  369. elif tax.charge_type == "On Previous Row Amount":
  370. current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[
  371. cint(tax.row_id) - 1
  372. ].tax_amount_for_current_item
  373. elif tax.charge_type == "On Previous Row Total":
  374. current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[
  375. cint(tax.row_id) - 1
  376. ].grand_total_for_current_item
  377. elif tax.charge_type == "On Item Quantity":
  378. current_tax_amount = tax_rate * item.qty
  379. if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
  380. self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
  381. return current_tax_amount
  382. def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
  383. # store tax breakup for each item
  384. key = item.item_code or item.item_name
  385. item_wise_tax_amount = current_tax_amount * self.doc.conversion_rate
  386. if tax.item_wise_tax_detail.get(key):
  387. item_wise_tax_amount += tax.item_wise_tax_detail[key][1]
  388. tax.item_wise_tax_detail[key] = [tax_rate, flt(item_wise_tax_amount)]
  389. def round_off_totals(self, tax):
  390. if tax.account_head in frappe.flags.round_off_applicable_accounts:
  391. tax.tax_amount = round(tax.tax_amount, 0)
  392. tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
  393. tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
  394. tax.tax_amount_after_discount_amount = flt(
  395. tax.tax_amount_after_discount_amount, tax.precision("tax_amount")
  396. )
  397. def round_off_base_values(self, tax):
  398. # Round off to nearest integer based on regional settings
  399. if tax.account_head in frappe.flags.round_off_applicable_accounts:
  400. tax.base_tax_amount = round(tax.base_tax_amount, 0)
  401. tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
  402. def manipulate_grand_total_for_inclusive_tax(self):
  403. # if fully inclusive taxes and diff
  404. if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")):
  405. last_tax = self.doc.get("taxes")[-1]
  406. non_inclusive_tax_amount = sum(
  407. flt(d.tax_amount_after_discount_amount)
  408. for d in self.doc.get("taxes")
  409. if not d.included_in_print_rate
  410. )
  411. diff = (
  412. self.doc.total + non_inclusive_tax_amount - flt(last_tax.total, last_tax.precision("total"))
  413. )
  414. # If discount amount applied, deduct the discount amount
  415. # because self.doc.total is always without discount, but last_tax.total is after discount
  416. if self.discount_amount_applied and self.doc.discount_amount:
  417. diff -= flt(self.doc.discount_amount)
  418. diff = flt(diff, self.doc.precision("rounding_adjustment"))
  419. if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")):
  420. self.doc.rounding_adjustment = diff
  421. def calculate_totals(self):
  422. if self.doc.get("taxes"):
  423. self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment)
  424. else:
  425. self.doc.grand_total = flt(self.doc.net_total)
  426. if self.doc.get("taxes"):
  427. self.doc.total_taxes_and_charges = flt(
  428. self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
  429. self.doc.precision("total_taxes_and_charges"),
  430. )
  431. else:
  432. self.doc.total_taxes_and_charges = 0.0
  433. self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
  434. if self.doc.doctype in [
  435. "Quotation",
  436. "Sales Order",
  437. "Delivery Note",
  438. "Sales Invoice",
  439. "POS Invoice",
  440. ]:
  441. self.doc.base_grand_total = (
  442. flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total"))
  443. if self.doc.total_taxes_and_charges
  444. else self.doc.base_net_total
  445. )
  446. else:
  447. self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0
  448. for tax in self.doc.get("taxes"):
  449. if tax.category in ["Valuation and Total", "Total"]:
  450. if tax.add_deduct_tax == "Add":
  451. self.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount)
  452. else:
  453. self.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount)
  454. self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
  455. self.doc.base_grand_total = (
  456. flt(self.doc.grand_total * self.doc.conversion_rate)
  457. if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted)
  458. else self.doc.base_net_total
  459. )
  460. self._set_in_company_currency(
  461. self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"]
  462. )
  463. self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
  464. self.set_rounded_total()
  465. def calculate_total_net_weight(self):
  466. if self.doc.meta.get_field("total_net_weight"):
  467. self.doc.total_net_weight = 0.0
  468. for d in self._items:
  469. if d.total_weight:
  470. self.doc.total_net_weight += d.total_weight
  471. def set_rounded_total(self):
  472. if self.doc.get("is_consolidated") and self.doc.get("rounding_adjustment"):
  473. return
  474. if self.doc.meta.get_field("rounded_total"):
  475. if self.doc.is_rounded_total_disabled():
  476. self.doc.rounded_total = self.doc.base_rounded_total = 0
  477. return
  478. self.doc.rounded_total = round_based_on_smallest_currency_fraction(
  479. self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
  480. )
  481. # if print_in_rate is set, we would have already calculated rounding adjustment
  482. self.doc.rounding_adjustment += flt(
  483. self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment")
  484. )
  485. self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
  486. def _cleanup(self):
  487. if not self.doc.get("is_consolidated"):
  488. for tax in self.doc.get("taxes"):
  489. if not tax.get("dont_recompute_tax"):
  490. tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(",", ":"))
  491. def set_discount_amount(self):
  492. if self.doc.additional_discount_percentage:
  493. self.doc.discount_amount = flt(
  494. flt(self.doc.get(scrub(self.doc.apply_discount_on)))
  495. * self.doc.additional_discount_percentage
  496. / 100,
  497. self.doc.precision("discount_amount"),
  498. )
  499. def apply_discount_amount(self):
  500. if self.doc.discount_amount:
  501. if not self.doc.apply_discount_on:
  502. frappe.throw(_("Please select Apply Discount On"))
  503. self.doc.base_discount_amount = flt(
  504. self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
  505. )
  506. if self.doc.apply_discount_on == "Grand Total" and self.doc.get(
  507. "is_cash_or_non_trade_discount"
  508. ):
  509. self.discount_amount_applied = True
  510. return
  511. total_for_discount_amount = self.get_total_for_discount_amount()
  512. taxes = self.doc.get("taxes")
  513. net_total = 0
  514. if total_for_discount_amount:
  515. # calculate item amount after Discount Amount
  516. for i, item in enumerate(self._items):
  517. distributed_amount = (
  518. flt(self.doc.discount_amount) * item.net_amount / total_for_discount_amount
  519. )
  520. item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
  521. net_total += item.net_amount
  522. # discount amount rounding loss adjustment if no taxes
  523. if (
  524. self.doc.apply_discount_on == "Net Total"
  525. or not taxes
  526. or total_for_discount_amount == self.doc.net_total
  527. ) and i == len(self._items) - 1:
  528. discount_amount_loss = flt(
  529. self.doc.net_total - net_total - self.doc.discount_amount, self.doc.precision("net_total")
  530. )
  531. item.net_amount = flt(item.net_amount + discount_amount_loss, item.precision("net_amount"))
  532. item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) if item.qty else 0
  533. self._set_in_company_currency(item, ["net_rate", "net_amount"])
  534. self.discount_amount_applied = True
  535. self._calculate()
  536. else:
  537. self.doc.base_discount_amount = 0
  538. def get_total_for_discount_amount(self):
  539. if self.doc.apply_discount_on == "Net Total":
  540. return self.doc.net_total
  541. else:
  542. actual_taxes_dict = {}
  543. for tax in self.doc.get("taxes"):
  544. if tax.charge_type in ["Actual", "On Item Quantity"]:
  545. tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
  546. actual_taxes_dict.setdefault(tax.idx, tax_amount)
  547. elif tax.row_id in actual_taxes_dict:
  548. actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * flt(tax.rate) / 100
  549. actual_taxes_dict.setdefault(tax.idx, actual_tax_amount)
  550. return flt(
  551. self.doc.grand_total - sum(actual_taxes_dict.values()), self.doc.precision("grand_total")
  552. )
  553. def calculate_total_advance(self):
  554. if not self.doc.docstatus.is_cancelled():
  555. total_allocated_amount = sum(
  556. flt(adv.allocated_amount, adv.precision("allocated_amount"))
  557. for adv in self.doc.get("advances")
  558. )
  559. self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
  560. grand_total = self.doc.rounded_total or self.doc.grand_total
  561. if self.doc.party_account_currency == self.doc.currency:
  562. invoice_total = flt(
  563. grand_total - flt(self.doc.write_off_amount), self.doc.precision("grand_total")
  564. )
  565. else:
  566. base_write_off_amount = flt(
  567. flt(self.doc.write_off_amount) * self.doc.conversion_rate,
  568. self.doc.precision("base_write_off_amount"),
  569. )
  570. invoice_total = (
  571. flt(grand_total * self.doc.conversion_rate, self.doc.precision("grand_total"))
  572. - base_write_off_amount
  573. )
  574. if invoice_total > 0 and self.doc.total_advance > invoice_total:
  575. frappe.throw(
  576. _("Advance amount cannot be greater than {0} {1}").format(
  577. self.doc.party_account_currency, invoice_total
  578. )
  579. )
  580. if self.doc.docstatus.is_draft():
  581. if self.doc.get("write_off_outstanding_amount_automatically"):
  582. self.doc.write_off_amount = 0
  583. self.calculate_outstanding_amount()
  584. self.calculate_write_off_amount()
  585. def is_internal_invoice(self):
  586. """
  587. Checks if its an internal transfer invoice
  588. and decides if to calculate any out standing amount or not
  589. """
  590. if self.doc.doctype in ("Sales Invoice", "Purchase Invoice") and self.doc.is_internal_transfer():
  591. return True
  592. return False
  593. def calculate_outstanding_amount(self):
  594. # NOTE:
  595. # write_off_amount is only for POS Invoice
  596. # total_advance is only for non POS Invoice
  597. if self.doc.doctype == "Sales Invoice":
  598. self.calculate_paid_amount()
  599. if (
  600. self.doc.is_return
  601. and self.doc.return_against
  602. and not self.doc.get("is_pos")
  603. or self.is_internal_invoice()
  604. ):
  605. return
  606. self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
  607. self._set_in_company_currency(self.doc, ["write_off_amount"])
  608. if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
  609. grand_total = self.doc.rounded_total or self.doc.grand_total
  610. base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
  611. if self.doc.party_account_currency == self.doc.currency:
  612. total_amount_to_pay = flt(
  613. grand_total - self.doc.total_advance - flt(self.doc.write_off_amount),
  614. self.doc.precision("grand_total"),
  615. )
  616. else:
  617. total_amount_to_pay = flt(
  618. flt(base_grand_total, self.doc.precision("base_grand_total"))
  619. - self.doc.total_advance
  620. - flt(self.doc.base_write_off_amount),
  621. self.doc.precision("base_grand_total"),
  622. )
  623. self.doc.round_floats_in(self.doc, ["paid_amount"])
  624. change_amount = 0
  625. if self.doc.doctype == "Sales Invoice" and not self.doc.get("is_return"):
  626. self.calculate_change_amount()
  627. change_amount = (
  628. self.doc.change_amount
  629. if self.doc.party_account_currency == self.doc.currency
  630. else self.doc.base_change_amount
  631. )
  632. paid_amount = (
  633. self.doc.paid_amount
  634. if self.doc.party_account_currency == self.doc.currency
  635. else self.doc.base_paid_amount
  636. )
  637. self.doc.outstanding_amount = flt(
  638. total_amount_to_pay - flt(paid_amount) + flt(change_amount),
  639. self.doc.precision("outstanding_amount"),
  640. )
  641. if (
  642. self.doc.doctype == "Sales Invoice"
  643. and self.doc.get("is_pos")
  644. and self.doc.get("pos_profile")
  645. and self.doc.get("is_consolidated")
  646. ):
  647. write_off_limit = flt(
  648. frappe.db.get_value("POS Profile", self.doc.pos_profile, "write_off_limit")
  649. )
  650. if write_off_limit and abs(self.doc.outstanding_amount) <= write_off_limit:
  651. self.doc.write_off_outstanding_amount_automatically = 1
  652. if (
  653. self.doc.doctype == "Sales Invoice"
  654. and self.doc.get("is_pos")
  655. and self.doc.get("is_return")
  656. and not self.doc.get("is_consolidated")
  657. ):
  658. self.set_total_amount_to_default_mop(total_amount_to_pay)
  659. self.calculate_paid_amount()
  660. def calculate_paid_amount(self):
  661. paid_amount = base_paid_amount = 0.0
  662. if self.doc.is_pos:
  663. for payment in self.doc.get("payments"):
  664. payment.amount = flt(payment.amount)
  665. payment.base_amount = payment.amount * flt(self.doc.conversion_rate)
  666. paid_amount += payment.amount
  667. base_paid_amount += payment.base_amount
  668. elif not self.doc.is_return:
  669. self.doc.set("payments", [])
  670. if self.doc.redeem_loyalty_points and self.doc.loyalty_amount:
  671. base_paid_amount += self.doc.loyalty_amount
  672. paid_amount += self.doc.loyalty_amount / flt(self.doc.conversion_rate)
  673. self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount"))
  674. self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount"))
  675. def calculate_change_amount(self):
  676. self.doc.change_amount = 0.0
  677. self.doc.base_change_amount = 0.0
  678. grand_total = self.doc.rounded_total or self.doc.grand_total
  679. base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
  680. if (
  681. self.doc.doctype == "Sales Invoice"
  682. and self.doc.paid_amount > grand_total
  683. and not self.doc.is_return
  684. and any(d.type == "Cash" for d in self.doc.payments)
  685. ):
  686. self.doc.change_amount = flt(
  687. self.doc.paid_amount - grand_total, self.doc.precision("change_amount")
  688. )
  689. self.doc.base_change_amount = flt(
  690. self.doc.base_paid_amount - base_grand_total, self.doc.precision("base_change_amount")
  691. )
  692. def calculate_write_off_amount(self):
  693. if self.doc.get("write_off_outstanding_amount_automatically"):
  694. self.doc.write_off_amount = flt(
  695. self.doc.outstanding_amount, self.doc.precision("write_off_amount")
  696. )
  697. self.doc.base_write_off_amount = flt(
  698. self.doc.write_off_amount * self.doc.conversion_rate,
  699. self.doc.precision("base_write_off_amount"),
  700. )
  701. self.calculate_outstanding_amount()
  702. def calculate_margin(self, item):
  703. rate_with_margin = 0.0
  704. base_rate_with_margin = 0.0
  705. if item.price_list_rate:
  706. if item.pricing_rules and not self.doc.ignore_pricing_rule:
  707. has_margin = False
  708. for d in get_applied_pricing_rules(item.pricing_rules):
  709. pricing_rule = frappe.get_cached_doc("Pricing Rule", d)
  710. if pricing_rule.margin_rate_or_amount and (
  711. (
  712. pricing_rule.currency == self.doc.currency
  713. and pricing_rule.margin_type in ["Amount", "Percentage"]
  714. )
  715. or pricing_rule.margin_type == "Percentage"
  716. ):
  717. item.margin_type = pricing_rule.margin_type
  718. item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
  719. has_margin = True
  720. if not has_margin:
  721. item.margin_type = None
  722. item.margin_rate_or_amount = 0.0
  723. if not item.pricing_rules and flt(item.rate) > flt(item.price_list_rate):
  724. item.margin_type = "Amount"
  725. item.margin_rate_or_amount = flt(
  726. item.rate - item.price_list_rate, item.precision("margin_rate_or_amount")
  727. )
  728. item.rate_with_margin = item.rate
  729. elif item.margin_type and item.margin_rate_or_amount:
  730. margin_value = (
  731. item.margin_rate_or_amount
  732. if item.margin_type == "Amount"
  733. else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100
  734. )
  735. rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
  736. base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
  737. return rate_with_margin, base_rate_with_margin
  738. def set_item_wise_tax_breakup(self):
  739. self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
  740. def set_total_amount_to_default_mop(self, total_amount_to_pay):
  741. total_paid_amount = 0
  742. for payment in self.doc.get("payments"):
  743. total_paid_amount += (
  744. payment.amount if self.doc.party_account_currency == self.doc.currency else payment.base_amount
  745. )
  746. pending_amount = total_amount_to_pay - total_paid_amount
  747. if pending_amount > 0:
  748. default_mode_of_payment = frappe.db.get_value(
  749. "POS Payment Method",
  750. {"parent": self.doc.pos_profile, "default": 1},
  751. ["mode_of_payment"],
  752. as_dict=1,
  753. )
  754. if default_mode_of_payment:
  755. self.doc.payments = []
  756. self.doc.append(
  757. "payments",
  758. {
  759. "mode_of_payment": default_mode_of_payment.mode_of_payment,
  760. "amount": pending_amount,
  761. "default": 1,
  762. },
  763. )
  764. def get_itemised_tax_breakup_html(doc):
  765. if not doc.taxes:
  766. return
  767. frappe.flags.company = doc.company
  768. # get headers
  769. tax_accounts = []
  770. for tax in doc.taxes:
  771. if getattr(tax, "category", None) and tax.category == "Valuation":
  772. continue
  773. if tax.description not in tax_accounts:
  774. tax_accounts.append(tax.description)
  775. headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
  776. # get tax breakup data
  777. itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc)
  778. get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes"))
  779. update_itemised_tax_data(doc)
  780. frappe.flags.company = None
  781. return frappe.render_template(
  782. "templates/includes/itemised_tax_breakup.html",
  783. dict(
  784. headers=headers,
  785. itemised_tax=itemised_tax,
  786. itemised_taxable_amount=itemised_taxable_amount,
  787. tax_accounts=tax_accounts,
  788. doc=doc,
  789. ),
  790. )
  791. @frappe.whitelist()
  792. def get_round_off_applicable_accounts(company, account_list):
  793. # required to set correct region
  794. frappe.flags.company = company
  795. account_list = get_regional_round_off_accounts(company, account_list)
  796. return account_list
  797. @erpnext.allow_regional
  798. def get_regional_round_off_accounts(company, account_list):
  799. pass
  800. @erpnext.allow_regional
  801. def update_itemised_tax_data(doc):
  802. # Don't delete this method, used for localization
  803. pass
  804. @erpnext.allow_regional
  805. def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
  806. return [_("Item"), _("Taxable Amount")] + tax_accounts
  807. @erpnext.allow_regional
  808. def get_itemised_tax_breakup_data(doc):
  809. itemised_tax = get_itemised_tax(doc.taxes)
  810. itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
  811. return itemised_tax, itemised_taxable_amount
  812. def get_itemised_tax(taxes, with_tax_account=False):
  813. itemised_tax = {}
  814. for tax in taxes:
  815. if getattr(tax, "category", None) and tax.category == "Valuation":
  816. continue
  817. item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
  818. if item_tax_map:
  819. for item_code, tax_data in item_tax_map.items():
  820. itemised_tax.setdefault(item_code, frappe._dict())
  821. tax_rate = 0.0
  822. tax_amount = 0.0
  823. if isinstance(tax_data, list):
  824. tax_rate = flt(tax_data[0])
  825. tax_amount = flt(tax_data[1])
  826. else:
  827. tax_rate = flt(tax_data)
  828. itemised_tax[item_code][tax.description] = frappe._dict(
  829. dict(tax_rate=tax_rate, tax_amount=tax_amount)
  830. )
  831. if with_tax_account:
  832. itemised_tax[item_code][tax.description].tax_account = tax.account_head
  833. return itemised_tax
  834. def get_itemised_taxable_amount(items):
  835. itemised_taxable_amount = frappe._dict()
  836. for item in items:
  837. item_code = item.item_code or item.item_name
  838. itemised_taxable_amount.setdefault(item_code, 0)
  839. itemised_taxable_amount[item_code] += item.net_amount
  840. return itemised_taxable_amount
  841. def get_rounded_tax_amount(itemised_tax, precision):
  842. # Rounding based on tax_amount precision
  843. for taxes in itemised_tax.values():
  844. for tax_account in taxes:
  845. taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
  846. class init_landed_taxes_and_totals(object):
  847. def __init__(self, doc):
  848. self.doc = doc
  849. self.tax_field = "taxes" if self.doc.doctype == "Landed Cost Voucher" else "additional_costs"
  850. self.set_account_currency()
  851. self.set_exchange_rate()
  852. self.set_amounts_in_company_currency()
  853. def set_account_currency(self):
  854. company_currency = erpnext.get_company_currency(self.doc.company)
  855. for d in self.doc.get(self.tax_field):
  856. if not d.account_currency:
  857. account_currency = frappe.get_cached_value("Account", d.expense_account, "account_currency")
  858. d.account_currency = account_currency or company_currency
  859. def set_exchange_rate(self):
  860. company_currency = erpnext.get_company_currency(self.doc.company)
  861. for d in self.doc.get(self.tax_field):
  862. if d.account_currency == company_currency:
  863. d.exchange_rate = 1
  864. elif not d.exchange_rate:
  865. d.exchange_rate = get_exchange_rate(
  866. self.doc.posting_date,
  867. account=d.expense_account,
  868. account_currency=d.account_currency,
  869. company=self.doc.company,
  870. )
  871. if not d.exchange_rate:
  872. frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
  873. def set_amounts_in_company_currency(self):
  874. for d in self.doc.get(self.tax_field):
  875. d.amount = flt(d.amount, d.precision("amount"))
  876. d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))