From e3a10170f790eedad72c23f08201ce2762df71a6 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sun, 26 Jun 2016 17:48:07 +0530 Subject: [PATCH] Payment Entry submission and more --- .../doctype/payment_entry/payment_entry.js | 146 ++++++-- .../doctype/payment_entry/payment_entry.json | 285 +++++++-------- .../doctype/payment_entry/payment_entry.py | 326 +++++++++++------- .../payment_entry_reference.json | 4 +- erpnext/accounts/utils.py | 6 +- erpnext/controllers/accounts_controller.py | 2 +- erpnext/setup/doctype/company/company.js | 1 + erpnext/setup/doctype/company/company.json | 28 +- 8 files changed, 468 insertions(+), 330 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 826f2360f2..3fb8fa5fcb 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -3,8 +3,10 @@ frappe.ui.form.on('Payment Entry', { onload: function(frm) { - frm.set_value("paid_from_account_currency", null); - frm.set_value("paid_from_account_currency", null); + if(frm.doc.__islocal) { + frm.set_value("paid_from_account_currency", null); + frm.set_value("paid_to_account_currency", null); + } }, setup: function(frm) { @@ -14,7 +16,7 @@ frappe.ui.form.on('Payment Entry', { {fieldname: 'outstanding_amount', columns: 3}, {fieldname: 'allocated_amount', columns: 3} ]; - + var party_account_type = frm.doc.party_type=="Customer" ? "Receivable" : "Payable"; frm.set_query("paid_from", function() { @@ -79,23 +81,44 @@ frappe.ui.form.on('Payment Entry', { refresh: function(frm) { frm.events.hide_unhide_fields(frm); frm.events.set_dynamic_labels(frm); + frm.events.show_general_ledger(frm); }, hide_unhide_fields: function(frm) { var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; - frm.toggle_display(["source_exchange_rate", "base_paid_amount"], - (frm.doc.paid_from && frm.doc.paid_from_account_currency != company_currency)); - - frm.toggle_display(["target_exchange_rate", "base_received_amount"], - (frm.doc.paid_to && frm.doc.paid_to_account_currency != company_currency)); - - frm.toggle_display(["base_total_allocated_amount"], - ((frm.doc.payment_type=="Receive" && frm.doc.paid_from_account_currency != company_currency) || - (frm.doc.payment_type=="Pay" && frm.doc.paid_to_account_currency != company_currency))); + frm.toggle_display("source_exchange_rate", + (frm.doc.paid_amount && frm.doc.paid_from_account_currency != company_currency)); + + frm.toggle_display("target_exchange_rate", + (frm.doc.received_amount && frm.doc.paid_to_account_currency != company_currency)); + + frm.toggle_display("base_paid_amount", + (!frm.doc.party && frm.doc.paid_from_account_currency != company_currency)); + + frm.toggle_display("base_received_amount", + (!frm.doc.party && frm.doc.paid_to_account_currency != company_currency)); - frm.toggle_display(["received_amount"], + frm.toggle_display("received_amount", frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency) + + frm.toggle_display(["base_total_allocated_amount"], + (frm.doc.paid_amount && frm.doc.received_amount && frm.doc.base_total_allocated_amount && + ((frm.doc.payment_type=="Receive" && frm.doc.paid_from_account_currency != company_currency) || + (frm.doc.payment_type=="Pay" && frm.doc.paid_to_account_currency != company_currency)))); + + var party_amount = frm.doc.payment_type=="Receive" ? + frm.doc.paid_amount : frm.doc.received_amount; + + frm.toggle_display("write_off_difference_amount", (frm.doc.difference_amount && frm.doc.party && + (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) && + (frm.doc.total_allocated_amount > party_amount))); + + frm.toggle_display("set_exchange_gain_loss", + (frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount && + (frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency))); + + frm.refresh_fields(); }, set_dynamic_labels: function(frm) { @@ -128,16 +151,17 @@ frappe.ui.form.on('Payment Entry', { var party_account_currency = frm.doc.payment_type=="Receive" ? frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency; - - setup_field_label_map(["total_allocated_amount"], party_account_currency); + + setup_field_label_map(["total_allocated_amount", "unallocated_amount"], party_account_currency); $.each(field_label_map, function(fname, label) { me.frm.fields_dict[fname].set_label(label); }); - setup_field_label_map(["total_amount"], company_currency, "references"); - setup_field_label_map(["outstanding_amount", "allocated_amount"], + setup_field_label_map(["total_amount", "outstanding_amount", "allocated_amount"], party_account_currency, "references"); + + setup_field_label_map(["amount"], company_currency, "deductions"); $.each(grid_field_label_map, function(fname, label) { fname = fname.split("-"); @@ -149,7 +173,24 @@ frappe.ui.form.on('Payment Entry', { ("1 " + frm.doc.paid_from_account_currency + " = [?] " + company_currency)); cur_frm.set_df_property("target_exchange_rate", "description", - ("1 " + frm.doc.paid_to_account_currency + " = [?] " + company_currency)); + ("1 " + frm.doc.paid_to_account_currency + " = [?] " + company_currency)); + + frm.refresh_fields(); + }, + + show_general_ledger: function(frm) { + if(frm.doc.docstatus==1) { + frm.add_custom_button(__('Ledger'), function() { + frappe.route_options = { + "voucher_no": frm.doc.name, + "from_date": frm.doc.posting_date, + "to_date": frm.doc.posting_date, + "company": frm.doc.company, + group_by_voucher: 0 + }; + frappe.set_route("query-report", "General Ledger"); + }, "icon-table"); + } }, "party": function(frm) { @@ -190,7 +231,7 @@ frappe.ui.form.on('Payment Entry', { if(frm.doc.payment_type == "Internal Transfer") { $.each(["party", "party_balance", "paid_from", "paid_to", "references", "total_allocated_amount"], function(i, field) { - frm.set_value(field, ""); + frm.set_value(field, null); }) } else { frm.events.party(frm); @@ -256,6 +297,7 @@ frappe.ui.form.on('Payment Entry', { }, paid_from_account_currency: function(frm) { + if(!frm.doc.paid_from_account_currency) return; var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; if (frm.doc.paid_from_account_currency == company_currency) { @@ -279,6 +321,7 @@ frappe.ui.form.on('Payment Entry', { }, paid_to_account_currency: function(frm) { + if(!frm.doc.paid_to_account_currency) return; var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; frm.events.set_current_exchange_rate(frm, "target_exchange_rate", @@ -319,14 +362,20 @@ frappe.ui.form.on('Payment Entry', { frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate); frm.set_value("base_received_amount", frm.doc.base_paid_amount); } - - frm.events.set_difference_amount(frm); + if(frm.doc.payment_type == "Receive") + frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount); + else + frm.events.set_difference_amount(frm); }, received_amount: function(frm) { frm.set_value("base_received_amount", flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)); - frm.events.set_difference_amount(frm); + + if(frm.doc.payment_type == "Pay") + frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount); + else + frm.events.set_difference_amount(frm); }, get_outstanding_documents: function(frm) { @@ -354,24 +403,40 @@ frappe.ui.form.on('Payment Entry', { c.reference_name = d.voucher_no; c.total_amount = d.invoice_amount; c.outstanding_amount = d.outstanding_amount; + + var party_account_currency = frm.doc.payment_type=="Receive" ? + frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency; - if(frm.doc.party_account_currency != company_currency) { + if(party_account_currency != company_currency) { c.exchange_rate = d.exchange_rate; } else { c.exchange_rate = 1; } - if (in_list(['Sales Invoice', 'Purchase Invoice'], d.reference_doctype)){ c.due_date = d.due_date } }); } - frm.events.set_total_allocated_amount(frm); - frm.refresh_fields() + + frm.events.allocate_party_amount_against_ref_docs(frm, + (frm.doc.payment_type=="Receive" ? frm.doc.paid_amount : frm.doc.received_amount)); } }); }, + allocate_party_amount_against_ref_docs: function(frm, amount) { + $.each(frm.doc.references || [], function(i, row) { + if (!amount) return false; + + if(row.outstanding_amount >= amount) row.allocated_amount = amount; + else row.allocated_amount = row.outstanding_amount; + + amount -= flt(row.allocated_amount); + }) + frm.refresh_fields() + frm.events.set_total_allocated_amount(frm); + }, + set_total_allocated_amount: function(frm) { var total_allocated_amount = base_total_allocated_amount = 0.0; $.each(frm.doc.references || [], function(i, row) { @@ -399,12 +464,15 @@ frappe.ui.form.on('Payment Entry', { set_difference_amount: function(frm) { var unallocated_amount = 0; - var party_amount = frm.doc.payment_type=="Receive" ? frm.doc.paid_amount : frm.doc.received_amount; + if(frm.doc.party) { + var party_amount = frm.doc.payment_type=="Receive" ? + frm.doc.paid_amount : frm.doc.received_amount; - if(frm.doc.total_allocated_amount < party_amount) - unallocated_amount = party_amount - frm.doc.total_allocated_amount; + if(frm.doc.total_allocated_amount < party_amount) + unallocated_amount = party_amount - frm.doc.total_allocated_amount; + } - frm.set_value("unallocated_amount", unallocated_amount) + frm.set_value("unallocated_amount", unallocated_amount); var difference_amount = 0; var base_unallocated_amount = flt(frm.doc.unallocated_amount) * @@ -426,8 +494,7 @@ frappe.ui.form.on('Payment Entry', { frm.set_value("difference_amount", difference_amount); - frm.toggle_display("write_off_difference_amount", - (frm.doc.difference_amount && frm.doc.total_allocated_amount > party_amount)); + frm.events.hide_unhide_fields(frm); }, check_mandatory_to_fetch: function(frm) { @@ -455,7 +522,6 @@ frappe.ui.form.on('Payment Entry', { frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Purchase Order, Purchase Invoice or Journal Entry", [row.idx])); return false; } - } if (row) { @@ -466,20 +532,28 @@ frappe.ui.form.on('Payment Entry', { }, write_off_difference_amount: function(frm) { + frm.events.set_deductions_entry(frm, "write_off_account"); + }, + + set_exchange_gain_loss: function(frm) { + frm.events.set_deductions_entry(frm, "exchange_gain_loss_account"); + }, + + set_deductions_entry: function(frm, account) { if(frm.doc.difference_amount) { frappe.call({ - method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_write_off_account_and_cost_center", + method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_company_defaults", args: { company: frm.doc.company }, callback: function(r, rt) { if(r.message) { var write_off_row = $.map(frm.doc["deductions"] || [], function(t) { - return t.account==r.message["write_off_account"] ? t : null; }); + return t.account==r.message[account] ? t : null; }); if (!write_off_row.length) { var row = frm.add_child("deductions"); - row.account = r.message["write_off_account"]; + row.account = r.message[account]; row.cost_center = r.message["cost_center"]; } else { var row = write_off_row[0]; diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 5994aa352b..f21fe561fa 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -353,7 +353,7 @@ "depends_on": "paid_from", "fieldname": "paid_from_account_currency", "fieldtype": "Link", - "hidden": 1, + "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, @@ -368,7 +368,7 @@ "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -473,7 +473,7 @@ "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -547,6 +547,7 @@ "label": "Paid Amount", "length": 0, "no_copy": 0, + "options": "paid_from_account_currency", "permlevel": 0, "precision": "", "print_hide": 0, @@ -579,7 +580,7 @@ "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -599,13 +600,14 @@ "label": "Paid Amount (Company Currency)", "length": 0, "no_copy": 0, + "options": "Company:company:default_currency", "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -649,6 +651,7 @@ "label": "Received Amount", "length": 0, "no_copy": 0, + "options": "paid_to_account_currency", "permlevel": 0, "precision": "", "print_hide": 0, @@ -681,7 +684,7 @@ "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -701,13 +704,14 @@ "label": "Received Amount (Company Currency)", "length": 0, "no_copy": 0, + "options": "Company:company:default_currency", "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -758,7 +762,7 @@ "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -808,6 +812,32 @@ "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "depends_on": "eval:doc.docstatus==1", + "fieldname": "clearance_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Clearance Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, "reqd": 0, "search_index": 0, "set_only_once": 0, @@ -896,7 +926,7 @@ "allow_on_submit": 0, "bold": 1, "collapsible": 0, - "depends_on": "", + "depends_on": "eval:(doc.paid_amount && doc.received_amount && doc.references)", "fieldname": "total_allocated_amount", "fieldtype": "Currency", "hidden": 0, @@ -933,32 +963,7 @@ "label": "Total Allocated Amount (Company Currency)", "length": 0, "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "", - "fieldname": "difference_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Difference Amount (Company Currency)", - "length": 0, - "no_copy": 0, + "options": "Company:company:default_currency", "permlevel": 0, "precision": "", "print_hide": 0, @@ -996,6 +1001,31 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "set_exchange_gain_loss", + "fieldtype": "Button", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Set Exchange Gain / Loss", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -1024,7 +1054,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "depends_on": "", + "depends_on": "eval:(doc.paid_amount && doc.received_amount && doc.references)", "fieldname": "unallocated_amount", "fieldtype": "Currency", "hidden": 0, @@ -1046,6 +1076,33 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 1, + "collapsible": 0, + "depends_on": "eval:(doc.paid_amount && doc.received_amount)", + "fieldname": "difference_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Difference Amount (Company Currency)", + "length": 0, + "no_copy": 0, + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -1099,134 +1156,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "write_off_amount", - "depends_on": "difference_amount", - "fieldname": "write_off_section", - "fieldtype": "Section Break", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Write Off", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "write_off_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Write Off Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "write_off_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Write Off Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "column_break_39", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "write_off_cost_center", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Write Off Cost Center", - "length": 0, - "no_copy": 0, - "options": "Cost Center", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -1406,6 +1335,31 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Title", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "hide_heading": 0, @@ -1418,7 +1372,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-06-26 01:00:56.344341", + "modified": "2016-06-26 17:36:58.416685", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", @@ -1471,5 +1425,6 @@ "read_only_onload": 0, "sort_field": "modified", "sort_order": "DESC", + "title_field": "title", "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 30848d87aa..c421d3298c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, json from frappe import _, scrub -from frappe.utils import flt +from frappe.utils import flt, comma_or 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.doctype.journal_entry.journal_entry import get_average_exchange_rate @@ -17,13 +17,18 @@ from erpnext.controllers.accounts_controller import AccountsController class PaymentEntry(AccountsController): def validate(self): + self.define_party_account_field() self.set_missing_values() self.validate_party_details() - self.validate_allocated_amounts() + self.validate_bank_accounts() self.set_exchange_rate() - self.set_amounts() - self.validate_mandatory() - self.set_write_off_amount() + self.set_amounts_in_company_currency() + self.validate_reference_documents() + self.set_total_allocated_amount() + self.set_unallocated_amount() + self.set_difference_amount() + self.clear_unallocated_reference_document_rows() + self.set_title() def on_submit(self): self.make_gl_entries() @@ -32,32 +37,45 @@ class PaymentEntry(AccountsController): def on_cancel(self): self.make_gl_entries() self.update_advance_paid() - - def set_missing_values(self): + + def define_party_account_field(self): + self.party_account_field = None + if self.payment_type == "Receive": - self.paid_from = self.paid_from_account_currency = self.paid_from_account_balance = None + self.party_account_field = "paid_from" elif self.payment_type == "Pay": - self.paid_to = self.paid_to_account_currency = self.paid_to_account_balance = None - elif self.payment_type == "Internal Transfer": - self.party = self.party_account = self.party_account_currency = self.party_balance = None + self.party_account_field = "paid_to" + + def set_missing_values(self): + if self.payment_type == "Internal Transfer": + for field in ("party", "party_balance", "total_allocated_amount", + "base_total_allocated_amount", "unallocated_amount"): + self.set(field, None) + self.references = [] + else: + if not self.party_type: + frappe.throw(_("Party Type is mandatory")) + + if not self.party: + frappe.throw(_("Party is mandatory")) if self.party: - if self.party_account: - if not self.party_account_currency: - self.party_account_currency = get_account_currency(self.party_account) + if not self.party_balance: + self.party_balance = get_balance_on(party_type=self.party_type, + party=self.party, date=self.posting_date) + + if not self.get(self.party_account_field): + party_account = get_party_account(self.party_type, self.party, self.company) + self.set(self.party_account_field, party_account) - if not self.party_balance: - self.party_balance = get_balance_on(party_type=self.party_type, - party=self.party, date=self.posting_date) - else: - self.party_account = get_party_account(self.party_type, self.party, self.company) + self.party_account = self.get(self.party_account_field) - if self.paid_from: + if self.paid_from and not (self.paid_from_account_currency or self.paid_from_account_balance): acc = get_account_currency_and_balance(self.paid_from, self.posting_date) self.paid_from_account_currency = acc.account_currency self.paid_from_account_balance = acc.account_balance - if self.paid_to: + if self.paid_to and not (self.paid_to_account_currency or self.paid_to_account_balance): acc = get_account_currency_and_balance(self.paid_to, self.posting_date) self.paid_to_account_currency = acc.account_currency self.paid_to_account_balance = acc.account_balance @@ -66,178 +84,233 @@ class PaymentEntry(AccountsController): if self.party: if not frappe.db.exists(self.party_type, self.party): frappe.throw(_("Invalid {0}: {1}").format(self.party_type, self.party)) - + if self.party_account: - account_type = frappe.db.get_value("Account", self.party_account, "account_type") - - if self.party_type == "Customer" and account_type != "Receivable": - frappe.throw(_("Account Type must be Receivable for {0}").format(self.party_account)) + party_account_type = "Receivable" if self.party_type=="Customer" else "Payable" + self.validate_account_type(self.party_account, [party_account_type]) - if self.party_type == "Supplier" and account_type != "Payable": - frappe.throw(_("Account Type must be Payable for {0}").format(self.party_account)) + def validate_bank_accounts(self): + if self.payment_type in ("Pay", "Internal Transfer"): + self.validate_account_type(self.paid_from, ["Bank", "Cash"]) + + if self.payment_type in ("Receive", "Internal Transfer"): + self.validate_account_type(self.paid_to, ["Bank", "Cash"]) + + def validate_account_type(self, account, account_types): + account_type = frappe.db.get_value("Account", account, "account_type") + if account_type not in account_types: + frappe.throw(_("Account Type for {0} must be {1}").format(comma_or(account_types))) - def validate_allocated_amounts(self): + def set_exchange_rate(self): + if self.paid_from: + if self.paid_from_account_currency == self.company_currency: + self.source_exchange_rate = 1 + elif self.payment_type in ("Pay", "Internal Transfer"): + self.source_exchange_rate = get_average_exchange_rate(self.paid_from) + else: + self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency, + self.company_currency) + + if self.paid_to: + self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency, + self.company_currency) + + def set_amounts_in_company_currency(self): + self.base_paid_amount, self.base_received_amount, self.difference_amount = 0, 0, 0 + if self.paid_amount: + self.base_paid_amount = flt(flt(self.paid_amount) * flt(self.source_exchange_rate), + self.precision("base_paid_amount")) + + if self.received_amount: + self.base_received_amount = flt(flt(self.received_amount) * flt(self.target_exchange_rate), + self.precision("base_received_amount")) + + def validate_reference_documents(self): + if self.party_type == "Customer": + valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry") + else: + valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry") + + for d in self.get("references"): + if d.reference_doctype not in valid_reference_doctypes: + frappe.throw(_("Reference Doctype must be one of {0}") + .format(comma_or(valid_reference_doctypes))) + + elif d.reference_name: + if not frappe.db.exists(d.reference_doctype, d.reference_name): + frappe.throw(_("{0} {1} does not exist").format(d.reference_doctype, d.reference_name)) + else: + ref_doc = frappe.get_doc(d.reference_doctype, d.reference_name) + + if d.reference_doctype != "Journal Entry" \ + and self.party != ref_doc.get(scrub(self.party_type)): + frappe.throw(_("{0} {1} does not associated with {2} {3}") + .format(d.reference_doctype, d.reference_name, self.party_type, self.party)) + + if ref_doc.docstatus != 1: + frappe.throw(_("{0} {1} must be submitted") + .format(d.reference_doctype, d.reference_name)) + + def set_total_allocated_amount(self): if self.payment_type == "Internal Transfer": - self.references = [] - self.total_allocated_amount = 0 return self.total_allocated_amount, self.base_total_allocated_amount = 0, 0 for d in self.get("references"): if d.allocated_amount: + if d.allocated_amount > d.outstanding_amount: + frappe.throw(_("Row #{0}: Allocated amount cannot be greater than outstanding amount") + .format(d.idx)) + self.total_allocated_amount += flt(d.allocated_amount) self.base_total_allocated_amount += flt(flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount")) - - party_amount_field = "received_amount" if self.payment_type == "Pay" else "paid_amount" + + def set_unallocated_amount(self): + self.unallocated_amount = 0; + if self.party: + party_amount = self.paid_amount if self.payment_type=="Receive" else self.received_amount - if self.total_allocated_amount != self.get(party_amount_field): - frappe.throw(_("Total Allocated Amount must be equal to {0} ({1})") - .format(self.get(party_amount_field), self.meta.get_label(party_amount_field))) + if self.total_allocated_amount < party_amount: + self.unallocated_amount = party_amount - self.total_allocated_amount - def set_exchange_rate(self): - if self.paid_from: - if self.paid_from_account_currency != self.company_currency: - self.source_exchange_rate = get_average_exchange_rate(self.paid_from) - else: - self.source_exchange_rate = 1 - - if self.paid_to: - self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency, - self.company_currency) + def set_difference_amount(self): + base_unallocated_amount = self.unallocated_amount * \ + (self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate) + + base_party_amount = self.base_total_allocated_amount + base_unallocated_amount - def set_amounts(self): - self.base_paid_amount, self.base_received_amount, self.difference_amount = 0, 0, 0 - if self.paid_amount: - if self.paid_from: - self.base_paid_amount = flt(flt(self.paid_amount) * flt(self.source_exchange_rate), - self.precision("base_paid_amount")) - else: - self.base_paid_amount = self.base_total_allocated_amount - - if self.received_amount: - if self.paid_to: - self.base_received_amount = flt(flt(self.received_amount) * flt(self.target_exchange_rate), - self.precision("base_received_amount")) - else: - self.base_received_amount = self.base_total_allocated_amount - - self.difference_amount = self.base_paid_amount - self.base_received_amount - - def validate_mandatory(self): - mandatory_fields = ["paid_amount", "received_amount", "base_paid_amount", "base_received_amount", - "reference_no", "reference_date"] if self.payment_type == "Receive": - mandatory_fields += ["party_type", "party", "party_account", "party_account_currency", - "paid_to", "paid_to_account_currency", "references", "total_allocated_amount"] + self.difference_amount = base_party_amount - self.base_received_amount elif self.payment_type == "Pay": - mandatory_fields += ["party_type", "party", "party_account", "party_account_currency", - "paid_from", "paid_from_account_currency", "references", "total_allocated_amount"] + self.difference_amount = self.base_paid_amount - base_party_amount else: - mandatory_fields += ["paid_from", "paid_from_account_currency", - "paid_to", "paid_to_account_currency"] + self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) + + for d in self.get("deductions"): + if d.amount: + self.difference_amount -= flt(d.amount) - if self.paid_from: - mandatory_fields.append("source_exchange_rate") - if self.paid_to: - mandatory_fields.append("target_exchange_rate") + def clear_unallocated_reference_document_rows(self): + self.set("references", self.get("references", {"allocated_amount": ["not in", [0, None, ""]]})) - for field in mandatory_fields: - if not self.get(field): - frappe.throw(_("{0} is mandatory").format(self.meta.get_label(field))) - - def set_write_off_amount(self): + frappe.db.sql("""delete from `tabPayment Entry Reference` + where parent = %s and allocated_amount = 0""", self.name) + + def set_title(self): if self.payment_type in ("Receive", "Pay"): - bank_account_currency = self.paid_from_account_currency \ - if self.paid_from else self.paid_to_account_currency - - if self.party_account_currency == bank_account_currency and self.difference_amount: - self.write_off_amount = self.difference_amount + self.title = self.party + else: + self.title = self.paid_from + " - " + self.paid_to def make_gl_entries(self): gl_entries = [] self.add_party_gl_entries(gl_entries) self.add_bank_gl_entries(gl_entries) - self.add_write_off_gl_entries(gl_entries) self.add_deductions_gl_entries(gl_entries) make_gl_entries(gl_entries, cancel = (self.docstatus==2)) - def add_party_gl_entries(self, gl_entries): if self.party_account: + if self.payment_type=="Receive": + party_account_currency = self.paid_from_account_currency + against_account = self.paid_to + else: + party_account_currency = self.paid_to_account_currency + against_account = self.paid_from + + party_gl_dict = self.get_gl_dict({ "account": self.party_account, "party_type": self.party_type, "party": self.party, - "against": self.paid_from or self.paid_to, - "account_currency": self.party_account_currency + "against": against_account, + "account_currency": party_account_currency }) for d in self.get("references"): - party_gl_dict.update({ + gle = party_gl_dict.copy() + gle.update({ "against_voucher_type": d.reference_doctype, "against_voucher": d.reference_name }) allocated_amount_in_company_currency = flt(flt(d.allocated_amount) * flt(d.exchange_rate), - self.precision("paid_amount")) - + self.precision("paid_amount")) + if self.payment_type == "Receive": - party_gl_dict.update({ + gle.update({ "credit_in_account_currency": d.allocated_amount, "credit": allocated_amount_in_company_currency }) elif self.payment_type == "Pay": - party_gl_dict.update({ + gle.update({ "debit_in_account_currency": d.allocated_amount, "debit": allocated_amount_in_company_currency }) - gl_entries.append(party_gl_dict) + gl_entries.append(gle) + + if self.unallocated_amount: + base_unallocated_amount = base_unallocated_amount = self.unallocated_amount * \ + (self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate) + + gle = party_gl_dict.copy() + if self.payment_type == "Receive": + gle.update({ + "credit_in_account_currency": self.unallocated_amount, + "credit": base_unallocated_amount + }) + elif self.payment_type == "Pay": + gle.update({ + "debit_in_account_currency": self.unallocated_amount, + "debit": base_unallocated_amount + }) + + gl_entries.append(gle) + def add_bank_gl_entries(self, gl_entries): - if self.paid_from and self.paid_amount: + if self.payment_type in ("Pay", "Internal Transfer"): gl_entries.append( self.get_gl_dict({ "account": self.paid_from, "account_currency": self.paid_from_account_currency, - "against": self.party_account, + "against": self.party if self.payment_type=="Pay" else self.paid_to, "credit_in_account_currency": self.paid_amount, "credit": self.base_paid_amount }) ) - if self.paid_to and self.received_amount: + if self.payment_type in ("Receive", "Internal Transfer"): gl_entries.append( self.get_gl_dict({ "account": self.paid_to, "account_currency": self.paid_to_account_currency, - "against": self.party, + "against": self.party if self.payment_type=="Receive" else self.paid_from, "debit_in_account_currency": self.received_amount, "debit": self.base_received_amount }) ) - def add_write_off_gl_entries(self, gl_entries): - if self.write_off_account and self.write_off_amount: - write_off_account_currency = get_account_currency(self.write_off_account) - if self.write_off_account_currency != self.company_currency: - frappe.throw(_("Write Off Account currency must be same as {0}") - .format(self.company_currency)) - - gl_entries.append( - self.get_gl_dict({ - "account": self.write_off_account, - "against": self.party, - "debit_in_account_currency": self.write_off_amount, - "debit": self.write_off_amount, - "cost_center": self.write_off_cost_center - }, write_off_account_currency) - ) - def add_deductions_gl_entries(self, gl_entries): - pass - + for d in self.get("deductions"): + if d.amount: + account_currency = get_account_currency(d.account) + if account_currency != self.company_currency: + frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency)) + + gl_entries.append( + self.get_gl_dict({ + "account": d.account, + "account_currency": account_currency, + "against": self.party or self.paid_from, + "debit_in_account_currency": d.amount, + "debit": d.amount, + "cost_center": d.cost_center + }) + ) + def update_advance_paid(self): if self.payment_type in ("Receive", "Pay") and self.party: for d in self.get("references"): @@ -324,6 +397,13 @@ def get_account_currency_and_balance(account, date): }) @frappe.whitelist() -def get_write_off_account_and_cost_center(company): - return frappe.db.get_value("Company", company, ["write_off_account", "cost_center"], as_dict=1) - \ No newline at end of file +def get_company_defaults(company): + fields = ["write_off_account", "exchange_gain_loss_account", "cost_center"] + ret = frappe.db.get_value("Company", company, fields, as_dict=1) + + for fieldname in fields: + if not ret[fieldname]: + frappe.throw(_("Please set default {0} in Company {1}") + .format(frappe.get_meta("Company").get_label(fieldname), company)) + + return ret \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json index a82270afae..62a0d17f28 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json @@ -30,7 +30,7 @@ "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -221,7 +221,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2016-06-22 16:15:10.404692", + "modified": "2016-06-26 15:01:17.161402", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Reference", diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 2ae8bbad8a..20416e5381 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -290,7 +290,7 @@ def get_company_default(company, fieldname): value = frappe.db.get_value("Company", company, fieldname) if not value: - throw(_("Please set default value {0} in Company {1}").format(frappe.get_meta("Company").get_label(fieldname), company)) + throw(_("Please set default {0} in Company {1}").format(frappe.get_meta("Company").get_label(fieldname), company)) return value @@ -427,7 +427,9 @@ def get_outstanding_invoices(party_type, party, account, condition=None): 'due_date': frappe.db.get_value(d.voucher_type, d.voucher_no, "due_date"), 'exchange_rate': frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate") }) - + + outstanding_invoices = sorted(outstanding_invoices, key=lambda k: k['due_date']) + return outstanding_invoices diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index fb03721754..36abae87d0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -255,7 +255,7 @@ class AccountsController(TransactionBase): if not account_currency: account_currency = get_account_currency(gl_dict.account) - if self.doctype not in ["Journal Entry", "Period Closing Voucher"]: + if self.doctype not in ["Journal Entry", "Period Closing Voucher", "Payment Entry"]: self.validate_account_currency(gl_dict.account, account_currency) set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"), self.company_currency) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 9f9b1cc344..ff12f68eb0 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -137,6 +137,7 @@ erpnext.company.setup_queries = function(frm) { ["default_income_account", {"root_type": "Income"}], ["round_off_account", {"root_type": "Expense"}], ["write_off_account", {"root_type": "Expense"}], + ["exchange_gain_loss_account", {"root_type": "Expense"}], ["accumulated_depreciation_account", {"root_type": "Asset"}], ["depreciation_expense_account", {"root_type": "Expense"}], ["disposal_account", {"report_type": "Profit and Loss"}], diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 754504edaf..25e0cfb2ab 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -521,6 +521,32 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "exchange_gain_loss_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Exchange Gain / Loss Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -1359,7 +1385,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2016-06-26 00:44:30.299891", + "modified": "2016-06-26 09:08:50.476200", "modified_by": "Administrator", "module": "Setup", "name": "Company",