Payment to invoice matching tool

This commit is contained in:
Nabin Hait 2014-04-30 19:30:50 +05:30
parent b54307d58e
commit 576f025c84
8 changed files with 255 additions and 188 deletions

View File

@ -10,7 +10,7 @@
{
"fieldname": "properties",
"fieldtype": "Section Break",
"in_list_view": 1,
"in_list_view": 0,
"label": "Account Details",
"oldfieldtype": "Section Break",
"permlevel": 0
@ -18,7 +18,7 @@
{
"fieldname": "column_break0",
"fieldtype": "Column Break",
"in_list_view": 1,
"in_list_view": 0,
"permlevel": 0,
"width": "50%"
},
@ -36,18 +36,6 @@
"reqd": 1,
"search_index": 1
},
{
"fieldname": "level",
"fieldtype": "Int",
"hidden": 1,
"in_list_view": 1,
"label": "Level",
"oldfieldname": "level",
"oldfieldtype": "Int",
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
{
"default": "Ledger",
"fieldname": "group_or_ledger",
@ -211,7 +199,7 @@
"icon": "icon-money",
"idx": 1,
"in_create": 1,
"modified": "2014-04-28 16:52:32.059072",
"modified": "2014-04-30 11:28:52.916199",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account",

View File

@ -1,7 +1,7 @@
{
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-03-25 10:53:52.000000",
"creation": "2013-03-25 10:53:52",
"docstatus": 0,
"doctype": "DocType",
"fields": [
@ -34,6 +34,7 @@
"reqd": 1
},
{
"default": "Journal Entry",
"fieldname": "voucher_type",
"fieldtype": "Select",
"in_filter": 1,
@ -41,10 +42,11 @@
"label": "Voucher Type",
"oldfieldname": "voucher_type",
"oldfieldtype": "Select",
"options": "\nJournal Entry\nBank Voucher\nCash Voucher\nCredit Card Voucher\nDebit Note\nCredit Note\nContra Voucher\nExcise Voucher\nWrite Off Voucher\nOpening Entry",
"options": "Journal Entry\nBank Voucher\nCash Voucher\nCredit Card Voucher\nDebit Note\nCredit Note\nContra Voucher\nExcise Voucher\nWrite Off Voucher\nOpening Entry",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"reqd": 1,
"search_index": 1
},
{
@ -438,7 +440,7 @@
"icon": "icon-file-text",
"idx": 1,
"is_submittable": 1,
"modified": "2014-01-20 17:48:51.000000",
"modified": "2014-04-29 14:55:19.872882",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Voucher",

View File

@ -1,36 +1,51 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
cur_frm.add_fetch("account", "company", "company")
cur_frm.cscript.onload_post_render = function(doc) {
$(cur_frm.get_field("reconcile").input).addClass("btn-info");
}
cur_frm.cscript.refresh = function(doc) {
cur_frm.set_intro("");
if(!doc.voucher_no) {
cur_frm.set_intro(__("Select the Invoice against which you want to allocate payments."));
} else {
cur_frm.set_intro(__("Set allocated amount against each Payment Entry and click 'Allocate'."));
}
}
cur_frm.fields_dict.voucher_no.get_query = function(doc) {
// TO-do: check for pos, it should not come
if (!doc.account) msgprint(__("Please select Account first"));
else {
return {
doctype: doc.voucher_type,
query: "erpnext.accounts.doctype.payment_to_invoice_matching_tool.payment_to_invoice_matching_tool.gl_entry_details",
query: "erpnext.accounts.doctype.payment_to_invoice_matching_tool.payment_to_invoice_matching_tool.get_voucher_nos",
filters: {
"dt": doc.voucher_type,
"acc": doc.account
"voucher_type": doc.voucher_type,
"account": doc.account
}
}
}
}
cur_frm.cscript.voucher_no = function(doc, cdt, cdn) {
return get_server_fields('get_voucher_details', '', '', doc, cdt, cdn, 1)
cur_frm.cscript.voucher_no = function() {
return cur_frm.call({
doc: cur_frm.doc,
method: "get_voucher_details"
});
}
cur_frm.cscript.get_against_entries = function() {
return cur_frm.call({
doc: cur_frm.doc,
method: "get_against_entries"
});
}
cur_frm.cscript.reconcile = function() {
return cur_frm.call({
doc: cur_frm.doc,
method: "reconcile"
});
}
cur_frm.cscript.amt_to_be_reconciled = function(doc, cdt, cdn) {
var total_allocated_amount = 0
$.each(cur_frm.doc.against_entries, function(i, d) {
if(d.amt_to_be_reconciled > 0) total_allocated_amount += flt(d.amt_to_be_reconciled);
else if (d.amt_to_be_reconciled < 0) frappe.throw(__("Allocated amount can not be negative"));
})
cur_frm.set_value("total_allocated_amount", total_allocated_amount);
}

View File

@ -1,5 +1,5 @@
{
"creation": "2013-01-30 12:49:46.000000",
"creation": "2013-01-30 12:49:46",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Other",
@ -7,31 +7,17 @@
{
"fieldname": "account",
"fieldtype": "Link",
"in_list_view": 0,
"label": "Account",
"options": "Account",
"permlevel": 0,
"reqd": 1
},
{
"fieldname": "account_type",
"fieldtype": "Data",
"hidden": 1,
"label": "Account Type",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"hidden": 1,
"label": "Company",
"options": "Company",
"permlevel": 0,
"print_hide": 1
},
{
"default": "Journal Voucher",
"fieldname": "voucher_type",
"fieldtype": "Select",
"in_list_view": 0,
"label": "Voucher Type",
"options": "Sales Invoice\nPurchase Invoice\nJournal Voucher",
"permlevel": 0,
@ -40,21 +26,16 @@
{
"fieldname": "voucher_no",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Voucher No",
"options": "[Select]",
"permlevel": 0,
"reqd": 1
},
{
"fieldname": "pull_payment_entries",
"fieldtype": "Button",
"label": "Pull Payment Entries",
"options": "get_payment_entries",
"permlevel": 0
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"in_list_view": 0,
"permlevel": 0,
"print_width": "50%",
"width": "50%"
@ -62,54 +43,26 @@
{
"fieldname": "total_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Total Amount",
"options": "Company:company:default_currency",
"options": "",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "pending_amt_to_reconcile",
"fieldname": "unmatched_amount",
"fieldtype": "Currency",
"label": "Outstanding Amount",
"options": "Company:company:default_currency",
"label": "Unmatched Amount",
"options": "",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "payment_entries",
"fieldname": "against_entries_section",
"fieldtype": "Section Break",
"label": "Payment Entries",
"label": "Against Entries",
"permlevel": 0
},
{
"description": "Update allocated amount in the above table and then click \"Allocate\" button",
"fieldname": "ir_payment_details",
"fieldtype": "Table",
"label": "Payment Entries",
"options": "Payment to Invoice Matching Tool Detail",
"permlevel": 0
},
{
"fieldname": "reconcile",
"fieldtype": "Button",
"label": "Allocate",
"options": "reconcile",
"permlevel": 0
},
{
"fieldname": "section_break0",
"fieldtype": "Section Break",
"options": "Simple",
"permlevel": 0
},
{
"fieldname": "column_break2",
"fieldtype": "Column Break",
"label": "Filter By Date",
"permlevel": 0,
"print_width": "50%",
"width": "50%"
},
{
"fieldname": "from_date",
"fieldtype": "Date",
@ -122,39 +75,87 @@
"label": "To Date",
"permlevel": 0
},
{
"fieldname": "help_html",
"fieldtype": "HTML",
"label": "Help HTML",
"options": "Click \"Pull Payment Entries\" to refresh the table with filters.",
"permlevel": 0
},
{
"fieldname": "column_break3",
"fieldtype": "Column Break",
"label": "Filter By Amount",
"label": "",
"permlevel": 0,
"print_width": "50%",
"width": "50%"
},
{
"fieldname": "amt_greater_than",
"fieldtype": "Data",
"fieldtype": "Currency",
"label": "Amount >=",
"permlevel": 0
},
{
"fieldname": "amt_less_than",
"fieldtype": "Data",
"fieldtype": "Currency",
"label": "Amount <=",
"permlevel": 0
},
{
"fieldname": "section_break0",
"fieldtype": "Section Break",
"options": "Simple",
"permlevel": 0
},
{
"fieldname": "get_against_entries",
"fieldtype": "Button",
"label": "Get Against Entries",
"options": "",
"permlevel": 0
},
{
"description": "Update allocated amount in the above table and then click \"Allocate\" button",
"fieldname": "against_entries",
"fieldtype": "Table",
"label": "Against Entries",
"options": "Payment to Invoice Matching Tool Detail",
"permlevel": 0
},
{
"fieldname": "sec_break1",
"fieldtype": "Section Break",
"options": "Simple",
"permlevel": 0
},
{
"fieldname": "total_allocated_amount",
"fieldtype": "Currency",
"label": "Total Allocated Amount",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "col_breal4",
"fieldtype": "Column Break",
"permlevel": 0
},
{
"default": "",
"fieldname": "allocate_amount_automatically",
"fieldtype": "Button",
"hidden": 1,
"label": "Allocate Amount Automatically",
"permlevel": 0,
"reqd": 0
},
{
"fieldname": "reconcile",
"fieldtype": "Button",
"label": "Reconcile",
"options": "",
"permlevel": 0
}
],
"hide_toolbar": 1,
"hide_toolbar": 0,
"icon": "icon-magic",
"idx": 1,
"issingle": 1,
"modified": "2013-12-20 19:23:24.000000",
"modified": "2014-04-30 17:11:05.908619",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment to Invoice Matching Tool",

View File

@ -17,69 +17,99 @@ class PaymenttoInvoiceMatchingTool(Document):
where voucher_type = %s and voucher_no = %s
and account = %s""", (self.voucher_type, self.voucher_no, self.account))
total_amount = total_amount and flt(total_amount[0][0]) or 0
self.total_amount = total_amount and flt(total_amount[0][0]) or 0
reconciled_payment = frappe.db.sql("""
select abs(sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))) from `tabGL Entry` where
against_voucher = %s and voucher_no != %s
and account = %s""", (self.voucher_no, self.voucher_no, self.account))
select abs(sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)))
from `tabGL Entry`
where against_voucher = %s and account = %s
""", (self.voucher_no, self.account))
reconciled_payment = reconciled_payment and flt(reconciled_payment[0][0]) or 0
ret = {
'total_amount': total_amount,
'pending_amt_to_reconcile': total_amount - reconciled_payment
}
self.unmatched_amount = self.total_amount - reconciled_payment
return ret
def get_payment_entries(self):
def get_against_entries(self):
"""
Get payment entries for the account and period
Payment entry will be decided based on account type (Dr/Cr)
"""
self.set('ir_payment_details', [])
self.set('against_entries', [])
gle = self.get_gl_entries()
self.create_payment_table(gle)
self.create_against_entries_table(gle)
def get_gl_entries(self):
self.validate_mandatory()
dr_or_cr = "credit" if self.total_amount > 0 else "debit"
cond = self.from_date and " and t1.posting_date >= '" + self.from_date + "'" or ""
cond += self.to_date and " and t1.posting_date <= '" + self.to_date + "'"or ""
cond += self.to_date and " and t1.posting_date <= '" + self.to_date + "'" or ""
if self.amt_greater_than:
cond += ' and abs(ifnull(t2.debit, 0) - ifnull(t2.credit, 0)) >= ' + \
self.amt_greater_than
cond += ' and abs(ifnull(t2.debit, 0) - ifnull(t2.credit, 0)) >= ' + self.amt_greater_than
if self.amt_less_than:
cond += ' and abs(ifnull(t2.debit, 0) - ifnull(t2.credit, 0)) >= ' + \
self.amt_less_than
cond += ' and abs(ifnull(t2.debit, 0) - ifnull(t2.credit, 0)) >= ' + self.amt_less_than
gle = frappe.db.sql("""
select t1.name as voucher_no, t1.posting_date, t1.total_debit as total_amt,
abs(sum(ifnull(t2.credit, 0)) - sum(ifnull(t2.debit, 0))) as amt_due, t1.remark,
t2.against_account, t2.name as voucher_detail_no
select
t1.name as voucher_no, t1.posting_date, t1.total_debit as total_amt,
abs(ifnull(t2.debit, 0) - ifnull(t2.credit, 0)) as unmatched_amount, t1.remark,
t2.against_account, t2.name as voucher_detail_no, t2.is_advance
from `tabJournal Voucher` t1, `tabJournal Voucher Detail` t2
where t1.name = t2.parent and t1.docstatus = 1 and t2.account = %s
and ifnull(t2.against_voucher, '')='' and ifnull(t2.against_invoice, '')=''
and ifnull(t2.against_jv, '')='' and t1.name != %s %s group by t1.name, t2.name """ %
('%s', '%s', cond), (self.account, self.voucher_no), as_dict=1)
and ifnull(t2.against_jv, '')='' and t2.%s > 0 and t1.name != %s
and not exists (select * from `tabJournal Voucher Detail`
where parent=%s and against_jv = t1.name) %s
group by t1.name, t2.name """ %
('%s', dr_or_cr, '%s', '%s', cond), (self.account, self.voucher_no, self.voucher_no), as_dict=1)
return gle
def create_payment_table(self, gle):
def create_against_entries_table(self, gle):
adjusted_jv = {}
for d in gle:
ch = self.append('ir_payment_details', {})
ch.voucher_no = d.get('voucher_no')
ch.posting_date = d.get('posting_date')
ch.amt_due = flt(d.get('amt_due'))
ch.total_amt = flt(d.get('total_amt'))
ch.against_account = d.get('against_account')
ch.remarks = d.get('remark')
ch.voucher_detail_no = d.get('voucher_detail_no')
if not adjusted_jv.has_key(d.get("voucher_no")):
matched_amount = frappe.db.sql("""
select
ifnull(abs(sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))), 0)
from
`tabGL Entry`
where
account = %s and against_voucher_type = "Journal Voucher"
and ifnull(against_voucher, '') = %s
""", (self.account, d.get('voucher_no')))
matched_amount = matched_amount[0][0] if matched_amount else 0
else:
matched_amount = adjusted_jv.get(d.get("voucher_no"))
if matched_amount < flt(d.get('unmatched_amount')):
unmatched_amount = flt(d.get('unmatched_amount')) - matched_amount
adjusted_jv.setdefault(d.get("voucher_no"), 0)
else:
unmatched_amount = 0
adjusted_jv.setdefault(d.get("voucher_no"), matched_amount - flt(d.get('unmatched_amount')))
if unmatched_amount:
ch = self.append('against_entries', {})
ch.voucher_no = d.get('voucher_no')
ch.posting_date = d.get('posting_date')
ch.unmatched_amount = unmatched_amount
ch.total_amt = flt(d.get('total_amt'))
ch.against_account = d.get('against_account')
ch.remarks = d.get('remark')
ch.voucher_detail_no = d.get('voucher_detail_no')
ch.is_advance = d.get("is_advance")
ch.original_amount = flt(d.get('unmatched_amount'))
def validate_mandatory(self):
if not self.account:
msgprint(_("Please select Account first"), raise_exception=1)
for fieldname in ["account", "voucher_type", "voucher_no"]:
if not self.get(fieldname):
frappe.throw(_("Please select {0} first").format(self.meta.get_label("fieldname")))
if not frappe.db.exists(self.voucher_type, self.voucher_no):
frappe.throw(_("Voucher No is not valid"))
def reconcile(self):
"""
@ -88,23 +118,26 @@ class PaymenttoInvoiceMatchingTool(Document):
2. split into multiple rows if partially adjusted, assign against voucher
3. submit payment voucher
"""
if not self.voucher_no or not frappe.db.sql("""select name from `tab%s`
where name = %s""" % (self.voucher_type, '%s'), self.voucher_no):
frappe.throw(_("Please select valid Voucher No to proceed"))
self.validate_mandatory()
if not self.total_allocated_amount:
frappe.throw(_("You must allocate amount before reconcile"))
dr_or_cr = "credit" if self.total_amount > 0 else "debit"
lst = []
for d in self.get('ir_payment_details'):
if flt(d.amt_to_be_reconciled) > 0:
for d in self.get('against_entries'):
if flt(d.allocated_amount) > 0:
args = {
'voucher_no' : d.voucher_no,
'voucher_detail_no' : d.voucher_detail_no,
'against_voucher_type' : self.voucher_type,
'against_voucher' : self.voucher_no,
'account' : self.account,
'is_advance' : 'No',
# 'dr_or_cr' : self.account_type=='debit' and 'credit' or 'debit',
'unadjusted_amt' : flt(d.amt_due),
'allocated_amt' : flt(d.amt_to_be_reconciled)
'is_advance' : d.is_advance,
'dr_or_cr' : dr_or_cr,
'unadjusted_amt' : flt(d.original_amount),
'allocated_amt' : flt(d.allocated_amount)
}
lst.append(args)
@ -112,35 +145,34 @@ class PaymenttoInvoiceMatchingTool(Document):
if lst:
from erpnext.accounts.utils import reconcile_against_document
reconcile_against_document(lst)
self.get_against_entries()
msgprint(_("Successfully allocated"))
else:
msgprint(_("No amount allocated"), raise_exception=1)
def gl_entry_details(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond
return frappe.db.sql("""select gle.voucher_no, gle.posting_date,
gle.debit, gle.credit from `tabGL Entry` gle
where gle.account = '%(acc)s'
and gle.voucher_type = '%(dt)s'
and gle.voucher_no like '%(txt)s'
and (ifnull(gle.against_voucher, '') = ''
or ifnull(gle.against_voucher, '') = gle.voucher_no )
and (select ifnull(abs(sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))), 0)
from `tabGL Entry`
where account = '%(acc)s'
and against_voucher_type = '%(dt)s'
and against_voucher = gle.voucher_no
and voucher_no != gle.voucher_no)
!= abs(ifnull(gle.debit, 0) - ifnull(gle.credit, 0))
and if(gle.voucher_type='Sales Invoice', ifnull((select is_pos from `tabSales Invoice`
where name=gle.voucher_no), 0), 0)=0
%(mcond)s
ORDER BY gle.posting_date desc, gle.voucher_no desc
limit %(start)s, %(page_len)s""" % {
"dt":filters["dt"],
"acc":filters["acc"],
'mcond':get_match_cond(doctype),
'txt': "%%%s%%" % txt,
"start": start,
"page_len": page_len
})
def get_voucher_nos(doctype, txt, searchfield, start, page_len, filters):
non_reconclied_entries = []
entries = frappe.db.sql("""
select
voucher_no, posting_date, ifnull(abs(sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))), 0) as amount
from
`tabGL Entry`
where
account = %s and voucher_type = %s and voucher_no like %s
and ifnull(against_voucher, '') = ''
group by voucher_no
""", (filters["account"], filters["voucher_type"], "%%%s%%" % txt), as_dict=True)
for d in entries:
adjusted_amount = frappe.db.sql("""
select
ifnull(abs(sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))), 0)
from
`tabGL Entry`
where
account = %s and against_voucher_type = %s and ifnull(against_voucher, '') = %s
""", (filters["account"], filters["voucher_type"], d.voucher_no))
adjusted_amount = adjusted_amount[0][0] if adjusted_amount else 0
if adjusted_amount != d.amount:
non_reconclied_entries.append([d.voucher_no, d.posting_date, d.amount])
return non_reconclied_entries

View File

@ -1,5 +1,5 @@
{
"creation": "2013-02-22 01:27:39.000000",
"creation": "2013-02-22 01:27:39",
"docstatus": 0,
"doctype": "DocType",
"fields": [
@ -16,7 +16,7 @@
"width": "140px"
},
{
"fieldname": "amt_due",
"fieldname": "unmatched_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Unmatched Amount",
@ -25,7 +25,7 @@
"read_only": 1
},
{
"fieldname": "amt_to_be_reconciled",
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Allocated Amount",
@ -33,6 +33,11 @@
"permlevel": 0,
"reqd": 1
},
{
"fieldname": "col_break1",
"fieldtype": "Column Break",
"permlevel": 0
},
{
"fieldname": "posting_date",
"fieldtype": "Date",
@ -76,13 +81,30 @@
"print_hide": 1,
"read_only": 1,
"reqd": 0
},
{
"fieldname": "is_advance",
"fieldtype": "Data",
"hidden": 1,
"label": "Is Advance",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "original_amount",
"fieldtype": "Currency",
"hidden": 1,
"label": "Original Amount",
"permlevel": 0
}
],
"hide_toolbar": 1,
"idx": 1,
"istable": 1,
"modified": "2013-12-20 19:23:24.000000",
"modified": "2014-04-30 19:27:15.993641",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment to Invoice Matching Tool Detail",
"owner": "Administrator"
"owner": "Administrator",
"permissions": []
}

View File

@ -125,7 +125,7 @@ def reconcile_against_document(args):
"""
for d in args:
check_if_jv_modified(d)
validate_allocated_amount(d)
against_fld = {
'Journal Voucher' : 'against_jv',
'Sales Invoice' : 'against_invoice',
@ -159,11 +159,17 @@ def check_if_jv_modified(args):
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 and t2.%(dr_or_cr)s = %(unadjusted_amt)s""" % args)
and t1.docstatus=1 """ % args)
if not ret:
throw(_("""Payment Entry has been modified after you pulled it. Please pull it again."""))
def validate_allocated_amount(args):
if args.get("allocated_amt") < 0:
throw(_("Allocated amount can not be negative"))
elif args.get("allocated_amt") > args.get("unadjusted_amt"):
throw(_("Allocated amount can not greater than unadusted amount"))
def update_against_doc(d, jv_obj):
"""
Updates against document, if partial amount splits into rows

View File

@ -35,3 +35,4 @@ erpnext.patches.4_0.countrywise_coa
execute:frappe.delete_doc("DocType", "MIS Control")
execute:frappe.delete_doc("Page", "Financial Statements")
execute:frappe.delete_doc("DocType", "Stock Ledger")
execute:frappe.db.sql("update `tabJournal Voucher` set voucher_type='Journal Entry' where ifnull(voucher_type, '')=''")