From 97ab9028957026477f856a27aeb20c7de9d09510 Mon Sep 17 00:00:00 2001 From: nabinhait Date: Mon, 21 Jul 2014 19:22:02 +0530 Subject: [PATCH] payment reconciliation fixes --- .../payment_reconciliation.js | 42 +++++-- .../payment_reconciliation.json | 4 +- .../payment_reconciliation.py | 117 +++++++++++------- .../payment_reconciliation_payment.json | 4 +- erpnext/accounts/utils.py | 8 +- 5 files changed, 108 insertions(+), 67 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 14520c2a05..c495a35825 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -7,22 +7,40 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext onload: function() { var me = this - this.frm.set_query ('party_account', function() { - return{ - filters:[ - ['Account', 'company', '=', me.frm.doc.company], - ['Account', 'group_or_ledger', '=', 'Ledger'], - ['Account', 'master_type', 'in', ['Customer', 'Supplier']] - ] - }; + this.frm.set_query('party_account', function() { + if(!me.frm.doc.company) { + msgprint(__("Please select company first")); + } else { + return{ + filters:[ + ['Account', 'company', '=', me.frm.doc.company], + ['Account', 'group_or_ledger', '=', 'Ledger'], + ['Account', 'master_type', 'in', ['Customer', 'Supplier']] + ] + }; + } + + }); + + this.frm.set_query('bank_cash_account', function() { + if(!me.frm.doc.company) { + msgprint(__("Please select company first")); + } else { + return{ + filters:[ + ['Account', 'company', '=', me.frm.doc.company], + ['Account', 'group_or_ledger', '=', 'Ledger'], + ['Account', 'account_type', 'in', ['Bank', 'Cash']] + ] + }; + } }); - var help_content = [' Note:', - ''].join("\n"); + var help_content = ' Note:
'+ + ''; this.frm.set_value("reconcile_help", help_content); }, - - + get_unreconciled_entries: function() { var me = this; return this.frm.call({ diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index 40b5706260..a83d45363e 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -31,7 +31,7 @@ "hidden": 1, "in_list_view": 1, "label": "Party Type", - "options": "Customer\nSupplier", + "options": "\nCustomer\nSupplier", "permlevel": 0, "read_only": 1, "reqd": 0 @@ -129,7 +129,7 @@ ], "hide_toolbar": 1, "issingle": 1, - "modified": "2014-07-18 15:53:20.638456", + "modified": "2014-07-21 16:54:31.454679", "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 be53aca928..2d844bb189 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -18,7 +18,12 @@ class PaymentReconciliation(Document): def get_jv_entries(self): self.check_mandatory_to_fetch() dr_or_cr = "credit" if self.party_type == "Customer" else "debit" - cond = self.check_condition(dr_or_cr) + if self.party_type=="Customer": + amount_query = "ifnull(t2.credit, 0) - ifnull(t2.debit, 0)" + else: + amount_query = "ifnull(t2.debit, 0) - ifnull(t2.credit, 0)" + + cond = self.check_condition(amount_query) bank_account_condition = "t2.against_account like %(bank_cash_account)s" \ if self.bank_cash_account else "1=1" @@ -26,22 +31,26 @@ class PaymentReconciliation(Document): jv_entries = frappe.db.sql(""" select t1.name as voucher_no, t1.posting_date, t1.remark, t2.account, - t2.name as voucher_detail_no, t2.{dr_or_cr}, t2.is_advance + t2.name as voucher_detail_no, {amount_query} as payment_amount, t2.is_advance from `tabJournal Voucher` t1, `tabJournal Voucher Detail` t2 where - t1.name = t2.parent and t1.docstatus = 1 and t2.account = %(party_account)s - and t2.{dr_or_cr} > 0 and ifnull(t2.against_voucher, '')='' and ifnull(t2.against_invoice, '')='' + t1.name = t2.parent and t1.docstatus = 1 and t2.docstatus = 1 + and t2.account = %(party_account)s and {amount_query} > 0 + and ifnull((select ifnull(sum(ifnull(credit, 0) - ifnull(debit, 0)), 0) from `tabJournal Voucher Detail` + where parent=t1.name and account=t2.account and docstatus=1 group by account), 0) > 0 + and ifnull(t2.against_voucher, '')='' and ifnull(t2.against_invoice, '')='' and ifnull(t2.against_jv, '')='' {cond} and (CASE WHEN t1.voucher_type in ('Debit Note', 'Credit Note') THEN 1=1 ELSE {bank_account_condition} END) - group by t1.name, t2.name """.format(**{ + """.format(**{ "dr_or_cr": dr_or_cr, "cond": cond, - "bank_account_condition": bank_account_condition + "bank_account_condition": bank_account_condition, + "amount_query": amount_query }), { "party_account": self.party_account, "bank_cash_account": "%%%s%%" % self.bank_cash_account @@ -55,7 +64,7 @@ class PaymentReconciliation(Document): ent = self.append('payment_reconciliation_payments', {}) ent.journal_voucher = e.get('voucher_no') ent.posting_date = e.get('posting_date') - ent.amount = flt(e.get('credit')) or flt(e.get('debit')) + ent.amount = flt(e.get('payment_amount')) ent.remark = e.get('remark') ent.voucher_detail_number = e.get('voucher_detail_no') ent.is_advance = e.get('is_advance') @@ -64,55 +73,63 @@ class PaymentReconciliation(Document): #Fetch JVs, Sales and Purchase Invoices for 'payment_reconciliation_invoices' to reconcile against non_reconciled_invoices = [] dr_or_cr = "debit" if self.party_type == "Customer" else "credit" - cond = self.check_condition(dr_or_cr) + if self.party_type=="Customer": + amount_query = "ifnull(debit, 0) - ifnull(credit, 0)" + else: + amount_query = "ifnull(credit, 0) - ifnull(debit, 0)" + + cond = self.check_condition(amount_query) invoice_list = frappe.db.sql(""" select - voucher_no, voucher_type, posting_date, ifnull(sum(ifnull(%s, 0)), 0) as amount + voucher_no, voucher_type, posting_date, + ifnull(sum({amount_query}), 0) as invoice_amount from `tabGL Entry` where - account = %s and ifnull(%s, 0) > 0 %s - group by voucher_no, voucher_type""" % (dr_or_cr, '%s', - dr_or_cr, cond), (self.party_account), as_dict=True) + account = %s and {amount_query} > 0 {cond} + group by voucher_type, voucher_no + """.format(**{ + "cond": cond, + "amount_query": amount_query + }), (self.party_account), as_dict=True) for d in invoice_list: payment_amount = frappe.db.sql(""" select - ifnull(sum(ifnull(%s, 0)), 0) + ifnull(sum(ifnull({amount_query}, 0)), 0) from `tabGL Entry` where - account = %s and against_voucher_type = %s and ifnull(against_voucher, '') = %s""" % - (("credit" if self.party_type == "Customer" else "debit"), '%s', '%s', '%s'), - (self.party_account, d.voucher_type, d.voucher_no)) + account = %s and {amount_query} < 0 + and against_voucher_type = %s and ifnull(against_voucher, '') = %s + """.format(**{ + "cond": cond, + "amount_query": amount_query + }), (self.party_account, d.voucher_type, d.voucher_no)) + + payment_amount = -1*payment_amount[0][0] if payment_amount else 0 - payment_amount = payment_amount[0][0] if payment_amount else 0 - - if d.amount > payment_amount: + if d.invoice_amount > payment_amount: non_reconciled_invoices.append({ 'voucher_no': d.voucher_no, 'voucher_type': d.voucher_type, 'posting_date': d.posting_date, - 'amount': flt(d.amount), - 'outstanding_amount': d.amount - payment_amount}) + 'invoice_amount': flt(d.invoice_amount), + 'outstanding_amount': d.invoice_amount - payment_amount}) self.add_invoice_entries(non_reconciled_invoices) - def add_invoice_entries(self, non_reconciled_invoices): #Populate 'payment_reconciliation_invoices' with JVs and Invoices to reconcile against self.set('payment_reconciliation_invoices', []) - if not non_reconciled_invoices: - frappe.throw(_("No invoices found to be reconciled")) - - + for e in non_reconciled_invoices: ent = self.append('payment_reconciliation_invoices', {}) ent.invoice_type = e.get('voucher_type') ent.invoice_number = e.get('voucher_no') ent.invoice_date = e.get('posting_date') - ent.amount = flt(e.get('amount')) + ent.amount = flt(e.get('invoice_amount')) ent.outstanding_amount = e.get('outstanding_amount') def reconcile(self, args): @@ -121,24 +138,24 @@ class PaymentReconciliation(Document): dr_or_cr = "credit" if self.party_type == "Customer" else "debit" lst = [] for e in self.get('payment_reconciliation_payments'): - lst.append({ - 'voucher_no' : e.journal_voucher, - 'voucher_detail_no' : e.voucher_detail_number, - 'against_voucher_type' : e.invoice_type, - 'against_voucher' : e.invoice_number, - 'account' : self.party_account, - 'is_advance' : e.is_advance, - 'dr_or_cr' : dr_or_cr, - 'unadjusted_amt' : flt(e.amount), - 'allocated_amt' : flt(e.amount) - }) + if e.invoice_type and e.invoice_number: + lst.append({ + 'voucher_no' : e.journal_voucher, + 'voucher_detail_no' : e.voucher_detail_number, + 'against_voucher_type' : e.invoice_type, + 'against_voucher' : e.invoice_number, + 'account' : self.party_account, + 'is_advance' : e.is_advance, + 'dr_or_cr' : dr_or_cr, + 'unadjusted_amt' : flt(e.amount), + 'allocated_amt' : flt(e.amount) + }) if lst: from erpnext.accounts.utils import reconcile_against_document reconcile_against_document(lst) - self.get_unreconciled_entries() msgprint(_("Successfully Reconciled")) - + self.get_unreconciled_entries() def check_mandatory_to_fetch(self): for fieldname in ["company", "party_account"]: @@ -147,32 +164,38 @@ class PaymentReconciliation(Document): def validate_invoice(self): + if not self.get("payment_reconciliation_invoices"): + frappe.throw(_("No records found in the Invoice table")) + + if not self.get("payment_reconciliation_payments"): + frappe.throw(_("No records found in the Payment table")) + unreconciled_invoices = frappe._dict() for d in self.get("payment_reconciliation_invoices"): unreconciled_invoices.setdefault(d.invoice_type, {}).setdefault(d.invoice_number, d.outstanding_amount) - + invoices_to_reconcile = [] for p in self.get("payment_reconciliation_payments"): if p.invoice_type and p.invoice_number: invoices_to_reconcile.append(p.invoice_number) - if p.invoice_number not in unreconciled_invoices.get(p.invoice_type): + 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").format(p.idx)) + 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 not invoices_to_reconcile: frappe.throw(_("Please select Invoice Type and Invoice Number in atleast one row")) - def check_condition(self, dr_or_cr): + def check_condition(self, amount_query): cond = self.from_date and " and posting_date >= '" + self.from_date + "'" or "" cond += self.to_date and " and posting_date <= '" + self.to_date + "'" or "" if self.minimum_amount: - cond += " and ifnull(%s, 0) >= %s" % (dr_or_cr, self.minimum_amount) + cond += " and {0} >= %s".format(amount_query) % self.minimum_amount if self.maximum_amount: - cond += " and ifnull(%s, 0) <= %s" % (dr_or_cr, self.maximum_amount) + cond += " and {0} <= %s".format(amount_query) % self.maximum_amount return cond \ No newline at end of file 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 3dd36fc465..73fd0f50f3 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json +++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json @@ -59,7 +59,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Invoice Type", - "options": "Sales Invoice\nPurchase Invoice\nJournal Voucher", + "options": "\nSales Invoice\nPurchase Invoice\nJournal Voucher", "permlevel": 0, "read_only": 0, "reqd": 1 @@ -95,7 +95,7 @@ } ], "istable": 1, - "modified": "2014-07-18 15:53:15.589501", + "modified": "2014-07-21 16:53:56.206169", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Payment", diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index bc005d7c94..25a8adfca1 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -153,12 +153,12 @@ def check_if_jv_modified(args): check if jv is submitted """ ret = frappe.db.sql(""" - select t2.%(dr_or_cr)s from `tabJournal Voucher` t1, `tabJournal Voucher Detail` t2 - where t1.name = t2.parent and t2.account = '%(account)s' + select t2.{dr_or_cr} from `tabJournal Voucher` t1, `tabJournal Voucher Detail` t2 + where t1.name = t2.parent and t2.account = %(account)s and ifnull(t2.against_voucher, '')='' and ifnull(t2.against_invoice, '')='' and ifnull(t2.against_jv, '')='' - and t1.name = '%(voucher_no)s' and t2.name = '%(voucher_detail_no)s' - and t1.docstatus=1 """ % args) + and t1.name = %(voucher_no)s and t2.name = %(voucher_detail_no)s + and t1.docstatus=1 """.format(dr_or_cr = args.get("dr_or_cr")), args) if not ret: throw(_("""Payment Entry has been modified after you pulled it. Please pull it again."""))