Merge branch 'version-13-hotfix' into version-13-pre-release

This commit is contained in:
Nabin Hait 2021-06-10 20:26:31 +05:30
commit 42d72b55b8
26 changed files with 2243 additions and 582 deletions

View File

@ -0,0 +1,197 @@
{
"actions": [],
"creation": "2020-09-12 22:26:19.594367",
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"add_deduct_tax",
"charge_type",
"row_id",
"account_head",
"col_break_1",
"description",
"included_in_paid_amount",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"section_break_8",
"rate",
"section_break_9",
"currency",
"tax_amount",
"total",
"allocated_amount",
"column_break_13",
"base_tax_amount",
"base_total",
"base_allocated_amount"
],
"fields": [
{
"columns": 2,
"fieldname": "charge_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Type",
"oldfieldname": "charge_type",
"oldfieldtype": "Select",
"options": "\nActual\nOn Paid Amount\nOn Previous Row Amount\nOn Previous Row Total",
"reqd": 1
},
{
"depends_on": "eval:[\"On Previous Row Amount\", \"On Previous Row Total\"].indexOf(doc.charge_type)!==-1",
"fieldname": "row_id",
"fieldtype": "Data",
"label": "Reference Row #",
"oldfieldname": "row_id",
"oldfieldtype": "Data"
},
{
"columns": 2,
"fieldname": "account_head",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Account Head",
"oldfieldname": "account_head",
"oldfieldtype": "Link",
"options": "Account",
"reqd": 1,
"search_index": 1
},
{
"fieldname": "col_break_1",
"fieldtype": "Column Break",
"width": "50%"
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description",
"oldfieldname": "description",
"oldfieldtype": "Small Text",
"print_width": "300px",
"reqd": 1,
"width": "300px"
},
{
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"default": ":Company",
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"oldfieldname": "cost_center_other_charges",
"oldfieldtype": "Link",
"options": "Cost Center"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_8",
"fieldtype": "Section Break"
},
{
"columns": 2,
"fieldname": "rate",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Rate",
"oldfieldname": "rate",
"oldfieldtype": "Currency"
},
{
"fieldname": "section_break_9",
"fieldtype": "Section Break"
},
{
"columns": 2,
"fieldname": "tax_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"options": "currency"
},
{
"columns": 2,
"fieldname": "total",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Total",
"options": "currency",
"read_only": 1
},
{
"fieldname": "column_break_13",
"fieldtype": "Column Break"
},
{
"fieldname": "base_tax_amount",
"fieldtype": "Currency",
"label": "Amount (Company Currency)",
"oldfieldname": "tax_amount",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "base_total",
"fieldtype": "Currency",
"label": "Total (Company Currency)",
"oldfieldname": "total",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "add_deduct_tax",
"fieldtype": "Select",
"label": "Add Or Deduct",
"options": "Add\nDeduct",
"reqd": 1
},
{
"default": "0",
"fieldname": "included_in_paid_amount",
"fieldtype": "Check",
"label": "Considered In Paid Amount"
},
{
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"label": "Allocated Amount",
"options": "currency"
},
{
"fieldname": "base_allocated_amount",
"fieldtype": "Currency",
"label": "Allocated Amount (Company Currency)",
"options": "Company:company:default_currency"
},
{
"fetch_from": "account_head.account_currency",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Account Currency",
"options": "Currency",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-06-09 11:46:58.373170",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Taxes and Charges",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "ASC"
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class AdvanceTaxesandCharges(Document):
pass

View File

@ -3,6 +3,8 @@
{% include "erpnext/public/js/controllers/accounts.js" %} {% include "erpnext/public/js/controllers/accounts.js" %}
frappe.provide("erpnext.accounts.dimensions"); frappe.provide("erpnext.accounts.dimensions");
cur_frm.cscript.tax_table = "Advance Taxes and Charges";
frappe.ui.form.on('Payment Entry', { frappe.ui.form.on('Payment Entry', {
onload: function(frm) { onload: function(frm) {
if(frm.doc.__islocal) { if(frm.doc.__islocal) {
@ -91,6 +93,16 @@ frappe.ui.form.on('Payment Entry', {
} }
}); });
frm.set_query("advance_tax_account", function() {
return {
filters: {
"company": frm.doc.company,
"root_type": ["in", ["Asset", "Liability"]],
"is_group": 0
}
}
});
frm.set_query("reference_doctype", "references", function() { frm.set_query("reference_doctype", "references", function() {
if (frm.doc.party_type == "Customer") { if (frm.doc.party_type == "Customer") {
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"]; var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
@ -182,6 +194,8 @@ frappe.ui.form.on('Payment Entry', {
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency)); frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency));
frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency); frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency);
frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
(frm.doc.paid_from_account_currency != company_currency));
frm.toggle_display("base_received_amount", ( frm.toggle_display("base_received_amount", (
frm.doc.paid_to_account_currency != company_currency frm.doc.paid_to_account_currency != company_currency
@ -216,7 +230,7 @@ frappe.ui.form.on('Payment Entry', {
var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: ""; var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: "";
frm.set_currency_labels(["base_paid_amount", "base_received_amount", "base_total_allocated_amount", frm.set_currency_labels(["base_paid_amount", "base_received_amount", "base_total_allocated_amount",
"difference_amount"], company_currency); "difference_amount", "base_paid_amount_after_tax", "base_received_amount_after_tax"], company_currency);
frm.set_currency_labels(["paid_amount"], frm.doc.paid_from_account_currency); frm.set_currency_labels(["paid_amount"], frm.doc.paid_from_account_currency);
frm.set_currency_labels(["received_amount"], frm.doc.paid_to_account_currency); frm.set_currency_labels(["received_amount"], frm.doc.paid_to_account_currency);
@ -224,11 +238,13 @@ frappe.ui.form.on('Payment Entry', {
var party_account_currency = frm.doc.payment_type=="Receive" ? var party_account_currency = frm.doc.payment_type=="Receive" ?
frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency; frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency;
frm.set_currency_labels(["total_allocated_amount", "unallocated_amount"], party_account_currency); frm.set_currency_labels(["total_allocated_amount", "unallocated_amount",
"total_taxes_and_charges"], party_account_currency);
var currency_field = (frm.doc.payment_type=="Receive") ? "paid_from_account_currency" : "paid_to_account_currency" var currency_field = (frm.doc.payment_type=="Receive") ? "paid_from_account_currency" : "paid_to_account_currency"
frm.set_df_property("total_allocated_amount", "options", currency_field); frm.set_df_property("total_allocated_amount", "options", currency_field);
frm.set_df_property("unallocated_amount", "options", currency_field); frm.set_df_property("unallocated_amount", "options", currency_field);
frm.set_df_property("total_taxes_and_charges", "options", currency_field);
frm.set_df_property("party_balance", "options", currency_field); frm.set_df_property("party_balance", "options", currency_field);
frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"], frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"],
@ -364,6 +380,16 @@ frappe.ui.form.on('Payment Entry', {
} }
}, },
apply_tax_withholding_amount: function(frm) {
if (!frm.doc.apply_tax_withholding_amount) {
frm.set_value("tax_withholding_category", '');
} else {
frappe.db.get_value('Supplier', frm.doc.party, 'tax_withholding_category', (values) => {
frm.set_value("tax_withholding_category", values.tax_withholding_category);
});
}
},
paid_from: function(frm) { paid_from: function(frm) {
if(frm.set_party_account_based_on_party) return; if(frm.set_party_account_based_on_party) return;
@ -843,12 +869,12 @@ frappe.ui.form.on('Payment Entry', {
if(frm.doc.payment_type == "Receive" if(frm.doc.payment_type == "Receive"
&& frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions && frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions
&& frm.doc.total_allocated_amount < frm.doc.paid_amount + (total_deductions / frm.doc.source_exchange_rate)) { && frm.doc.total_allocated_amount < frm.doc.paid_amount + (total_deductions / frm.doc.source_exchange_rate)) {
unallocated_amount = (frm.doc.base_received_amount + total_deductions unallocated_amount = (frm.doc.base_received_amount + total_deductions + frm.doc.base_total_taxes_and_charges
- frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate; + frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
} else if (frm.doc.payment_type == "Pay" } else if (frm.doc.payment_type == "Pay"
&& frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions && frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions
&& frm.doc.total_allocated_amount < frm.doc.received_amount + (total_deductions / frm.doc.target_exchange_rate)) { && frm.doc.total_allocated_amount < frm.doc.received_amount + (total_deductions / frm.doc.target_exchange_rate)) {
unallocated_amount = (frm.doc.base_paid_amount - (total_deductions unallocated_amount = (frm.doc.base_paid_amount + frm.doc.base_total_taxes_and_charges - (total_deductions
+ frm.doc.base_total_allocated_amount)) / frm.doc.target_exchange_rate; + frm.doc.base_total_allocated_amount)) / frm.doc.target_exchange_rate;
} }
} }
@ -874,7 +900,8 @@ frappe.ui.form.on('Payment Entry', {
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [], var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
function(d) { return flt(d.amount) })); function(d) { return flt(d.amount) }));
frm.set_value("difference_amount", difference_amount - total_deductions); frm.set_value("difference_amount", difference_amount - total_deductions +
frm.doc.base_total_taxes_and_charges);
frm.events.hide_unhide_fields(frm); frm.events.hide_unhide_fields(frm);
}, },
@ -1002,7 +1029,203 @@ frappe.ui.form.on('Payment Entry', {
} }
}); });
} }
} },
sales_taxes_and_charges_template: function(frm) {
frm.trigger('fetch_taxes_from_template');
},
purchase_taxes_and_charges_template: function(frm) {
frm.trigger('fetch_taxes_from_template');
},
fetch_taxes_from_template: function(frm) {
let master_doctype = '';
let taxes_and_charges = '';
if (frm.doc.party_type == 'Supplier') {
master_doctype = 'Purchase Taxes and Charges Template';
taxes_and_charges = frm.doc.purchase_taxes_and_charges_template;
} else if (frm.doc.party_type == 'Customer') {
master_doctype = 'Sales Taxes and Charges Template';
taxes_and_charges = frm.doc.sales_taxes_and_charges_template;
}
if (!taxes_and_charges) {
return;
}
frappe.call({
method: "erpnext.controllers.accounts_controller.get_taxes_and_charges",
args: {
"master_doctype": master_doctype,
"master_name": taxes_and_charges
},
callback: function(r) {
if(!r.exc && r.message) {
// set taxes table
if(r.message) {
for (let tax of r.message) {
if (tax.charge_type === 'On Net Total') {
tax.charge_type = 'On Paid Amount';
}
me.frm.add_child("taxes", tax);
}
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
}
}
}
});
},
apply_taxes: function(frm) {
frm.events.initialize_taxes(frm);
frm.events.determine_exclusive_rate(frm);
frm.events.calculate_taxes(frm);
},
initialize_taxes: function(frm) {
$.each(frm.doc["taxes"] || [], function(i, tax) {
tax.item_wise_tax_detail = {};
let tax_fields = ["total", "tax_fraction_for_current_item",
"grand_total_fraction_for_current_item"];
if (cstr(tax.charge_type) != "Actual") {
tax_fields.push("tax_amount");
}
$.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0; });
frm.doc.paid_amount_after_tax = frm.doc.paid_amount;
});
},
determine_exclusive_rate: function(frm) {
let has_inclusive_tax = false;
$.each(frm.doc["taxes"] || [], function(i, row) {
if(cint(row.included_in_paid_amount)) has_inclusive_tax = true;
});
if(has_inclusive_tax==false) return;
let cumulated_tax_fraction = 0.0;
$.each(frm.doc["taxes"] || [], function(i, tax) {
let current_tax_fraction = frm.events.get_current_tax_fraction(frm, tax);
tax.tax_fraction_for_current_item = current_tax_fraction[0];
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;
frm.doc.paid_amount_after_tax = flt(frm.doc.paid_amount/(1+cumulated_tax_fraction))
});
},
get_current_tax_fraction: function(frm, tax) {
let current_tax_fraction = 0.0;
if(cint(tax.included_in_paid_amount)) {
let tax_rate = tax.rate;
if (tax.charge_type == "Actual") {
current_tax_fraction = tax.tax_amount/(frm.doc.paid_amount_after_tax + frm.doc.tax_amount);
} else if(tax.charge_type == "On Paid Amount") {
current_tax_fraction = (tax_rate / 100.0);
} else if(tax.charge_type == "On Previous Row Amount") {
current_tax_fraction = (tax_rate / 100.0) *
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) *
frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item;
}
}
if(tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") {
current_tax_fraction *= -1;
inclusive_tax_amount_per_qty *= -1;
}
return current_tax_fraction;
},
calculate_taxes: function(frm) {
frm.doc.total_taxes_and_charges = 0.0;
frm.doc.base_total_taxes_and_charges = 0.0;
let actual_tax_dict = {};
// maintain actual tax rate based on idx
$.each(frm.doc["taxes"] || [], function(i, tax) {
if (tax.charge_type == "Actual") {
actual_tax_dict[tax.idx] = flt(tax.tax_amount, precision("tax_amount", tax));
}
});
$.each(me.frm.doc["taxes"] || [], function(i, tax) {
let current_tax_amount = frm.events.get_current_tax_amount(frm, tax);
// Adjust divisional loss to the last item
if (tax.charge_type == "Actual") {
actual_tax_dict[tax.idx] -= current_tax_amount;
if (i == frm.doc["taxes"].length - 1) {
current_tax_amount += actual_tax_dict[tax.idx];
}
}
tax.tax_amount = current_tax_amount;
tax.base_tax_amount = tax.tax_amount * frm.doc.source_exchange_rate;
current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
if(i==0) {
tax.total = flt(frm.doc.paid_amount_after_tax + current_tax_amount, precision("total", tax));
} else {
tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax));
}
tax.base_total = tax.total * frm.doc.source_exchange_rate;
frm.doc.total_taxes_and_charges += current_tax_amount;
frm.doc.base_total_taxes_and_charges += current_tax_amount * frm.doc.source_exchange_rate;
frm.refresh_field('taxes');
frm.refresh_field('total_taxes_and_charges');
frm.refresh_field('base_total_taxes_and_charges');
});
},
get_current_tax_amount: function(frm, tax) {
let tax_rate = tax.rate;
let current_tax_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_tax_amount = flt(tax.tax_amount, precision("tax_amount", tax))
} else if(tax.charge_type == "On Paid Amount") {
current_tax_amount = flt((tax_rate / 100.0) * frm.doc.paid_amount_after_tax);
} else if(tax.charge_type == "On Previous Row Amount") {
current_tax_amount = flt((tax_rate / 100.0) *
frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount);
} else if(tax.charge_type == "On Previous Row Total") {
current_tax_amount = flt((tax_rate / 100.0) *
frm.doc["taxes"][cint(tax.row_id) - 1].total);
}
return current_tax_amount;
},
}); });
@ -1049,6 +1272,33 @@ frappe.ui.form.on('Payment Entry Reference', {
} }
}) })
frappe.ui.form.on('Advance Taxes and Charges', {
rate: function(frm) {
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
},
tax_amount : function(frm) {
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
},
row_id: function(frm) {
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
},
taxes_remove: function(frm) {
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
},
included_in_paid_amount: function(frm) {
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
}
})
frappe.ui.form.on('Payment Entry Deduction', { frappe.ui.form.on('Payment Entry Deduction', {
amount: function(frm) { amount: function(frm) {
frm.events.set_unallocated_amount(frm); frm.events.set_unallocated_amount(frm);

View File

@ -35,12 +35,16 @@
"paid_to_account_balance", "paid_to_account_balance",
"payment_amounts_section", "payment_amounts_section",
"paid_amount", "paid_amount",
"paid_amount_after_tax",
"source_exchange_rate", "source_exchange_rate",
"base_paid_amount", "base_paid_amount",
"base_paid_amount_after_tax",
"column_break_21", "column_break_21",
"received_amount", "received_amount",
"received_amount_after_tax",
"target_exchange_rate", "target_exchange_rate",
"base_received_amount", "base_received_amount",
"base_received_amount_after_tax",
"section_break_14", "section_break_14",
"get_outstanding_invoice", "get_outstanding_invoice",
"references", "references",
@ -52,6 +56,17 @@
"unallocated_amount", "unallocated_amount",
"difference_amount", "difference_amount",
"write_off_difference_amount", "write_off_difference_amount",
"taxes_and_charges_section",
"purchase_taxes_and_charges_template",
"sales_taxes_and_charges_template",
"advance_tax_account",
"column_break_55",
"apply_tax_withholding_amount",
"tax_withholding_category",
"section_break_56",
"taxes",
"base_total_taxes_and_charges",
"total_taxes_and_charges",
"deductions_or_loss_section", "deductions_or_loss_section",
"deductions", "deductions",
"transaction_references", "transaction_references",
@ -320,6 +335,7 @@
"reqd": 1 "reqd": 1
}, },
{ {
"depends_on": "doc.received_amount",
"fieldname": "base_received_amount", "fieldname": "base_received_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Received Amount (Company Currency)", "label": "Received Amount (Company Currency)",
@ -584,12 +600,114 @@
"fieldname": "custom_remarks", "fieldname": "custom_remarks",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Custom Remarks" "label": "Custom Remarks"
},
{
"depends_on": "eval:doc.apply_tax_withholding_amount",
"fieldname": "tax_withholding_category",
"fieldtype": "Link",
"label": "Tax Withholding Category",
"mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
"options": "Tax Withholding Category"
},
{
"default": "0",
"depends_on": "eval:doc.party_type == 'Supplier'",
"fieldname": "apply_tax_withholding_amount",
"fieldtype": "Check",
"label": "Apply Tax Withholding Amount"
},
{
"collapsible": 1,
"fieldname": "taxes_and_charges_section",
"fieldtype": "Section Break",
"label": "Taxes and Charges"
},
{
"depends_on": "eval:doc.party_type == 'Supplier'",
"fieldname": "purchase_taxes_and_charges_template",
"fieldtype": "Link",
"label": "Taxes and Charges Template",
"options": "Purchase Taxes and Charges Template"
},
{
"depends_on": "eval: doc.party_type == 'Customer'",
"fieldname": "sales_taxes_and_charges_template",
"fieldtype": "Link",
"label": "Taxes and Charges Template",
"options": "Sales Taxes and Charges Template"
},
{
"depends_on": "eval: doc.party_type == 'Supplier' || doc.party_type == 'Customer'",
"fieldname": "taxes",
"fieldtype": "Table",
"label": "Advance Taxes and Charges",
"options": "Advance Taxes and Charges"
},
{
"fieldname": "base_total_taxes_and_charges",
"fieldtype": "Currency",
"label": "Total Taxes and Charges (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "total_taxes_and_charges",
"fieldtype": "Currency",
"label": "Total Taxes and Charges",
"read_only": 1
},
{
"fieldname": "paid_amount_after_tax",
"fieldtype": "Currency",
"hidden": 1,
"label": "Paid Amount After Tax",
"options": "paid_from_account_currency",
"read_only": 1
},
{
"fieldname": "base_paid_amount_after_tax",
"fieldtype": "Currency",
"label": "Paid Amount After Tax (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "column_break_55",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_56",
"fieldtype": "Section Break",
"hide_border": 1
},
{
"depends_on": "eval:doc.apply_tax_withholding_amount",
"description": "Provisional tax account for advance tax. Taxes are parked in this account until payments are allocated to invoices",
"fieldname": "advance_tax_account",
"fieldtype": "Link",
"label": "Advance Tax Account",
"mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
"options": "Account"
},
{
"depends_on": "eval:doc.received_amount",
"fieldname": "received_amount_after_tax",
"fieldtype": "Currency",
"label": "Received Amount After Tax",
"options": "paid_to_account_currency"
},
{
"depends_on": "doc.received_amount",
"fieldname": "base_received_amount_after_tax",
"fieldtype": "Currency",
"label": "Received Amount After Tax (Company Currency)",
"options": "Company:company:default_currency"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-03-08 13:05:16.958866", "modified": "2021-06-09 11:55:04.215050",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry", "name": "Payment Entry",
@ -633,4 +751,4 @@
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "title", "title_field": "title",
"track_changes": 1 "track_changes": 1
} }

View File

@ -5,7 +5,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext, json import frappe, erpnext, json
from frappe import _, scrub, ValidationError from frappe import _, scrub, ValidationError
from frappe.utils import flt, comma_or, nowdate, getdate from frappe.utils import flt, comma_or, nowdate, getdate, cint
from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on
from erpnext.accounts.party import get_party_account from erpnext.accounts.party import get_party_account
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
@ -15,6 +15,7 @@ from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amo
from erpnext.accounts.doctype.bank_account.bank_account import get_party_bank_account, get_bank_account_details from erpnext.accounts.doctype.bank_account.bank_account import get_party_bank_account, get_bank_account_details
from erpnext.controllers.accounts_controller import AccountsController, get_supplier_block_status from erpnext.controllers.accounts_controller import AccountsController, get_supplier_block_status
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
from six import string_types, iteritems from six import string_types, iteritems
@ -52,6 +53,8 @@ class PaymentEntry(AccountsController):
self.set_exchange_rate() self.set_exchange_rate()
self.validate_mandatory() self.validate_mandatory()
self.validate_reference_documents() self.validate_reference_documents()
self.set_tax_withholding()
self.apply_taxes()
self.set_amounts() self.set_amounts()
self.clear_unallocated_reference_document_rows() self.clear_unallocated_reference_document_rows()
self.validate_payment_against_negative_invoice() self.validate_payment_against_negative_invoice()
@ -310,7 +313,6 @@ class PaymentEntry(AccountsController):
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."), + "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
title=_("Warning"), indicator="orange") title=_("Warning"), indicator="orange")
def validate_journal_entry(self): def validate_journal_entry(self):
for d in self.get("references"): for d in self.get("references"):
if d.allocated_amount and d.reference_doctype == "Journal Entry": if d.allocated_amount and d.reference_doctype == "Journal Entry":
@ -391,12 +393,110 @@ class PaymentEntry(AccountsController):
self.db_set('status', self.status, update_modified = True) self.db_set('status', self.status, update_modified = True)
def set_tax_withholding(self):
if not self.party_type == 'Supplier':
return
if not self.apply_tax_withholding_amount:
return
if not self.advance_tax_account:
frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction"))
reference_doclist = []
net_total = self.paid_amount
included_in_paid_amount = 0
if self.get('references'):
for doc in self.get('references'):
if doc.reference_doctype == 'Purchase Order':
reference_doclist.append(doc.reference_name)
if reference_doclist:
order_amount = frappe.db.get_all('Purchase Order', fields=['sum(net_total)'],
filters = {'name': ('in', reference_doclist), 'docstatus': 1,
'apply_tds': 1}, as_list=1)
if order_amount:
net_total = order_amount[0][0]
included_in_paid_amount = 1
# Adding args as purchase invoice to get TDS amount
args = frappe._dict({
'company': self.company,
'doctype': 'Purchase Invoice',
'supplier': self.party,
'posting_date': self.posting_date,
'net_total': net_total
})
tax_withholding_details = get_party_tax_withholding_details(args, self.tax_withholding_category)
if not tax_withholding_details:
return
tax_withholding_details.update({
'included_in_paid_amount': included_in_paid_amount,
'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company)
})
accounts = []
for d in self.taxes:
if d.account_head == tax_withholding_details.get("account_head"):
# Preserve user updated included in paid amount
if d.included_in_paid_amount:
tax_withholding_details.update({'included_in_paid_amount': d.included_in_paid_amount})
d.update(tax_withholding_details)
accounts.append(d.account_head)
if not accounts or tax_withholding_details.get("account_head") not in accounts:
self.append("taxes", tax_withholding_details)
to_remove = [d for d in self.taxes
if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")]
for d in to_remove:
self.remove(d)
def apply_taxes(self):
self.initialize_taxes()
self.determine_exclusive_rate()
self.calculate_taxes()
def set_amounts(self): def set_amounts(self):
self.set_received_amount()
self.set_amounts_in_company_currency() self.set_amounts_in_company_currency()
self.set_amounts_after_tax()
self.set_total_allocated_amount() self.set_total_allocated_amount()
self.set_unallocated_amount() self.set_unallocated_amount()
self.set_difference_amount() self.set_difference_amount()
def set_received_amount(self):
self.base_received_amount = self.base_paid_amount
def set_amounts_after_tax(self):
applicable_tax = 0
base_applicable_tax = 0
for tax in self.get('taxes'):
if not tax.included_in_paid_amount:
amount = -1 * tax.tax_amount if tax.add_deduct_tax == 'Deduct' else tax.tax_amount
base_amount = -1 * tax.base_tax_amount if tax.add_deduct_tax == 'Deduct' else tax.base_tax_amount
applicable_tax += amount
base_applicable_tax += base_amount
self.paid_amount_after_tax = flt(flt(self.paid_amount) + flt(applicable_tax),
self.precision("paid_amount_after_tax"))
self.base_paid_amount_after_tax = flt(flt(self.paid_amount_after_tax) * flt(self.source_exchange_rate),
self.precision("base_paid_amount_after_tax"))
self.received_amount_after_tax = flt(flt(self.received_amount) + flt(applicable_tax),
self.precision("paid_amount_after_tax"))
self.base_received_amount_after_tax = flt(flt(self.received_amount_after_tax) * flt(self.target_exchange_rate),
self.precision("base_paid_amount_after_tax"))
def set_amounts_in_company_currency(self): def set_amounts_in_company_currency(self):
self.base_paid_amount, self.base_received_amount, self.difference_amount = 0, 0, 0 self.base_paid_amount, self.base_received_amount, self.difference_amount = 0, 0, 0
if self.paid_amount: if self.paid_amount:
@ -426,15 +526,15 @@ class PaymentEntry(AccountsController):
if self.party: if self.party:
total_deductions = sum([flt(d.amount) for d in self.get("deductions")]) total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
if self.payment_type == "Receive" \ if self.payment_type == "Receive" \
and self.base_total_allocated_amount < self.base_received_amount + total_deductions \ and self.base_total_allocated_amount < self.base_received_amount_after_tax + total_deductions \
and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): and self.total_allocated_amount < self.paid_amount_after_tax + (total_deductions / self.source_exchange_rate):
self.unallocated_amount = (self.base_received_amount + total_deductions - self.unallocated_amount = (self.received_amount_after_tax + total_deductions -
self.base_total_allocated_amount) / self.source_exchange_rate self.base_total_allocated_amount) / self.source_exchange_rate
elif self.payment_type == "Pay" \ elif self.payment_type == "Pay" \
and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \ and self.base_total_allocated_amount < (self.base_paid_amount_after_tax - total_deductions) \
and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate): and self.total_allocated_amount < self.received_amount_after_tax + (total_deductions / self.target_exchange_rate):
self.unallocated_amount = (self.base_paid_amount - (total_deductions + self.unallocated_amount = (self.base_paid_amount_after_tax - (total_deductions +
self.base_total_allocated_amount)) / self.target_exchange_rate self.base_total_allocated_amount)) / self.target_exchange_rate
def set_difference_amount(self): def set_difference_amount(self):
base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate) base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)
@ -443,11 +543,11 @@ class PaymentEntry(AccountsController):
base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount) base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount)
if self.payment_type == "Receive": if self.payment_type == "Receive":
self.difference_amount = base_party_amount - self.base_received_amount self.difference_amount = base_party_amount - self.base_received_amount_after_tax
elif self.payment_type == "Pay": elif self.payment_type == "Pay":
self.difference_amount = self.base_paid_amount - base_party_amount self.difference_amount = self.base_paid_amount_after_tax - base_party_amount
else: else:
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) self.difference_amount = self.base_paid_amount_after_tax - flt(self.base_received_amount_after_tax)
total_deductions = sum([flt(d.amount) for d in self.get("deductions")]) total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
@ -537,6 +637,7 @@ class PaymentEntry(AccountsController):
self.add_party_gl_entries(gl_entries) self.add_party_gl_entries(gl_entries)
self.add_bank_gl_entries(gl_entries) self.add_bank_gl_entries(gl_entries)
self.add_deductions_gl_entries(gl_entries) self.add_deductions_gl_entries(gl_entries)
self.add_tax_gl_entries(gl_entries)
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj) make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
@ -576,7 +677,7 @@ class PaymentEntry(AccountsController):
gl_entries.append(gle) gl_entries.append(gle)
if self.unallocated_amount: if self.unallocated_amount:
base_unallocated_amount = base_unallocated_amount = self.unallocated_amount * \ base_unallocated_amount = self.unallocated_amount * \
(self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate) (self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate)
gle = party_gl_dict.copy() gle = party_gl_dict.copy()
@ -595,8 +696,8 @@ class PaymentEntry(AccountsController):
"account": self.paid_from, "account": self.paid_from,
"account_currency": self.paid_from_account_currency, "account_currency": self.paid_from_account_currency,
"against": self.party if self.payment_type=="Pay" else self.paid_to, "against": self.party if self.payment_type=="Pay" else self.paid_to,
"credit_in_account_currency": self.paid_amount, "credit_in_account_currency": self.paid_amount_after_tax,
"credit": self.base_paid_amount, "credit": self.base_paid_amount_after_tax,
"cost_center": self.cost_center "cost_center": self.cost_center
}, item=self) }, item=self)
) )
@ -606,12 +707,48 @@ class PaymentEntry(AccountsController):
"account": self.paid_to, "account": self.paid_to,
"account_currency": self.paid_to_account_currency, "account_currency": self.paid_to_account_currency,
"against": self.party if self.payment_type=="Receive" else self.paid_from, "against": self.party if self.payment_type=="Receive" else self.paid_from,
"debit_in_account_currency": self.received_amount, "debit_in_account_currency": self.received_amount_after_tax,
"debit": self.base_received_amount, "debit": self.base_received_amount_after_tax,
"cost_center": self.cost_center "cost_center": self.cost_center
}, item=self) }, item=self)
) )
def add_tax_gl_entries(self, gl_entries):
for d in self.get('taxes'):
account_currency = get_account_currency(d.account_head)
if account_currency != self.company_currency:
frappe.throw(_("Currency for {0} must be {1}").format(d.account_head, self.company_currency))
if (self.payment_type == 'Pay' and self.advance_tax_account) or self.payment_type == 'Receive':
dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
elif (self.payment_type == 'Receive' and self.advance_tax_account) or self.payment_type == 'Pay':
dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
payment_or_advance_account = self.get_party_account_for_taxes()
gl_entries.append(
self.get_gl_dict({
"account": d.account_head,
"against": self.party if self.payment_type=="Receive" else self.paid_from,
dr_or_cr: d.base_tax_amount,
dr_or_cr + "_in_account_currency": d.base_tax_amount
if account_currency==self.company_currency
else d.tax_amount,
"cost_center": d.cost_center
}, account_currency, item=d))
#Intentionally use -1 to get net values in party account
gl_entries.append(
self.get_gl_dict({
"account": payment_or_advance_account,
"against": self.party if self.payment_type=="Receive" else self.paid_from,
dr_or_cr: -1 * d.base_tax_amount,
dr_or_cr + "_in_account_currency": -1*d.base_tax_amount
if account_currency==self.company_currency
else d.tax_amount,
"cost_center": self.cost_center,
}, account_currency, item=d))
def add_deductions_gl_entries(self, gl_entries): def add_deductions_gl_entries(self, gl_entries):
for d in self.get("deductions"): for d in self.get("deductions"):
if d.amount: if d.amount:
@ -630,6 +767,14 @@ class PaymentEntry(AccountsController):
}, item=d) }, item=d)
) )
def get_party_account_for_taxes(self):
if self.advance_tax_account:
return self.advance_tax_account
elif self.payment_type == 'Pay':
return self.paid_from
elif self.payment_type == 'Receive':
return self.paid_to
def update_advance_paid(self): def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party: if self.payment_type in ("Receive", "Pay") and self.party:
for d in self.get("references"): for d in self.get("references"):
@ -676,6 +821,121 @@ class PaymentEntry(AccountsController):
self.append('deductions', row) self.append('deductions', row)
self.set_unallocated_amount() self.set_unallocated_amount()
def initialize_taxes(self):
for tax in self.get("taxes"):
tax_fields = ["total", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
if tax.charge_type != "Actual":
tax_fields.append("tax_amount")
for fieldname in tax_fields:
tax.set(fieldname, 0.0)
self.paid_amount_after_tax = self.paid_amount
def determine_exclusive_rate(self):
if not any((cint(tax.included_in_paid_amount) for tax in self.get("taxes"))):
return
cumulated_tax_fraction = 0
for i, tax in enumerate(self.get("taxes")):
tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax)
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 = \
self.get("taxes")[i-1].grand_total_fraction_for_current_item \
+ tax.tax_fraction_for_current_item
cumulated_tax_fraction += tax.tax_fraction_for_current_item
self.paid_amount_after_tax = flt(self.paid_amount/(1+cumulated_tax_fraction))
def calculate_taxes(self):
self.total_taxes_and_charges = 0.0
self.base_total_taxes_and_charges = 0.0
actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
for tax in self.get("taxes") if tax.charge_type == "Actual"])
for i, tax in enumerate(self.get('taxes')):
current_tax_amount = self.get_current_tax_amount(tax)
if tax.charge_type == "Actual":
actual_tax_dict[tax.idx] -= current_tax_amount
if i == len(self.get("taxes")) - 1:
current_tax_amount += actual_tax_dict[tax.idx]
tax.tax_amount = current_tax_amount
tax.base_tax_amount = tax.tax_amount * self.source_exchange_rate
if tax.add_deduct_tax == "Deduct":
current_tax_amount *= -1.0
else:
current_tax_amount *= 1.0
if i == 0:
tax.total = flt(self.paid_amount_after_tax + current_tax_amount, self.precision("total", tax))
else:
tax.total = flt(self.get('taxes')[i-1].total + current_tax_amount, self.precision("total", tax))
tax.base_total = tax.total * self.source_exchange_rate
self.total_taxes_and_charges += current_tax_amount
self.base_total_taxes_and_charges += current_tax_amount * self.source_exchange_rate
if self.get('taxes'):
self.paid_amount_after_tax = self.get('taxes')[-1].base_total
def get_current_tax_amount(self, tax):
tax_rate = tax.rate
# To set row_id by default as previous row.
if tax.charge_type in ["On Previous Row Amount", "On Previous Row Total"]:
if tax.idx == 1:
frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"))
if not tax.row_id:
tax.row_id = tax.idx - 1
if tax.charge_type == "Actual":
current_tax_amount = flt(tax.tax_amount, self.precision("tax_amount", tax))
elif tax.charge_type == "On Paid Amount":
current_tax_amount = (tax_rate / 100.0) * self.paid_amount_after_tax
elif tax.charge_type == "On Previous Row Amount":
current_tax_amount = (tax_rate / 100.0) * \
self.get('taxes')[cint(tax.row_id) - 1].tax_amount
elif tax.charge_type == "On Previous Row Total":
current_tax_amount = (tax_rate / 100.0) * \
self.get('taxes')[cint(tax.row_id) - 1].total
return current_tax_amount
def get_current_tax_fraction(self, tax):
current_tax_fraction = 0
if cint(tax.included_in_paid_amount):
tax_rate = tax.rate
if tax.charge_type == 'Actual':
current_tax_fraction = tax.tax_amount/ (self.paid_amount_after_tax + tax.tax_amount)
elif tax.charge_type == "On Paid Amount":
current_tax_fraction = tax_rate / 100.0
elif tax.charge_type == "On Previous Row Amount":
current_tax_fraction = (tax_rate / 100.0) * \
self.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
elif tax.charge_type == "On Previous Row Total":
current_tax_fraction = (tax_rate / 100.0) * \
self.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
current_tax_fraction *= -1.0
return current_tax_fraction
@frappe.whitelist() @frappe.whitelist()
def get_outstanding_reference_documents(args): def get_outstanding_reference_documents(args):
@ -1241,6 +1501,13 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
}) })
pe.set_difference_amount() pe.set_difference_amount()
if doc.doctype == 'Purchase Order' and doc.apply_tds:
pe.apply_tax_withholding_amount = 1
pe.tax_withholding_category = doc.tax_withholding_category
if not pe.advance_tax_account:
pe.advance_tax_account = frappe.db.get_value('Company', pe.company, 'unrealized_profit_loss_account')
return pe return pe
def get_bank_cash_account(doc, bank_account): def get_bank_cash_account(doc, bank_account):

View File

@ -1,140 +1,70 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "creation": "2016-06-15 15:56:30.815503",
"allow_guest_to_view": 0, "doctype": "DocType",
"allow_import": 0, "editable_grid": 1,
"allow_rename": 0, "field_order": [
"beta": 0, "account",
"creation": "2016-06-15 15:56:30.815503", "cost_center",
"custom": 0, "amount",
"docstatus": 0, "column_break_2",
"doctype": "DocType", "description"
"document_type": "", ],
"editable_grid": 1,
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "account",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Account",
"collapsible": 0, "options": "Account",
"columns": 0, "reqd": 1,
"fieldname": "account", "show_days": 1,
"fieldtype": "Link", "show_seconds": 1
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "cost_center",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Cost Center",
"collapsible": 0, "options": "Cost Center",
"columns": 0, "print_hide": 1,
"fieldname": "cost_center", "reqd": 1,
"fieldtype": "Link", "show_days": 1,
"hidden": 0, "show_seconds": 1
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Cost Center",
"length": 0,
"no_copy": 0,
"options": "Cost Center",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "amount",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Amount",
"collapsible": 0, "reqd": 1,
"columns": 0, "show_days": 1,
"fieldname": "amount", "show_seconds": 1
"fieldtype": "Currency", },
"hidden": 0, {
"ignore_user_permissions": 0, "fieldname": "column_break_2",
"ignore_xss_filter": 0, "fieldtype": "Column Break",
"in_filter": 0, "show_days": 1,
"in_global_search": 0, "show_seconds": 1
"in_list_view": 1, },
"in_standard_filter": 0, {
"label": "Amount", "fieldname": "description",
"length": 0, "fieldtype": "Small Text",
"no_copy": 0, "label": "Description",
"permlevel": 0, "show_days": 1,
"precision": "", "show_seconds": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "index_web_pages_for_search": 1,
"hide_heading": 0, "istable": 1,
"hide_toolbar": 0, "links": [],
"idx": 0, "modified": "2020-09-12 20:38:08.110674",
"image_view": 0, "modified_by": "Administrator",
"in_create": 0, "module": "Accounts",
"is_submittable": 0, "name": "Payment Entry Deduction",
"issingle": 0, "owner": "Administrator",
"istable": 1, "permissions": [],
"max_attachments": 0, "quick_entry": 1,
"modified": "2019-01-07 16:52:07.040146", "sort_field": "modified",
"modified_by": "Administrator", "sort_order": "DESC"
"module": "Accounts",
"name": "Payment Entry Deduction",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@ -68,9 +68,6 @@ class PurchaseInvoice(BuyingController):
super(PurchaseInvoice, self).validate() super(PurchaseInvoice, self).validate()
# apply tax withholding only if checked and applicable
self.set_tax_withholding()
if not self.is_return: if not self.is_return:
self.po_required() self.po_required()
self.pr_required() self.pr_required()
@ -251,11 +248,9 @@ class PurchaseInvoice(BuyingController):
if self.update_stock and (not item.from_warehouse): if self.update_stock and (not item.from_warehouse):
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]: if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(warehouse_account[item.warehouse]["account"])) msg = _("Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account").format(
msg += _("because account {} is not linked to warehouse {} ").format(frappe.bold(item.expense_account), frappe.bold(item.warehouse)) item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]), frappe.bold(item.expense_account), frappe.bold(item.warehouse))
msg += _("or it is not the default inventory account")
frappe.msgprint(msg, title=_("Expense Head Changed")) frappe.msgprint(msg, title=_("Expense Head Changed"))
item.expense_account = warehouse_account[item.warehouse]["account"] item.expense_account = warehouse_account[item.warehouse]["account"]
else: else:
# check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not # check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not
@ -266,8 +261,8 @@ class PurchaseInvoice(BuyingController):
if negative_expense_booked_in_pr: if negative_expense_booked_in_pr:
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account: if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account)) msg = _("Row {0}: Expense Head changed to {1} because expense is booked against this account in Purchase Receipt {2}").format(
msg += _("because expense is booked against this account in Purchase Receipt {}").format(frappe.bold(item.purchase_receipt)) item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt))
frappe.msgprint(msg, title=_("Expense Head Changed")) frappe.msgprint(msg, title=_("Expense Head Changed"))
item.expense_account = stock_not_billed_account item.expense_account = stock_not_billed_account
@ -275,8 +270,9 @@ class PurchaseInvoice(BuyingController):
# If no purchase receipt present then book expense in 'Stock Received But Not Billed' # If no purchase receipt present then book expense in 'Stock Received But Not Billed'
# This is done in cases when Purchase Invoice is created before Purchase Receipt # This is done in cases when Purchase Invoice is created before Purchase Receipt
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account: if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account)) msg = _("Row {0}: Expense Head changed to {1} as no Purchase Receipt is created against Item {2}.").format(
msg += _("as no Purchase Receipt is created against Item {}. ").format(frappe.bold(item.item_code)) item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.item_code))
msg += "<br>"
msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice") msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice")
frappe.msgprint(msg, title=_("Expense Head Changed")) frappe.msgprint(msg, title=_("Expense Head Changed"))
@ -308,8 +304,8 @@ class PurchaseInvoice(BuyingController):
if not d.purchase_order: if not d.purchase_order:
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code)) msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
msg += "<br><br>" msg += "<br><br>"
msg += _("To submit the invoice without purchase order please set {} ").format(frappe.bold(_('Purchase Order Required'))) msg += _("To submit the invoice without purchase order please set {0} as {1} in {2}").format(
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')) frappe.bold(_('Purchase Order Required')), frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
throw(msg, title=_("Mandatory Purchase Order")) throw(msg, title=_("Mandatory Purchase Order"))
def pr_required(self): def pr_required(self):
@ -323,8 +319,8 @@ class PurchaseInvoice(BuyingController):
if not d.purchase_receipt and d.item_code in stock_items: if not d.purchase_receipt and d.item_code in stock_items:
msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code)) msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code))
msg += "<br><br>" msg += "<br><br>"
msg += _("To submit the invoice without purchase receipt please set {} ").format(frappe.bold(_('Purchase Receipt Required'))) msg += _("To submit the invoice without purchase receipt please set {0} as {1} in {2}").format(
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')) frappe.bold(_('Purchase Receipt Required')), frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
throw(msg, title=_("Mandatory Purchase Receipt")) throw(msg, title=_("Mandatory Purchase Receipt"))
def validate_write_off_account(self): def validate_write_off_account(self):
@ -456,6 +452,8 @@ class PurchaseInvoice(BuyingController):
self.make_tax_gl_entries(gl_entries) self.make_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries)
self.allocate_advance_taxes(gl_entries)
gl_entries = make_regional_gl_entries(gl_entries, self) gl_entries = make_regional_gl_entries(gl_entries, self)
gl_entries = merge_similar_entries(gl_entries) gl_entries = merge_similar_entries(gl_entries)
@ -1090,6 +1088,7 @@ class PurchaseInvoice(BuyingController):
for d in self.taxes: for d in self.taxes:
if d.account_head == tax_withholding_details.get("account_head"): if d.account_head == tax_withholding_details.get("account_head"):
d.update(tax_withholding_details) d.update(tax_withholding_details)
accounts.append(d.account_head) accounts.append(d.account_head)
if not accounts or tax_withholding_details.get("account_head") not in accounts: if not accounts or tax_withholding_details.get("account_head") not in accounts:

View File

@ -16,6 +16,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_tra
from erpnext.projects.doctype.project.test_project import make_project from erpnext.projects.doctype.project.test_project import make_project
from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.item.test_item import create_item
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"] test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"]
test_ignore = ["Serial No"] test_ignore = ["Serial No"]
@ -950,6 +951,102 @@ class TestPurchaseInvoice(unittest.TestCase):
acc_settings.submit_journal_entriessubmit_journal_entries = 0 acc_settings.submit_journal_entriessubmit_journal_entries = 0
acc_settings.save() acc_settings.save()
def test_purchase_invoice_advance_taxes(self):
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
# create a new supplier to test
supplier = create_supplier(supplier_name = '_Test TDS Advance Supplier',
tax_withholding_category = 'TDS - 194 - Dividends - Individual')
# Update tax withholding category with current fiscal year and rate details
update_tax_witholding_category('_Test Company', 'TDS Payable - _TC', nowdate())
# Create Purchase Order with TDS applied
po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000)
po.apply_tds = 1
po.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
po.save()
po.submit()
# Update Unrealized Profit / Loss Account which is used as default advance tax account
frappe.db.set_value('Company', '_Test Company', 'unrealized_profit_loss_account', '_Test Account Excise Duty - _TC')
# Create Payment Entry Against the order
payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name)
payment_entry.paid_from = 'Cash - _TC'
payment_entry.save()
payment_entry.submit()
# Check GLE for Payment Entry
expected_gle = [
['_Test Account Excise Duty - _TC', 3000, 0],
['Cash - _TC', 0, 27000],
['Creditors - _TC', 27000, 0],
['TDS Payable - _TC', 0, 3000],
]
gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry`
where voucher_type='Payment Entry' and voucher_no=%s
order by account asc""", (payment_entry.name), as_dict=1)
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[i][0], gle.account)
self.assertEqual(expected_gle[i][1], gle.debit)
self.assertEqual(expected_gle[i][2], gle.credit)
# Create Purchase Invoice against Purchase Order
purchase_invoice = get_mapped_purchase_invoice(po.name)
purchase_invoice.allocate_advances_automatically = 1
purchase_invoice.items[0].expense_account = '_Test Account Cost for Goods Sold - _TC'
purchase_invoice.save()
purchase_invoice.submit()
# Check GLE for Purchase Invoice
# Zero net effect on final TDS Payable on invoice
expected_gle = [
['_Test Account Cost for Goods Sold - _TC', 30000, 0],
['_Test Account Excise Duty - _TC', 0, 3000],
['Creditors - _TC', 0, 27000],
['TDS Payable - _TC', 3000, 3000]
]
gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry`
where voucher_type='Purchase Invoice' and voucher_no=%s
order by account asc""", (purchase_invoice.name), as_dict=1)
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[i][0], gle.account)
self.assertEqual(expected_gle[i][1], gle.debit)
self.assertEqual(expected_gle[i][2], gle.credit)
def update_tax_witholding_category(company, account, date):
from erpnext.accounts.utils import get_fiscal_year
fiscal_year = get_fiscal_year(date=date, company=company)
if not frappe.db.get_value('Tax Withholding Rate',
{'parent': 'TDS - 194 - Dividends - Individual', 'fiscal_year': fiscal_year[0]}):
tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual')
tds_category.append('rates', {
'fiscal_year': fiscal_year[0],
'tax_withholding_rate': 10,
'single_threshold': 2500,
'cumulative_threshold': 0
})
tds_category.save()
if not frappe.db.get_value('Tax Withholding Account',
{'parent': 'TDS - 194 - Dividends - Individual', 'account': account}):
tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual')
tds_category.append('accounts', {
'company': company,
'account': account
})
tds_category.save()
def unlink_payment_on_cancel_of_invoice(enable=1): def unlink_payment_on_cancel_of_invoice(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings") accounts_settings = frappe.get_doc("Accounts Settings")

View File

@ -12,6 +12,7 @@
"charge_type", "charge_type",
"row_id", "row_id",
"included_in_print_rate", "included_in_print_rate",
"included_in_paid_amount",
"col_break1", "col_break1",
"account_head", "account_head",
"description", "description",
@ -21,6 +22,7 @@
"cost_center", "cost_center",
"dimension_col_break", "dimension_col_break",
"section_break_9", "section_break_9",
"currency",
"tax_amount", "tax_amount",
"tax_amount_after_discount_amount", "tax_amount_after_discount_amount",
"total", "total",
@ -205,12 +207,27 @@
{ {
"fieldname": "dimension_col_break", "fieldname": "dimension_col_break",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"fetch_from": "account_head.account_currency",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Account Currency",
"options": "Currency",
"read_only": 1
},
{
"default": "0",
"description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry",
"fieldname": "included_in_paid_amount",
"fieldtype": "Check",
"label": "Considered In Paid Amount"
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-09-18 17:26:09.703215", "modified": "2021-06-09 11:48:25.335733",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Taxes and Charges", "name": "Purchase Taxes and Charges",

View File

@ -842,6 +842,8 @@ class SalesInvoice(SellingController):
self.make_tax_gl_entries(gl_entries) self.make_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries)
self.allocate_advance_taxes(gl_entries)
self.make_item_gl_entries(gl_entries) self.make_item_gl_entries(gl_entries)
# merge gl entries before adding pos entries # merge gl entries before adding pos entries

View File

@ -1,8 +1,10 @@
{ {
"actions": [],
"creation": "2013-04-24 11:39:32", "creation": "2013-04-24 11:39:32",
"doctype": "DocType", "doctype": "DocType",
"document_type": "Setup", "document_type": "Setup",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB",
"field_order": [ "field_order": [
"charge_type", "charge_type",
"row_id", "row_id",
@ -10,12 +12,14 @@
"col_break_1", "col_break_1",
"description", "description",
"included_in_print_rate", "included_in_print_rate",
"included_in_paid_amount",
"accounting_dimensions_section", "accounting_dimensions_section",
"cost_center", "cost_center",
"dimension_col_break", "dimension_col_break",
"section_break_8", "section_break_8",
"rate", "rate",
"section_break_9", "section_break_9",
"currency",
"tax_amount", "tax_amount",
"total", "total",
"tax_amount_after_discount_amount", "tax_amount_after_discount_amount",
@ -23,8 +27,7 @@
"base_tax_amount", "base_tax_amount",
"base_total", "base_total",
"base_tax_amount_after_discount_amount", "base_tax_amount_after_discount_amount",
"item_wise_tax_detail", "item_wise_tax_detail"
"parenttype"
], ],
"fields": [ "fields": [
{ {
@ -173,17 +176,6 @@
"oldfieldtype": "Small Text", "oldfieldtype": "Small Text",
"read_only": 1 "read_only": 1
}, },
{
"fieldname": "parenttype",
"fieldtype": "Data",
"hidden": 1,
"in_filter": 1,
"label": "Parenttype",
"oldfieldname": "parenttype",
"oldfieldtype": "Data",
"print_hide": 1,
"search_index": 1
},
{ {
"fieldname": "accounting_dimensions_section", "fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@ -192,15 +184,33 @@
{ {
"fieldname": "dimension_col_break", "fieldname": "dimension_col_break",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"fetch_from": "account_head.account_currency",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Account Currency",
"options": "Currency",
"read_only": 1
},
{
"default": "0",
"description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry",
"fieldname": "included_in_paid_amount",
"fieldtype": "Check",
"label": "Considered In Paid Amount"
} }
], ],
"idx": 1, "idx": 1,
"index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"modified": "2019-05-25 22:59:38.740883", "links": [],
"modified": "2021-06-09 11:48:04.691596",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Taxes and Charges", "name": "Sales Taxes and Charges",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified",
"sort_order": "ASC" "sort_order": "ASC"
} }

View File

@ -49,7 +49,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
if not parties: if not parties:
parties.append(party) parties.append(party)
fiscal_year = get_fiscal_year(inv.posting_date, company=inv.company) fiscal_year = get_fiscal_year(inv.get('posting_date') or inv.get('transaction_date'), company=inv.company)
tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company) tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company)
if not tax_details: if not tax_details:
@ -154,7 +154,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, p
tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details) tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details)
tax_amount = 0 tax_amount = 0
posting_date = inv.posting_date posting_date = inv.get('posting_date') or inv.get('transaction_date')
if party_type == 'Supplier': if party_type == 'Supplier':
ldc = get_lower_deduction_certificate(fiscal_year, pan_no) ldc = get_lower_deduction_certificate(fiscal_year, pan_no)
if tax_deducted: if tax_deducted:
@ -257,7 +257,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)): if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
if ldc and is_valid_certificate( if ldc and is_valid_certificate(
ldc.valid_from, ldc.valid_upto, ldc.valid_from, ldc.valid_upto,
inv.posting_date, tax_deducted, inv.get('posting_date') or inv.get('transaction_date'), tax_deducted,
inv.net_total, ldc.certificate_limit inv.net_total, ldc.certificate_limit
): ):
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details) tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)

View File

@ -54,6 +54,32 @@ frappe.query_reports["TDS Payable Monthly"] = {
frappe.query_report.refresh(); frappe.query_report.refresh();
} }
}, },
{
"fieldname":"purchase_order",
"label": __("Purchase Order"),
"fieldtype": "Link",
"options": "Purchase Order",
"get_query": function() {
return {
"filters": {
"name": ["in", frappe.query_report.invoices]
}
}
},
on_change: function() {
let supplier = frappe.query_report.get_filter_value('supplier');
if(!supplier) return; // return if no supplier selected
// filter invoices based on selected supplier
let invoices = [];
frappe.query_report.invoice_data.map(d => {
if(d.supplier==supplier)
invoices.push(d.name)
});
frappe.query_report.invoices = invoices;
frappe.query_report.refresh();
}
},
{ {
"fieldname":"from_date", "fieldname":"from_date",
"label": __("From Date"), "label": __("From Date"),
@ -75,15 +101,17 @@ frappe.query_reports["TDS Payable Monthly"] = {
onload: function(report) { onload: function(report) {
// fetch all tds applied invoices // fetch all tds applied invoices
frappe.call({ frappe.call({
"method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices", "method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices_and_orders",
callback: function(r) { callback: function(r) {
let invoices = []; let invoices = [];
r.message.map(d => { r.message.map(d => {
invoices.push(d.name); invoices.push(d.name);
}); });
report["invoice_data"] = r.message; report["invoice_data"] = r.message.invoices;
report["invoices"] = invoices; report["invoices"] = invoices;
} }
}); });
} }

View File

@ -11,11 +11,14 @@ def execute(filters=None):
validate_filters(filters) validate_filters(filters)
set_filters(filters) set_filters(filters)
# TDS payment entries
payment_entries = get_payment_entires(filters)
columns = get_columns(filters) columns = get_columns(filters)
if not filters["invoices"]: if not filters.get("invoices"):
return columns, [] return columns, []
res = get_result(filters) res = get_result(filters, payment_entries)
return columns, res return columns, res
@ -27,8 +30,9 @@ def validate_filters(filters):
def set_filters(filters): def set_filters(filters):
invoices = [] invoices = []
if not filters["invoices"]: if not filters.get("invoices"):
filters["invoices"] = get_tds_invoices() filters["invoices"] = get_tds_invoices_and_orders()
if filters.supplier and filters.purchase_invoice: if filters.supplier and filters.purchase_invoice:
for d in filters["invoices"]: for d in filters["invoices"]:
if d.name == filters.purchase_invoice and d.supplier == filters.supplier: if d.name == filters.purchase_invoice and d.supplier == filters.supplier:
@ -41,13 +45,29 @@ def set_filters(filters):
for d in filters["invoices"]: for d in filters["invoices"]:
if d.name == filters.purchase_invoice: if d.name == filters.purchase_invoice:
invoices.append(d) invoices.append(d)
elif filters.supplier and filters.purchase_order:
for d in filters.get("invoices"):
if d.name == filters.purchase_order and d.supplier == filters.supplier:
invoices.append(d)
elif filters.supplier and not filters.purchase_order:
for d in filters.get("invoices"):
if d.supplier == filters.supplier:
invoices.append(d)
elif filters.purchase_order and not filters.supplier:
for d in filters.get("invoices"):
if d.name == filters.purchase_order:
invoices.append(d)
filters["invoices"] = invoices if invoices else filters["invoices"] filters["invoices"] = invoices if invoices else filters["invoices"]
filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name') filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name')
def get_result(filters): #print(filters.get('invoices'))
supplier_map, tds_docs = get_supplier_map(filters)
gle_map = get_gle_map(filters) def get_result(filters, payment_entries):
supplier_map, tds_docs = get_supplier_map(filters, payment_entries)
documents = [d.get('name') for d in filters.get('invoices')] + [d.get('name') for d in payment_entries]
gle_map = get_gle_map(filters, documents)
out = [] out = []
for d in gle_map: for d in gle_map:
@ -62,10 +82,11 @@ def get_result(filters):
for k in gle_map[d]: for k in gle_map[d]:
if k.party == supplier_map[d] and k.credit > 0: if k.party == supplier_map[d] and k.credit > 0:
total_amount_credited += k.credit total_amount_credited += (k.credit - k.debit)
elif account_list and k.account == account and k.credit > 0: elif account_list and k.account == account and (k.credit - k.debit) > 0:
tds_deducted = k.credit tds_deducted = (k.credit - k.debit)
total_amount_credited += k.credit total_amount_credited += (k.credit - k.debit)
voucher_type = k.voucher_type
rate = [i.tax_withholding_rate for i in tds_doc.rates rate = [i.tax_withholding_rate for i in tds_doc.rates
if i.fiscal_year == gle_map[d][0].fiscal_year] if i.fiscal_year == gle_map[d][0].fiscal_year]
@ -73,32 +94,36 @@ def get_result(filters):
if rate and len(rate) > 0 and tds_deducted: if rate and len(rate) > 0 and tds_deducted:
rate = rate[0] rate = rate[0]
if getdate(filters.from_date) <= gle_map[d][0].posting_date \ row = [supplier.pan, supplier.name]
and getdate(filters.to_date) >= gle_map[d][0].posting_date:
row = [supplier.pan, supplier.name]
if filters.naming_series == 'Naming Series': if filters.naming_series == 'Naming Series':
row.append(supplier.supplier_name) row.append(supplier.supplier_name)
row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited, row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited,
tds_deducted, gle_map[d][0].posting_date, "Purchase Invoice", d]) tds_deducted, gle_map[d][0].posting_date, voucher_type, d])
out.append(row) out.append(row)
return out return out
def get_supplier_map(filters): def get_supplier_map(filters, payment_entries):
# create a supplier_map of the form {"purchase_invoice": {supplier_name, pan, tds_name}} # create a supplier_map of the form {"purchase_invoice": {supplier_name, pan, tds_name}}
# pre-fetch all distinct applicable tds docs # pre-fetch all distinct applicable tds docs
supplier_map, tds_docs = {}, {} supplier_map, tds_docs = {}, {}
pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id" pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
supplier_list = [d.supplier for d in filters["invoices"]]
supplier_detail = frappe.db.get_all('Supplier', supplier_detail = frappe.db.get_all('Supplier',
{"name": ["in", [d.supplier for d in filters["invoices"]]]}, {"name": ["in", supplier_list]},
["tax_withholding_category", "name", pan+" as pan", "supplier_type", "supplier_name"]) ["tax_withholding_category", "name", pan+" as pan", "supplier_type", "supplier_name"])
for d in filters["invoices"]: for d in filters["invoices"]:
supplier_map[d.get("name")] = [k for k in supplier_detail supplier_map[d.get("name")] = [k for k in supplier_detail
if k.name == d.get("supplier")][0] if k.name == d.get("supplier")][0]
for d in payment_entries:
supplier_map[d.get("name")] = [k for k in supplier_detail
if k.name == d.get("supplier")][0]
for d in supplier_detail: for d in supplier_detail:
if d.get("tax_withholding_category") not in tds_docs: if d.get("tax_withholding_category") not in tds_docs:
tds_docs[d.get("tax_withholding_category")] = \ tds_docs[d.get("tax_withholding_category")] = \
@ -106,13 +131,19 @@ def get_supplier_map(filters):
return supplier_map, tds_docs return supplier_map, tds_docs
def get_gle_map(filters): def get_gle_map(filters, documents):
# create gle_map of the form # create gle_map of the form
# {"purchase_invoice": list of dict of all gle created for this invoice} # {"purchase_invoice": list of dict of all gle created for this invoice}
gle_map = {} gle_map = {}
gle = frappe.db.get_all('GL Entry',\
{"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]], 'is_cancelled': 0}, gle = frappe.db.get_all('GL Entry',
["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date"]) {
"voucher_no": ["in", documents],
'is_cancelled': 0,
'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]),
},
["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date", "voucher_type"],
)
for d in gle: for d in gle:
if not d.voucher_no in gle_map: if not d.voucher_no in gle_map:
@ -201,8 +232,26 @@ def get_columns(filters):
return columns return columns
def get_payment_entires(filters):
filter_dict = {
'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]),
'party_type': 'Supplier',
'apply_tax_withholding_amount': 1
}
if filters.get('purchase_invoice') or filters.get('purchase_order'):
parent = frappe.db.get_all('Payment Entry Reference',
{'reference_name': ('in', [d.get('name') for d in filters.get('invoices')])}, ['parent'])
filter_dict.update({'name': ('in', [d.get('parent') for d in parent])})
payment_entries = frappe.get_all('Payment Entry', fields=['name', 'party_name as supplier'],
filters=filter_dict)
return payment_entries
@frappe.whitelist() @frappe.whitelist()
def get_tds_invoices(): def get_tds_invoices_and_orders():
# fetch tds applicable supplier and fetch invoices for these suppliers # fetch tds applicable supplier and fetch invoices for these suppliers
suppliers = [d.name for d in frappe.db.get_list("Supplier", suppliers = [d.name for d in frappe.db.get_list("Supplier",
{"tax_withholding_category": ["!=", ""]}, ["name"])] {"tax_withholding_category": ["!=", ""]}, ["name"])]
@ -210,7 +259,12 @@ def get_tds_invoices():
invoices = frappe.db.get_list("Purchase Invoice", invoices = frappe.db.get_list("Purchase Invoice",
{"supplier": ["in", suppliers]}, ["name", "supplier"]) {"supplier": ["in", suppliers]}, ["name", "supplier"])
orders = frappe.db.get_list("Purchase Order",
{"supplier": ["in", suppliers]}, ["name", "supplier"])
invoices = invoices + orders
invoices = [d for d in invoices if d.supplier] invoices = [d for d in invoices if d.supplier]
frappe.cache().hset("invoices", frappe.session.user, invoices) frappe.cache().hset("invoices", frappe.session.user, invoices)
return invoices return invoices

View File

@ -45,6 +45,14 @@ frappe.ui.form.on("Purchase Order", {
}); });
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
},
apply_tds: function(frm) {
if (!frm.doc.apply_tds) {
frm.set_value("tax_withholding_category", '');
} else {
frm.set_value("tax_withholding_category", frm.supplier_tds);
}
} }
}); });
@ -313,7 +321,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
if(me.values) { if(me.values) {
me.values.sub_con_rm_items.map((row,i) => { me.values.sub_con_rm_items.map((row,i) => {
if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) { if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
frappe.throw(__("Item Code, warehouse, quantity are required on row {0}", [i+1])); let row_id = i+1;
frappe.throw(__("Item Code, warehouse and quantity are required on row {0}", [row_id]));
} }
}) })
me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children()) me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children())
@ -509,7 +518,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
args: { args: {
reference_doctype: me.frm.doctype, reference_doctype: me.frm.doctype,
reference_name: me.frm.docname, reference_name: me.frm.docname,
content: __('Reason for hold: ')+data.reason_for_hold, content: __('Reason for hold:') + " " +data.reason_for_hold,
comment_email: frappe.session.user, comment_email: frappe.session.user,
comment_by: frappe.session.user_fullname comment_by: frappe.session.user_fullname
}, },

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@ from erpnext.accounts.party import get_party_account_currency
from six import string_types from six import string_types
from erpnext.stock.doctype.item.item import get_item_defaults from erpnext.stock.doctype.item.item import get_item_defaults
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\ from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\
unlink_inter_company_doc unlink_inter_company_doc
@ -39,11 +40,18 @@ class PurchaseOrder(BuyingController):
'percent_join_field': 'material_request' 'percent_join_field': 'material_request'
}] }]
def onload(self):
supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
self.set_onload("supplier_tds", supplier_tds)
def validate(self): def validate(self):
super(PurchaseOrder, self).validate() super(PurchaseOrder, self).validate()
self.set_status() self.set_status()
# apply tax withholding only if checked and applicable
self.set_tax_withholding()
self.validate_supplier() self.validate_supplier()
self.validate_schedule_date() self.validate_schedule_date()
validate_for_items(self) validate_for_items(self)
@ -87,6 +95,33 @@ class PurchaseOrder(BuyingController):
if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')): if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')):
self.validate_rate_with_reference_doc([["Supplier Quotation", "supplier_quotation", "supplier_quotation_item"]]) self.validate_rate_with_reference_doc([["Supplier Quotation", "supplier_quotation", "supplier_quotation_item"]])
def set_tax_withholding(self):
if not self.apply_tds:
return
tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
if not tax_withholding_details:
return
accounts = []
for d in self.taxes:
if d.account_head == tax_withholding_details.get("account_head"):
d.update(tax_withholding_details)
accounts.append(d.account_head)
if not accounts or tax_withholding_details.get("account_head") not in accounts:
self.append("taxes", tax_withholding_details)
to_remove = [d for d in self.taxes
if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")]
for d in to_remove:
self.remove(d)
# calculate totals again after applying TDS
self.calculate_taxes_and_totals()
def validate_supplier(self): def validate_supplier(self):
prevent_po = frappe.db.get_value("Supplier", self.supplier, 'prevent_pos') prevent_po = frappe.db.get_value("Supplier", self.supplier, 'prevent_pos')
if prevent_po: if prevent_po:

View File

@ -1111,7 +1111,7 @@ def create_purchase_order(**args):
po.schedule_date = add_days(nowdate(), 1) po.schedule_date = add_days(nowdate(), 1)
po.company = args.company or "_Test Company" po.company = args.company or "_Test Company"
po.supplier = args.customer or "_Test Supplier" po.supplier = args.supplier or "_Test Supplier"
po.is_subcontracted = args.is_subcontracted or "No" po.is_subcontracted = args.is_subcontracted or "No"
po.currency = args.currency or frappe.get_cached_value('Company', po.company, "default_currency") po.currency = args.currency or frappe.get_cached_value('Company', po.company, "default_currency")
po.conversion_factor = args.conversion_factor or 1 po.conversion_factor = args.conversion_factor or 1

View File

@ -116,6 +116,8 @@ class AccountsController(TransactionBase):
if self.doctype == 'Purchase Invoice': if self.doctype == 'Purchase Invoice':
self.calculate_paid_amount() self.calculate_paid_amount()
# apply tax withholding only if checked and applicable
self.set_tax_withholding()
if self.doctype in ['Purchase Invoice', 'Sales Invoice']: if self.doctype in ['Purchase Invoice', 'Sales Invoice']:
pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid" pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid"
@ -700,6 +702,7 @@ class AccountsController(TransactionBase):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
if self.doctype in ["Sales Invoice", "Purchase Invoice"]: if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
self.update_allocated_advance_taxes_on_cancel()
if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'): if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
unlink_ref_doc_from_payment_entries(self) unlink_ref_doc_from_payment_entries(self)
@ -707,6 +710,87 @@ class AccountsController(TransactionBase):
if frappe.db.get_single_value('Accounts Settings', 'unlink_advance_payment_on_cancelation_of_order'): if frappe.db.get_single_value('Accounts Settings', 'unlink_advance_payment_on_cancelation_of_order'):
unlink_ref_doc_from_payment_entries(self) unlink_ref_doc_from_payment_entries(self)
def get_tax_map(self):
tax_map = {}
for tax in self.get('taxes'):
tax_map.setdefault(tax.account_head, 0.0)
tax_map[tax.account_head] += tax.tax_amount
return tax_map
def update_allocated_advance_taxes_on_cancel(self):
if self.get('advances'):
tax_accounts = [d.account_head for d in self.get('taxes')]
allocated_tax_map = frappe._dict(frappe.get_all('GL Entry', fields=['account', 'sum(credit - debit)'],
filters={'voucher_no': self.name, 'account': ('in', tax_accounts)},
group_by='account', as_list=1))
tax_map = self.get_tax_map()
for pe in self.get('advances'):
if pe.reference_type == 'Payment Entry':
pe = frappe.get_doc('Payment Entry', pe.reference_name)
for tax in pe.get('taxes'):
allocated_amount = tax_map.get(tax.account_head) - allocated_tax_map.get(tax.account_head)
if allocated_amount > tax.tax_amount:
allocated_amount = tax.tax_amount
if allocated_amount:
frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount',
tax.allocated_amount - allocated_amount)
tax_map[tax.account_head] -= allocated_amount
allocated_tax_map[tax.account_head] -= allocated_amount
def allocate_advance_taxes(self, gl_entries):
tax_map = self.get_tax_map()
for pe in self.get("advances"):
if pe.reference_type == "Payment Entry" and \
frappe.db.get_value('Payment Entry', pe.reference_name, 'advance_tax_account'):
pe = frappe.get_doc("Payment Entry", pe.reference_name)
for tax in pe.get("taxes"):
account_currency = get_account_currency(tax.account_head)
if self.doctype == "Purchase Invoice":
dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
else:
dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
party = self.supplier if self.doctype == "Purchase Invoice" else self.customer
unallocated_amount = tax.tax_amount - tax.allocated_amount
if tax_map.get(tax.account_head):
amount = tax_map.get(tax.account_head)
if amount < unallocated_amount:
unallocated_amount = amount
gl_entries.append(
self.get_gl_dict({
"account": tax.account_head,
"against": party,
dr_or_cr: unallocated_amount,
dr_or_cr + "_in_account_currency": unallocated_amount
if account_currency==self.company_currency
else unallocated_amount,
"cost_center": tax.cost_center
}, account_currency, item=tax))
gl_entries.append(
self.get_gl_dict({
"account": pe.advance_tax_account,
"against": party,
rev_dr_cr: unallocated_amount,
rev_dr_cr + "_in_account_currency": unallocated_amount
if account_currency==self.company_currency
else unallocated_amount,
"cost_center": tax.cost_center
}, account_currency, item=tax))
frappe.db.set_value("Advance Taxes and Charges", tax.name, "allocated_amount",
tax.allocated_amount + unallocated_amount)
tax_map[tax.account_head] -= unallocated_amount
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
from erpnext.controllers.status_updater import get_allowance_for from erpnext.controllers.status_updater import get_allowance_for
item_allowance = {} item_allowance = {}
@ -1240,7 +1324,6 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype,
return list(payment_entries_against_order) + list(unallocated_payment_entries) return list(payment_entries_against_order) + list(unallocated_payment_entries)
def update_invoice_status(): def update_invoice_status():
# Daily update the status of the invoices # Daily update the status of the invoices

View File

@ -686,7 +686,6 @@ class calculate_taxes_and_totals(object):
self.calculate_paid_amount() self.calculate_paid_amount()
def get_itemised_tax_breakup_html(doc): def get_itemised_tax_breakup_html(doc):
if not doc.taxes: if not doc.taxes:
return return

View File

@ -156,31 +156,31 @@ cur_frm.cscript.validate_taxes_and_charges = function(cdt, cdn) {
var d = locals[cdt][cdn]; var d = locals[cdt][cdn];
var msg = ""; var msg = "";
if(d.account_head && !d.description) { if (d.account_head && !d.description) {
// set description from account head // set description from account head
d.description = d.account_head.split(' - ').slice(0, -1).join(' - '); d.description = d.account_head.split(' - ').slice(0, -1).join(' - ');
} }
if(!d.charge_type && (d.row_id || d.rate || d.tax_amount)) { if (!d.charge_type && (d.row_id || d.rate || d.tax_amount)) {
msg = __("Please select Charge Type first"); msg = __("Please select Charge Type first");
d.row_id = ""; d.row_id = "";
d.rate = d.tax_amount = 0.0; d.rate = d.tax_amount = 0.0;
} else if((d.charge_type == 'Actual' || d.charge_type == 'On Net Total') && d.row_id) { } else if ((d.charge_type == 'Actual' || d.charge_type == 'On Net Total' || d.charge_type == 'On Paid Amount') && d.row_id) {
msg = __("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'"); msg = __("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'");
d.row_id = ""; d.row_id = "";
} else if((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) { } else if ((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) {
if (d.idx == 1) { if (d.idx == 1) {
msg = __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"); msg = __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row");
d.charge_type = ''; d.charge_type = '';
} else if (!d.row_id) { } else if (!d.row_id) {
msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]); msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]);
d.row_id = ""; d.row_id = "";
} else if(d.row_id && d.row_id >= d.idx) { } else if (d.row_id && d.row_id >= d.idx) {
msg = __("Cannot refer row number greater than or equal to current row number for this Charge type"); msg = __("Cannot refer row number greater than or equal to current row number for this Charge type");
d.row_id = ""; d.row_id = "";
} }
} }
if(msg) { if (msg) {
frappe.validated = false; frappe.validated = false;
refresh_field("taxes"); refresh_field("taxes");
frappe.throw(msg); frappe.throw(msg);

View File

@ -531,7 +531,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
}, },
calculate_totals: function() { calculate_totals: function() {
// Changing sequence can cause rounding_adjustmentng issue and on-screen discrepency // Changing sequence can because of rounding adjustment issue and on-screen discrepancy
var me = this; var me = this;
var tax_count = this.frm.doc["taxes"] ? this.frm.doc["taxes"].length : 0; var tax_count = this.frm.doc["taxes"] ? this.frm.doc["taxes"].length : 0;
this.frm.doc.grand_total = flt(tax_count this.frm.doc.grand_total = flt(tax_count

View File

@ -8,24 +8,23 @@ erpnext.payments = erpnext.stock.StockController.extend({
this.dialog = new frappe.ui.Dialog({ this.dialog = new frappe.ui.Dialog({
title: 'Payment' title: 'Payment'
}); });
this.dialog.show(); this.dialog.show();
this.$body = this.dialog.body; this.$body = this.dialog.body;
this.set_payment_primary_action(); this.set_payment_primary_action();
this.make_keyboard(); this.make_keyboard();
this.select_text() this.select_text();
}, },
select_text: function(){ select_text() {
var me = this; $(this.$body).find('.form-control').click(function() {
$(this.$body).find('.form-control').click(function(){
$(this).select(); $(this).select();
}) });
}, },
set_payment_primary_action: function(){ set_payment_primary_action: function() {
var me = this; var me = this;
this.dialog.set_primary_action(__("Submit"), function() { this.dialog.set_primary_action(__("Submit"), function() {
// Allow no ZERO payment // Allow no ZERO payment
$.each(me.frm.doc.payments, function (index, data) { $.each(me.frm.doc.payments, function (index, data) {
@ -38,20 +37,20 @@ erpnext.payments = erpnext.stock.StockController.extend({
}) })
}, },
make_keyboard: function(){ make_keyboard: function() {
var me = this; var me = this;
$(this.$body).empty(); $(this.$body).empty();
$(this.$body).html(frappe.render_template('pos_payment', this.frm.doc)) $(this.$body).html(frappe.render_template('pos_payment', this.frm.doc))
this.show_payment_details(); this.show_payment_details();
this.bind_keyboard_event() this.bind_keyboard_event();
this.clear_amount() this.clear_amount();
}, },
make_multimode_payment: function(){ make_multimode_payment: function() {
var me = this; var me = this;
if(this.frm.doc.change_amount > 0){ if (this.frm.doc.change_amount > 0) {
me.payment_val = me.doc.outstanding_amount me.payment_val = me.doc.outstanding_amount;
} }
this.payments = frappe.model.add_child(this.frm.doc, 'Multi Mode Payment', "payments"); this.payments = frappe.model.add_child(this.frm.doc, 'Multi Mode Payment', "payments");
@ -59,11 +58,11 @@ erpnext.payments = erpnext.stock.StockController.extend({
this.payments.amount = flt(this.payment_val); this.payments.amount = flt(this.payment_val);
}, },
show_payment_details: function(){ show_payment_details: function() {
var me = this; var me = this;
var multimode_payments = $(this.$body).find('.multimode-payments').empty(); var multimode_payments = $(this.$body).find('.multimode-payments').empty();
if(this.frm.doc.payments.length){ if (this.frm.doc.payments.length) {
$.each(this.frm.doc.payments, function(index, data){ $.each(this.frm.doc.payments, function(index, data) {
$(frappe.render_template('payment_details', { $(frappe.render_template('payment_details', {
mode_of_payment: data.mode_of_payment, mode_of_payment: data.mode_of_payment,
amount: data.amount, amount: data.amount,
@ -84,92 +83,90 @@ erpnext.payments = erpnext.stock.StockController.extend({
} }
}, },
set_outstanding_amount: function(){ set_outstanding_amount: function() {
this.selected_mode = $(this.$body).find(repl("input[idx='%(idx)s']",{'idx': this.idx})); this.selected_mode = $(this.$body).find(repl("input[idx='%(idx)s']",{'idx': this.idx}));
this.highlight_selected_row() this.highlight_selected_row();
this.payment_val = 0.0 this.payment_val = 0.0;
if(this.frm.doc.outstanding_amount > 0 && flt(this.selected_mode.val()) == 0.0){ if (this.frm.doc.outstanding_amount > 0 && flt(this.selected_mode.val()) == 0.0) {
//When user first time click on row //When user first time click on row
this.payment_val = flt(this.frm.doc.outstanding_amount / this.frm.doc.conversion_rate, precision("outstanding_amount")) this.payment_val = flt(this.frm.doc.outstanding_amount / this.frm.doc.conversion_rate, precision("outstanding_amount"))
this.selected_mode.val(format_currency(this.payment_val, this.frm.doc.currency)); this.selected_mode.val(format_currency(this.payment_val, this.frm.doc.currency));
this.update_payment_amount() this.update_payment_amount();
}else if(flt(this.selected_mode.val()) > 0){ } else if (flt(this.selected_mode.val()) > 0) {
//If user click on existing row which has value //If user click on existing row which has value
this.payment_val = flt(this.selected_mode.val()); this.payment_val = flt(this.selected_mode.val());
} }
this.selected_mode.select() this.selected_mode.select()
this.bind_amount_change_event(); this.bind_amount_change_event();
}, },
bind_keyboard_event: function(){ bind_keyboard_event() {
var me = this;
this.payment_val = ''; this.payment_val = '';
this.bind_form_control_event(); this.bind_form_control_event();
this.bind_numeric_keys_event(); this.bind_numeric_keys_event();
}, },
bind_form_control_event: function(){ bind_form_control_event: function() {
var me = this; var me = this;
$(this.$body).find('.pos-payment-row').click(function(){ $(this.$body).find('.pos-payment-row').click(function() {
me.idx = $(this).attr("idx"); me.idx = $(this).attr("idx");
me.set_outstanding_amount() me.set_outstanding_amount();
}) });
$(this.$body).find('.form-control').click(function(){ $(this.$body).find('.form-control').click(function() {
me.idx = $(this).attr("idx"); me.idx = $(this).attr("idx");
me.set_outstanding_amount(); me.set_outstanding_amount();
me.update_paid_amount(true); me.update_paid_amount(true);
}) });
$(this.$body).find('.write_off_amount').change(function(){ $(this.$body).find('.write_off_amount').change(function() {
me.write_off_amount(flt($(this).val()), precision("write_off_amount")); me.write_off_amount(flt($(this).val()), precision("write_off_amount"));
}) });
$(this.$body).find('.change_amount').change(function(){ $(this.$body).find('.change_amount').change(function() {
me.change_amount(flt($(this).val()), precision("change_amount")); me.change_amount(flt($(this).val()), precision("change_amount"));
}) });
}, },
highlight_selected_row: function(){ highlight_selected_row() {
var me = this; var selected_row = $(this.$body).find(repl(".pos-payment-row[idx='%(idx)s']", {'idx': this.idx}));
var selected_row = $(this.$body).find(repl(".pos-payment-row[idx='%(idx)s']",{'idx': this.idx})); $(this.$body).find('.pos-payment-row').removeClass('selected-payment-mode');
$(this.$body).find('.pos-payment-row').removeClass('selected-payment-mode') selected_row.addClass('selected-payment-mode');
selected_row.addClass('selected-payment-mode')
$(this.$body).find('.amount').attr('disabled', true); $(this.$body).find('.amount').attr('disabled', true);
this.selected_mode.attr('disabled', false); this.selected_mode.attr('disabled', false);
}, },
bind_numeric_keys_event: function(){ bind_numeric_keys_event: function() {
var me = this; var me = this;
$(this.$body).find('.pos-keyboard-key').click(function(){ $(this.$body).find('.pos-keyboard-key').click(function(){
me.payment_val += $(this).text(); me.payment_val += $(this).text();
me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency)) me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency));
me.idx = me.selected_mode.attr("idx") me.idx = me.selected_mode.attr("idx");
me.update_paid_amount() me.update_paid_amount();
}) });
$(this.$body).find('.delete-btn').click(function(){ $(this.$body).find('.delete-btn').click(function() {
me.payment_val = cstr(flt(me.selected_mode.val())).slice(0, -1); me.payment_val = cstr(flt(me.selected_mode.val())).slice(0, -1);
me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency)); me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency));
me.idx = me.selected_mode.attr("idx") me.idx = me.selected_mode.attr("idx");
me.update_paid_amount(); me.update_paid_amount();
}) })
}, },
bind_amount_change_event: function(){ bind_amount_change_event() {
var me = this; var me = this;
this.selected_mode.change(function(){ this.selected_mode.change(function() {
me.payment_val = flt($(this).val()) || 0.0; me.payment_val = flt($(this).val()) || 0.0;
me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency)) me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency));
me.idx = me.selected_mode.attr("idx") me.idx = me.selected_mode.attr("idx");
me.update_payment_amount() me.update_payment_amount();
}) });
}, },
clear_amount: function() { clear_amount: function() {
var me = this; var me = this;
$(this.$body).find('.clr').click(function(e){ $(this.$body).find('.clr').click(function(e) {
e.stopPropagation(); e.stopPropagation();
me.idx = $(this).attr("idx"); me.idx = $(this).attr("idx");
me.selected_mode = $(me.$body).find(repl("input[idx='%(idx)s']",{'idx': me.idx})); me.selected_mode = $(me.$body).find(repl("input[idx='%(idx)s']",{'idx': me.idx}));
@ -177,50 +174,48 @@ erpnext.payments = erpnext.stock.StockController.extend({
me.selected_mode.val(0.0); me.selected_mode.val(0.0);
me.highlight_selected_row(); me.highlight_selected_row();
me.update_payment_amount(); me.update_payment_amount();
}) });
}, },
write_off_amount: function(write_off_amount) { write_off_amount(write_off_amount) {
var me = this;
this.frm.doc.write_off_amount = flt(write_off_amount, precision("write_off_amount")); this.frm.doc.write_off_amount = flt(write_off_amount, precision("write_off_amount"));
this.frm.doc.base_write_off_amount = flt(this.frm.doc.write_off_amount * this.frm.doc.conversion_rate, this.frm.doc.base_write_off_amount = flt(this.frm.doc.write_off_amount * this.frm.doc.conversion_rate,
precision("base_write_off_amount")); precision("base_write_off_amount"));
this.calculate_outstanding_amount(false) this.calculate_outstanding_amount(false);
this.show_amounts() this.show_amounts();
}, },
change_amount: function(change_amount) { change_amount: function(change_amount) {
var me = this; var me = this;
this.frm.doc.change_amount = flt(change_amount, precision("change_amount")); this.frm.doc.change_amount = flt(change_amount, precision("change_amount"));
this.calculate_write_off_amount() this.calculate_write_off_amount();
this.show_amounts() this.show_amounts();
}, },
update_paid_amount: function(update_write_off) { update_paid_amount: function(update_write_off) {
var me = this; var me = this;
if(in_list(['change_amount', 'write_off_amount'], this.idx)){ if (in_list(['change_amount', 'write_off_amount'], this.idx)) {
var value = me.selected_mode.val(); var value = me.selected_mode.val();
if(me.idx == 'change_amount'){ if (me.idx == 'change_amount') {
me.change_amount(value) me.change_amount(value);
} else{ } else {
if(flt(value) == 0 && update_write_off && me.frm.doc.outstanding_amount > 0) { if(flt(value) == 0 && update_write_off && me.frm.doc.outstanding_amount > 0) {
value = flt(me.frm.doc.outstanding_amount / me.frm.doc.conversion_rate, precision(me.idx)); value = flt(me.frm.doc.outstanding_amount / me.frm.doc.conversion_rate, precision(me.idx));
} }
me.write_off_amount(value) me.write_off_amount(value);
} }
}else{ } else {
this.update_payment_amount() this.update_payment_amount();
} }
}, },
update_payment_amount: function(){ update_payment_amount: function() {
var me = this; var me = this;
$.each(this.frm.doc.payments, function(index, data){ $.each(this.frm.doc.payments, function(index, data) {
if(cint(me.idx) == cint(data.idx)){ if (cint(me.idx) == cint(data.idx)) {
data.amount = flt(me.selected_mode.val(), 2) data.amount = flt(me.selected_mode.val(), 2);
} }
}) })
@ -228,12 +223,12 @@ erpnext.payments = erpnext.stock.StockController.extend({
this.show_amounts(); this.show_amounts();
}, },
show_amounts: function(){ show_amounts: function() {
var me = this; var me = this;
$(this.$body).find(".write_off_amount").val(format_currency(this.frm.doc.write_off_amount, this.frm.doc.currency)); $(this.$body).find(".write_off_amount").val(format_currency(this.frm.doc.write_off_amount, this.frm.doc.currency));
$(this.$body).find('.paid_amount').text(format_currency(this.frm.doc.paid_amount, this.frm.doc.currency)); $(this.$body).find('.paid_amount').text(format_currency(this.frm.doc.paid_amount, this.frm.doc.currency));
$(this.$body).find('.change_amount').val(format_currency(this.frm.doc.change_amount, this.frm.doc.currency)) $(this.$body).find('.change_amount').val(format_currency(this.frm.doc.change_amount, this.frm.doc.currency));
$(this.$body).find('.outstanding_amount').text(format_currency(this.frm.doc.outstanding_amount, frappe.get_doc(":Company", this.frm.doc.company).default_currency)) $(this.$body).find('.outstanding_amount').text(format_currency(this.frm.doc.outstanding_amount, frappe.get_doc(":Company", this.frm.doc.company).default_currency));
this.update_invoice(); this.update_invoice();
} }
}) })

View File

@ -269,7 +269,7 @@ erpnext.company.setup_queries = function(frm) {
["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}], ["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}],
["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}], ["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}],
["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}], ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}],
["unrealized_profit_loss_account", {"root_type": "Liability"},] ["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}]
], function(i, v) { ], function(i, v) {
erpnext.company.set_custom_query(frm, v); erpnext.company.set_custom_query(frm, v);
}); });