').data("data", value).appendTo(me.$result).get(0);
- new erpnext.accounts.ReconciliationRow(row, value);
- })
- }
-
- render_header() {
- const me = this;
- if ($(this.wrapper).find('.transaction-header').length === 0) {
- me.$result.append(frappe.render_template("bank_transaction_header"));
- }
- }
-}
-
-erpnext.accounts.ReconciliationRow = class ReconciliationRow {
- constructor(row, data) {
- this.data = data;
- this.row = row;
- this.make();
- this.bind_events();
- }
-
- make() {
- $(this.row).append(frappe.render_template("bank_transaction_row", this.data))
- }
-
- bind_events() {
- const me = this;
- $(me.row).on('click', '.clickable-section', function() {
- me.bank_entry = $(this).attr("data-name");
- me.show_dialog($(this).attr("data-name"));
- })
-
- $(me.row).on('click', '.new-reconciliation', function() {
- me.bank_entry = $(this).attr("data-name");
- me.show_dialog($(this).attr("data-name"));
- })
-
- $(me.row).on('click', '.new-payment', function() {
- me.bank_entry = $(this).attr("data-name");
- me.new_payment();
- })
-
- $(me.row).on('click', '.new-invoice', function() {
- me.bank_entry = $(this).attr("data-name");
- me.new_invoice();
- })
-
- $(me.row).on('click', '.new-expense', function() {
- me.bank_entry = $(this).attr("data-name");
- me.new_expense();
- })
- }
-
- new_payment() {
- const me = this;
- const paid_amount = me.data.credit > 0 ? me.data.credit : me.data.debit;
- const payment_type = me.data.credit > 0 ? "Receive": "Pay";
- const party_type = me.data.credit > 0 ? "Customer": "Supplier";
-
- frappe.new_doc("Payment Entry", {"payment_type": payment_type, "paid_amount": paid_amount,
- "party_type": party_type, "paid_from": me.data.bank_account})
- }
-
- new_invoice() {
- const me = this;
- const invoice_type = me.data.credit > 0 ? "Sales Invoice" : "Purchase Invoice";
-
- frappe.new_doc(invoice_type)
- }
-
- new_expense() {
- frappe.new_doc("Expense Claim")
- }
-
-
- show_dialog(data) {
- const me = this;
-
- frappe.db.get_value("Bank Account", me.data.bank_account, "account", (r) => {
- me.gl_account = r.account;
- })
-
- frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
- { bank_transaction: data, freeze: true, freeze_message: __("Finding linked payments") }
- ).then((result) => {
- me.make_dialog(result)
- })
- }
-
- make_dialog(data) {
- const me = this;
- me.selected_payment = null;
-
- const fields = [
- {
- fieldtype: 'Section Break',
- fieldname: 'section_break_1',
- label: __('Automatic Reconciliation')
- },
- {
- fieldtype: 'HTML',
- fieldname: 'payment_proposals'
- },
- {
- fieldtype: 'Section Break',
- fieldname: 'section_break_2',
- label: __('Search for a payment')
- },
- {
- fieldtype: 'Link',
- fieldname: 'payment_doctype',
- options: 'DocType',
- label: 'Payment DocType',
- get_query: () => {
- return {
- filters : {
- "name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Expense Claim"]]
- }
- }
- },
- },
- {
- fieldtype: 'Column Break',
- fieldname: 'column_break_1',
- },
- {
- fieldtype: 'Dynamic Link',
- fieldname: 'payment_entry',
- options: 'payment_doctype',
- label: 'Payment Document',
- get_query: () => {
- let dt = this.dialog.fields_dict.payment_doctype.value;
- if (dt === "Payment Entry") {
- return {
- query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.payment_entry_query",
- filters : {
- "bank_account": this.data.bank_account,
- "company": this.data.company
- }
- }
- } else if (dt === "Journal Entry") {
- return {
- query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.journal_entry_query",
- filters : {
- "bank_account": this.data.bank_account,
- "company": this.data.company
- }
- }
- } else if (dt === "Sales Invoice") {
- return {
- query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.sales_invoices_query"
- }
- } else if (dt === "Purchase Invoice") {
- return {
- filters : [
- ["Purchase Invoice", "ifnull(clearance_date, '')", "=", ""],
- ["Purchase Invoice", "docstatus", "=", 1],
- ["Purchase Invoice", "company", "=", this.data.company]
- ]
- }
- } else if (dt === "Expense Claim") {
- return {
- filters : [
- ["Expense Claim", "ifnull(clearance_date, '')", "=", ""],
- ["Expense Claim", "docstatus", "=", 1],
- ["Expense Claim", "company", "=", this.data.company]
- ]
- }
- }
- },
- onchange: function() {
- if (me.selected_payment !== this.value) {
- me.selected_payment = this.value;
- me.display_payment_details(this);
- }
- }
- },
- {
- fieldtype: 'Section Break',
- fieldname: 'section_break_3'
- },
- {
- fieldtype: 'HTML',
- fieldname: 'payment_details'
- },
- ];
-
- me.dialog = new frappe.ui.Dialog({
- title: __("Choose a corresponding payment"),
- fields: fields,
- size: "large"
- });
-
- const proposals_wrapper = me.dialog.fields_dict.payment_proposals.$wrapper;
- if (data && data.length > 0) {
- proposals_wrapper.append(frappe.render_template("linked_payment_header"));
- data.map(value => {
- proposals_wrapper.append(frappe.render_template("linked_payment_row", value))
- })
- } else {
- const empty_data_msg = __("ERPNext could not find any matching payment entry")
- proposals_wrapper.append(`
${empty_data_msg}
`)
- }
-
- $(me.dialog.body).on('click', '.reconciliation-btn', (e) => {
- const payment_entry = $(e.target).attr('data-name');
- const payment_doctype = $(e.target).attr('data-doctype');
- frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.reconcile',
- {bank_transaction: me.bank_entry, payment_doctype: payment_doctype, payment_name: payment_entry})
- .then((result) => {
- setTimeout(function(){
- erpnext.accounts.ReconciliationList.refresh();
- }, 2000);
- me.dialog.hide();
- })
- })
-
- me.dialog.show();
- }
-
- display_payment_details(event) {
- const me = this;
- if (event.value) {
- let dt = me.dialog.fields_dict.payment_doctype.value;
- me.dialog.fields_dict['payment_details'].$wrapper.empty();
- frappe.db.get_doc(dt, event.value)
- .then(doc => {
- let displayed_docs = []
- let payment = []
- if (dt === "Payment Entry") {
- payment.currency = doc.payment_type == "Receive" ? doc.paid_to_account_currency : doc.paid_from_account_currency;
- payment.doctype = dt
- payment.posting_date = doc.posting_date;
- payment.party = doc.party;
- payment.reference_no = doc.reference_no;
- payment.reference_date = doc.reference_date;
- payment.paid_amount = doc.paid_amount;
- payment.name = doc.name;
- displayed_docs.push(payment);
- } else if (dt === "Journal Entry") {
- doc.accounts.forEach(payment => {
- if (payment.account === me.gl_account) {
- payment.doctype = dt;
- payment.posting_date = doc.posting_date;
- payment.party = doc.pay_to_recd_from;
- payment.reference_no = doc.cheque_no;
- payment.reference_date = doc.cheque_date;
- payment.currency = payment.account_currency;
- payment.paid_amount = payment.credit > 0 ? payment.credit : payment.debit;
- payment.name = doc.name;
- displayed_docs.push(payment);
- }
- })
- } else if (dt === "Sales Invoice") {
- doc.payments.forEach(payment => {
- if (payment.clearance_date === null || payment.clearance_date === "") {
- payment.doctype = dt;
- payment.posting_date = doc.posting_date;
- payment.party = doc.customer;
- payment.reference_no = doc.remarks;
- payment.currency = doc.currency;
- payment.paid_amount = payment.amount;
- payment.name = doc.name;
- displayed_docs.push(payment);
- }
- })
- }
-
- const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper;
- details_wrapper.append(frappe.render_template("linked_payment_header"));
- displayed_docs.forEach(payment => {
- details_wrapper.append(frappe.render_template("linked_payment_row", payment));
- })
- })
- }
-
- }
-}
diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.json b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.json
deleted file mode 100644
index feea36860b1..00000000000
--- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "content": null,
- "creation": "2018-11-24 12:03:14.646669",
- "docstatus": 0,
- "doctype": "Page",
- "idx": 0,
- "modified": "2018-11-24 12:03:14.646669",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "bank-reconciliation",
- "owner": "Administrator",
- "page_name": "bank-reconciliation",
- "roles": [
- {
- "role": "System Manager"
- },
- {
- "role": "Accounts Manager"
- },
- {
- "role": "Accounts User"
- }
- ],
- "script": null,
- "standard": "Yes",
- "style": null,
- "system_page": 0,
- "title": "Bank Reconciliation"
-}
\ No newline at end of file
diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
deleted file mode 100644
index 8abe20c00a4..00000000000
--- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
+++ /dev/null
@@ -1,369 +0,0 @@
-# -*- 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)]])
-
- jea_side = "debit" if transaction.credit > 0 else "credit"
- journal_entries = frappe.db.sql(f"""
- 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.{jea_side}_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.{jea_side}_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:
- # Sequence Matcher does not handle None as input
- am_reference = am_match["reference_no"] or ""
- des_reference = des_match["reference_no"] or ""
-
- if difflib.SequenceMatcher(lambda x: x == " ", am_reference, des_reference).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()
-@frappe.validate_and_sanitize_search_inputs
-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()
-@frappe.validate_and_sanitize_search_inputs
-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()
-@frappe.validate_and_sanitize_search_inputs
-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
- }
- )
diff --git a/erpnext/accounts/page/bank_reconciliation/bank_transaction_header.html b/erpnext/accounts/page/bank_reconciliation/bank_transaction_header.html
deleted file mode 100644
index 94f183b793b..00000000000
--- a/erpnext/accounts/page/bank_reconciliation/bank_transaction_header.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
diff --git a/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html b/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html
deleted file mode 100644
index 742b84c63f5..00000000000
--- a/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
- {%= frappe.datetime.str_to_user(date) %}
-
-
- {{ description }}
-
-
- {%= format_currency(debit, currency) %}
-
-
- {%= format_currency(credit, currency) %}
-
-
- {{ currency }}
-
-
-
-
-
diff --git a/erpnext/accounts/page/bank_reconciliation/linked_payment_header.html b/erpnext/accounts/page/bank_reconciliation/linked_payment_header.html
deleted file mode 100644
index 4542c36e0dc..00000000000
--- a/erpnext/accounts/page/bank_reconciliation/linked_payment_header.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
diff --git a/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html b/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html
deleted file mode 100644
index bdbc9fce033..00000000000
--- a/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
- {{ name }}
-
-
- {% if (typeof reference_date !== "undefined") %}
- {%= frappe.datetime.str_to_user(reference_date) %}
- {% else %}
- {% if (typeof posting_date !== "undefined") %}
- {%= frappe.datetime.str_to_user(posting_date) %}
- {% endif %}
- {% endif %}
-
-
- {{ format_currency(paid_amount, currency) }}
-
-
- {% if (typeof party !== "undefined") %}
- {{ party }}
- {% endif %}
-
-
- {% if (typeof reference_no !== "undefined") %}
- {{ reference_no }}
- {% else %}
- {{ "" }}
- {% endif %}
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 60d1e20feaf..89a05b187d1 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -897,18 +897,18 @@ def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, wa
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
-
if not warehouse_account:
warehouse_account = get_warehouse_account_map(company)
- gle = get_voucherwise_gl_entries(stock_vouchers, posting_date)
+ precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit")) or 2
+ gle = get_voucherwise_gl_entries(stock_vouchers, posting_date)
for voucher_type, voucher_no in stock_vouchers:
existing_gle = gle.get((voucher_type, voucher_no), [])
- voucher_obj = frappe.get_doc(voucher_type, voucher_no)
+ voucher_obj = frappe.get_cached_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle:
- if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle):
+ if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
_delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
else:
@@ -954,16 +954,17 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
return gl_entries
-def compare_existing_and_expected_gle(existing_gle, expected_gle):
+def compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
matched = True
for entry in expected_gle:
account_existed = False
for e in existing_gle:
if entry.account == e.account:
account_existed = True
- if entry.account == e.account and entry.against_account == e.against_account \
- and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) \
- and (entry.debit != e.debit or entry.credit != e.credit):
+ if (entry.account == e.account and entry.against_account == e.against_account
+ and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center)
+ and ( flt(entry.debit, precision) != flt(e.debit, precision) or
+ flt(entry.credit, precision) != flt(e.credit, precision))):
matched = False
break
if not account_existed:
diff --git a/erpnext/assets/doctype/asset_category/asset_category.json b/erpnext/assets/doctype/asset_category/asset_category.json
index b7d12269c62..a25f5469039 100644
--- a/erpnext/assets/doctype/asset_category/asset_category.json
+++ b/erpnext/assets/doctype/asset_category/asset_category.json
@@ -19,7 +19,6 @@
],
"fields": [
{
- "depends_on": "eval:!doc.asset_category_name",
"fieldname": "asset_category_name",
"fieldtype": "Data",
"in_list_view": 1,
@@ -67,7 +66,7 @@
}
],
"links": [],
- "modified": "2021-01-22 12:31:14.425319",
+ "modified": "2021-02-24 15:05:38.621803",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Category",
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index ea9659ce017..e0031c9c699 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -74,7 +74,7 @@ class StockController(AccountsController):
gl_list = []
warehouse_with_no_account = []
- precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
+ precision = self.get_debit_field_precision()
for item_row in voucher_details:
sle_list = sle_map.get(item_row.name)
@@ -131,7 +131,13 @@ class StockController(AccountsController):
if frappe.db.get_value("Warehouse", wh, "company"):
frappe.throw(_("Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}.").format(wh, self.company))
- return process_gl_map(gl_list)
+ return process_gl_map(gl_list, precision=precision)
+
+ def get_debit_field_precision(self):
+ if not frappe.flags.debit_field_precision:
+ frappe.flags.debit_field_precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
+
+ return frappe.flags.debit_field_precision
def update_stock_ledger_entries(self, sle):
sle.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
@@ -244,7 +250,7 @@ class StockController(AccountsController):
.format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing"))
else:
- is_expense_account = frappe.db.get_value("Account",
+ is_expense_account = frappe.get_cached_value("Account",
item.get("expense_account"), "report_type")=="Profit and Loss"
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry") and not is_expense_account:
frappe.throw(_("Expense / Difference account ({0}) must be a 'Profit or Loss' account")
@@ -493,7 +499,7 @@ class StockController(AccountsController):
elif not is_reposting_pending():
check_if_stock_and_account_balance_synced(self.posting_date,
self.company, self.doctype, self.name)
-
+
def is_reposting_pending():
return frappe.db.exists("Repost Item Valuation",
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index 70c7f3fe5d7..21f6fee79c8 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -204,8 +204,8 @@ def new_bank_transaction(transaction):
"date": getdate(transaction["date"]),
"status": status,
"bank_account": bank_account,
- "debit": debit,
- "credit": credit,
+ "deposit": debit,
+ "withdrawal": credit,
"currency": transaction["iso_currency_code"],
"transaction_id": transaction["transaction_id"],
"reference_number": transaction["payment_meta"]["reference_number"],
diff --git a/erpnext/loan_management/dashboard_chart/loan_disbursements/loan_disbursements.json b/erpnext/loan_management/dashboard_chart/loan_disbursements/loan_disbursements.json
new file mode 100644
index 00000000000..b8abf210f81
--- /dev/null
+++ b/erpnext/loan_management/dashboard_chart/loan_disbursements/loan_disbursements.json
@@ -0,0 +1,29 @@
+{
+ "based_on": "disbursement_date",
+ "chart_name": "Loan Disbursements",
+ "chart_type": "Sum",
+ "creation": "2021-02-06 18:40:36.148470",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Loan Disbursement",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[[\"Loan Disbursement\",\"docstatus\",\"=\",\"1\",false]]",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "modified": "2021-02-06 18:40:49.308663",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Loan Disbursements",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "source": "",
+ "time_interval": "Daily",
+ "timeseries": 1,
+ "timespan": "Last Month",
+ "type": "Line",
+ "use_report_chart": 0,
+ "value_based_on": "disbursed_amount",
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/dashboard_chart/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/dashboard_chart/loan_interest_accrual/loan_interest_accrual.json
new file mode 100644
index 00000000000..aa0f78a2f6e
--- /dev/null
+++ b/erpnext/loan_management/dashboard_chart/loan_interest_accrual/loan_interest_accrual.json
@@ -0,0 +1,31 @@
+{
+ "based_on": "posting_date",
+ "chart_name": "Loan Interest Accrual",
+ "chart_type": "Sum",
+ "color": "#39E4A5",
+ "creation": "2021-02-18 20:07:04.843876",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Loan Interest Accrual",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[[\"Loan Interest Accrual\",\"docstatus\",\"=\",\"1\",false]]",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "last_synced_on": "2021-02-21 21:01:26.022634",
+ "modified": "2021-02-21 21:01:44.930712",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Loan Interest Accrual",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "source": "",
+ "time_interval": "Monthly",
+ "timeseries": 1,
+ "timespan": "Last Year",
+ "type": "Line",
+ "use_report_chart": 0,
+ "value_based_on": "interest_amount",
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/dashboard_chart/new_loans/new_loans.json b/erpnext/loan_management/dashboard_chart/new_loans/new_loans.json
new file mode 100644
index 00000000000..35bd43b994f
--- /dev/null
+++ b/erpnext/loan_management/dashboard_chart/new_loans/new_loans.json
@@ -0,0 +1,31 @@
+{
+ "based_on": "creation",
+ "chart_name": "New Loans",
+ "chart_type": "Count",
+ "color": "#449CF0",
+ "creation": "2021-02-06 16:59:27.509170",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Loan",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[[\"Loan\",\"docstatus\",\"=\",\"1\",false]]",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "last_synced_on": "2021-02-21 20:55:33.515025",
+ "modified": "2021-02-21 21:00:33.900821",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "New Loans",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "source": "",
+ "time_interval": "Daily",
+ "timeseries": 1,
+ "timespan": "Last Month",
+ "type": "Bar",
+ "use_report_chart": 0,
+ "value_based_on": "",
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/dashboard_chart/top_10_pledged_loan_securities/top_10_pledged_loan_securities.json b/erpnext/loan_management/dashboard_chart/top_10_pledged_loan_securities/top_10_pledged_loan_securities.json
new file mode 100644
index 00000000000..76c27b062d6
--- /dev/null
+++ b/erpnext/loan_management/dashboard_chart/top_10_pledged_loan_securities/top_10_pledged_loan_securities.json
@@ -0,0 +1,31 @@
+{
+ "based_on": "",
+ "chart_name": "Top 10 Pledged Loan Securities",
+ "chart_type": "Custom",
+ "color": "#EC864B",
+ "creation": "2021-02-06 22:02:46.284479",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[]",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "last_synced_on": "2021-02-21 21:00:57.043034",
+ "modified": "2021-02-21 21:01:10.048623",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Top 10 Pledged Loan Securities",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "source": "Top 10 Pledged Loan Securities",
+ "time_interval": "Yearly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Bar",
+ "use_report_chart": 0,
+ "value_based_on": "",
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/bank_statement_transaction_entry/__init__.py b/erpnext/loan_management/dashboard_chart_source/__init__.py
similarity index 100%
rename from erpnext/accounts/doctype/bank_statement_transaction_entry/__init__.py
rename to erpnext/loan_management/dashboard_chart_source/__init__.py
diff --git a/erpnext/accounts/doctype/bank_statement_transaction_invoice_item/__init__.py b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/__init__.py
similarity index 100%
rename from erpnext/accounts/doctype/bank_statement_transaction_invoice_item/__init__.py
rename to erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/__init__.py
diff --git a/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.js b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.js
new file mode 100644
index 00000000000..cf75cc8e41a
--- /dev/null
+++ b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.js
@@ -0,0 +1,14 @@
+frappe.provide('frappe.dashboards.chart_sources');
+
+frappe.dashboards.chart_sources["Top 10 Pledged Loan Securities"] = {
+ method: "erpnext.loan_management.dashboard_chart_source.top_10_pledged_loan_securities.top_10_pledged_loan_securities.get_data",
+ filters: [
+ {
+ fieldname: "company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company")
+ }
+ ]
+};
\ No newline at end of file
diff --git a/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.json b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.json
new file mode 100644
index 00000000000..42c9b1c335a
--- /dev/null
+++ b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.json
@@ -0,0 +1,13 @@
+{
+ "creation": "2021-02-06 22:01:01.332628",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart Source",
+ "idx": 0,
+ "modified": "2021-02-06 22:01:01.332628",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Top 10 Pledged Loan Securities",
+ "owner": "Administrator",
+ "source_name": "Top 10 Pledged Loan Securities ",
+ "timeseries": 0
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py
new file mode 100644
index 00000000000..6bb04401bed
--- /dev/null
+++ b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py
@@ -0,0 +1,76 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils.dashboard import cache_source
+from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applicant_wise_loan_security_exposure \
+ import get_loan_security_details
+from six import iteritems
+
+@frappe.whitelist()
+@cache_source
+def get_data(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
+ to_date = None, timespan = None, time_interval = None, heatmap_year = None):
+ if chart_name:
+ chart = frappe.get_doc('Dashboard Chart', chart_name)
+ else:
+ chart = frappe._dict(frappe.parse_json(chart))
+
+ filters = {}
+ current_pledges = {}
+
+ if filters:
+ filters = frappe.parse_json(filters)[0]
+
+ conditions = ""
+ labels = []
+ values = []
+
+ if filters.get('company'):
+ conditions = "AND company = %(company)s"
+
+ loan_security_details = get_loan_security_details()
+
+ unpledges = frappe._dict(frappe.db.sql("""
+ SELECT u.loan_security, sum(u.qty) as qty
+ FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
+ WHERE u.parent = up.name
+ AND up.status = 'Approved'
+ {conditions}
+ GROUP BY u.loan_security
+ """.format(conditions=conditions), filters, as_list=1))
+
+ pledges = frappe._dict(frappe.db.sql("""
+ SELECT p.loan_security, sum(p.qty) as qty
+ FROM `tabLoan Security Pledge` lp, `tabPledge`p
+ WHERE p.parent = lp.name
+ AND lp.status = 'Pledged'
+ {conditions}
+ GROUP BY p.loan_security
+ """.format(conditions=conditions), filters, as_list=1))
+
+ for security, qty in iteritems(pledges):
+ current_pledges.setdefault(security, qty)
+ current_pledges[security] -= unpledges.get(security, 0.0)
+
+ sorted_pledges = dict(sorted(current_pledges.items(), key=lambda item: item[1], reverse=True))
+
+ count = 0
+ for security, qty in iteritems(sorted_pledges):
+ values.append(qty * loan_security_details.get(security, {}).get('latest_price', 0))
+ labels.append(security)
+ count +=1
+
+ ## Just need top 10 securities
+ if count == 10:
+ break
+
+ return {
+ 'labels': labels,
+ 'datasets': [{
+ 'name': 'Top 10 Securities',
+ 'chartType': 'bar',
+ 'values': values
+ }]
+ }
\ No newline at end of file
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index e607d4f3cbf..83a813f947b 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -201,7 +201,9 @@ def request_loan_closure(loan, posting_date=None):
write_off_limit = frappe.get_value('Loan Type', loan_type, 'write_off_amount')
# checking greater than 0 as there may be some minor precision error
- if pending_amount < write_off_limit:
+ if not pending_amount:
+ frappe.db.set_value('Loan', loan, 'status', 'Loan Closure Requested')
+ elif pending_amount < write_off_limit:
# Auto create loan write off and update status as loan closure requested
write_off = make_loan_write_off(loan)
write_off.submit()
@@ -348,3 +350,13 @@ def validate_employee_currency_with_company_currency(applicant, company):
if employee_currency != company_currency:
frappe.throw(_("Loan cannot be repayed from salary for Employee {0} because salary is processed in currency {1}")
.format(applicant, employee_currency))
+
+@frappe.whitelist()
+def get_shortfall_applicants():
+ loans = frappe.get_all('Loan Security Shortfall', {'status': 'Pending'}, pluck='loan')
+ applicants = set(frappe.get_all('Loan', {'name': ('in', loans)}, pluck='name'))
+
+ return {
+ "value": len(applicants),
+ "fieldtype": "Int"
+ }
\ No newline at end of file
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index f3c9db62338..13a209418d1 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -547,7 +547,7 @@ class TestLoan(unittest.TestCase):
# 30 days - grace period
penalty_days = 30 - 4
- penalty_applicable_amount = flt(amounts['interest_amount']/2, 2)
+ penalty_applicable_amount = flt(amounts['interest_amount']/2)
penalty_amount = flt((((penalty_applicable_amount * 25) / 100) * penalty_days), 2)
process = process_loan_interest_accrual_for_demand_loans(posting_date = '2019-11-30')
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py
index e59db4c12dc..9c0147e55ba 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.py
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.py
@@ -197,7 +197,7 @@ def get_proposed_pledge(securities):
security.qty = cint(security.amount/security.loan_security_price)
security.amount = security.qty * security.loan_security_price
- security.post_haircut_amount = security.amount - (security.amount * security.haircut/100)
+ security.post_haircut_amount = cint(security.amount - (security.amount * security.haircut/100))
maximum_loan_amount += security.post_haircut_amount
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
index 7d7992d40ae..7978350adf8 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
@@ -246,7 +246,5 @@ def get_per_day_interest(principal_amount, rate_of_interest, posting_date=None):
if not posting_date:
posting_date = getdate()
- precision = cint(frappe.db.get_default("currency_precision")) or 2
-
- return flt((principal_amount * rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100), precision)
+ return flt((principal_amount * rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100))
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index ac30c91b670..bac06c4e9e6 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -81,8 +81,8 @@ class LoanRepayment(AccountsController):
last_accrual_date = get_last_accrual_date(self.against_loan)
# get posting date upto which interest has to be accrued
- per_day_interest = flt(get_per_day_interest(self.pending_principal_amount,
- self.rate_of_interest, self.posting_date), 2)
+ per_day_interest = get_per_day_interest(self.pending_principal_amount,
+ self.rate_of_interest, self.posting_date)
no_of_days = flt(flt(self.total_interest_paid - self.interest_payable,
precision)/per_day_interest, 0) - 1
@@ -105,8 +105,6 @@ class LoanRepayment(AccountsController):
})
def update_paid_amount(self):
- precision = cint(frappe.db.get_default("currency_precision")) or 2
-
loan = frappe.get_doc("Loan", self.against_loan)
for payment in self.repayment_details:
@@ -114,7 +112,7 @@ class LoanRepayment(AccountsController):
SET paid_principal_amount = `paid_principal_amount` + %s,
paid_interest_amount = `paid_interest_amount` + %s
WHERE name = %s""",
- (flt(payment.paid_principal_amount, precision), flt(payment.paid_interest_amount, precision), payment.loan_interest_accrual))
+ (flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual))
frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s
WHERE name = %s """, (loan.total_amount_paid + self.amount_paid,
@@ -148,8 +146,6 @@ class LoanRepayment(AccountsController):
frappe.db.set_value("Loan", self.against_loan, "status", "Disbursed")
def allocate_amounts(self, repayment_details):
- precision = cint(frappe.db.get_default("currency_precision")) or 2
-
self.set('repayment_details', [])
self.principal_amount_paid = 0
total_interest_paid = 0
@@ -185,21 +181,18 @@ class LoanRepayment(AccountsController):
# no of days for which to accrue interest
# Interest can only be accrued for an entire day and not partial
if interest_paid > repayment_details['unaccrued_interest']:
- per_day_interest = flt(get_per_day_interest(self.pending_principal_amount,
- self.rate_of_interest, self.posting_date), precision)
interest_paid -= repayment_details['unaccrued_interest']
total_interest_paid += repayment_details['unaccrued_interest']
else:
# get no of days for which interest can be paid
- per_day_interest = flt(get_per_day_interest(self.pending_principal_amount,
- self.rate_of_interest, self.posting_date), precision)
+ per_day_interest = get_per_day_interest(self.pending_principal_amount,
+ self.rate_of_interest, self.posting_date)
no_of_days = cint(interest_paid/per_day_interest)
total_interest_paid += no_of_days * per_day_interest
interest_paid -= no_of_days * per_day_interest
self.total_interest_paid = total_interest_paid
-
if interest_paid:
self.principal_amount_paid += interest_paid
@@ -369,7 +362,7 @@ def get_amounts(amounts, against_loan, posting_date):
if pending_days > 0:
principal_amount = flt(pending_principal_amount, precision)
per_day_interest = get_per_day_interest(principal_amount, loan_type_details.rate_of_interest, posting_date)
- unaccrued_interest += (pending_days * flt(per_day_interest, precision))
+ unaccrued_interest += (pending_days * per_day_interest)
amounts["pending_principal_amount"] = flt(pending_principal_amount, precision)
amounts["payable_principal_amount"] = flt(payable_principal_amount, precision)
diff --git a/erpnext/loan_management/loan_management_dashboard/loan_dashboard/loan_dashboard.json b/erpnext/loan_management/loan_management_dashboard/loan_dashboard/loan_dashboard.json
new file mode 100644
index 00000000000..e060253d34c
--- /dev/null
+++ b/erpnext/loan_management/loan_management_dashboard/loan_dashboard/loan_dashboard.json
@@ -0,0 +1,70 @@
+{
+ "cards": [
+ {
+ "card": "New Loans"
+ },
+ {
+ "card": "Active Loans"
+ },
+ {
+ "card": "Closed Loans"
+ },
+ {
+ "card": "Total Disbursed"
+ },
+ {
+ "card": "Open Loan Applications"
+ },
+ {
+ "card": "New Loan Applications"
+ },
+ {
+ "card": "Total Sanctioned Amount"
+ },
+ {
+ "card": "Active Securities"
+ },
+ {
+ "card": "Applicants With Unpaid Shortfall"
+ },
+ {
+ "card": "Total Shortfall Amount"
+ },
+ {
+ "card": "Total Repayment"
+ },
+ {
+ "card": "Total Write Off"
+ }
+ ],
+ "charts": [
+ {
+ "chart": "New Loans",
+ "width": "Half"
+ },
+ {
+ "chart": "Loan Disbursements",
+ "width": "Half"
+ },
+ {
+ "chart": "Top 10 Pledged Loan Securities",
+ "width": "Half"
+ },
+ {
+ "chart": "Loan Interest Accrual",
+ "width": "Half"
+ }
+ ],
+ "creation": "2021-02-06 16:52:43.484752",
+ "dashboard_name": "Loan Dashboard",
+ "docstatus": 0,
+ "doctype": "Dashboard",
+ "idx": 0,
+ "is_default": 0,
+ "is_standard": 1,
+ "modified": "2021-02-21 20:53:47.531699",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Loan Dashboard",
+ "owner": "Administrator"
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/number_card/active_loans/active_loans.json b/erpnext/loan_management/number_card/active_loans/active_loans.json
new file mode 100644
index 00000000000..7e0db472882
--- /dev/null
+++ b/erpnext/loan_management/number_card/active_loans/active_loans.json
@@ -0,0 +1,23 @@
+{
+ "aggregate_function_based_on": "",
+ "creation": "2021-02-06 17:10:26.132493",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Loan",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[[\"Loan\",\"docstatus\",\"=\",\"1\",false],[\"Loan\",\"status\",\"in\",[\"Disbursed\",\"Partially Disbursed\",null],false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "label": "Active Loans",
+ "modified": "2021-02-06 17:29:20.304087",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Active Loans",
+ "owner": "Administrator",
+ "report_function": "Sum",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/number_card/active_securities/active_securities.json b/erpnext/loan_management/number_card/active_securities/active_securities.json
new file mode 100644
index 00000000000..298e41061a8
--- /dev/null
+++ b/erpnext/loan_management/number_card/active_securities/active_securities.json
@@ -0,0 +1,23 @@
+{
+ "aggregate_function_based_on": "",
+ "creation": "2021-02-06 19:07:21.344199",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Loan Security",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[[\"Loan Security\",\"disabled\",\"=\",0,false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "label": "Active Securities",
+ "modified": "2021-02-06 19:07:26.671516",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Active Securities",
+ "owner": "Administrator",
+ "report_function": "Sum",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/number_card/applicants_with_unpaid_shortfall/applicants_with_unpaid_shortfall.json b/erpnext/loan_management/number_card/applicants_with_unpaid_shortfall/applicants_with_unpaid_shortfall.json
new file mode 100644
index 00000000000..3b9eba15536
--- /dev/null
+++ b/erpnext/loan_management/number_card/applicants_with_unpaid_shortfall/applicants_with_unpaid_shortfall.json
@@ -0,0 +1,21 @@
+{
+ "creation": "2021-02-07 18:55:12.632616",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "filters_json": "null",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "label": "Applicants With Unpaid Shortfall",
+ "method": "erpnext.loan_management.doctype.loan.loan.get_shortfall_applicants",
+ "modified": "2021-02-07 21:46:27.369795",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Applicants With Unpaid Shortfall",
+ "owner": "Administrator",
+ "report_function": "Sum",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Custom"
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/number_card/closed_loans/closed_loans.json b/erpnext/loan_management/number_card/closed_loans/closed_loans.json
new file mode 100644
index 00000000000..c2f22442653
--- /dev/null
+++ b/erpnext/loan_management/number_card/closed_loans/closed_loans.json
@@ -0,0 +1,23 @@
+{
+ "aggregate_function_based_on": "",
+ "creation": "2021-02-21 19:51:49.261813",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Loan",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[[\"Loan\",\"docstatus\",\"=\",\"1\",false],[\"Loan\",\"status\",\"=\",\"Closed\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "label": "Closed Loans",
+ "modified": "2021-02-21 19:51:54.087903",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Closed Loans",
+ "owner": "Administrator",
+ "report_function": "Sum",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/number_card/last_interest_accrual/last_interest_accrual.json b/erpnext/loan_management/number_card/last_interest_accrual/last_interest_accrual.json
new file mode 100644
index 00000000000..65c8ce67d21
--- /dev/null
+++ b/erpnext/loan_management/number_card/last_interest_accrual/last_interest_accrual.json
@@ -0,0 +1,21 @@
+{
+ "creation": "2021-02-07 21:57:14.758007",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "filters_json": "null",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "label": "Last Interest Accrual",
+ "method": "erpnext.loan_management.doctype.loan.loan.get_last_accrual_date",
+ "modified": "2021-02-07 21:59:47.525197",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Last Interest Accrual",
+ "owner": "Administrator",
+ "report_function": "Sum",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Custom"
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/number_card/new_loan_applications/new_loan_applications.json b/erpnext/loan_management/number_card/new_loan_applications/new_loan_applications.json
new file mode 100644
index 00000000000..7e655ff35c7
--- /dev/null
+++ b/erpnext/loan_management/number_card/new_loan_applications/new_loan_applications.json
@@ -0,0 +1,23 @@
+{
+ "aggregate_function_based_on": "",
+ "creation": "2021-02-06 17:59:10.051269",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Loan Application",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[[\"Loan Application\",\"docstatus\",\"=\",\"1\",false],[\"Loan Application\",\"creation\",\"Timespan\",\"today\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "label": "New Loan Applications",
+ "modified": "2021-02-06 17:59:21.880979",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "New Loan Applications",
+ "owner": "Administrator",
+ "report_function": "Sum",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/number_card/new_loans/new_loans.json b/erpnext/loan_management/number_card/new_loans/new_loans.json
new file mode 100644
index 00000000000..424f0f14958
--- /dev/null
+++ b/erpnext/loan_management/number_card/new_loans/new_loans.json
@@ -0,0 +1,23 @@
+{
+ "aggregate_function_based_on": "",
+ "creation": "2021-02-06 17:56:34.624031",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Loan",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[[\"Loan\",\"docstatus\",\"=\",\"1\",false],[\"Loan\",\"creation\",\"Timespan\",\"today\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "label": "New Loans",
+ "modified": "2021-02-06 17:58:20.209166",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "New Loans",
+ "owner": "Administrator",
+ "report_function": "Sum",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/number_card/open_loan_applications/open_loan_applications.json b/erpnext/loan_management/number_card/open_loan_applications/open_loan_applications.json
new file mode 100644
index 00000000000..1d5e84ed7f0
--- /dev/null
+++ b/erpnext/loan_management/number_card/open_loan_applications/open_loan_applications.json
@@ -0,0 +1,23 @@
+{
+ "aggregate_function_based_on": "",
+ "creation": "2021-02-06 17:23:32.509899",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Loan Application",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[[\"Loan Application\",\"docstatus\",\"=\",\"1\",false],[\"Loan Application\",\"status\",\"=\",\"Open\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "label": "Open Loan Applications",
+ "modified": "2021-02-06 17:29:09.761011",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Open Loan Applications",
+ "owner": "Administrator",
+ "report_function": "Sum",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/number_card/total_disbursed/total_disbursed.json b/erpnext/loan_management/number_card/total_disbursed/total_disbursed.json
new file mode 100644
index 00000000000..4a3f8699a04
--- /dev/null
+++ b/erpnext/loan_management/number_card/total_disbursed/total_disbursed.json
@@ -0,0 +1,23 @@
+{
+ "aggregate_function_based_on": "disbursed_amount",
+ "creation": "2021-02-06 16:52:19.505462",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Loan Disbursement",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[[\"Loan Disbursement\",\"docstatus\",\"=\",\"1\",false]]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "label": "Total Disbursed Amount",
+ "modified": "2021-02-06 17:29:38.453870",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Total Disbursed",
+ "owner": "Administrator",
+ "report_function": "Sum",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/number_card/total_repayment/total_repayment.json b/erpnext/loan_management/number_card/total_repayment/total_repayment.json
new file mode 100644
index 00000000000..38de42b89c8
--- /dev/null
+++ b/erpnext/loan_management/number_card/total_repayment/total_repayment.json
@@ -0,0 +1,24 @@
+{
+ "aggregate_function_based_on": "amount_paid",
+ "color": "#29CD42",
+ "creation": "2021-02-21 19:27:45.989222",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Loan Repayment",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[[\"Loan Repayment\",\"docstatus\",\"=\",\"1\",false]]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "label": "Total Repayment",
+ "modified": "2021-02-21 19:34:59.656546",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Total Repayment",
+ "owner": "Administrator",
+ "report_function": "Sum",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/number_card/total_sanctioned_amount/total_sanctioned_amount.json b/erpnext/loan_management/number_card/total_sanctioned_amount/total_sanctioned_amount.json
new file mode 100644
index 00000000000..dfb9d24e925
--- /dev/null
+++ b/erpnext/loan_management/number_card/total_sanctioned_amount/total_sanctioned_amount.json
@@ -0,0 +1,23 @@
+{
+ "aggregate_function_based_on": "loan_amount",
+ "creation": "2021-02-06 17:05:04.704162",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Loan",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[[\"Loan\",\"docstatus\",\"=\",\"1\",false],[\"Loan\",\"status\",\"=\",\"Sanctioned\",false]]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "label": "Total Sanctioned Amount",
+ "modified": "2021-02-06 17:29:29.930557",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Total Sanctioned Amount",
+ "owner": "Administrator",
+ "report_function": "Sum",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/number_card/total_shortfall_amount/total_shortfall_amount.json b/erpnext/loan_management/number_card/total_shortfall_amount/total_shortfall_amount.json
new file mode 100644
index 00000000000..aa6b0937323
--- /dev/null
+++ b/erpnext/loan_management/number_card/total_shortfall_amount/total_shortfall_amount.json
@@ -0,0 +1,23 @@
+{
+ "aggregate_function_based_on": "shortfall_amount",
+ "creation": "2021-02-09 08:07:20.096995",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Loan Security Shortfall",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "label": "Total Unpaid Shortfall Amount",
+ "modified": "2021-02-09 08:09:00.355547",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Total Shortfall Amount",
+ "owner": "Administrator",
+ "report_function": "Sum",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/number_card/total_write_off/total_write_off.json b/erpnext/loan_management/number_card/total_write_off/total_write_off.json
new file mode 100644
index 00000000000..c85169acf8d
--- /dev/null
+++ b/erpnext/loan_management/number_card/total_write_off/total_write_off.json
@@ -0,0 +1,24 @@
+{
+ "aggregate_function_based_on": "write_off_amount",
+ "color": "#CB2929",
+ "creation": "2021-02-21 19:48:29.004429",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Loan Write Off",
+ "dynamic_filters_json": "[]",
+ "filters_json": "[[\"Loan Write Off\",\"docstatus\",\"=\",\"1\",false]]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "label": "Total Write Off",
+ "modified": "2021-02-21 19:48:58.604159",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Total Write Off",
+ "owner": "Administrator",
+ "report_function": "Sum",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py
index ab586bc09c4..0ccd149e5fb 100644
--- a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py
+++ b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py
@@ -36,7 +36,7 @@ def get_columns(filters):
def get_data(filters):
data = []
- loan_security_details = get_loan_security_details(filters)
+ loan_security_details = get_loan_security_details()
pledge_values, total_value_map, applicant_type_map = get_applicant_wise_total_loan_security_qty(filters,
loan_security_details)
@@ -64,7 +64,7 @@ def get_data(filters):
return data
-def get_loan_security_details(filters):
+def get_loan_security_details():
security_detail_map = {}
loan_security_price_map = {}
lsp_validity_map = {}
diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
index a3e69bbfbfc..0f72c3cce7c 100644
--- a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
+++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
@@ -171,7 +171,7 @@ def get_loan_wise_pledges(filters):
return current_pledges
def get_loan_wise_security_value(filters, current_pledges):
- loan_security_details = get_loan_security_details(filters)
+ loan_security_details = get_loan_security_details()
loan_wise_security_value = {}
for key in current_pledges:
diff --git a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py
index adc8013c686..887a86a46c5 100644
--- a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py
+++ b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py
@@ -35,7 +35,7 @@ def get_columns(filters):
def get_data(filters):
data = []
- loan_security_details = get_loan_security_details(filters)
+ loan_security_details = get_loan_security_details()
current_pledges, total_portfolio_value = get_company_wise_loan_security_details(filters, loan_security_details)
currency = erpnext.get_company_currency(filters.get('company'))
@@ -76,7 +76,7 @@ def get_company_wise_loan_security_details(filters, loan_security_details):
if qty:
security_wise_map[key[1]]['applicant_count'] += 1
- total_portfolio_value += flt(qty * loan_security_details.get(key[1])['latest_price'])
+ total_portfolio_value += flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0))
return security_wise_map, total_portfolio_value
diff --git a/erpnext/loan_management/workspace/loan_management/loan_management.json b/erpnext/loan_management/workspace/loan_management/loan_management.json
index 2e8b5bf5b31..18559dceef7 100644
--- a/erpnext/loan_management/workspace/loan_management/loan_management.json
+++ b/erpnext/loan_management/workspace/loan_management/loan_management.json
@@ -10,6 +10,7 @@
"hide_custom": 0,
"icon": "loan",
"idx": 0,
+ "is_default": 0,
"is_standard": 1,
"label": "Loan Management",
"links": [
@@ -219,7 +220,7 @@
"type": "Link"
}
],
- "modified": "2021-01-12 11:27:56.079724",
+ "modified": "2021-02-18 17:31:53.586508",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Management",
@@ -239,6 +240,12 @@
"label": "Loan",
"link_to": "Loan",
"type": "DocType"
+ },
+ {
+ "doc_view": "",
+ "label": "Dashboard",
+ "link_to": "Loan Dashboard",
+ "type": "Dashboard"
}
]
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
index f7b407b7922..ffd9242e1b8 100644
--- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
+++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
@@ -88,11 +88,11 @@ def get_bom_stock(filters):
GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1)
def get_manufacturer_records():
- details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no, parent"])
+ details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"])
manufacture_details = frappe._dict()
for detail in details:
dic = manufacture_details.setdefault(detail.get('parent'), {})
dic.setdefault('manufacturer', []).append(detail.get('manufacturer'))
dic.setdefault('manufacturer_part', []).append(detail.get('manufacturer_part_no'))
- return manufacture_details
\ No newline at end of file
+ return manufacture_details
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 01d9ad32e77..ba31feeefc1 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -754,4 +754,5 @@ erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes
erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021
erpnext.patches.v12_0.add_state_code_for_ladakh
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
-erpnext.patches.v13_0.update_vehicle_no_reqd_condition
\ No newline at end of file
+erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
+erpnext.patches.v13_0.update_vehicle_no_reqd_condition
diff --git a/erpnext/patches/v11_1/update_bank_transaction_status.py b/erpnext/patches/v11_1/update_bank_transaction_status.py
index 1acdfcccf9f..544bc5e6911 100644
--- a/erpnext/patches/v11_1/update_bank_transaction_status.py
+++ b/erpnext/patches/v11_1/update_bank_transaction_status.py
@@ -7,9 +7,20 @@ import frappe
def execute():
frappe.reload_doc("accounts", "doctype", "bank_transaction")
- frappe.db.sql(""" UPDATE `tabBank Transaction`
- SET status = 'Reconciled'
- WHERE
- status = 'Settled' and (debit = allocated_amount or credit = allocated_amount)
- and ifnull(allocated_amount, 0) > 0
- """)
\ No newline at end of file
+ bank_transaction_fields = frappe.get_meta("Bank Transaction").get_valid_columns()
+
+ if 'debit' in bank_transaction_fields:
+ frappe.db.sql(""" UPDATE `tabBank Transaction`
+ SET status = 'Reconciled'
+ WHERE
+ status = 'Settled' and (debit = allocated_amount or credit = allocated_amount)
+ and ifnull(allocated_amount, 0) > 0
+ """)
+
+ elif 'deposit' in bank_transaction_fields:
+ frappe.db.sql(""" UPDATE `tabBank Transaction`
+ SET status = 'Reconciled'
+ WHERE
+ status = 'Settled' and (deposit = allocated_amount or withdrawal = allocated_amount)
+ and ifnull(allocated_amount, 0) > 0
+ """)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py
new file mode 100644
index 00000000000..af1f6e7ec17
--- /dev/null
+++ b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+from frappe.model.utils.rename_field import rename_field
+
+def execute():
+ doctypes = [
+ "Bank Statement Settings",
+ "Bank Statement Settings Item",
+ "Bank Statement Transaction Entry",
+ "Bank Statement Transaction Invoice Item",
+ "Bank Statement Transaction Payment Item",
+ "Bank Statement Transaction Settings Item",
+ "Bank Statement Transaction Settings",
+ ]
+
+ for doctype in doctypes:
+ frappe.delete_doc("DocType", doctype, force=1)
+
+ frappe.delete_doc("Page", "bank-reconciliation", force=1)
+
+ rename_field("Bank Transaction", "debit", "deposit")
+ rename_field("Bank Transaction", "credit", "withdrawal")
diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
index f60e0d3036c..ca04e8acc22 100644
--- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
+++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
@@ -1,14 +1,30 @@
import frappe
from frappe import _
+from frappe.utils import getdate, get_time
from erpnext.stock.stock_ledger import update_entries_after
from erpnext.accounts.utils import update_gl_entries_after
def execute():
- data = frappe.db.sql(''' SELECT name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time
- from `tabStock Ledger Entry` where creation > '2020-12-26 12:58:55.903836' and is_cancelled = 0
- order by timestamp(posting_date, posting_time) asc, creation asc''', as_dict=1)
+ frappe.reload_doc('stock', 'doctype', 'repost_item_valuation')
- for index, d in enumerate(data):
+ reposting_project_deployed_on = frappe.db.get_value("DocType", "Repost Item Valuation", "creation")
+
+ data = frappe.db.sql('''
+ SELECT
+ name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time
+ FROM
+ `tabStock Ledger Entry`
+ WHERE
+ creation > %s
+ and is_cancelled = 0
+ ORDER BY timestamp(posting_date, posting_time) asc, creation asc
+ ''', reposting_project_deployed_on, as_dict=1)
+
+ frappe.db.auto_commit_on_many_writes = 1
+ print("Reposting Stock Ledger Entries...")
+ total_sle = len(data)
+ i = 0
+ for d in data:
update_entries_after({
"item_code": d.item_code,
"warehouse": d.warehouse,
@@ -19,9 +35,16 @@ def execute():
"sle_id": d.name
}, allow_negative_stock=True)
- frappe.db.auto_commit_on_many_writes = 1
+ i += 1
+ if i%100 == 0:
+ print(i, "/", total_sle)
+
+
+ print("Reposting General Ledger Entries...")
+ posting_date = getdate(reposting_project_deployed_on)
+ posting_time = get_time(reposting_project_deployed_on)
for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}):
- update_gl_entries_after('2020-12-25', '01:58:55', company=row.name)
+ update_gl_entries_after(posting_date, posting_time, company=row.name)
- frappe.db.auto_commit_on_many_writes = 0
\ No newline at end of file
+ frappe.db.auto_commit_on_many_writes = 0
diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js
index 3570a0f2be4..077011ace07 100644
--- a/erpnext/projects/doctype/project/project.js
+++ b/erpnext/projects/doctype/project/project.js
@@ -75,24 +75,27 @@ frappe.ui.form.on("Project", {
frm.add_custom_button(__('Cancelled'), () => {
frm.events.set_status(frm, 'Cancelled');
}, __('Set Status'));
- }
- if (frappe.model.can_read("Task")) {
- frm.add_custom_button(__("Gantt Chart"), function () {
- frappe.route_options = {
- "project": frm.doc.name
- };
- frappe.set_route("List", "Task", "Gantt");
- });
- frm.add_custom_button(__("Kanban Board"), () => {
- frappe.call('erpnext.projects.doctype.project.project.create_kanban_board_if_not_exists', {
- project: frm.doc.project_name
- }).then(() => {
- frappe.set_route('List', 'Task', 'Kanban', frm.doc.project_name);
+ if (frappe.model.can_read("Task")) {
+ frm.add_custom_button(__("Gantt Chart"), function () {
+ frappe.route_options = {
+ "project": frm.doc.name
+ };
+ frappe.set_route("List", "Task", "Gantt");
});
- });
+
+ frm.add_custom_button(__("Kanban Board"), () => {
+ frappe.call('erpnext.projects.doctype.project.project.create_kanban_board_if_not_exists', {
+ project: frm.doc.project_name
+ }).then(() => {
+ frappe.set_route('List', 'Task', 'Kanban', frm.doc.project_name);
+ });
+ });
+ }
}
+
+
},
create_duplicate: function(frm) {
@@ -135,4 +138,4 @@ function open_form(frm, doctype, child_doctype, parentfield) {
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
});
-}
\ No newline at end of file
+}
diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py
index d85c82612a2..62905385a31 100644
--- a/erpnext/projects/doctype/project/test_project.py
+++ b/erpnext/projects/doctype/project/test_project.py
@@ -37,7 +37,7 @@ class TestProject(unittest.TestCase):
task1 = task_exists("Test Template Task Parent")
if not task1:
- task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=1)
+ task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=4)
task2 = task_exists("Test Template Task Child 1")
if not task2:
@@ -52,7 +52,7 @@ class TestProject(unittest.TestCase):
tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks', 'name', 'parent_task'], dict(project=project.name), order_by='creation asc')
self.assertEqual(tasks[0].subject, 'Test Template Task Parent')
- self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 1))
+ self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 4))
self.assertEqual(tasks[1].subject, 'Test Template Task Child 1')
self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 1, 3))
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index a2095c95d51..71163485d23 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -30,6 +30,7 @@ class Task(NestedSet):
def validate(self):
self.validate_dates()
+ self.validate_parent_expected_end_date()
self.validate_parent_project_dates()
self.validate_progress()
self.validate_status()
@@ -45,6 +46,12 @@ class Task(NestedSet):
frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Actual Start Date"), \
frappe.bold("Actual End Date")))
+ def validate_parent_expected_end_date(self):
+ if self.parent_task:
+ parent_exp_end_date = frappe.db.get_value("Task", self.parent_task, "exp_end_date")
+ if parent_exp_end_date and getdate(self.get("exp_end_date")) > getdate(parent_exp_end_date):
+ frappe.throw(_("Expected End Date should be less than or equal to parent task's Expected End Date {0}.").format(getdate(parent_exp_end_date)))
+
def validate_parent_project_dates(self):
if not self.project or frappe.flags.in_test:
return
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index 73262382739..7a3cb838a99 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -61,5 +61,10 @@
"selling/page/point_of_sale/pos_past_order_list.js",
"selling/page/point_of_sale/pos_past_order_summary.js",
"selling/page/point_of_sale/pos_controller.js"
+ ],
+ "js/bank-reconciliation-tool.min.js": [
+ "public/js/bank_reconciliation_tool/data_table_manager.js",
+ "public/js/bank_reconciliation_tool/number_card.js",
+ "public/js/bank_reconciliation_tool/dialog_manager.js"
]
}
diff --git a/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js
new file mode 100644
index 00000000000..5bb58faf2fc
--- /dev/null
+++ b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js
@@ -0,0 +1,220 @@
+frappe.provide("erpnext.accounts.bank_reconciliation");
+
+erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
+ constructor(opts) {
+ Object.assign(this, opts);
+ this.dialog_manager = new erpnext.accounts.bank_reconciliation.DialogManager(
+ this.company,
+ this.bank_account
+ );
+ this.make_dt();
+ }
+
+ make_dt() {
+ var me = this;
+ frappe.call({
+ method:
+ "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_bank_transactions",
+ args: {
+ bank_account: this.bank_account,
+ },
+ callback: function (response) {
+ me.format_data(response.message);
+ me.get_dt_columns();
+ me.get_datatable();
+ me.set_listeners();
+ },
+ });
+ }
+
+ get_dt_columns() {
+ this.columns = [
+ {
+ name: "Date",
+ editable: false,
+ width: 100,
+ },
+
+ {
+ name: "Party Type",
+ editable: false,
+ width: 95,
+ },
+ {
+ name: "Party",
+ editable: false,
+ width: 100,
+ },
+ {
+ name: "Description",
+ editable: false,
+ width: 350,
+ },
+ {
+ name: "Deposit",
+ editable: false,
+ width: 100,
+ format: (value) =>
+ "
" +
+ format_currency(value, this.currency) +
+ "",
+ },
+ {
+ name: "Withdrawal",
+ editable: false,
+ width: 100,
+ format: (value) =>
+ "
" +
+ format_currency(value, this.currency) +
+ "",
+ },
+ {
+ name: "Unallocated Amount",
+ editable: false,
+ width: 100,
+ format: (value) =>
+ "
" +
+ format_currency(value, this.currency) +
+ "",
+ },
+ {
+ name: "Reference Number",
+ editable: false,
+ width: 140,
+ },
+ {
+ name: "Actions",
+ editable: false,
+ sortable: false,
+ focusable: false,
+ dropdown: false,
+ width: 80,
+ },
+ ];
+ }
+
+ format_data(transactions) {
+ this.transactions = [];
+ if (transactions[0]) {
+ this.currency = transactions[0]["currency"];
+ }
+ this.transaction_dt_map = {};
+ let length;
+ transactions.forEach((row) => {
+ length = this.transactions.push(this.format_row(row));
+ this.transaction_dt_map[row["name"]] = length - 1;
+ });
+ }
+
+ format_row(row) {
+ return [
+ row["date"],
+ row["party_type"],
+ row["party"],
+ row["description"],
+ row["deposit"],
+ row["withdrawal"],
+ row["unallocated_amount"],
+ row["reference_number"],
+ `
+