// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { setup() { this.fetch_round_off_accounts(); } apply_pricing_rule_on_item(item) { let effective_item_rate = item.price_list_rate; let item_rate = item.rate; if (["Sales Order", "Quotation"].includes(item.parenttype) && item.blanket_order_rate) { effective_item_rate = item.blanket_order_rate; } if (item.margin_type == "Percentage") { item.rate_with_margin = flt(effective_item_rate) + flt(effective_item_rate) * (flt(item.margin_rate_or_amount) / 100); } else { item.rate_with_margin = flt(effective_item_rate) + flt(item.margin_rate_or_amount); } item.base_rate_with_margin = flt(item.rate_with_margin) * flt(this.frm.doc.conversion_rate); item_rate = flt(item.rate_with_margin, precision("rate", item)); if (item.discount_percentage && !item.discount_amount) { item.discount_amount = (flt(item.rate_with_margin) * flt(item.discount_percentage)) / 100; } if (item.discount_amount > 0) { item_rate = flt(item.rate_with_margin - item.discount_amount, precision("rate", item)); item.discount_percentage = (100 * flt(item.discount_amount)) / flt(item.rate_with_margin); } frappe.model.set_value(item.doctype, item.name, "rate", item_rate); } async calculate_taxes_and_totals(update_paid_amount) { this.discount_amount_applied = false; this._calculate_taxes_and_totals(); this.calculate_discount_amount(); // # Update grand total as per cash and non trade discount if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) { this.frm.doc.grand_total -= this.frm.doc.discount_amount; this.frm.doc.base_grand_total -= this.frm.doc.base_discount_amount; this.frm.doc.rounding_adjustment = 0; this.frm.doc.base_rounding_adjustment = 0; this.set_rounded_total(); } await this.calculate_shipping_charges(); // Advance calculation applicable to Sales/Purchase Invoice if ( ["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.docstatus < 2 && !this.frm.doc.is_return ) { this.calculate_total_advance(update_paid_amount); } if ( ["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.is_pos && this.frm.doc.is_return ) { await this.set_total_amount_to_default_mop(); this.calculate_paid_amount(); } // Sales person's commission if (["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"].includes(this.frm.doc.doctype)) { this.calculate_commission(); this.calculate_contribution(); } // Update paid amount on return/debit note creation if ( this.frm.doc.doctype === "Purchase Invoice" && this.frm.doc.is_return && this.frm.doc.grand_total < 0 && this.frm.doc.grand_total > this.frm.doc.paid_amount ) { this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total")); } this.frm.refresh_fields(); } calculate_discount_amount() { if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) { this.set_discount_amount(); this.apply_discount_amount(); } } _calculate_taxes_and_totals() { const is_quotation = this.frm.doc.doctype == "Quotation"; this.frm._items = is_quotation ? this.filtered_items() : this.frm.doc.items; this.validate_conversion_rate(); this.calculate_item_values(); this.initialize_taxes(); this.determine_exclusive_rate(); this.calculate_net_total(); this.calculate_taxes(); this.adjust_grand_total_for_inclusive_tax(); this.calculate_totals(); this._cleanup(); } validate_conversion_rate() { this.frm.doc.conversion_rate = flt( this.frm.doc.conversion_rate, cur_frm ? precision("conversion_rate") : 9 ); var conversion_rate_label = frappe.meta.get_label( this.frm.doc.doctype, "conversion_rate", this.frm.doc.name ); var company_currency = this.get_company_currency(); if (!this.frm.doc.conversion_rate) { if (this.frm.doc.currency == company_currency) { this.frm.set_value("conversion_rate", 1); } else { const subs = [conversion_rate_label, this.frm.doc.currency, company_currency]; const err_message = __( "{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}", subs ); frappe.throw(err_message); } } } calculate_item_values() { var me = this; if (!this.discount_amount_applied) { for (const item of this.frm.doc.items || []) { frappe.model.round_floats_in(item); item.net_rate = item.rate; item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty; if (!(me.frm.doc.is_return || me.frm.doc.is_debit_note)) { item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item)); } else { // allow for '0' qty on Credit/Debit notes let qty = flt(item.qty); if (!qty) { qty = me.frm.doc.is_debit_note ? 1 : -1; if (me.frm.doc.doctype !== "Purchase Receipt" && me.frm.doc.is_return === 1) { // In case of Purchase Receipt, qty can be 0 if all items are rejected qty = flt(item.qty); } } item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item)); } item.item_tax_amount = 0.0; item.total_weight = flt(item.weight_per_unit * item.stock_qty); me.set_in_company_currency(item, [ "price_list_rate", "rate", "amount", "net_rate", "net_amount", ]); } } } set_in_company_currency(doc, fields) { var me = this; $.each(fields, function (i, f) { doc["base_" + f] = flt( flt(doc[f], precision(f, doc)) * me.frm.doc.conversion_rate, precision("base_" + f, doc) ); }); } initialize_taxes() { var me = this; $.each(this.frm.doc["taxes"] || [], function (i, tax) { if (!tax.dont_recompute_tax) { tax.item_wise_tax_detail = {}; } var tax_fields = [ "total", "tax_amount_after_discount_amount", "tax_amount_for_current_item", "grand_total_for_current_item", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item", ]; if ( cstr(tax.charge_type) != "Actual" && !(me.discount_amount_applied && me.frm.doc.apply_discount_on == "Grand Total") ) { tax_fields.push("tax_amount"); } $.each(tax_fields, function (i, fieldname) { tax[fieldname] = 0.0; }); if (!this.discount_amount_applied) { erpnext.accounts.taxes.validate_taxes_and_charges(tax.doctype, tax.name); erpnext.accounts.taxes.validate_inclusive_tax(tax, this.frm); } frappe.model.round_floats_in(tax); }); } fetch_round_off_accounts() { let me = this; frappe.flags.round_off_applicable_accounts = []; if (me.frm.doc.company) { frappe.call({ method: "erpnext.controllers.taxes_and_totals.get_round_off_applicable_accounts", args: { company: me.frm.doc.company, account_list: frappe.flags.round_off_applicable_accounts, }, callback(r) { if (r.message) { frappe.flags.round_off_applicable_accounts.push(...r.message); } }, }); } frappe.call({ method: "erpnext.controllers.taxes_and_totals.get_rounding_tax_settings", callback: function (r) { frappe.flags.round_off_settings = r.message; }, }); } determine_exclusive_rate() { var me = this; var has_inclusive_tax = false; $.each(me.frm.doc["taxes"] || [], function (i, row) { if (cint(row.included_in_print_rate)) has_inclusive_tax = true; }); if (has_inclusive_tax == false) return; $.each(this.frm.doc.items || [], function (n, item) { var item_tax_map = me._load_item_tax_rate(item.item_tax_rate); var cumulated_tax_fraction = 0.0; var total_inclusive_tax_amount_per_qty = 0; $.each(me.frm.doc["taxes"] || [], function (i, tax) { var current_tax_fraction = me.get_current_tax_fraction(tax, item_tax_map); tax.tax_fraction_for_current_item = current_tax_fraction[0]; var inclusive_tax_amount_per_qty = current_tax_fraction[1]; if (i == 0) { tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item; } else { tax.grand_total_fraction_for_current_item = me.frm.doc["taxes"][i - 1].grand_total_fraction_for_current_item + tax.tax_fraction_for_current_item; } cumulated_tax_fraction += tax.tax_fraction_for_current_item; total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty); }); if ( !me.discount_amount_applied && item.qty && (total_inclusive_tax_amount_per_qty || cumulated_tax_fraction) ) { var amount = flt(item.amount) - total_inclusive_tax_amount_per_qty; item.net_amount = flt(amount / (1 + cumulated_tax_fraction), precision("net_amount", item)); item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0; me.set_in_company_currency(item, ["net_rate", "net_amount"]); } }); } get_current_tax_fraction(tax, item_tax_map) { // Get tax fraction for calculating tax exclusive amount // from tax inclusive amount var current_tax_fraction = 0.0; var inclusive_tax_amount_per_qty = 0; if (cint(tax.included_in_print_rate)) { var tax_rate = this._get_tax_rate(tax, item_tax_map); if (tax.charge_type == "On Net Total") { current_tax_fraction = tax_rate / 100.0; } else if (tax.charge_type == "On Previous Row Amount") { current_tax_fraction = (tax_rate / 100.0) * this.frm.doc["taxes"][cint(tax.row_id) - 1].tax_fraction_for_current_item; } else if (tax.charge_type == "On Previous Row Total") { current_tax_fraction = (tax_rate / 100.0) * this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item; } else if (tax.charge_type == "On Item Quantity") { inclusive_tax_amount_per_qty = flt(tax_rate); } } if (tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") { current_tax_fraction *= -1; inclusive_tax_amount_per_qty *= -1; } return [current_tax_fraction, inclusive_tax_amount_per_qty]; } _get_tax_rate(tax, item_tax_map) { return Object.keys(item_tax_map).indexOf(tax.account_head) != -1 ? flt(item_tax_map[tax.account_head], precision("rate", tax)) : tax.rate; } calculate_net_total() { var me = this; this.frm.doc.total_qty = this.frm.doc.total = this.frm.doc.base_total = this.frm.doc.net_total = this.frm.doc.base_net_total = 0.0; $.each(this.frm._items || [], function (i, item) { me.frm.doc.total += item.amount; me.frm.doc.total_qty += item.qty; me.frm.doc.base_total += item.base_amount; me.frm.doc.net_total += item.net_amount; me.frm.doc.base_net_total += item.base_net_amount; }); frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); } calculate_shipping_charges() { // Do not apply shipping rule for POS if (this.frm.doc.is_pos) { return; } frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); if (frappe.meta.get_docfield(this.frm.doc.doctype, "shipping_rule", this.frm.doc.name)) { return this.shipping_rule(); } } add_taxes_from_item_tax_template(item_tax_map) { let me = this; if (item_tax_map && cint(frappe.defaults.get_default("add_taxes_from_item_tax_template"))) { if (typeof item_tax_map == "string") { item_tax_map = JSON.parse(item_tax_map); } $.each(item_tax_map, function (tax, rate) { let found = (me.frm.doc.taxes || []).find((d) => d.account_head === tax); if (!found) { let child = frappe.model.add_child(me.frm.doc, "taxes"); child.charge_type = "On Net Total"; child.account_head = tax; child.rate = 0; child.set_by_item_tax_template = true; } }); } } calculate_taxes() { // reset value from earlier calculations this.grand_total_diff = 0; const doc = this.frm.doc; if (!doc.taxes?.length) return; var me = this; var actual_tax_dict = {}; // maintain actual tax rate based on idx $.each(doc.taxes, function (i, tax) { if (tax.charge_type == "Actual") { actual_tax_dict[tax.idx] = flt(tax.tax_amount, precision("tax_amount", tax)); } }); $.each(this.frm._items || [], function (n, item) { var item_tax_map = me._load_item_tax_rate(item.item_tax_rate); $.each(doc.taxes, function (i, tax) { // tax_amount represents the amount of tax for the current step var current_tax_amount = me.get_current_tax_amount(item, tax, item_tax_map); if (frappe.flags.round_row_wise_tax) { current_tax_amount = flt(current_tax_amount, precision("tax_amount", tax)); } // Adjust divisional loss to the last item if (tax.charge_type == "Actual") { actual_tax_dict[tax.idx] -= current_tax_amount; if (n == me.frm._items.length - 1) { current_tax_amount += actual_tax_dict[tax.idx]; } } // accumulate tax amount into tax.tax_amount if ( tax.charge_type != "Actual" && !(me.discount_amount_applied && me.frm.doc.apply_discount_on == "Grand Total") ) { tax.tax_amount += current_tax_amount; } // store tax_amount for current item as it will be used for // charge type = 'On Previous Row Amount' tax.tax_amount_for_current_item = current_tax_amount; // tax amount after discount amount tax.tax_amount_after_discount_amount += current_tax_amount; // for buying if (tax.category) { // if just for valuation, do not add the tax amount in total // hence, setting it as 0 for further steps current_tax_amount = tax.category == "Valuation" ? 0.0 : current_tax_amount; current_tax_amount *= tax.add_deduct_tax == "Deduct" ? -1.0 : 1.0; } // note: grand_total_for_current_item contains the contribution of // item's amount, previously applied tax and the current tax on that item if (i == 0) { tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount); } else { tax.grand_total_for_current_item = flt( me.frm.doc["taxes"][i - 1].grand_total_for_current_item + current_tax_amount ); } }); }); const discount_amount_applied = this.discount_amount_applied; if ( doc.apply_discount_on === "Grand Total" && (discount_amount_applied || doc.discount_amount || doc.additional_discount_percentage) ) { const tax_amount_precision = precision("tax_amount", doc.taxes[0]); for (const [i, tax] of doc.taxes.entries()) { if (discount_amount_applied) tax.tax_amount_after_discount_amount = flt( tax.tax_amount_after_discount_amount, tax_amount_precision ); this.set_cumulative_total(i, tax); } if (!this.discount_amount_applied) { this.grand_total_for_distributing_discount = doc.taxes[doc.taxes.length - 1].total; } else { this.grand_total_diff = flt( this.grand_total_for_distributing_discount - doc.discount_amount - doc.taxes[doc.taxes.length - 1].total, precision("grand_total") ); } } for (const [i, tax] of doc.taxes.entries()) { me.round_off_totals(tax); me.set_in_company_currency(tax, ["tax_amount", "tax_amount_after_discount_amount"]); me.round_off_base_values(tax); // in tax.total, accumulate grand total for each tax me.set_cumulative_total(i, tax); me.set_in_company_currency(tax, ["total"]); } } set_cumulative_total(row_idx, tax) { var tax_amount = tax.tax_amount_after_discount_amount; if (tax.category == "Valuation") { tax_amount = 0; } if (tax.add_deduct_tax == "Deduct") { tax_amount = -1 * tax_amount; } if (row_idx == 0) { tax.total = flt(this.frm.doc.net_total + tax_amount, precision("total", tax)); } else { tax.total = flt(this.frm.doc["taxes"][row_idx - 1].total + tax_amount, precision("total", tax)); } } _load_item_tax_rate(item_tax_rate) { return item_tax_rate ? JSON.parse(item_tax_rate) : {}; } get_current_tax_amount(item, tax, item_tax_map) { var tax_rate = this._get_tax_rate(tax, item_tax_map); var current_tax_amount = 0.0; var current_net_amount = 0.0; // To set row_id by default as previous row. if (["On Previous Row Amount", "On Previous Row Total"].includes(tax.charge_type)) { if (tax.idx === 1) { frappe.throw( __( "Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row" ) ); } if (!tax.row_id) { tax.row_id = tax.idx - 1; } } if (tax.charge_type == "Actual") { current_net_amount = item.net_amount; // distribute the tax amount proportionally to each item row var actual = flt(tax.tax_amount, precision("tax_amount", tax)); current_tax_amount = this.frm.doc.net_total ? (item.net_amount / this.frm.doc.net_total) * actual : 0.0; } else if (tax.charge_type == "On Net Total") { if (tax.account_head in item_tax_map) { current_net_amount = item.net_amount; } current_tax_amount = (tax_rate / 100.0) * item.net_amount; } else if (tax.charge_type == "On Previous Row Amount") { current_net_amount = this.frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount_for_current_item; current_tax_amount = (tax_rate / 100.0) * this.frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount_for_current_item; } else if (tax.charge_type == "On Previous Row Total") { current_net_amount = this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_for_current_item; current_tax_amount = (tax_rate / 100.0) * this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_for_current_item; } else if (tax.charge_type == "On Item Quantity") { // don't sum current net amount due to the field being a currency field current_tax_amount = tax_rate * item.qty; } return current_tax_amount; } round_off_totals(tax) { if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) { tax.tax_amount = Math.round(tax.tax_amount); tax.tax_amount_after_discount_amount = Math.round(tax.tax_amount_after_discount_amount); } tax.tax_amount = flt(tax.tax_amount, precision("tax_amount", tax)); tax.tax_amount_after_discount_amount = flt( tax.tax_amount_after_discount_amount, precision("tax_amount", tax) ); } round_off_base_values(tax) { if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) { tax.base_tax_amount = Math.round(tax.base_tax_amount); tax.base_tax_amount_after_discount_amount = Math.round(tax.base_tax_amount_after_discount_amount); } } /** * @deprecated Use adjust_grand_total_for_inclusive_tax instead. */ manipulate_grand_total_for_inclusive_tax() { // for backward compatablility - if in case used by an external application this.adjust_grand_total_for_inclusive_tax(); } adjust_grand_total_for_inclusive_tax() { var me = this; // if any inclusive taxes and diff if (this.frm.doc["taxes"] && this.frm.doc["taxes"].length) { var any_inclusive_tax = false; $.each(this.frm.doc.taxes || [], function (i, d) { if (cint(d.included_in_print_rate)) any_inclusive_tax = true; }); if (any_inclusive_tax) { var last_tax = me.frm.doc["taxes"].slice(-1)[0]; var non_inclusive_tax_amount = frappe.utils.sum( $.map(this.frm.doc.taxes || [], function (d) { if (!d.included_in_print_rate) { let tax_amount = d.category === "Valuation" ? 0 : d.tax_amount_after_discount_amount; if (d.add_deduct_tax === "Deduct") tax_amount *= -1; return tax_amount; } }) ); var diff = me.frm.doc.total + non_inclusive_tax_amount - flt(last_tax.total, precision("grand_total")); if (me.discount_amount_applied && me.frm.doc.discount_amount) { diff -= flt(me.frm.doc.discount_amount); } diff = flt(diff, precision("rounding_adjustment")); if (diff && Math.abs(diff) <= 5.0 / Math.pow(10, precision("tax_amount", last_tax))) { me.grand_total_diff = diff; } else { me.grand_total_diff = 0; } } } } calculate_totals() { // Changing sequence can cause rounding_adjustmentng issue and on-screen discrepency const me = this; const tax_count = this.frm.doc.taxes?.length; const grand_total_diff = this.grand_total_diff; this.frm.doc.grand_total = flt( tax_count ? this.frm.doc["taxes"][tax_count - 1].total + grand_total_diff : this.frm.doc.net_total ); if ( ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes( this.frm.doc.doctype ) ) { this.frm.doc.base_grand_total = this.frm.doc.total_taxes_and_charges ? flt(this.frm.doc.grand_total * this.frm.doc.conversion_rate) : this.frm.doc.base_net_total; } else { // other charges added/deducted this.frm.doc.taxes_and_charges_added = this.frm.doc.taxes_and_charges_deducted = 0.0; if (tax_count) { $.each(this.frm.doc["taxes"] || [], function (i, tax) { if (["Valuation and Total", "Total"].includes(tax.category)) { if (tax.add_deduct_tax == "Add") { me.frm.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount); } else { me.frm.doc.taxes_and_charges_deducted += flt( tax.tax_amount_after_discount_amount ); } } }); frappe.model.round_floats_in(this.frm.doc, [ "taxes_and_charges_added", "taxes_and_charges_deducted", ]); } this.frm.doc.base_grand_total = flt( this.frm.doc.taxes_and_charges_added || this.frm.doc.taxes_and_charges_deducted ? flt(this.frm.doc.grand_total * this.frm.doc.conversion_rate) : this.frm.doc.base_net_total ); this.set_in_company_currency(this.frm.doc, [ "taxes_and_charges_added", "taxes_and_charges_deducted", ]); } this.frm.doc.total_taxes_and_charges = flt( this.frm.doc.grand_total - this.frm.doc.net_total - grand_total_diff, precision("total_taxes_and_charges") ); this.set_in_company_currency(this.frm.doc, ["total_taxes_and_charges"]); // Round grand total as per precision frappe.model.round_floats_in(this.frm.doc, ["grand_total", "base_grand_total"]); // rounded totals this.set_rounded_total(); } set_rounded_total() { var disable_rounded_total = 0; if (frappe.meta.get_docfield(this.frm.doc.doctype, "disable_rounded_total", this.frm.doc.name)) { disable_rounded_total = this.frm.doc.disable_rounded_total; } else if (frappe.sys_defaults.disable_rounded_total) { disable_rounded_total = frappe.sys_defaults.disable_rounded_total; } if (cint(disable_rounded_total)) { this.frm.doc.rounded_total = 0; this.frm.doc.base_rounded_total = 0; this.frm.doc.rounding_adjustment = 0; return; } if (frappe.meta.get_docfield(this.frm.doc.doctype, "rounded_total", this.frm.doc.name)) { this.frm.doc.rounded_total = round_based_on_smallest_currency_fraction( this.frm.doc.grand_total, this.frm.doc.currency, precision("rounded_total") ); this.frm.doc.rounding_adjustment = flt( this.frm.doc.rounded_total - this.frm.doc.grand_total, precision("rounding_adjustment") ); this.set_in_company_currency(this.frm.doc, ["rounding_adjustment", "rounded_total"]); } } _cleanup() { this.frm.doc.base_in_words = this.frm.doc.in_words = ""; let items = this.frm.doc.items; if (items && items.length) { if (!frappe.meta.get_docfield(items[0].doctype, "item_tax_amount", this.frm.doctype)) { $.each(items || [], function (i, item) { delete item["item_tax_amount"]; }); } } if (this.frm.doc["taxes"] && this.frm.doc["taxes"].length) { var temporary_fields = [ "tax_amount_for_current_item", "grand_total_for_current_item", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item", ]; if ( !frappe.meta.get_docfield( this.frm.doc["taxes"][0].doctype, "tax_amount_after_discount_amount", this.frm.doctype ) ) { temporary_fields.push("tax_amount_after_discount_amount"); } $.each(this.frm.doc["taxes"] || [], function (i, tax) { $.each(temporary_fields, function (i, fieldname) { delete tax[fieldname]; }); }); } } set_discount_amount() { if (this.frm.doc.additional_discount_percentage) { this.frm.doc.discount_amount = flt( (flt(this.frm.doc[frappe.scrub(this.frm.doc.apply_discount_on)]) * this.frm.doc.additional_discount_percentage) / 100, precision("discount_amount") ); } } apply_discount_amount() { var me = this; var distributed_amount = 0.0; this.frm.doc.base_discount_amount = 0.0; if (this.frm.doc.discount_amount) { if (!this.frm.doc.apply_discount_on) frappe.throw(__("Please select Apply Discount On")); this.frm.doc.base_discount_amount = flt( this.frm.doc.discount_amount * this.frm.doc.conversion_rate, precision("base_discount_amount") ); if ( this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount ) { return; } const total_for_discount_amount = this.get_total_for_discount_amount(); let net_total = 0; let expected_net_total = 0; // calculate item amount after Discount Amount if (total_for_discount_amount) { $.each(this.frm._items || [], function (i, item) { distributed_amount = (flt(me.frm.doc.discount_amount) * item.net_amount) / total_for_discount_amount; const adjusted_net_amount = item.net_amount - distributed_amount; expected_net_total += adjusted_net_amount; item.net_amount = flt(adjusted_net_amount, precision("net_amount", item)); net_total += item.net_amount; // discount amount rounding adjustment // assignment to rounding_difference is intentional const rounding_difference = flt(expected_net_total - net_total, precision("net_total")); if (rounding_difference) { item.net_amount = flt( item.net_amount + rounding_difference, precision("net_amount", item) ); net_total += rounding_difference; } item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0; me.set_in_company_currency(item, ["net_rate", "net_amount"]); }); this.discount_amount_applied = true; this._calculate_taxes_and_totals(); } } } get_total_for_discount_amount() { const doc = this.frm.doc; if (doc.apply_discount_on == "Net Total" || !doc.taxes?.length) return doc.net_total; let total_actual_tax = 0.0; let actual_taxes_dict = {}; function update_actual_taxes_dict(tax, tax_amount) { if (tax.add_deduct_tax == "Deduct") tax_amount *= -1; if (tax.category != "Valuation") total_actual_tax += tax_amount; actual_taxes_dict[tax.idx] = { tax_amount: tax_amount, cumulative_total: total_actual_tax, }; } doc.taxes.forEach((tax) => { if (["Actual", "On Item Quantity"].includes(tax.charge_type)) { update_actual_taxes_dict(tax, tax.tax_amount); return; } const base_row = actual_taxes_dict[tax.row_id]; if (!base_row) return; // if charge type is 'On Previous Row Amount', calculate tax on previous row amount // else (On Previous Row Total) calculate tax on cumulative total const base_tax_amount = tax.charge_type == "On Previous Row Amount" ? base_row["tax_amount"] : base_row["cumulative_total"]; update_actual_taxes_dict(tax, (base_tax_amount * tax.rate) / 100); }); return (this.grand_total_for_distributing_discount || doc.grand_total) - total_actual_tax; } calculate_total_advance(update_paid_amount) { var total_allocated_amount = frappe.utils.sum( $.map(this.frm.doc["advances"] || [], function (adv) { return flt(adv.allocated_amount, precision("allocated_amount", adv)); }) ); this.frm.doc.total_advance = flt(total_allocated_amount, precision("total_advance")); if (this.frm.doc.write_off_outstanding_amount_automatically) { this.frm.doc.write_off_amount = 0; } this.calculate_outstanding_amount(update_paid_amount); this.calculate_write_off_amount(); } is_internal_invoice() { if (["Sales Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)) { if (this.frm.doc.company === this.frm.doc.represents_company) { return true; } } return false; } calculate_outstanding_amount(update_paid_amount) { // NOTE: // paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice // total_advance is only for non POS Invoice if (["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.is_return) { this.calculate_paid_amount(); } if (this.frm.doc.is_return || this.frm.doc.docstatus > 0 || this.is_internal_invoice()) return; frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]); if (["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)) { let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total; let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total; if (this.frm.doc.party_account_currency == this.frm.doc.currency) { var total_amount_to_pay = flt( grand_total - this.frm.doc.total_advance - this.frm.doc.write_off_amount, precision("grand_total") ); } else { var total_amount_to_pay = flt( flt(base_grand_total, precision("base_grand_total")) - this.frm.doc.total_advance - this.frm.doc.base_write_off_amount, precision("base_grand_total") ); } frappe.model.round_floats_in(this.frm.doc, ["paid_amount"]); this.set_in_company_currency(this.frm.doc, ["paid_amount"]); if (this.frm.refresh_field) { this.frm.refresh_field("paid_amount"); this.frm.refresh_field("base_paid_amount"); } if (["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) { let total_amount_for_payment = this.frm.doc.redeem_loyalty_points && this.frm.doc.loyalty_amount ? flt( total_amount_to_pay - this.frm.doc.loyalty_amount, precision("base_grand_total") ) : total_amount_to_pay; this.set_default_payment(total_amount_for_payment, update_paid_amount); this.calculate_paid_amount(); } this.calculate_change_amount(); var paid_amount = this.frm.doc.party_account_currency == this.frm.doc.currency ? this.frm.doc.paid_amount : this.frm.doc.base_paid_amount; this.frm.doc.outstanding_amount = flt( total_amount_to_pay - flt(paid_amount) + flt(this.frm.doc.change_amount * this.frm.doc.conversion_rate), precision("outstanding_amount") ); } } async set_total_amount_to_default_mop() { let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total; let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total; if (this.frm.doc.party_account_currency == this.frm.doc.currency) { var total_amount_to_pay = flt( grand_total - this.frm.doc.total_advance - this.frm.doc.write_off_amount, precision("grand_total") ); } else { var total_amount_to_pay = flt( flt(base_grand_total, precision("base_grand_total")) - this.frm.doc.total_advance - this.frm.doc.base_write_off_amount, precision("base_grand_total") ); } /* During returns, if an user select mode of payment other than default mode of payment, it should retain the user selection instead resetting it to default mode of payment. */ let payment_amount = 0; this.frm.doc.payments.forEach((payment) => { payment_amount += payment.amount; }); if (payment_amount == total_amount_to_pay) { return; } /* For partial return, if the payment was made using single mode of payment it should set the return to that mode of payment only. */ if (this.frm.doc.return_against) { let { message: return_against_mop } = await frappe.call({ method: "erpnext.controllers.sales_and_purchase_return.get_payment_data", args: { invoice: this.frm.doc.return_against, }, }); if (return_against_mop.length === 1) { this.frm.doc.payments.forEach((payment) => { if (payment.mode_of_payment == return_against_mop[0].mode_of_payment) { payment.amount = total_amount_to_pay; } else { payment.amount = 0; } }); this.frm.refresh_fields(); return; } } this.frm.doc.payments.find((payment) => { if (payment.default) { payment.amount = total_amount_to_pay; } else { payment.amount = 0; } }); this.frm.refresh_fields(); } set_default_payment(total_amount_to_pay, update_paid_amount) { var me = this; var payment_status = true; if ( this.frm.doc.is_pos && cint(this.frm.set_default_payment) && (update_paid_amount === undefined || update_paid_amount) ) { $.each(this.frm.doc["payments"] || [], function (index, data) { if (data.default && payment_status && total_amount_to_pay > 0) { let base_amount, amount; if (me.frm.doc.party_account_currency == me.frm.doc.currency) { // if customer/supplier currency is same as company currency // total_amount_to_pay is already in customer/supplier currency // so base_amount has to be calculated using total_amount_to_pay base_amount = flt( total_amount_to_pay * me.frm.doc.conversion_rate, precision("base_amount", data) ); amount = flt(total_amount_to_pay, precision("amount", data)); } else { base_amount = flt(total_amount_to_pay, precision("base_amount", data)); amount = flt( total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount", data) ); } frappe.model.set_value(data.doctype, data.name, "base_amount", base_amount); frappe.model.set_value(data.doctype, data.name, "amount", amount); payment_status = false; } else if (me.frm.doc.paid_amount) { frappe.model.set_value(data.doctype, data.name, "amount", 0.0); } }); } } calculate_paid_amount() { var me = this; var paid_amount = 0.0; var base_paid_amount = 0.0; if (this.frm.doc.is_pos) { $.each(this.frm.doc["payments"] || [], function (index, data) { data.base_amount = flt( data.amount * me.frm.doc.conversion_rate, precision("base_amount", data) ); paid_amount += data.amount; base_paid_amount += data.base_amount; }); } else if (!this.frm.doc.is_return) { this.frm.doc.payments = []; } if (this.frm.doc.redeem_loyalty_points && this.frm.doc.loyalty_amount) { base_paid_amount += this.frm.doc.loyalty_amount; paid_amount += flt( this.frm.doc.loyalty_amount / me.frm.doc.conversion_rate, precision("paid_amount") ); } this.frm.set_value("paid_amount", flt(paid_amount, precision("paid_amount"))); this.frm.set_value("base_paid_amount", flt(base_paid_amount, precision("base_paid_amount"))); } calculate_change_amount() { this.frm.doc.change_amount = 0.0; this.frm.doc.base_change_amount = 0.0; if ( ["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return ) { var payment_types = $.map(this.frm.doc.payments, function (d) { return d.type; }); if (in_list(payment_types, "Cash")) { var grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total; var base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total; this.frm.doc.change_amount = flt( this.frm.doc.paid_amount - grand_total, precision("change_amount") ); this.frm.doc.base_change_amount = flt( this.frm.doc.base_paid_amount - base_grand_total, precision("base_change_amount") ); } } } calculate_write_off_amount() { if (this.frm.doc.write_off_outstanding_amount_automatically) { this.frm.doc.write_off_amount = flt( this.frm.doc.outstanding_amount, precision("write_off_amount") ); this.frm.doc.base_write_off_amount = flt( this.frm.doc.write_off_amount * this.frm.doc.conversion_rate, precision("base_write_off_amount") ); this.calculate_outstanding_amount(false); } } filtered_items() { return this.frm.doc.items.filter((item) => !item["is_alternative"]); } };