384 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			384 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8 -*-
 | |
| # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
 | |
| # For license information, please see license.txt
 | |
| 
 | |
| from __future__ import unicode_literals
 | |
| import frappe
 | |
| from frappe import _
 | |
| import difflib
 | |
| from frappe.utils import flt
 | |
| from six import iteritems
 | |
| from erpnext import get_company_currency
 | |
| 
 | |
| @frappe.whitelist()
 | |
| def reconcile(bank_transaction, payment_doctype, payment_name):
 | |
| 	transaction = frappe.get_doc("Bank Transaction", bank_transaction)
 | |
| 	payment_entry = frappe.get_doc(payment_doctype, payment_name)
 | |
| 
 | |
| 	account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
 | |
| 	gl_entry = frappe.get_doc("GL Entry", dict(account=account, voucher_type=payment_doctype, voucher_no=payment_name))
 | |
| 
 | |
| 	if payment_doctype == "Payment Entry" and payment_entry.unallocated_amount > transaction.unallocated_amount:
 | |
| 		frappe.throw(_("The unallocated amount of Payment Entry {0} \
 | |
| 			is greater than the Bank Transaction's unallocated amount").format(payment_name))
 | |
| 
 | |
| 	if transaction.unallocated_amount == 0:
 | |
| 		frappe.throw(_("This bank transaction is already fully reconciled"))
 | |
| 
 | |
| 	if transaction.credit > 0 and gl_entry.credit > 0:
 | |
| 		frappe.throw(_("The selected payment entry should be linked with a debtor bank transaction"))
 | |
| 
 | |
| 	if transaction.debit > 0 and gl_entry.debit > 0:
 | |
| 		frappe.throw(_("The selected payment entry should be linked with a creditor bank transaction"))
 | |
| 
 | |
| 	add_payment_to_transaction(transaction, payment_entry, gl_entry)
 | |
| 
 | |
| 	return 'reconciled'
 | |
| 
 | |
| def add_payment_to_transaction(transaction, payment_entry, gl_entry):
 | |
| 	gl_amount, transaction_amount = (gl_entry.credit, transaction.debit) if gl_entry.credit > 0 else (gl_entry.debit, transaction.credit)
 | |
| 	allocated_amount = gl_amount if gl_amount <= transaction_amount else transaction_amount
 | |
| 	transaction.append("payment_entries", {
 | |
| 		"payment_document": payment_entry.doctype,
 | |
| 		"payment_entry": payment_entry.name,
 | |
| 		"allocated_amount": allocated_amount
 | |
| 	})
 | |
| 
 | |
| 	transaction.save()
 | |
| 	transaction.update_allocations()
 | |
| 
 | |
| @frappe.whitelist()
 | |
| def get_linked_payments(bank_transaction):
 | |
| 	transaction = frappe.get_doc("Bank Transaction", bank_transaction)
 | |
| 	bank_account = frappe.db.get_values("Bank Account", transaction.bank_account, ["account", "company"], as_dict=True)
 | |
| 
 | |
| 	# Get all payment entries with a matching amount
 | |
| 	amount_matching = check_matching_amount(bank_account[0].account, bank_account[0].company, transaction)
 | |
| 
 | |
| 	# Get some data from payment entries linked to a corresponding bank transaction
 | |
| 	description_matching = get_matching_descriptions_data(bank_account[0].company, transaction)
 | |
| 
 | |
| 	if amount_matching:
 | |
| 		return check_amount_vs_description(amount_matching, description_matching)
 | |
| 
 | |
| 	elif description_matching:
 | |
| 		description_matching = filter(lambda x: not x.get('clearance_date'), description_matching)
 | |
| 		if not description_matching:
 | |
| 			return []
 | |
| 
 | |
| 		return sorted(list(description_matching), key = lambda x: x["posting_date"], reverse=True)
 | |
| 
 | |
| 	else:
 | |
| 		return []
 | |
| 
 | |
| def check_matching_amount(bank_account, company, transaction):
 | |
| 	payments = []
 | |
| 	amount = transaction.credit if transaction.credit > 0 else transaction.debit
 | |
| 
 | |
| 	payment_type = "Receive" if transaction.credit > 0 else "Pay"
 | |
| 	account_from_to = "paid_to" if transaction.credit > 0 else "paid_from"
 | |
| 	currency_field = "paid_to_account_currency as currency" if transaction.credit > 0 else "paid_from_account_currency as currency"
 | |
| 
 | |
| 	payment_entries = frappe.get_all("Payment Entry", fields=["'Payment Entry' as doctype", "name", "paid_amount", "payment_type", "reference_no", "reference_date",
 | |
| 		"party", "party_type", "posting_date", "{0}".format(currency_field)], filters=[["paid_amount", "like", "{0}%".format(amount)],
 | |
| 		["docstatus", "=", "1"], ["payment_type", "=", [payment_type, "Internal Transfer"]], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]])
 | |
| 
 | |
| 	if transaction.credit > 0:
 | |
| 		journal_entries = frappe.db.sql("""
 | |
| 			SELECT
 | |
| 				'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no,
 | |
| 				je.pay_to_recd_from as party, je.cheque_date as reference_date, jea.debit_in_account_currency as paid_amount
 | |
| 			FROM
 | |
| 				`tabJournal Entry Account` as jea
 | |
| 			JOIN
 | |
| 				`tabJournal Entry` as je
 | |
| 			ON
 | |
| 				jea.parent = je.name
 | |
| 			WHERE
 | |
| 				(je.clearance_date is null or je.clearance_date='0000-00-00')
 | |
| 			AND
 | |
| 				jea.account = %s
 | |
| 			AND
 | |
| 				jea.debit_in_account_currency like %s
 | |
| 			AND
 | |
| 				je.docstatus = 1
 | |
| 		""", (bank_account, amount), as_dict=True)
 | |
| 	else:
 | |
| 		journal_entries = frappe.db.sql("""
 | |
| 			SELECT
 | |
| 				'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no,
 | |
| 				jea.account_currency as currency, je.pay_to_recd_from as party, je.cheque_date as reference_date,
 | |
| 				jea.credit_in_account_currency as paid_amount
 | |
| 			FROM
 | |
| 				`tabJournal Entry Account` as jea
 | |
| 			JOIN
 | |
| 				`tabJournal Entry` as je
 | |
| 			ON
 | |
| 				jea.parent = je.name
 | |
| 			WHERE
 | |
| 				(je.clearance_date is null or je.clearance_date='0000-00-00')
 | |
| 			AND
 | |
| 				jea.account = %(bank_account)s
 | |
| 			AND
 | |
| 				jea.credit_in_account_currency like %(txt)s
 | |
| 			AND
 | |
| 				je.docstatus = 1
 | |
| 		""", {
 | |
| 			'bank_account': bank_account,
 | |
| 			'txt': '%%%s%%' % amount
 | |
| 		}, as_dict=True)
 | |
| 
 | |
| 	if transaction.credit > 0:
 | |
| 		sales_invoices = frappe.db.sql("""
 | |
| 			SELECT
 | |
| 				'Sales Invoice' as doctype, si.name, si.customer as party,
 | |
| 				si.posting_date, sip.amount as paid_amount
 | |
| 			FROM
 | |
| 				`tabSales Invoice Payment` as sip
 | |
| 			JOIN
 | |
| 				`tabSales Invoice` as si
 | |
| 			ON
 | |
| 				sip.parent = si.name
 | |
| 			WHERE
 | |
| 				(sip.clearance_date is null or sip.clearance_date='0000-00-00')
 | |
| 			AND
 | |
| 				sip.account = %s
 | |
| 			AND
 | |
| 				sip.amount like %s
 | |
| 			AND
 | |
| 				si.docstatus = 1
 | |
| 		""", (bank_account, amount), as_dict=True)
 | |
| 	else:
 | |
| 		sales_invoices = []
 | |
| 
 | |
| 	if transaction.debit > 0:
 | |
| 		purchase_invoices = frappe.get_all("Purchase Invoice",
 | |
| 			fields = ["'Purchase Invoice' as doctype", "name", "paid_amount", "supplier as party", "posting_date", "currency"],
 | |
| 			filters=[
 | |
| 				["paid_amount", "like", "{0}%".format(amount)],
 | |
| 				["docstatus", "=", "1"],
 | |
| 				["is_paid", "=", "1"],
 | |
| 				["ifnull(clearance_date, '')", "=", ""],
 | |
| 				["cash_bank_account", "=", "{0}".format(bank_account)]
 | |
| 			]
 | |
| 		)
 | |
| 
 | |
| 		mode_of_payments = [x["parent"] for x in frappe.db.get_list("Mode of Payment Account",
 | |
| 			filters={"default_account": bank_account}, fields=["parent"])]
 | |
| 
 | |
| 		company_currency = get_company_currency(company)
 | |
| 
 | |
| 		expense_claims = frappe.get_all("Expense Claim",
 | |
| 			fields=["'Expense Claim' as doctype", "name", "total_sanctioned_amount as paid_amount",
 | |
| 				"employee as party", "posting_date", "'{0}' as currency".format(company_currency)],
 | |
| 			filters=[
 | |
| 				["total_sanctioned_amount", "like", "{0}%".format(amount)],
 | |
| 				["docstatus", "=", "1"],
 | |
| 				["is_paid", "=", "1"],
 | |
| 				["ifnull(clearance_date, '')", "=", ""],
 | |
| 				["mode_of_payment", "in", "{0}".format(tuple(mode_of_payments))]
 | |
| 			]
 | |
| 		)
 | |
| 	else:
 | |
| 		purchase_invoices = expense_claims = []
 | |
| 
 | |
| 	for data in [payment_entries, journal_entries, sales_invoices, purchase_invoices, expense_claims]:
 | |
| 		if data:
 | |
| 			payments.extend(data)
 | |
| 
 | |
| 	return payments
 | |
| 
 | |
| def get_matching_descriptions_data(company, transaction):
 | |
| 	if not transaction.description :
 | |
| 		return []
 | |
| 
 | |
| 	bank_transactions = frappe.db.sql("""
 | |
| 		SELECT
 | |
| 			bt.name, bt.description, bt.date, btp.payment_document, btp.payment_entry
 | |
| 		FROM
 | |
| 			`tabBank Transaction` as bt
 | |
| 		LEFT JOIN
 | |
| 			`tabBank Transaction Payments` as btp
 | |
| 		ON
 | |
| 			bt.name = btp.parent
 | |
| 		WHERE
 | |
| 			bt.allocated_amount > 0
 | |
| 		AND
 | |
| 			bt.docstatus = 1
 | |
| 		""", as_dict=True)
 | |
| 
 | |
| 	selection = []
 | |
| 	for bank_transaction in bank_transactions:
 | |
| 		if bank_transaction.description:
 | |
| 			seq=difflib.SequenceMatcher(lambda x: x == " ", transaction.description, bank_transaction.description)
 | |
| 
 | |
| 			if seq.ratio() > 0.6:
 | |
| 				bank_transaction["ratio"] = seq.ratio()
 | |
| 				selection.append(bank_transaction)
 | |
| 
 | |
| 	document_types = set([x["payment_document"] for x in selection])
 | |
| 
 | |
| 	links = {}
 | |
| 	for document_type in document_types:
 | |
| 		links[document_type] = [x["payment_entry"] for x in selection if x["payment_document"]==document_type]
 | |
| 
 | |
| 
 | |
| 	data = []
 | |
| 	company_currency = get_company_currency(company)
 | |
| 	for key, value in iteritems(links):
 | |
| 		if key == "Payment Entry":
 | |
| 			data.extend(frappe.get_all("Payment Entry", filters=[["name", "in", value]],
 | |
| 				fields=["'Payment Entry' as doctype", "posting_date", "party", "reference_no",
 | |
| 					"reference_date", "paid_amount", "paid_to_account_currency as currency", "clearance_date"]))
 | |
| 		if key == "Journal Entry":
 | |
| 			journal_entries = frappe.get_all("Journal Entry", filters=[["name", "in", value]],
 | |
| 				fields=["name", "'Journal Entry' as doctype", "posting_date",
 | |
| 					"pay_to_recd_from as party", "cheque_no as reference_no", "cheque_date as reference_date",
 | |
| 					"total_credit as paid_amount", "clearance_date"])
 | |
| 			for journal_entry in journal_entries:
 | |
| 				journal_entry_accounts = frappe.get_all("Journal Entry Account", filters={"parenttype": journal_entry["doctype"], "parent": journal_entry["name"]}, fields=["account_currency"])
 | |
| 				journal_entry["currency"] = journal_entry_accounts[0]["account_currency"] if journal_entry_accounts else company_currency
 | |
| 			data.extend(journal_entries)
 | |
| 		if key == "Sales Invoice":
 | |
| 			data.extend(frappe.get_all("Sales Invoice", filters=[["name", "in", value]], fields=["'Sales Invoice' as doctype", "posting_date", "customer_name as party", "paid_amount", "currency"]))
 | |
| 		if key == "Purchase Invoice":
 | |
| 			data.extend(frappe.get_all("Purchase Invoice", filters=[["name", "in", value]], fields=["'Purchase Invoice' as doctype", "posting_date", "supplier_name as party", "paid_amount", "currency"]))
 | |
| 		if key == "Expense Claim":
 | |
| 			expense_claims = frappe.get_all("Expense Claim", filters=[["name", "in", value]], fields=["'Expense Claim' as doctype", "posting_date", "employee_name as party", "total_amount_reimbursed as paid_amount"])
 | |
| 			data.extend([dict(x,**{"currency": company_currency}) for x in expense_claims])
 | |
| 
 | |
| 	return data
 | |
| 
 | |
| def check_amount_vs_description(amount_matching, description_matching):
 | |
| 	result = []
 | |
| 
 | |
| 	if description_matching:
 | |
| 		for am_match in amount_matching:
 | |
| 			for des_match in description_matching:
 | |
| 				if des_match.get("clearance_date"):
 | |
| 					continue
 | |
| 
 | |
| 				if am_match["party"] == des_match["party"]:
 | |
| 					if am_match not in result:
 | |
| 						result.append(am_match)
 | |
| 						continue
 | |
| 
 | |
| 				if "reference_no" in am_match and "reference_no" in des_match:
 | |
| 					if difflib.SequenceMatcher(lambda x: x == " ", am_match["reference_no"], des_match["reference_no"]).ratio() > 70:
 | |
| 						if am_match not in result:
 | |
| 							result.append(am_match)
 | |
| 		if result:
 | |
| 			return sorted(result, key = lambda x: x["posting_date"], reverse=True)
 | |
| 		else:
 | |
| 			return sorted(amount_matching, key = lambda x: x["posting_date"], reverse=True)
 | |
| 
 | |
| 	else:
 | |
| 		return sorted(amount_matching, key = lambda x: x["posting_date"], reverse=True)
 | |
| 
 | |
| def get_matching_transactions_payments(description_matching):
 | |
| 	payments = [x["payment_entry"] for x in description_matching]
 | |
| 
 | |
| 	payment_by_ratio = {x["payment_entry"]: x["ratio"] for x in description_matching}
 | |
| 
 | |
| 	if payments:
 | |
| 		reference_payment_list = frappe.get_all("Payment Entry", fields=["name", "paid_amount", "payment_type", "reference_no", "reference_date",
 | |
| 			"party", "party_type", "posting_date", "paid_to_account_currency"], filters=[["name", "in", payments]])
 | |
| 
 | |
| 		return sorted(reference_payment_list, key=lambda x: payment_by_ratio[x["name"]])
 | |
| 
 | |
| 	else:
 | |
| 		return []
 | |
| 
 | |
| @frappe.whitelist()
 | |
| def payment_entry_query(doctype, txt, searchfield, start, page_len, filters):
 | |
| 	account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account")
 | |
| 	if not account:
 | |
| 		return
 | |
| 
 | |
| 	return frappe.db.sql("""
 | |
| 		SELECT
 | |
| 			name, party, paid_amount, received_amount, reference_no
 | |
| 		FROM
 | |
| 			`tabPayment Entry`
 | |
| 		WHERE
 | |
| 			(clearance_date is null or clearance_date='0000-00-00')
 | |
| 			AND (paid_from = %(account)s or paid_to = %(account)s)
 | |
| 			AND (name like %(txt)s or party like %(txt)s)
 | |
| 			AND docstatus = 1
 | |
| 		ORDER BY
 | |
| 			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), name
 | |
| 		LIMIT
 | |
| 			%(start)s, %(page_len)s""",
 | |
| 		{
 | |
| 			'txt': "%%%s%%" % txt,
 | |
| 			'_txt': txt.replace("%", ""),
 | |
| 			'start': start,
 | |
| 			'page_len': page_len,
 | |
| 			'account': account
 | |
| 		}
 | |
| 	)
 | |
| 
 | |
| @frappe.whitelist()
 | |
| def journal_entry_query(doctype, txt, searchfield, start, page_len, filters):
 | |
| 	account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account")
 | |
| 
 | |
| 	return frappe.db.sql("""
 | |
| 		SELECT
 | |
| 			jea.parent, je.pay_to_recd_from,
 | |
| 			if(jea.debit_in_account_currency > 0, jea.debit_in_account_currency, jea.credit_in_account_currency)
 | |
| 		FROM
 | |
| 			`tabJournal Entry Account` as jea
 | |
| 		LEFT JOIN
 | |
| 			`tabJournal Entry` as je
 | |
| 		ON
 | |
| 			jea.parent = je.name
 | |
| 		WHERE
 | |
| 			(je.clearance_date is null or je.clearance_date='0000-00-00')
 | |
| 		AND
 | |
| 			jea.account = %(account)s
 | |
| 		AND
 | |
| 			(jea.parent like %(txt)s or je.pay_to_recd_from like %(txt)s)
 | |
| 		AND
 | |
| 			je.docstatus = 1
 | |
| 		ORDER BY
 | |
| 			if(locate(%(_txt)s, jea.parent), locate(%(_txt)s, jea.parent), 99999),
 | |
| 			jea.parent
 | |
| 		LIMIT
 | |
| 			%(start)s, %(page_len)s""",
 | |
| 		{
 | |
| 			'txt': "%%%s%%" % txt,
 | |
| 			'_txt': txt.replace("%", ""),
 | |
| 			'start': start,
 | |
| 			'page_len': page_len,
 | |
| 			'account': account
 | |
| 		}
 | |
| 	)
 | |
| 
 | |
| @frappe.whitelist()
 | |
| def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters):
 | |
| 	return frappe.db.sql("""
 | |
| 		SELECT
 | |
| 			sip.parent, si.customer, sip.amount, sip.mode_of_payment
 | |
| 		FROM
 | |
| 			`tabSales Invoice Payment` as sip
 | |
| 		LEFT JOIN
 | |
| 			`tabSales Invoice` as si
 | |
| 		ON
 | |
| 			sip.parent = si.name
 | |
| 		WHERE
 | |
| 			(sip.clearance_date is null or sip.clearance_date='0000-00-00')
 | |
| 		AND
 | |
| 			(sip.parent like %(txt)s or si.customer like %(txt)s)
 | |
| 		ORDER BY
 | |
| 			if(locate(%(_txt)s, sip.parent), locate(%(_txt)s, sip.parent), 99999),
 | |
| 			sip.parent
 | |
| 		LIMIT
 | |
| 			%(start)s, %(page_len)s""",
 | |
| 		{
 | |
| 			'txt': "%%%s%%" % txt,
 | |
| 			'_txt': txt.replace("%", ""),
 | |
| 			'start': start,
 | |
| 			'page_len': page_len
 | |
| 		}
 | |
| 	)
 |