Merge branch 'develop' of https://github.com/frappe/erpnext into #34282-Record-advance-payment-as-a-liability

This commit is contained in:
Deepesh Garg
2023-06-19 21:39:01 +05:30
5 changed files with 126 additions and 68 deletions

View File

@@ -616,7 +616,7 @@ frappe.ui.form.on('Payment Entry', {
frm.events.set_unallocated_amount(frm); frm.events.set_unallocated_amount(frm);
}, },
get_outstanding_invoice: function(frm) { get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) {
const today = frappe.datetime.get_today(); const today = frappe.datetime.get_today();
const fields = [ const fields = [
{fieldtype:"Section Break", label: __("Posting Date")}, {fieldtype:"Section Break", label: __("Posting Date")},
@@ -646,12 +646,29 @@ frappe.ui.form.on('Payment Entry', {
{fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1}, {fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1},
]; ];
let btn_text = "";
if (get_outstanding_invoices) {
btn_text = "Get Outstanding Invoices";
}
else if (get_orders_to_be_billed) {
btn_text = "Get Outstanding Orders";
}
frappe.prompt(fields, function(filters){ frappe.prompt(fields, function(filters){
frappe.flags.allocate_payment_amount = true; frappe.flags.allocate_payment_amount = true;
frm.events.validate_filters_data(frm, filters); frm.events.validate_filters_data(frm, filters);
frm.doc.cost_center = filters.cost_center; frm.doc.cost_center = filters.cost_center;
frm.events.get_outstanding_documents(frm, filters); frm.events.get_outstanding_documents(frm, filters, get_outstanding_invoices, get_orders_to_be_billed);
}, __("Filters"), __("Get Outstanding Documents")); }, __("Filters"), __(btn_text));
},
get_outstanding_invoices: function(frm) {
frm.events.get_outstanding_invoices_or_orders(frm, true, false);
},
get_outstanding_orders: function(frm) {
frm.events.get_outstanding_invoices_or_orders(frm, false, true);
}, },
validate_filters_data: function(frm, filters) { validate_filters_data: function(frm, filters) {
@@ -677,7 +694,7 @@ frappe.ui.form.on('Payment Entry', {
} }
}, },
get_outstanding_documents: function(frm, filters) { get_outstanding_documents: function(frm, filters, get_outstanding_invoices, get_orders_to_be_billed) {
frm.clear_table("references"); frm.clear_table("references");
if(!frm.doc.party) { if(!frm.doc.party) {
@@ -701,6 +718,13 @@ frappe.ui.form.on('Payment Entry', {
args[key] = filters[key]; args[key] = filters[key];
} }
if (get_outstanding_invoices) {
args["get_outstanding_invoices"] = true;
}
else if (get_orders_to_be_billed) {
args["get_orders_to_be_billed"] = true;
}
frappe.flags.allocate_payment_amount = filters['allocate_payment_amount']; frappe.flags.allocate_payment_amount = filters['allocate_payment_amount'];
return frappe.call({ return frappe.call({

View File

@@ -48,7 +48,8 @@
"base_received_amount", "base_received_amount",
"base_received_amount_after_tax", "base_received_amount_after_tax",
"section_break_14", "section_break_14",
"get_outstanding_invoice", "get_outstanding_invoices",
"get_outstanding_orders",
"references", "references",
"section_break_34", "section_break_34",
"total_allocated_amount", "total_allocated_amount",
@@ -355,12 +356,6 @@
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Reference" "label": "Reference"
}, },
{
"depends_on": "eval:doc.docstatus==0",
"fieldname": "get_outstanding_invoice",
"fieldtype": "Button",
"label": "Get Outstanding Invoice"
},
{ {
"fieldname": "references", "fieldname": "references",
"fieldtype": "Table", "fieldtype": "Table",
@@ -728,12 +723,24 @@
"fieldname": "section_break_60", "fieldname": "section_break_60",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hide_border": 1 "hide_border": 1
},
{
"depends_on": "eval:doc.docstatus==0",
"fieldname": "get_outstanding_invoices",
"fieldtype": "Button",
"label": "Get Outstanding Invoices"
},
{
"depends_on": "eval:doc.docstatus==0",
"fieldname": "get_outstanding_orders",
"fieldtype": "Button",
"label": "Get Outstanding Orders"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2023-06-07 14:36:50.521884", "modified": "2023-06-19 11:38:04.387219",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry", "name": "Payment Entry",

View File

@@ -181,6 +181,19 @@ class PaymentEntry(AccountsController):
if self.payment_type == "Internal Transfer": if self.payment_type == "Internal Transfer":
return return
if self.party_type in ("Customer", "Supplier"):
self.validate_allocated_amount_with_latest_data()
else:
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
for d in self.get("references"):
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
def validate_allocated_amount_with_latest_data(self):
latest_references = get_outstanding_reference_documents( latest_references = get_outstanding_reference_documents(
{ {
"posting_date": self.posting_date, "posting_date": self.posting_date,
@@ -189,6 +202,8 @@ class PaymentEntry(AccountsController):
"payment_type": self.payment_type, "payment_type": self.payment_type,
"party": self.party, "party": self.party,
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to, "party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
"get_outstanding_invoices": True,
"get_orders_to_be_billed": True,
} }
) )
@@ -198,7 +213,7 @@ class PaymentEntry(AccountsController):
d = frappe._dict(d) d = frappe._dict(d)
latest_lookup.update({(d.voucher_type, d.voucher_no): d}) latest_lookup.update({(d.voucher_type, d.voucher_no): d})
for d in self.get("references").copy(): for d in self.get("references"):
latest = latest_lookup.get((d.reference_doctype, d.reference_name)) latest = latest_lookup.get((d.reference_doctype, d.reference_name))
# The reference has already been fully paid # The reference has already been fully paid
@@ -213,22 +228,18 @@ class PaymentEntry(AccountsController):
): ):
frappe.throw( frappe.throw(
_( _(
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' button to get the latest outstanding amount." "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
).format(d.reference_doctype, d.reference_name) ).format(d.reference_doctype, d.reference_name)
) )
d.outstanding_amount = latest.outstanding_amount
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.") fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
if (flt(d.allocated_amount)) > 0: if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
if flt(d.allocated_amount) > flt(d.outstanding_amount): frappe.throw(fail_message.format(d.idx))
frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well # Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0: if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
if flt(d.allocated_amount) < flt(d.outstanding_amount): frappe.throw(fail_message.format(d.idx))
frappe.throw(fail_message.format(d.idx))
def delink_advance_entry_references(self): def delink_advance_entry_references(self):
for reference in self.references: for reference in self.references:
@@ -1432,62 +1443,75 @@ def get_outstanding_reference_documents(args):
condition += " and company = {0}".format(frappe.db.escape(args.get("company"))) condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
common_filter.append(ple.company == args.get("company")) common_filter.append(ple.company == args.get("company"))
outstanding_invoices = get_outstanding_invoices( outstanding_invoices = []
args.get("party_type"),
args.get("party"),
args.get("party_account"),
common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
max_outstanding=args.get("outstanding_amt_less_than"),
accounting_dimensions=accounting_dimensions_filter,
)
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
for d in outstanding_invoices:
d["exchange_rate"] = 1
if party_account_currency != company_currency:
if d.voucher_type in frappe.get_hooks("invoice_doctypes"):
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
elif d.voucher_type == "Journal Entry":
d["exchange_rate"] = get_exchange_rate(
party_account_currency, company_currency, d.posting_date
)
if d.voucher_type in ("Purchase Invoice"):
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
# Get all SO / PO which are not fully billed or against which full advance not paid
orders_to_be_billed = []
orders_to_be_billed = get_orders_to_be_billed(
args.get("posting_date"),
args.get("party_type"),
args.get("party"),
args.get("company"),
party_account_currency,
company_currency,
filters=args,
)
# Get negative outstanding sales /purchase invoices
negative_outstanding_invoices = [] negative_outstanding_invoices = []
if args.get("party_type") != "Employee" and not args.get("voucher_no"):
negative_outstanding_invoices = get_negative_outstanding_invoices( if args.get("get_outstanding_invoices"):
outstanding_invoices = get_outstanding_invoices(
args.get("party_type"), args.get("party_type"),
args.get("party"), args.get("party"),
args.get("party_account"), args.get("party_account"),
common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
max_outstanding=args.get("outstanding_amt_less_than"),
accounting_dimensions=accounting_dimensions_filter,
)
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
for d in outstanding_invoices:
d["exchange_rate"] = 1
if party_account_currency != company_currency:
if d.voucher_type in frappe.get_hooks("invoice_doctypes"):
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
elif d.voucher_type == "Journal Entry":
d["exchange_rate"] = get_exchange_rate(
party_account_currency, company_currency, d.posting_date
)
if d.voucher_type in ("Purchase Invoice"):
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
# Get negative outstanding sales /purchase invoices
if args.get("party_type") != "Employee" and not args.get("voucher_no"):
negative_outstanding_invoices = get_negative_outstanding_invoices(
args.get("party_type"),
args.get("party"),
args.get("party_account"),
party_account_currency,
company_currency,
condition=condition,
)
# Get all SO / PO which are not fully billed or against which full advance not paid
orders_to_be_billed = []
if args.get("get_orders_to_be_billed"):
orders_to_be_billed = get_orders_to_be_billed(
args.get("posting_date"),
args.get("party_type"),
args.get("party"),
args.get("company"),
party_account_currency, party_account_currency,
company_currency, company_currency,
condition=condition, filters=args,
) )
data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
if not data: if not data:
if args.get("get_outstanding_invoices") and args.get("get_orders_to_be_billed"):
ref_document_type = "invoices or orders"
elif args.get("get_outstanding_invoices"):
ref_document_type = "invoices"
elif args.get("get_orders_to_be_billed"):
ref_document_type = "orders"
frappe.msgprint( frappe.msgprint(
_( _(
"No outstanding invoices found for the {0} {1} which qualify the filters you have specified." "No outstanding {0} found for the {1} {2} which qualify the filters you have specified."
).format(_(args.get("party_type")).lower(), frappe.bold(args.get("party"))) ).format(
ref_document_type, _(args.get("party_type")).lower(), frappe.bold(args.get("party"))
)
) )
return data return data

View File

@@ -320,6 +320,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval: !doc.is_debit_note",
"fieldname": "is_return", "fieldname": "is_return",
"fieldtype": "Check", "fieldtype": "Check",
"hide_days": 1, "hide_days": 1,
@@ -1960,6 +1961,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval: !doc.is_return",
"description": "Issue a debit note with 0 qty against an existing Sales Invoice", "description": "Issue a debit note with 0 qty against an existing Sales Invoice",
"fieldname": "is_debit_note", "fieldname": "is_debit_note",
"fieldtype": "Check", "fieldtype": "Check",
@@ -2155,7 +2157,7 @@
"link_fieldname": "consolidated_invoice" "link_fieldname": "consolidated_invoice"
} }
], ],
"modified": "2023-06-03 16:22:16.219333", "modified": "2023-06-19 16:02:05.309332",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@@ -125,7 +125,8 @@
"oldfieldname": "purpose", "oldfieldname": "purpose",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor", "options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
"read_only": 1 "read_only": 1,
"search_index": 1
}, },
{ {
"fieldname": "company", "fieldname": "company",
@@ -678,7 +679,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2023-06-16 14:59:10.917235", "modified": "2023-06-19 18:23:40.748114",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry", "name": "Stock Entry",