mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-07 15:12:51 +00:00
payment reconciliation fixes
This commit is contained in:
@@ -7,7 +7,10 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
|||||||
|
|
||||||
onload: function() {
|
onload: function() {
|
||||||
var me = this
|
var me = this
|
||||||
this.frm.set_query ('party_account', function() {
|
this.frm.set_query('party_account', function() {
|
||||||
|
if(!me.frm.doc.company) {
|
||||||
|
msgprint(__("Please select company first"));
|
||||||
|
} else {
|
||||||
return{
|
return{
|
||||||
filters:[
|
filters:[
|
||||||
['Account', 'company', '=', me.frm.doc.company],
|
['Account', 'company', '=', me.frm.doc.company],
|
||||||
@@ -15,14 +18,29 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
|||||||
['Account', 'master_type', 'in', ['Customer', 'Supplier']]
|
['Account', 'master_type', 'in', ['Customer', 'Supplier']]
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var help_content = ['<i class="icon-hand-right"></i> Note:',
|
this.frm.set_query('bank_cash_account', function() {
|
||||||
'<ul>If you are unable to match the exact amount, then amend your Journal Voucher and split rows such that your amounts match the invoice you are trying to reconcile. </ul>'].join("\n");
|
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 = '<i class="icon-hand-right"></i> Note:<br>'+
|
||||||
|
'<ul>If you are unable to match the exact amount, then amend your Journal Voucher and split rows such that payment amount match the invoice amount.</ul>';
|
||||||
this.frm.set_value("reconcile_help", help_content);
|
this.frm.set_value("reconcile_help", help_content);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
get_unreconciled_entries: function() {
|
get_unreconciled_entries: function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
return this.frm.call({
|
return this.frm.call({
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Party Type",
|
"label": "Party Type",
|
||||||
"options": "Customer\nSupplier",
|
"options": "\nCustomer\nSupplier",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 0
|
"reqd": 0
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
],
|
],
|
||||||
"hide_toolbar": 1,
|
"hide_toolbar": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"modified": "2014-07-18 15:53:20.638456",
|
"modified": "2014-07-21 16:54:31.454679",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Reconciliation",
|
"name": "Payment Reconciliation",
|
||||||
|
|||||||
@@ -18,7 +18,12 @@ class PaymentReconciliation(Document):
|
|||||||
def get_jv_entries(self):
|
def get_jv_entries(self):
|
||||||
self.check_mandatory_to_fetch()
|
self.check_mandatory_to_fetch()
|
||||||
dr_or_cr = "credit" if self.party_type == "Customer" else "debit"
|
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" \
|
bank_account_condition = "t2.against_account like %(bank_cash_account)s" \
|
||||||
if self.bank_cash_account else "1=1"
|
if self.bank_cash_account else "1=1"
|
||||||
@@ -26,22 +31,26 @@ class PaymentReconciliation(Document):
|
|||||||
jv_entries = frappe.db.sql("""
|
jv_entries = frappe.db.sql("""
|
||||||
select
|
select
|
||||||
t1.name as voucher_no, t1.posting_date, t1.remark, t2.account,
|
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
|
from
|
||||||
`tabJournal Voucher` t1, `tabJournal Voucher Detail` t2
|
`tabJournal Voucher` t1, `tabJournal Voucher Detail` t2
|
||||||
where
|
where
|
||||||
t1.name = t2.parent and t1.docstatus = 1 and t2.account = %(party_account)s
|
t1.name = t2.parent and t1.docstatus = 1 and t2.docstatus = 1
|
||||||
and t2.{dr_or_cr} > 0 and ifnull(t2.against_voucher, '')='' and ifnull(t2.against_invoice, '')=''
|
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 ifnull(t2.against_jv, '')='' {cond}
|
||||||
and (CASE
|
and (CASE
|
||||||
WHEN t1.voucher_type in ('Debit Note', 'Credit Note')
|
WHEN t1.voucher_type in ('Debit Note', 'Credit Note')
|
||||||
THEN 1=1
|
THEN 1=1
|
||||||
ELSE {bank_account_condition}
|
ELSE {bank_account_condition}
|
||||||
END)
|
END)
|
||||||
group by t1.name, t2.name """.format(**{
|
""".format(**{
|
||||||
"dr_or_cr": dr_or_cr,
|
"dr_or_cr": dr_or_cr,
|
||||||
"cond": cond,
|
"cond": cond,
|
||||||
"bank_account_condition": bank_account_condition
|
"bank_account_condition": bank_account_condition,
|
||||||
|
"amount_query": amount_query
|
||||||
}), {
|
}), {
|
||||||
"party_account": self.party_account,
|
"party_account": self.party_account,
|
||||||
"bank_cash_account": "%%%s%%" % self.bank_cash_account
|
"bank_cash_account": "%%%s%%" % self.bank_cash_account
|
||||||
@@ -55,7 +64,7 @@ class PaymentReconciliation(Document):
|
|||||||
ent = self.append('payment_reconciliation_payments', {})
|
ent = self.append('payment_reconciliation_payments', {})
|
||||||
ent.journal_voucher = e.get('voucher_no')
|
ent.journal_voucher = e.get('voucher_no')
|
||||||
ent.posting_date = e.get('posting_date')
|
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.remark = e.get('remark')
|
||||||
ent.voucher_detail_number = e.get('voucher_detail_no')
|
ent.voucher_detail_number = e.get('voucher_detail_no')
|
||||||
ent.is_advance = e.get('is_advance')
|
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
|
#Fetch JVs, Sales and Purchase Invoices for 'payment_reconciliation_invoices' to reconcile against
|
||||||
non_reconciled_invoices = []
|
non_reconciled_invoices = []
|
||||||
dr_or_cr = "debit" if self.party_type == "Customer" else "credit"
|
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("""
|
invoice_list = frappe.db.sql("""
|
||||||
select
|
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
|
from
|
||||||
`tabGL Entry`
|
`tabGL Entry`
|
||||||
where
|
where
|
||||||
account = %s and ifnull(%s, 0) > 0 %s
|
account = %s and {amount_query} > 0 {cond}
|
||||||
group by voucher_no, voucher_type""" % (dr_or_cr, '%s',
|
group by voucher_type, voucher_no
|
||||||
dr_or_cr, cond), (self.party_account), as_dict=True)
|
""".format(**{
|
||||||
|
"cond": cond,
|
||||||
|
"amount_query": amount_query
|
||||||
|
}), (self.party_account), as_dict=True)
|
||||||
|
|
||||||
for d in invoice_list:
|
for d in invoice_list:
|
||||||
payment_amount = frappe.db.sql("""
|
payment_amount = frappe.db.sql("""
|
||||||
select
|
select
|
||||||
ifnull(sum(ifnull(%s, 0)), 0)
|
ifnull(sum(ifnull({amount_query}, 0)), 0)
|
||||||
from
|
from
|
||||||
`tabGL Entry`
|
`tabGL Entry`
|
||||||
where
|
where
|
||||||
account = %s and against_voucher_type = %s and ifnull(against_voucher, '') = %s""" %
|
account = %s and {amount_query} < 0
|
||||||
(("credit" if self.party_type == "Customer" else "debit"), '%s', '%s', '%s'),
|
and against_voucher_type = %s and ifnull(against_voucher, '') = %s
|
||||||
(self.party_account, d.voucher_type, d.voucher_no))
|
""".format(**{
|
||||||
|
"cond": cond,
|
||||||
|
"amount_query": amount_query
|
||||||
|
}), (self.party_account, d.voucher_type, d.voucher_no))
|
||||||
|
|
||||||
payment_amount = payment_amount[0][0] if payment_amount else 0
|
payment_amount = -1*payment_amount[0][0] if payment_amount else 0
|
||||||
|
|
||||||
if d.amount > payment_amount:
|
if d.invoice_amount > payment_amount:
|
||||||
non_reconciled_invoices.append({
|
non_reconciled_invoices.append({
|
||||||
'voucher_no': d.voucher_no,
|
'voucher_no': d.voucher_no,
|
||||||
'voucher_type': d.voucher_type,
|
'voucher_type': d.voucher_type,
|
||||||
'posting_date': d.posting_date,
|
'posting_date': d.posting_date,
|
||||||
'amount': flt(d.amount),
|
'invoice_amount': flt(d.invoice_amount),
|
||||||
'outstanding_amount': d.amount - payment_amount})
|
'outstanding_amount': d.invoice_amount - payment_amount})
|
||||||
|
|
||||||
self.add_invoice_entries(non_reconciled_invoices)
|
self.add_invoice_entries(non_reconciled_invoices)
|
||||||
|
|
||||||
|
|
||||||
def add_invoice_entries(self, non_reconciled_invoices):
|
def add_invoice_entries(self, non_reconciled_invoices):
|
||||||
#Populate 'payment_reconciliation_invoices' with JVs and Invoices to reconcile against
|
#Populate 'payment_reconciliation_invoices' with JVs and Invoices to reconcile against
|
||||||
self.set('payment_reconciliation_invoices', [])
|
self.set('payment_reconciliation_invoices', [])
|
||||||
if not non_reconciled_invoices:
|
|
||||||
frappe.throw(_("No invoices found to be reconciled"))
|
|
||||||
|
|
||||||
|
|
||||||
for e in non_reconciled_invoices:
|
for e in non_reconciled_invoices:
|
||||||
ent = self.append('payment_reconciliation_invoices', {})
|
ent = self.append('payment_reconciliation_invoices', {})
|
||||||
ent.invoice_type = e.get('voucher_type')
|
ent.invoice_type = e.get('voucher_type')
|
||||||
ent.invoice_number = e.get('voucher_no')
|
ent.invoice_number = e.get('voucher_no')
|
||||||
ent.invoice_date = e.get('posting_date')
|
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')
|
ent.outstanding_amount = e.get('outstanding_amount')
|
||||||
|
|
||||||
def reconcile(self, args):
|
def reconcile(self, args):
|
||||||
@@ -121,6 +138,7 @@ class PaymentReconciliation(Document):
|
|||||||
dr_or_cr = "credit" if self.party_type == "Customer" else "debit"
|
dr_or_cr = "credit" if self.party_type == "Customer" else "debit"
|
||||||
lst = []
|
lst = []
|
||||||
for e in self.get('payment_reconciliation_payments'):
|
for e in self.get('payment_reconciliation_payments'):
|
||||||
|
if e.invoice_type and e.invoice_number:
|
||||||
lst.append({
|
lst.append({
|
||||||
'voucher_no' : e.journal_voucher,
|
'voucher_no' : e.journal_voucher,
|
||||||
'voucher_detail_no' : e.voucher_detail_number,
|
'voucher_detail_no' : e.voucher_detail_number,
|
||||||
@@ -136,9 +154,8 @@ class PaymentReconciliation(Document):
|
|||||||
if lst:
|
if lst:
|
||||||
from erpnext.accounts.utils import reconcile_against_document
|
from erpnext.accounts.utils import reconcile_against_document
|
||||||
reconcile_against_document(lst)
|
reconcile_against_document(lst)
|
||||||
self.get_unreconciled_entries()
|
|
||||||
msgprint(_("Successfully Reconciled"))
|
msgprint(_("Successfully Reconciled"))
|
||||||
|
self.get_unreconciled_entries()
|
||||||
|
|
||||||
def check_mandatory_to_fetch(self):
|
def check_mandatory_to_fetch(self):
|
||||||
for fieldname in ["company", "party_account"]:
|
for fieldname in ["company", "party_account"]:
|
||||||
@@ -147,6 +164,12 @@ class PaymentReconciliation(Document):
|
|||||||
|
|
||||||
|
|
||||||
def validate_invoice(self):
|
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()
|
unreconciled_invoices = frappe._dict()
|
||||||
for d in self.get("payment_reconciliation_invoices"):
|
for d in self.get("payment_reconciliation_invoices"):
|
||||||
unreconciled_invoices.setdefault(d.invoice_type, {}).setdefault(d.invoice_number, d.outstanding_amount)
|
unreconciled_invoices.setdefault(d.invoice_type, {}).setdefault(d.invoice_number, d.outstanding_amount)
|
||||||
@@ -156,23 +179,23 @@ class PaymentReconciliation(Document):
|
|||||||
if p.invoice_type and p.invoice_number:
|
if p.invoice_type and p.invoice_number:
|
||||||
invoices_to_reconcile.append(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")
|
frappe.throw(_("{0}: {1} not found in Invoice Details table")
|
||||||
.format(p.invoice_type, p.invoice_number))
|
.format(p.invoice_type, p.invoice_number))
|
||||||
|
|
||||||
if p.amount > unreconciled_invoices.get(p.invoice_type).get(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))
|
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:
|
if not invoices_to_reconcile:
|
||||||
frappe.throw(_("Please select Invoice Type and Invoice Number in atleast one row"))
|
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.from_date and " and posting_date >= '" + self.from_date + "'" or ""
|
||||||
cond += self.to_date and " and posting_date <= '" + self.to_date + "'" or ""
|
cond += self.to_date and " and posting_date <= '" + self.to_date + "'" or ""
|
||||||
|
|
||||||
if self.minimum_amount:
|
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:
|
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
|
return cond
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Invoice Type",
|
"label": "Invoice Type",
|
||||||
"options": "Sales Invoice\nPurchase Invoice\nJournal Voucher",
|
"options": "\nSales Invoice\nPurchase Invoice\nJournal Voucher",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2014-07-18 15:53:15.589501",
|
"modified": "2014-07-21 16:53:56.206169",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Reconciliation Payment",
|
"name": "Payment Reconciliation Payment",
|
||||||
|
|||||||
@@ -153,12 +153,12 @@ def check_if_jv_modified(args):
|
|||||||
check if jv is submitted
|
check if jv is submitted
|
||||||
"""
|
"""
|
||||||
ret = frappe.db.sql("""
|
ret = frappe.db.sql("""
|
||||||
select t2.%(dr_or_cr)s from `tabJournal Voucher` t1, `tabJournal Voucher Detail` t2
|
select t2.{dr_or_cr} from `tabJournal Voucher` t1, `tabJournal Voucher Detail` t2
|
||||||
where t1.name = t2.parent and t2.account = '%(account)s'
|
where t1.name = t2.parent and t2.account = %(account)s
|
||||||
and ifnull(t2.against_voucher, '')=''
|
and ifnull(t2.against_voucher, '')=''
|
||||||
and ifnull(t2.against_invoice, '')='' and ifnull(t2.against_jv, '')=''
|
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.name = %(voucher_no)s and t2.name = %(voucher_detail_no)s
|
||||||
and t1.docstatus=1 """ % args)
|
and t1.docstatus=1 """.format(dr_or_cr = args.get("dr_or_cr")), args)
|
||||||
|
|
||||||
if not ret:
|
if not ret:
|
||||||
throw(_("""Payment Entry has been modified after you pulled it. Please pull it again."""))
|
throw(_("""Payment Entry has been modified after you pulled it. Please pull it again."""))
|
||||||
|
|||||||
Reference in New Issue
Block a user