From fc2dd44694db94b11b17421e50267371481e64b4 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 17 Oct 2014 13:05:24 +0530 Subject: [PATCH] Partial payment reconciliation. Fixes #1982 --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 4 +++ .../journal_voucher/journal_voucher.py | 26 +++++++++++++------ .../payment_reconciliation.js | 18 +++++-------- .../payment_reconciliation.json | 9 +------ .../payment_reconciliation.py | 17 +++++++----- .../payment_reconciliation_payment.json | 13 ++++++++-- .../doctype/delivery_note/delivery_note.py | 2 +- erpnext/utilities/repost_stock.py | 2 +- 8 files changed, 54 insertions(+), 37 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index dab2d82092..4cc3d572ae 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -124,6 +124,10 @@ def update_outstanding_amt(account, against_voucher_type, against_voucher, on_ca from `tabGL Entry` where voucher_type = 'Journal Voucher' and voucher_no = %s and account = %s and ifnull(against_voucher, '') = ''""", (against_voucher, account))[0][0]) + if not against_voucher_amount: + frappe.throw(_("Against Journal Voucher {0} is already adjusted against some other voucher") + .format(against_voucher)) + bal = against_voucher_amount + bal if against_voucher_amount < 0: bal = -bal diff --git a/erpnext/accounts/doctype/journal_voucher/journal_voucher.py b/erpnext/accounts/doctype/journal_voucher/journal_voucher.py index 3c67508306..f9f6df1557 100644 --- a/erpnext/accounts/doctype/journal_voucher/journal_voucher.py +++ b/erpnext/accounts/doctype/journal_voucher/journal_voucher.py @@ -88,15 +88,24 @@ class JournalVoucher(AccountsController): def validate_against_jv(self): for d in self.get('entries'): if d.against_jv: + account_root_type = frappe.db.get_value("Account", d.account, "root_type") + if account_root_type == "Asset" and flt(d.debit) > 0: + frappe.throw(_("For {0}, only credit entries can be linked against another debit entry") + .format(d.account)) + elif account_root_type == "Liability" and flt(d.credit) > 0: + frappe.throw(_("For {0}, only debit entries can be linked against another credit entry") + .format(d.account)) + if d.against_jv == self.name: frappe.throw(_("You can not enter current voucher in 'Against Journal Voucher' column")) against_entries = frappe.db.sql("""select * from `tabJournal Voucher Detail` where account = %s and docstatus = 1 and parent = %s - and ifnull(against_jv, '') = ''""", (d.account, d.against_jv), as_dict=True) + and ifnull(against_jv, '') = '' and ifnull(against_invoice, '') = '' + and ifnull(against_voucher, '') = ''""", (d.account, d.against_jv), as_dict=True) if not against_entries: - frappe.throw(_("Journal Voucher {0} does not have account {1} or already matched") + frappe.throw(_("Journal Voucher {0} does not have account {1} or already matched against other voucher") .format(d.against_jv, d.account)) else: dr_or_cr = "debit" if d.credit > 0 else "credit" @@ -153,7 +162,7 @@ class JournalVoucher(AccountsController): and voucher_account != d.account: frappe.throw(_("Row {0}: Account {1} does not match with {2} {3} account") \ .format(d.idx, d.account, doctype, field_dict.get(doctype))) - + if against_field in ["against_sales_order", "against_purchase_order"]: if voucher_account != account_master_name: frappe.throw(_("Row {0}: Account {1} does not match with {2} {3} Name") \ @@ -165,7 +174,7 @@ class JournalVoucher(AccountsController): def validate_against_invoice_fields(self, doctype, payment_against_voucher): for voucher_no, payment_list in payment_against_voucher.items(): - voucher_properties = frappe.db.get_value(doctype, voucher_no, + voucher_properties = frappe.db.get_value(doctype, voucher_no, ["docstatus", "outstanding_amount"]) if voucher_properties[0] != 1: @@ -177,7 +186,7 @@ class JournalVoucher(AccountsController): def validate_against_order_fields(self, doctype, payment_against_voucher): for voucher_no, payment_list in payment_against_voucher.items(): - voucher_properties = frappe.db.get_value(doctype, voucher_no, + voucher_properties = frappe.db.get_value(doctype, voucher_no, ["docstatus", "per_billed", "status", "advance_paid", "grand_total"]) if voucher_properties[0] != 1: @@ -532,9 +541,10 @@ def get_against_sales_invoice(doctype, txt, searchfield, start, page_len, filter (filters["account"], "%%%s%%" % txt, start, page_len)) def get_against_jv(doctype, txt, searchfield, start, page_len, filters): - return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark - from `tabJournal Voucher` jv, `tabJournal Voucher Detail` jv_detail - where jv_detail.parent = jv.name and jv_detail.account = %s and jv.docstatus = 1 + return frappe.db.sql("""select distinct jv.name, jv.posting_date, jv.user_remark + from `tabJournal Voucher` jv, `tabJournal Voucher Detail` jvd + where jvd.parent = jv.name and jvd.account = %s and jv.docstatus = 1 + and (ifnull(jvd.against_invoice, '') = '' and ifnull(jvd.against_voucher, '') = '' and ifnull(jvd.against_jv, '') = '' ) and jv.%s like %s order by jv.name desc limit %s, %s""" % ("%s", searchfield, "%s", "%s", "%s"), (filters["account"], "%%%s%%" % txt, start, page_len)) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 97484da468..bfcd63ea01 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -19,9 +19,9 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext ] }; } - + }); - + this.frm.set_query('bank_cash_account', function() { if(!me.frm.doc.company) { msgprint(__("Please select company first")); @@ -35,12 +35,8 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext }; } }); - - var help_content = ' ' + __("Note") + ':
'+ - ''; - this.frm.set_value("reconcile_help", help_content); }, - + get_unreconciled_entries: function() { var me = this; return this.frm.call({ @@ -48,12 +44,12 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext method: 'get_unreconciled_entries', callback: function(r, rt) { var invoices = []; - + $.each(me.frm.doc.payment_reconciliation_invoices || [], function(i, row) { - if (row.invoice_number && !inList(invoices, row.invoice_number)) + if (row.invoice_number && !inList(invoices, row.invoice_number)) invoices.push(row.invoice_number); }); - + frappe.meta.get_docfield("Payment Reconciliation Payment", "invoice_number", me.frm.doc.name).options = invoices.join("\n"); @@ -79,4 +75,4 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext $.extend(cur_frm.cscript, new erpnext.accounts.PaymentReconciliationController({frm: cur_frm})); -cur_frm.add_fetch('party_account', 'master_type', 'party_type') \ No newline at end of file +cur_frm.add_fetch('party_account', 'master_type', 'party_type') diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index 51cb306157..bb07b4695b 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -118,19 +118,12 @@ "options": "Payment Reconciliation Invoice", "permlevel": 0, "read_only": 1 - }, - { - "fieldname": "reconcile_help", - "fieldtype": "Small Text", - "label": "", - "permlevel": 0, - "read_only": 1 } ], "hide_toolbar": 1, "icon": "icon-resize-horizontal", "issingle": 1, - "modified": "2014-07-31 05:43:03.410832", + "modified": "2014-10-16 17:51:44.367107", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation", diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index a5a56aee0a..c6a2b056da 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -124,7 +124,7 @@ class PaymentReconciliation(Document): dr_or_cr = "credit" if self.party_type == "Customer" else "debit" lst = [] for e in self.get('payment_reconciliation_payments'): - if e.invoice_type and e.invoice_number: + if e.invoice_type and e.invoice_number and e.allocated_amount: lst.append({ 'voucher_no' : e.journal_voucher, 'voucher_detail_no' : e.voucher_detail_number, @@ -134,7 +134,7 @@ class PaymentReconciliation(Document): 'is_advance' : e.is_advance, 'dr_or_cr' : dr_or_cr, 'unadjusted_amt' : flt(e.amount), - 'allocated_amt' : flt(e.amount) + 'allocated_amt' : flt(e.allocated_amount) }) if lst: @@ -162,18 +162,23 @@ class PaymentReconciliation(Document): invoices_to_reconcile = [] for p in self.get("payment_reconciliation_payments"): - if p.invoice_type and p.invoice_number: + if p.invoice_type and p.invoice_number and p.allocated_amount: invoices_to_reconcile.append(p.invoice_number) if p.invoice_number not in unreconciled_invoices.get(p.invoice_type, {}): frappe.throw(_("{0}: {1} not found in Invoice Details table") .format(p.invoice_type, p.invoice_number)) - if p.amount > unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number): - frappe.throw(_("Row {0}: Payment amount must be less than or equals to invoice outstanding amount. Please refer Note below.").format(p.idx)) + if flt(p.allocated_amount) > flt(p.amount): + frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equals to JV amount {2}") + .format(p.idx, p.allocated_amount, p.amount)) + + if flt(p.allocated_amount) > unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number): + frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equals to invoice outstanding amount {2}") + .format(p.idx, p.allocated_amount, unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number))) if not invoices_to_reconcile: - frappe.throw(_("Please select Invoice Type and Invoice Number in atleast one row")) + frappe.throw(_("Please select Allocated Amount, Invoice Type and Invoice Number in atleast one row")) def check_condition(self, dr_or_cr): cond = self.from_date and " and posting_date >= '" + self.from_date + "'" or "" diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json index 73fd0f50f3..6f576ca38b 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json +++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json @@ -53,11 +53,20 @@ "label": "Column Break", "permlevel": 0 }, + { + "fieldname": "allocated_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Allocated amount", + "permlevel": 0, + "precision": "", + "reqd": 1 + }, { "default": "Sales Invoice", "fieldname": "invoice_type", "fieldtype": "Select", - "in_list_view": 1, + "in_list_view": 0, "label": "Invoice Type", "options": "\nSales Invoice\nPurchase Invoice\nJournal Voucher", "permlevel": 0, @@ -95,7 +104,7 @@ } ], "istable": 1, - "modified": "2014-07-21 16:53:56.206169", + "modified": "2014-10-16 17:40:54.040194", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Payment", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 864329d3ea..079b7fc110 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -245,7 +245,7 @@ class DeliveryNote(SellingController): sl_entries = [] for d in self.get_item_list(): if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" \ - and d.warehouse: + and d.warehouse and flt(d['qty']): self.update_reserved_qty(d) sl_entries.append(self.get_sl_entries(d, { diff --git a/erpnext/utilities/repost_stock.py b/erpnext/utilities/repost_stock.py index 643bec986a..f1ba1798ce 100644 --- a/erpnext/utilities/repost_stock.py +++ b/erpnext/utilities/repost_stock.py @@ -73,7 +73,7 @@ def get_reserved_qty(item_code, warehouse): from `tabPacked Item` dnpi_in where item_code = %s and warehouse = %s and parenttype="Sales Order" - and item_code != parent_item + and item_code != parent_item and exists (select * from `tabSales Order` so where name = dnpi_in.parent and docstatus = 1 and status != 'Stopped') ) dnpi)