From af1f46f2d97dbdcfbd1ee020ac79072edcc375fa Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 7 Aug 2020 19:44:20 +0530 Subject: [PATCH 01/10] fix: Add default billing address for purchase documents --- erpnext/public/js/controllers/transaction.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 4e50f3d7f6..862b6fbf9b 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -781,10 +781,23 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ else var date = this.frm.doc.transaction_date; if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") && - in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)){ + in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) { erpnext.utils.get_shipping_address(this.frm, function(){ set_party_account(set_pricing); }) + + // Get default company billing address in Purchase Invoice, Order and Receipt + frappe.call({ + 'method': 'frappe.contacts.doctype.address.address.get_default_address', + 'args': { + 'doctype': 'Company', + 'name': this.frm.doc.company + }, + 'callback': function(r) { + me.frm.set_value('billing_address', r.message); + } + }); + } else { set_party_account(set_pricing); } From 069a54e5c38c10f8e97d16e4e279119adea69423 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 10 Aug 2020 16:01:01 +0530 Subject: [PATCH 02/10] fix: Cancellation of accounting transactions within closed accounting period --- erpnext/accounts/general_ledger.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index cf3deb828f..01d3903d28 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -45,8 +45,8 @@ def validate_accounting_period(gl_map): }, as_dict=1) if accounting_periods: - frappe.throw(_("You can't create accounting entries in the closed accounting period {0}") - .format(accounting_periods[0].name), ClosedAccountingPeriod) + frappe.throw(_("You cannot create or cancel any accounting entries with in the closed Accounting Period {0}") + .format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod) def process_gl_map(gl_map, merge_entries=True): if merge_entries: @@ -301,8 +301,9 @@ def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None, }) if gl_entries: - set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no']) + validate_accounting_period(gl_entries) check_freezing_date(gl_entries[0]["posting_date"], adv_adj) + set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no']) for entry in gl_entries: entry['name'] = None @@ -342,7 +343,7 @@ def set_as_cancel(voucher_type, voucher_no): """ Set is_cancelled=1 in all original gl entries for the voucher """ - frappe.db.sql("""update `tabGL Entry` set is_cancelled = 1, + frappe.db.sql("""UPDATE `tabGL Entry` SET is_cancelled = 1, modified=%s, modified_by=%s where voucher_type=%s and voucher_no=%s and is_cancelled = 0""", (now(), frappe.session.user, voucher_type, voucher_no)) From aaeb3980bcdc0df493c52d20c4440b71f4ad4af2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 19 Aug 2020 15:13:30 +0530 Subject: [PATCH 03/10] feat: JSON download for HSN wise outward summary --- .../hsn_wise_summary_of_outward_supplies.js | 23 +++++ .../hsn_wise_summary_of_outward_supplies.py | 95 +++++++++++++++++-- 2 files changed, 108 insertions(+), 10 deletions(-) diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.js b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.js index dfdf9dc095..b757d53aa2 100644 --- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.js +++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.js @@ -46,5 +46,28 @@ frappe.query_reports["HSN-wise-summary of outward supplies"] = { ], onload: (report) => { fetch_gstins(report); + + report.page.add_inner_button(__("Download JSON"), function () { + var filters = report.get_values(); + + frappe.call({ + method: 'erpnext.regional.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies.get_json', + args: { + data: report.data, + report_name: report.report_name, + filters: filters + }, + callback: function(r) { + if (r.message) { + const args = { + cmd: 'erpnext.regional.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies.download_json_file', + data: r.message.data, + report_name: r.message.report_name + }; + open_url_post(frappe.request.url, args); + } + } + }); + }); } }; diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py index a3ed4cebb1..6f3fff2932 100644 --- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py +++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py @@ -4,11 +4,13 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _ -from frappe.utils import flt +from frappe.utils import flt, getdate, cstr from frappe.model.meta import get_field_precision from frappe.utils.xlsxutils import handle_html from six import iteritems import json +from erpnext.regional.india.utils import get_gst_accounts +from erpnext.regional.report.gstr_1.gstr_1 import get_company_gstin_number def execute(filters=None): return _execute(filters) @@ -141,7 +143,7 @@ def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoic tax_details = frappe.db.sql(""" select - parent, description, item_wise_tax_detail, + parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount from `tab%s` where @@ -153,11 +155,11 @@ def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoic """ % (tax_doctype, '%s', ', '.join(['%s']*len(invoice_item_row)), conditions), tuple([doctype] + list(invoice_item_row))) - for parent, description, item_wise_tax_detail, tax_amount in tax_details: - description = handle_html(description) - if description not in tax_columns and tax_amount: + for parent, account_head, item_wise_tax_detail, tax_amount in tax_details: + + if account_head not in tax_columns and tax_amount: # as description is text editor earlier and markup can break the column convention in reports - tax_columns.append(description) + tax_columns.append(account_head) if item_wise_tax_detail: try: @@ -175,17 +177,17 @@ def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoic for d in item_row_map.get(parent, {}).get(item_code, []): item_tax_amount = tax_amount if item_tax_amount: - itemised_tax.setdefault((parent, item_code), {})[description] = frappe._dict({ + itemised_tax.setdefault((parent, item_code), {})[account_head] = frappe._dict({ "tax_amount": flt(item_tax_amount, tax_amount_precision) }) except ValueError: continue tax_columns.sort() - for desc in tax_columns: + for account_head in tax_columns: columns.append({ - "label": desc, - "fieldname": frappe.scrub(desc), + "label": account_head, + "fieldname": frappe.scrub(account_head), "fieldtype": "Float", "width": 110 }) @@ -212,3 +214,76 @@ def get_merged_data(columns, data): return result +@frappe.whitelist() +def get_json(filters, report_name, data): + filters = json.loads(filters) + report_data = json.loads(data) + gstin = filters.get('company_gstin') or get_company_gstin_number(filters["company"]) + + if not filters.get('from_date') or not filters.get('to_date'): + frappe.throw(_("Please enter From Date and To Date to generate JSON")) + + fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year) + + gst_json = {"gstin": "", "version": "GST2.3.4", + "hash": "hash", "gstin": gstin, "fp": fp} + + gst_json["hsn"] = { + "data": get_hsn_wise_json_data(filters, report_data) + } + + return { + 'report_name': report_name, + 'data': gst_json + } + +@frappe.whitelist() +def download_json_file(): + ''' download json content in a file ''' + data = frappe._dict(frappe.local.form_dict) + frappe.response['filename'] = frappe.scrub("{0}".format(data['report_name'])) + '.json' + frappe.response['filecontent'] = data['data'] + frappe.response['content_type'] = 'application/json' + frappe.response['type'] = 'download' + +def get_hsn_wise_json_data(filters, report_data): + + filters = frappe._dict(filters) + gst_accounts = get_gst_accounts(filters.company) + data = [] + count = 1 + + for hsn in report_data: + row = { + "num": count, + "hsn_sc": hsn.get("gst_hsn_code"), + "desc": hsn.get("description"), + "uqc": hsn.get("stock_uom").upper(), + "qty": hsn.get("stock_qty"), + "val": flt(hsn.get("total_amount"), 2), + "txval": flt(hsn.get("taxable_amount", 2)), + "iamt": 0.0, + "camt": 0.0, + "samt": 0.0, + "csamt": 0.0 + + } + + for account in gst_accounts.get('igst_account'): + row['iamt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2) + + for account in gst_accounts.get('cgst_account'): + row['camt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2) + + for account in gst_accounts.get('sgst_account'): + row['samt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2) + + for account in gst_accounts.get('cess_account'): + row['csamt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2) + + data.append(row) + count +=1 + + return data + + From f49665077c59716db6a985639dfb4388a43db976 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 24 Aug 2020 18:26:48 +0530 Subject: [PATCH 04/10] feat: Quoted Item Comparison Report Enhancements v2 --- .../quoted_item_comparison.js | 58 +++++++++++-- .../quoted_item_comparison.py | 83 +++++++++++++------ 2 files changed, 109 insertions(+), 32 deletions(-) diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js index a76ffeec2e..8718e4e2ec 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js @@ -12,7 +12,22 @@ frappe.query_reports["Quoted Item Comparison"] = { "reqd": 1 }, { - reqd: 1, + "fieldname":"from_date", + "label": __("From Date"), + "fieldtype": "Date", + "width": "80", + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + "fieldname":"to_date", + "label": __("To Date"), + "fieldtype": "Date", + "width": "80", + "reqd": 1, + "default": frappe.datetime.get_today() + }, + { default: "", options: "Item", label: __("Item"), @@ -45,13 +60,12 @@ frappe.query_reports["Quoted Item Comparison"] = { } }, { - fieldtype: "Link", + fieldtype: "MultiSelectList", label: __("Supplier Quotation"), - options: "Supplier Quotation", fieldname: "supplier_quotation", default: "", - get_query: () => { - return { filters: { "docstatus": ["<", 2] } } + get_data: function(txt) { + return frappe.db.get_link_options('Supplier Quotation', txt); } }, { @@ -63,9 +77,30 @@ frappe.query_reports["Quoted Item Comparison"] = { get_query: () => { return { filters: { "docstatus": ["<", 2] } } } + }, + { + fieldtype: "Check", + label: __("Include Expired"), + fieldname: "include_expired", + default: 0 } ], + formatter: (value, row, column, data, default_formatter) => { + value = default_formatter(value, row, column, data); + + if(column.fieldname === "valid_till" && data.valid_till){ + if(frappe.datetime.get_diff(data.valid_till, frappe.datetime.nowdate()) <= 1){ + value = `
${value}
`; + } + else if (frappe.datetime.get_diff(data.valid_till, frappe.datetime.nowdate()) <= 7){ + value = `
${value}
`; + } + } + + return value; + }, + onload: (report) => { // Create a button for setting the default supplier report.page.add_inner_button(__("Select Default Supplier"), () => { @@ -75,6 +110,19 @@ frappe.query_reports["Quoted Item Comparison"] = { reporter.make_default_supplier_dialog(report); }, 'Tools'); + const status_message = ` + + Valid till :    + + + Expires in a week + +      + + Expires today / Already Expired + ` + report.$status.html(status_message).show(); + }, make_default_supplier_dialog: (report) => { // Get the name of the item to change diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py index a33867a525..ffa138f1e9 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py @@ -16,44 +16,48 @@ def execute(filters=None): supplier_quotation_data = get_data(filters, conditions) columns = get_columns() - data, chart_data = prepare_data(supplier_quotation_data) + data, chart_data = prepare_data(supplier_quotation_data, filters) return columns, data, None, chart_data def get_conditions(filters): conditions = "" + if filters.get("item_code"): + conditions += " AND sqi.item_code = %(item_code)s" + if filters.get("supplier_quotation"): - conditions += " AND sqi.parent = %(supplier_quotation)s" + conditions += " AND sqi.parent in %(supplier_quotation)s" if filters.get("request_for_quotation"): conditions += " AND sqi.request_for_quotation = %(request_for_quotation)s" if filters.get("supplier"): conditions += " AND sq.supplier in %(supplier)s" + + if not filters.get("include_expired"): + conditions += " AND sq.status != 'Expired'" + return conditions def get_data(filters, conditions): - if not filters.get("item_code"): - return [] - supplier_quotation_data = frappe.db.sql("""SELECT - sqi.parent, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation, - sq.supplier + sqi.parent, sqi.item_code, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation, + sqi.lead_time_days, sq.supplier, sq.valid_till FROM `tabSupplier Quotation Item` sqi, `tabSupplier Quotation` sq WHERE - sqi.item_code = %(item_code)s - AND sqi.parent = sq.name + sqi.parent = sq.name AND sqi.docstatus < 2 AND sq.company = %(company)s - AND sq.status != 'Expired' - {0}""".format(conditions), filters, as_dict=1) + AND sq.transaction_date between %(from_date)s and %(to_date)s + {0} + order by sq.transaction_date, sqi.item_code""".format(conditions), filters, as_dict=1) return supplier_quotation_data -def prepare_data(supplier_quotation_data): - out, suppliers, qty_list = [], [], [] +def prepare_data(supplier_quotation_data, filters): + out, suppliers, qty_list, chart_data = [], [], [], [] supplier_wise_map = defaultdict(list) supplier_qty_price_map = {} @@ -70,20 +74,24 @@ def prepare_data(supplier_quotation_data): exchange_rate = 1 row = { + "item_code": data.get('item_code'), "quotation": data.get("parent"), "qty": data.get("qty"), "price": flt(data.get("rate") * exchange_rate, float_precision), "uom": data.get("uom"), "request_for_quotation": data.get("request_for_quotation"), + "valid_till": data.get('valid_till'), + "lead_time_days": data.get('lead_time_days') } # map for report view of form {'supplier1':[{},{},...]} supplier_wise_map[supplier].append(row) # map for chart preparation of the form {'supplier1': {'qty': 'price'}} - if not supplier in supplier_qty_price_map: - supplier_qty_price_map[supplier] = {} - supplier_qty_price_map[supplier][row["qty"]] = row["price"] + if filters.get("item_code"): + if not supplier in supplier_qty_price_map: + supplier_qty_price_map[supplier] = {} + supplier_qty_price_map[supplier][row["qty"]] = row["price"] suppliers.append(supplier) qty_list.append(data.get("qty")) @@ -97,7 +105,8 @@ def prepare_data(supplier_quotation_data): for entry in supplier_wise_map[supplier]: out.append(entry) - chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map) + if filters.get("item_code"): + chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map) return out, chart_data @@ -117,9 +126,10 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map): data_points_map[qty].append(None) dataset = [] + currency_symbol = frappe.db.get_value("Currency", frappe.db.get_default("currency"), "symbol") for qty in qty_list: datapoints = { - "name": _("Price for Qty ") + str(qty), + "name": currency_symbol + " (Qty " + str(qty) + " )", "values": data_points_map[qty] } dataset.append(datapoints) @@ -140,14 +150,21 @@ def get_columns(): "label": _("Supplier"), "fieldtype": "Link", "options": "Supplier", + "width": 150 + }, + { + "fieldname": "item_code", + "label": _("Item"), + "fieldtype": "Link", + "options": "Item", "width": 200 }, { - "fieldname": "quotation", - "label": _("Supplier Quotation"), + "fieldname": "uom", + "label": _("UOM"), "fieldtype": "Link", - "options": "Supplier Quotation", - "width": 200 + "options": "UOM", + "width": 90 }, { "fieldname": "qty", @@ -163,18 +180,30 @@ def get_columns(): "width": 110 }, { - "fieldname": "uom", - "label": _("UOM"), + "fieldname": "quotation", + "label": _("Supplier Quotation"), "fieldtype": "Link", - "options": "UOM", - "width": 90 + "options": "Supplier Quotation", + "width": 200 + }, + { + "fieldname": "valid_till", + "label": _("Valid Till"), + "fieldtype": "Date", + "width": 100 + }, + { + "fieldname": "lead_time_days", + "label": _("Lead Time (Days)"), + "fieldtype": "Int", + "width": 100 }, { "fieldname": "request_for_quotation", "label": _("Request for Quotation"), "fieldtype": "Link", "options": "Request for Quotation", - "width": 200 + "width": 150 } ] From 8f452b86630b306dd47c6360d074a9d1ec2ad040 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 24 Aug 2020 18:57:07 +0530 Subject: [PATCH 05/10] fix: Codacy and indicator message --- .../report/quoted_item_comparison/quoted_item_comparison.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js index 8718e4e2ec..ad390c446b 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js @@ -115,12 +115,12 @@ frappe.query_reports["Quoted Item Comparison"] = { Valid till :    - Expires in a week + Expires in a week or less      Expires today / Already Expired - ` + `; report.$status.html(status_message).show(); }, From f4b939754c84111e9417734ea248e1c85a16ce1d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 26 Aug 2020 12:10:12 +0530 Subject: [PATCH 06/10] fix: Codacy fixes --- .../hsn_wise_summary_of_outward_supplies.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py index 6f3fff2932..59389ce326 100644 --- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py +++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py @@ -225,7 +225,7 @@ def get_json(filters, report_name, data): fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year) - gst_json = {"gstin": "", "version": "GST2.3.4", + gst_json = {"version": "GST2.3.4", "hash": "hash", "gstin": gstin, "fp": fp} gst_json["hsn"] = { @@ -239,7 +239,7 @@ def get_json(filters, report_name, data): @frappe.whitelist() def download_json_file(): - ''' download json content in a file ''' + '''download json content in a file''' data = frappe._dict(frappe.local.form_dict) frappe.response['filename'] = frappe.scrub("{0}".format(data['report_name'])) + '.json' frappe.response['filecontent'] = data['data'] From 25042a22eb407486bb80099b8e8b416aec022f50 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 31 Aug 2020 19:17:29 +0530 Subject: [PATCH 07/10] fix: Pending amount after loan closure request --- .../loan_management/doctype/loan_repayment/loan_repayment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 451ae85afb..d1bb6ccedf 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -297,7 +297,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): if not final_due_date: final_due_date = add_days(due_date, loan_type_details.grace_period_in_days) - if against_loan_doc.status == 'Disbursed': + if against_loan_doc.status in ('Disbursed', 'Loan Closure Requested'): pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable else: pending_principal_amount = against_loan_doc.disbursed_amount From bc6c4e864dfe36835bffc7e0af03a27b32d0eb38 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 31 Aug 2020 20:32:17 +0530 Subject: [PATCH 08/10] fix: Status in Report and filter query --- .../quoted_item_comparison.js | 15 +-------------- .../quoted_item_comparison.py | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js index ad390c446b..518d665e7e 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js @@ -65,7 +65,7 @@ frappe.query_reports["Quoted Item Comparison"] = { fieldname: "supplier_quotation", default: "", get_data: function(txt) { - return frappe.db.get_link_options('Supplier Quotation', txt); + return frappe.db.get_link_options('Supplier Quotation', txt, {'docstatus': ["<", 2]}); } }, { @@ -110,19 +110,6 @@ frappe.query_reports["Quoted Item Comparison"] = { reporter.make_default_supplier_dialog(report); }, 'Tools'); - const status_message = ` - - Valid till :    - - - Expires in a week or less - -      - - Expires today / Already Expired - `; - report.$status.html(status_message).show(); - }, make_default_supplier_dialog: (report) => { // Get the name of the item to change diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py index ffa138f1e9..4426560c16 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py @@ -17,8 +17,9 @@ def execute(filters=None): columns = get_columns() data, chart_data = prepare_data(supplier_quotation_data, filters) + message = get_message() - return columns, data, None, chart_data + return columns, data, message, chart_data def get_conditions(filters): conditions = "" @@ -207,4 +208,16 @@ def get_columns(): } ] - return columns \ No newline at end of file + return columns + +def get_message(): + return """ + Valid till :    + + + Expires in a week or less + +    + + Expires today / Already Expired + """ \ No newline at end of file From 1dc9a9b669cb502e905318952bda9d8aa2234b22 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 31 Aug 2020 20:35:05 +0530 Subject: [PATCH 09/10] fix: reverse journal entry for multi-currency (#23165) * fix: reverse journal entry for multi-currency * fix: test case for reverse journal entry --- .../doctype/journal_entry/journal_entry.js | 20 +++------ .../doctype/journal_entry/journal_entry.py | 31 +++++++++++++ .../journal_entry/test_journal_entry.py | 43 +++++++++++++++++++ 3 files changed, 80 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index a09face791..409c15f75c 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -638,20 +638,12 @@ $.extend(erpnext.journal_entry, { return { filters: filters }; }, - reverse_journal_entry: function(frm) { - var me = frm.doc; - for(var i=0; i Date: Mon, 31 Aug 2020 21:21:05 +0530 Subject: [PATCH 10/10] fix: Add unit test for pending principal amount --- .../loan_management/doctype/loan/test_loan.py | 53 +++++++++++++++---- .../doctype/loan_repayment/loan_repayment.py | 8 +++ .../loan_security_unpledge.py | 8 +-- 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index b75f7bdd75..2f6cd25a36 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -18,6 +18,7 @@ from erpnext.loan_management.doctype.loan.loan import create_loan_security_unple from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge from erpnext.loan_management.doctype.loan_disbursement.loan_disbursement import get_disbursal_amount +from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts class TestLoan(unittest.TestCase): def setUp(self): @@ -194,18 +195,14 @@ class TestLoan(unittest.TestCase): make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) process_loan_interest_accrual_for_demand_loans(posting_date = last_date) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), "Loan Closure", flt(loan.loan_amount + accrued_interest_amount)) repayment_entry.submit() amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', 'paid_principal_amount']) - unaccrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * 6) \ - / (days_in_year(get_datetime(first_date).year) * 100) - - self.assertEquals(flt(amounts[0] + unaccrued_interest_amount, 3), - flt(accrued_interest_amount, 3)) + self.assertEquals(flt(amounts[0], 2),flt(accrued_interest_amount, 2)) self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) loan.load_from_db() @@ -307,9 +304,6 @@ class TestLoan(unittest.TestCase): "Loan Closure", flt(loan.loan_amount + accrued_interest_amount)) repayment_entry.submit() - amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', - 'paid_principal_amount']) - loan.load_from_db() self.assertEquals(loan.status, "Loan Closure Requested") @@ -374,6 +368,47 @@ class TestLoan(unittest.TestCase): self.assertEqual(get_disbursal_amount(loan.name), 300000) + def test_pending_loan_amount_after_closure_request(self): + pledge = [{ + "loan_security": "Test Security 1", + "qty": 4000.00 + }] + + loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) + create_pledge(loan_application) + + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate())) + loan.submit() + + self.assertEquals(loan.loan_amount, 1000000) + + first_date = '2019-10-01' + last_date = '2019-10-30' + + no_of_days = date_diff(last_date, first_date) + 1 + + no_of_days += 6 + + accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \ + / (days_in_year(get_datetime(first_date).year) * 100) + + make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) + process_loan_interest_accrual_for_demand_loans(posting_date = last_date) + + amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment") + + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), + "Loan Closure", flt(loan.loan_amount + accrued_interest_amount)) + repayment_entry.submit() + + amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', + 'paid_principal_amount']) + + loan.load_from_db() + self.assertEquals(loan.status, "Loan Closure Requested") + + amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment") + self.assertEquals(amounts['pending_principal_amount'], 0.0) def create_loan_accounts(): if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"): diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index d1bb6ccedf..7d83e32213 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -116,6 +116,7 @@ class LoanRepayment(AccountsController): def allocate_amounts(self, paid_entries): self.set('repayment_details', []) self.principal_amount_paid = 0 + total_interest_paid = 0 interest_paid = self.amount_paid - self.penalty_amount if self.amount_paid - self.penalty_amount > 0 and paid_entries: @@ -137,12 +138,19 @@ class LoanRepayment(AccountsController): interest_paid = 0 paid_principal=0 + total_interest_paid += interest_amount self.append('repayment_details', { 'loan_interest_accrual': lia, 'paid_interest_amount': interest_amount, 'paid_principal_amount': paid_principal }) + if self.payment_type == 'Loan Closure' and total_interest_paid < self.interest_payable: + unaccrued_interest = self.interest_payable - total_interest_paid + interest_paid -= unaccrued_interest + if self.repayment_details: + self.repayment_details[-1].paid_interest_amount += unaccrued_interest + if interest_paid: self.principal_amount_paid += interest_paid diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py index 5e9d82aa91..f6b28dae75 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py @@ -43,8 +43,10 @@ class LoanSecurityUnpledge(Document): "valid_upto": (">=", get_datetime()) }, as_list=1)) - loan_amount, principal_paid = frappe.get_value("Loan", self.loan, ['loan_amount', 'total_principal_paid']) - pending_principal_amount = loan_amount - principal_paid + total_payment, principal_paid, interest_payable = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid', + 'total_interest_payable']) + + pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) security_value = 0 for security in self.securities: @@ -60,7 +62,7 @@ class LoanSecurityUnpledge(Document): security_value += qty_after_unpledge * loan_security_price_map.get(security.loan_security) - if not security_value and pending_principal_amount > 0: + if not security_value and flt(pending_principal_amount, 2) > 0: frappe.throw("Cannot Unpledge, loan to value ratio is breaching") if security_value and (pending_principal_amount/security_value) * 100 > ltv_ratio: