From 318da16b99232033b592cc6e59f931a360758b1d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 29 Aug 2022 14:18:39 +0530 Subject: [PATCH 01/13] fix: Rounded total for cash and non trade discount invoices --- erpnext/controllers/taxes_and_totals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index bc38d08b809..9dbcdb04c5b 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -41,6 +41,7 @@ class calculate_taxes_and_totals(object): if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"): self.doc.grand_total -= self.doc.discount_amount self.doc.base_grand_total -= self.doc.base_discount_amount + self.set_rounded_total() self.calculate_shipping_charges() From 69ffef8c0ee728779dfb76b5eee4f15ef3421b6d Mon Sep 17 00:00:00 2001 From: MOHAMMED NIYAS <76736615+niyazrazak@users.noreply.github.com> Date: Mon, 29 Aug 2022 14:47:43 +0530 Subject: [PATCH 02/13] fix: lost quotation not to expired --- erpnext/selling/doctype/quotation/quotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 863fbc4059a..96092b15238 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -268,7 +268,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False): def set_expired_status(): # filter out submitted non expired quotations whose validity has been ended - cond = "`tabQuotation`.docstatus = 1 and `tabQuotation`.status != 'Expired' and `tabQuotation`.valid_till < %s" + cond = "`tabQuotation`.docstatus = 1 and `tabQuotation`.status NOT IN ('Expired', 'Lost') and `tabQuotation`.valid_till < %s" # check if those QUO have SO against it so_against_quo = """ SELECT From 9dbaaa33f5ec7df8904e71e305c26a5c904e2028 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 29 Aug 2022 15:07:20 +0530 Subject: [PATCH 03/13] fix: AD not getting copied from SCO while creating a SE (#32004) --- .../stock/doctype/stock_entry/stock_entry.py | 40 ++++-- .../subcontracting_order.js | 128 ++---------------- 2 files changed, 38 insertions(+), 130 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 7721efb639f..d70952282d6 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2568,27 +2568,26 @@ def get_supplied_items( @frappe.whitelist() def get_items_from_subcontracting_order(source_name, target_doc=None): - sco = frappe.get_doc("Subcontracting Order", source_name) + def post_process(source, target): + target.stock_entry_type = target.purpose = "Send to Subcontractor" + target.subcontracting_order = source_name - if sco.docstatus == 1: - if target_doc and isinstance(target_doc, str): - target_doc = frappe.get_doc(json.loads(target_doc)) - - if target_doc.items: - target_doc.items = [] + if target.items: + target.items = [] warehouses = {} - for item in sco.items: + for item in source.items: warehouses[item.name] = item.warehouse - for item in sco.supplied_items: - target_doc.append( + for item in source.supplied_items: + target.append( "items", { "s_warehouse": warehouses.get(item.reference_name), - "t_warehouse": sco.supplier_warehouse, + "t_warehouse": source.supplier_warehouse, + "subcontracted_item": item.main_item_code, "item_code": item.rm_item_code, - "qty": item.required_qty, + "qty": max(item.required_qty - item.total_supplied_qty, 0), "transfer_qty": item.required_qty, "uom": item.stock_uom, "stock_uom": item.stock_uom, @@ -2596,6 +2595,23 @@ def get_items_from_subcontracting_order(source_name, target_doc=None): }, ) + target_doc = get_mapped_doc( + "Subcontracting Order", + source_name, + { + "Subcontracting Order": { + "doctype": "Stock Entry", + "field_no_map": ["purchase_order"], + "validation": { + "docstatus": ["=", 1], + }, + }, + }, + target_doc, + post_process, + ignore_child_tables=True, + ) + return target_doc diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index bbc58fe29b2..065ef39db3e 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -164,10 +164,7 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll if (flt(doc.per_received) < 100) { cur_frm.add_custom_button(__('Subcontracting Receipt'), this.make_subcontracting_receipt, __('Create')); if (me.has_unsupplied_items()) { - cur_frm.add_custom_button(__('Material to Supplier'), - () => { - me.make_stock_entry(); - }, __('Transfer')); + cur_frm.add_custom_button(__('Material to Supplier'), this.make_stock_entry, __('Transfer')); } } cur_frm.page.set_inner_btn_group_as_primary(__('Create')); @@ -195,120 +192,6 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll transaction_controller.autofill_warehouse(child_table, warehouse_field, warehouse); } - make_stock_entry() { - var items = $.map(cur_frm.doc.items, (d) => d.bom ? d.item_code : false); - var me = this; - - if (items.length >= 1) { - me.raw_material_data = []; - me.show_dialog = 1; - let title = __('Transfer Material to Supplier'); - let fields = [ - { fieldtype: 'Section Break', label: __('Raw Materials') }, - { - fieldname: 'sub_con_rm_items', fieldtype: 'Table', label: __('Items'), - fields: [ - { - fieldtype: 'Data', - fieldname: 'item_code', - label: __('Item'), - read_only: 1, - in_list_view: 1 - }, - { - fieldtype: 'Data', - fieldname: 'rm_item_code', - label: __('Raw Material'), - read_only: 1, - in_list_view: 1 - }, - { - fieldtype: 'Float', - read_only: 1, - fieldname: 'qty', - label: __('Quantity'), - in_list_view: 1 - }, - { - fieldtype: 'Data', - read_only: 1, - fieldname: 'warehouse', - label: __('Reserve Warehouse'), - in_list_view: 1 - }, - { - fieldtype: 'Float', - read_only: 1, - fieldname: 'rate', - label: __('Rate'), - hidden: 1 - }, - { - fieldtype: 'Float', - read_only: 1, - fieldname: 'amount', - label: __('Amount'), - hidden: 1 - }, - { - fieldtype: 'Link', - read_only: 1, - fieldname: 'uom', - label: __('UOM'), - hidden: 1 - } - ], - data: me.raw_material_data, - get_data: () => me.raw_material_data - } - ]; - - me.dialog = new frappe.ui.Dialog({ - title: title, fields: fields - }); - - if (me.frm.doc['supplied_items']) { - me.frm.doc['supplied_items'].forEach((item) => { - if (item.rm_item_code && item.main_item_code && item.required_qty - item.supplied_qty != 0) { - me.raw_material_data.push({ - 'name': item.name, - 'item_code': item.main_item_code, - 'rm_item_code': item.rm_item_code, - 'item_name': item.rm_item_code, - 'qty': item.required_qty - item.supplied_qty, - 'warehouse': item.reserve_warehouse, - 'rate': item.rate, - 'amount': item.amount, - 'stock_uom': item.stock_uom - }); - me.dialog.fields_dict.sub_con_rm_items.grid.refresh(); - } - }); - } - - me.dialog.get_field('sub_con_rm_items').check_all_rows(); - - me.dialog.show(); - this.dialog.set_primary_action(__('Transfer'), () => { - me.values = me.dialog.get_values(); - if (me.values) { - me.values.sub_con_rm_items.map((row, i) => { - if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) { - let row_id = i + 1; - frappe.throw(__('Item Code, warehouse and quantity are required on row {0}', [row_id])); - } - }); - me.make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children()); - me.dialog.hide(); - } - }); - } - - me.dialog.get_close_btn().on('click', () => { - me.dialog.hide(); - }); - } - has_unsupplied_items() { return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty); } @@ -321,6 +204,15 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll }); } + make_stock_entry() { + frappe.model.open_mapped_doc({ + method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_items_from_subcontracting_order', + source_name: cur_frm.doc.name, + freeze: true, + freeze_message: __('Creating Stock Entry ...') + }); + } + make_rm_stock_entry(rm_items) { frappe.call({ method: 'erpnext.controllers.subcontracting_controller.make_rm_stock_entry', From 5782c4469ac98b691564af8f1e38330934584558 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 22 Aug 2022 15:46:04 +0530 Subject: [PATCH 04/13] refactor: re-add remarks field to payment ledger and AR/AP report --- .../payment_ledger_entry/payment_ledger_entry.json | 10 ++++++++-- .../report/accounts_receivable/accounts_receivable.js | 5 +++++ .../report/accounts_receivable/accounts_receivable.py | 5 +++++ erpnext/accounts/utils.py | 1 + 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json index 4596b00fc1a..22842cec0fe 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json @@ -22,7 +22,8 @@ "amount", "account_currency", "amount_in_account_currency", - "delinked" + "delinked", + "remarks" ], "fields": [ { @@ -136,12 +137,17 @@ "fieldtype": "Link", "label": "Finance Book", "options": "Finance Book" + }, + { + "fieldname": "remarks", + "fieldtype": "Text", + "label": "Remarks" } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2022-07-11 09:13:54.379168", + "modified": "2022-08-22 15:32:56.629430", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Ledger Entry", diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 0238711a70e..0b4e577f6cb 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -178,6 +178,11 @@ frappe.query_reports["Accounts Receivable"] = { "fieldtype": "Data", "hidden": 1 }, + { + "fieldname": "show_remarks", + "label": __("Show Remarks"), + "fieldtype": "Check", + }, { "fieldname": "customer_name", "label": __("Customer Name"), diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 63242e83fea..90a431d63c5 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -119,6 +119,7 @@ class ReceivablePayableReport(object): party_account=ple.account, posting_date=ple.posting_date, account_currency=ple.account_currency, + remarks=ple.remarks, invoiced=0.0, paid=0.0, credit_note=0.0, @@ -697,6 +698,7 @@ class ReceivablePayableReport(object): ple.account_currency, ple.amount, ple.amount_in_account_currency, + ple.remarks, ) .where(ple.delinked == 0) .where(Criterion.all(self.qb_selection_filter)) @@ -974,6 +976,9 @@ class ReceivablePayableReport(object): options="Supplier Group", ) + if self.filters.show_remarks: + self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200), + def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120): if not fieldname: fieldname = scrub(label) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 018e8f9301a..f61e8ac960b 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1424,6 +1424,7 @@ def create_payment_ledger_entry( "amount": dr_or_cr, "amount_in_account_currency": dr_or_cr_account_currency, "delinked": True if cancel else False, + "remarks": gle.remarks, } ) From 3a6b095ed45fbc47628e4b189e1d1a89e17ffc25 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 23 Aug 2022 15:10:55 +0530 Subject: [PATCH 05/13] chore: patch for migrating remarks to payment ledger --- ...grate_remarks_from_gl_to_payment_ledger.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py diff --git a/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py b/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py new file mode 100644 index 00000000000..062d24b78bd --- /dev/null +++ b/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py @@ -0,0 +1,56 @@ +import frappe +from frappe import qb +from frappe.utils import create_batch + + +def execute(): + if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"): + + gle = qb.DocType("GL Entry") + ple = qb.DocType("Payment Ledger Entry") + + # get ple and their remarks from GL Entry + pl_entries = ( + qb.from_(ple) + .left_join(gle) + .on( + (ple.account == gle.account) + & (ple.party_type == gle.party_type) + & (ple.party == gle.party) + & (ple.voucher_type == gle.voucher_type) + & (ple.voucher_no == gle.voucher_no) + & (ple.company == gle.company) + ) + .select( + ple.company, + ple.account, + ple.party_type, + ple.party, + ple.voucher_type, + ple.voucher_no, + gle.remarks.as_("gle_remarks"), + ) + .where((ple.delinked == 0) & (gle.is_cancelled == 0)) + .run(as_dict=True) + ) + + if pl_entries: + # split into multiple batches, update and commit for each batch + batch_size = 1000 + for batch in create_batch(pl_entries, batch_size): + for entry in batch: + query = ( + qb.update(ple) + .set(ple.remarks, entry.gle_remarks) + .where( + (ple.company == entry.company) + & (ple.account == entry.account) + & (ple.party_type == entry.party_type) + & (ple.party == entry.party) + & (ple.voucher_type == entry.voucher_type) + & (ple.voucher_no == entry.voucher_no) + ) + ) + query.run() + + frappe.db.commit() From d522f13d556af53ba6b6cb9c833e14aefc0226cb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 29 Aug 2022 15:31:26 +0530 Subject: [PATCH 06/13] chore: add remarks migration to patches.txt --- erpnext/patches.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d92353aca45..4729add16b3 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -311,4 +311,5 @@ erpnext.patches.v14_0.remove_india_localisation # 14-07-2022 erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022 erpnext.patches.v14_0.fix_crm_no_of_employees -erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes \ No newline at end of file +erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes +erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger From 2d41704424899a8cc1992a9c379a5d340effbce8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 29 Aug 2022 20:50:27 +0530 Subject: [PATCH 07/13] fix(patch): update sla doctype directly (#32014) fix: update sla doctype directly --- erpnext/patches/v13_0/add_doctype_to_sla.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/add_doctype_to_sla.py b/erpnext/patches/v13_0/add_doctype_to_sla.py index 5f5974f65d4..2d3b0de5b5c 100644 --- a/erpnext/patches/v13_0/add_doctype_to_sla.py +++ b/erpnext/patches/v13_0/add_doctype_to_sla.py @@ -14,7 +14,8 @@ def execute(): for sla in frappe.get_all("Service Level Agreement"): agreement = frappe.get_doc("Service Level Agreement", sla.name) - agreement.document_type = "Issue" + agreement.db_set("document_type", "Issue") + agreement.reload() agreement.apply_sla_for_resolution = 1 agreement.append("sla_fulfilled_on", {"status": "Resolved"}) agreement.append("sla_fulfilled_on", {"status": "Closed"}) From 73f4d5931d881b1eacc4fde89affd291f88c5ef1 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 29 Aug 2022 18:26:07 +0200 Subject: [PATCH 08/13] fix: permissions for Task Type (#32016) --- .../projects/doctype/task_type/task_type.json | 123 +++++------------- 1 file changed, 33 insertions(+), 90 deletions(-) diff --git a/erpnext/projects/doctype/task_type/task_type.json b/erpnext/projects/doctype/task_type/task_type.json index 3254444a48e..b04264e9c74 100644 --- a/erpnext/projects/doctype/task_type/task_type.json +++ b/erpnext/projects/doctype/task_type/task_type.json @@ -1,127 +1,70 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, + "actions": [], "autoname": "Prompt", - "beta": 0, "creation": "2019-04-19 15:04:05.317138", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "weight", + "description" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "weight", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Weight", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Weight" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Description" } ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-04-19 15:31:48.080164", + "links": [], + "modified": "2022-08-29 17:46:41.342979", "modified_by": "Administrator", "module": "Projects", "name": "Task Type", - "name_case": "", + "naming_rule": "Set by user", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects User", + "share": 1 } ], "quick_entry": 1, - "read_only": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "ASC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "states": [], + "track_changes": 1 } \ No newline at end of file From ffa3071d36e62eb721bc9a3105fb7af4b93cf8fc Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 30 Aug 2022 15:43:57 +0530 Subject: [PATCH 09/13] fix: force delete old report docs (#32026) --- erpnext/patches/v13_0/delete_old_sales_reports.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v13_0/delete_old_sales_reports.py b/erpnext/patches/v13_0/delete_old_sales_reports.py index b31c9d17d71..1b53da755cd 100644 --- a/erpnext/patches/v13_0/delete_old_sales_reports.py +++ b/erpnext/patches/v13_0/delete_old_sales_reports.py @@ -16,18 +16,18 @@ def execute(): delete_auto_email_reports(report) check_and_delete_linked_reports(report) - frappe.delete_doc("Report", report) + frappe.delete_doc("Report", report, force=True) def delete_auto_email_reports(report): """Check for one or multiple Auto Email Reports and delete""" auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"]) for auto_email_report in auto_email_reports: - frappe.delete_doc("Auto Email Report", auto_email_report[0]) + frappe.delete_doc("Auto Email Report", auto_email_report[0], force=True) def delete_links_from_desktop_icons(report): """Check for one or multiple Desktop Icons and delete""" desktop_icons = frappe.db.get_values("Desktop Icon", {"_report": report}, ["name"]) for desktop_icon in desktop_icons: - frappe.delete_doc("Desktop Icon", desktop_icon[0]) + frappe.delete_doc("Desktop Icon", desktop_icon[0], force=True) From eefc9b71725d5530e3bd259a299d3c0673385a4a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 30 Aug 2022 19:16:36 +0530 Subject: [PATCH 10/13] fix: Loan Interest accruals for 0 rated loans --- .../loan_interest_accrual/loan_interest_accrual.py | 1 - .../doctype/loan_repayment/loan_repayment.py | 1 + .../process_loan_interest_accrual.py | 13 ++++++++----- 3 files changed, 9 insertions(+), 6 deletions(-) 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 6d62aefdca3..9a6bcd4daa8 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 @@ -236,7 +236,6 @@ def get_term_loans(date, term_loan=None, loan_type=None): AND l.is_term_loan =1 AND rs.payment_date <= %s AND rs.is_accrued=0 {0} - AND rs.interest_amount > 0 AND l.status = 'Disbursed' ORDER BY rs.payment_date""".format( condition diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 29da988ce41..018832c7d79 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -735,6 +735,7 @@ def get_amounts(amounts, against_loan, posting_date): ) amounts["pending_accrual_entries"] = pending_accrual_entries amounts["unaccrued_interest"] = flt(unaccrued_interest, precision) + amounts["written_off_amount"] = flt(against_loan_doc.written_off_amount, precision) if final_due_date: amounts["due_date"] = final_due_date diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py index 81464a36c3d..25c72d91a7c 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py @@ -57,7 +57,7 @@ def process_loan_interest_accrual_for_demand_loans( def process_loan_interest_accrual_for_term_loans(posting_date=None, loan_type=None, loan=None): - if not term_loan_accrual_pending(posting_date or nowdate()): + if not term_loan_accrual_pending(posting_date or nowdate(), loan=loan): return loan_process = frappe.new_doc("Process Loan Interest Accrual") @@ -71,9 +71,12 @@ def process_loan_interest_accrual_for_term_loans(posting_date=None, loan_type=No return loan_process.name -def term_loan_accrual_pending(date): - pending_accrual = frappe.db.get_value( - "Repayment Schedule", {"payment_date": ("<=", date), "is_accrued": 0} - ) +def term_loan_accrual_pending(date, loan=None): + filters = {"payment_date": ("<=", date), "is_accrued": 0} + + if loan: + filters.update({"parent": loan}) + + pending_accrual = frappe.db.get_value("Repayment Schedule", filters) return pending_accrual From a76d3827ec5355b7eecedc3e66bfd85b119d5211 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 30 Aug 2022 19:24:57 +0530 Subject: [PATCH 11/13] chore: Add check for principal amount --- .../doctype/loan_interest_accrual/loan_interest_accrual.py | 1 + 1 file changed, 1 insertion(+) 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 9a6bcd4daa8..cac3f1f0f3f 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 @@ -236,6 +236,7 @@ def get_term_loans(date, term_loan=None, loan_type=None): AND l.is_term_loan =1 AND rs.payment_date <= %s AND rs.is_accrued=0 {0} + AND rs.principal_amount > 0 AND l.status = 'Disbursed' ORDER BY rs.payment_date""".format( condition From 4a38ce659d4da7400fd219060cbcdd77ce6ccf1b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 22 Aug 2022 00:23:22 +0530 Subject: [PATCH 12/13] refactor!: drop redisearch incr: replace text and tag fields incr: use rediswrapper's make key incr: indexDefinition from redis incr: replace index creation incr: replace AutoCompleter incr: replace product search ac incr: replace client querying fix: broken redisearch load test fix: pass actual query to get suggestion --- erpnext/e_commerce/redisearch_utils.py | 58 ++++++++++++----------- erpnext/templates/pages/product_search.py | 21 ++++---- pyproject.toml | 1 - 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/erpnext/e_commerce/redisearch_utils.py b/erpnext/e_commerce/redisearch_utils.py index 1f649c7b486..87ca9bd83d5 100644 --- a/erpnext/e_commerce/redisearch_utils.py +++ b/erpnext/e_commerce/redisearch_utils.py @@ -7,7 +7,9 @@ import frappe from frappe import _ from frappe.utils.redis_wrapper import RedisWrapper from redis import ResponseError -from redisearch import AutoCompleter, Client, IndexDefinition, Suggestion, TagField, TextField +from redis.commands.search.field import TagField, TextField +from redis.commands.search.indexDefinition import IndexDefinition +from redis.commands.search.suggestion import Suggestion WEBSITE_ITEM_INDEX = "website_items_index" WEBSITE_ITEM_KEY_PREFIX = "website_item:" @@ -35,12 +37,9 @@ def is_redisearch_enabled(): def is_search_module_loaded(): try: cache = frappe.cache() - out = cache.execute_command("MODULE LIST") - - parsed_output = " ".join( - (" ".join([frappe.as_unicode(s) for s in o if not isinstance(s, int)]) for o in out) - ) - return "search" in parsed_output + for module in cache.module_list(): + if module.get(b"name") == b"search": + return True except Exception: return False # handling older redis versions @@ -58,18 +57,18 @@ def if_redisearch_enabled(function): def make_key(key): - return "{0}|{1}".format(frappe.conf.db_name, key).encode("utf-8") + return frappe.cache().make_key(key) @if_redisearch_enabled def create_website_items_index(): "Creates Index Definition." - # CREATE index - client = Client(make_key(WEBSITE_ITEM_INDEX), conn=frappe.cache()) + redis = frappe.cache() + index = redis.ft(WEBSITE_ITEM_INDEX) try: - client.drop_index() # drop if already exists + index.dropindex() # drop if already exists except ResponseError: # will most likely raise a ResponseError if index does not exist # ignore and create index @@ -86,9 +85,10 @@ def create_website_items_index(): if "web_item_name" in idx_fields: idx_fields.remove("web_item_name") - idx_fields = list(map(to_search_field, idx_fields)) + idx_fields = [to_search_field(f) for f in idx_fields] - client.create_index( + # TODO: sortable? + index.create_index( [TextField("web_item_name", sortable=True)] + idx_fields, definition=idx_def, ) @@ -119,8 +119,8 @@ def insert_item_to_index(website_item_doc): @if_redisearch_enabled def insert_to_name_ac(web_name, doc_name): - ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=frappe.cache()) - ac.add_suggestions(Suggestion(web_name, payload=doc_name)) + ac = frappe.cache().ft() + ac.sugadd(WEBSITE_ITEM_NAME_AUTOCOMPLETE, Suggestion(web_name, payload=doc_name)) def create_web_item_map(website_item_doc): @@ -157,9 +157,8 @@ def delete_item_from_index(website_item_doc): @if_redisearch_enabled def delete_from_ac_dict(website_item_doc): """Removes this items's name from autocomplete dictionary""" - cache = frappe.cache() - name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache) - name_ac.delete(website_item_doc.web_item_name) + ac = frappe.cache().ft() + ac.sugdel(website_item_doc.web_item_name) @if_redisearch_enabled @@ -170,8 +169,6 @@ def define_autocomplete_dictionary(): """ cache = frappe.cache() - item_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache) - item_group_ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=cache) # Delete both autocomplete dicts try: @@ -180,38 +177,43 @@ def define_autocomplete_dictionary(): except Exception: raise_redisearch_error() - create_items_autocomplete_dict(autocompleter=item_ac) - create_item_groups_autocomplete_dict(autocompleter=item_group_ac) + create_items_autocomplete_dict() + create_item_groups_autocomplete_dict() @if_redisearch_enabled -def create_items_autocomplete_dict(autocompleter): +def create_items_autocomplete_dict(): "Add items as suggestions in Autocompleter." + + ac = frappe.cache().ft() items = frappe.get_all( "Website Item", fields=["web_item_name", "item_group"], filters={"published": 1} ) - for item in items: - autocompleter.add_suggestions(Suggestion(item.web_item_name)) + ac.sugadd(WEBSITE_ITEM_NAME_AUTOCOMPLETE, Suggestion(item.web_item_name)) @if_redisearch_enabled -def create_item_groups_autocomplete_dict(autocompleter): +def create_item_groups_autocomplete_dict(): "Add item groups with weightage as suggestions in Autocompleter." + published_item_groups = frappe.get_all( "Item Group", fields=["name", "route", "weightage"], filters={"show_in_website": 1} ) if not published_item_groups: return + ac = frappe.cache().ft() + for item_group in published_item_groups: payload = json.dumps({"name": item_group.name, "route": item_group.route}) - autocompleter.add_suggestions( + ac.sugadd( + WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE, Suggestion( string=item_group.name, score=frappe.utils.flt(item_group.weightage) or 1.0, payload=payload, # additional info that can be retrieved later - ) + ), ) diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py index 0768cc3fa69..f40fd479f4f 100644 --- a/erpnext/templates/pages/product_search.py +++ b/erpnext/templates/pages/product_search.py @@ -5,14 +5,13 @@ import json import frappe from frappe.utils import cint, cstr -from redisearch import AutoCompleter, Client, Query +from redis.commands.search.query import Query from erpnext.e_commerce.redisearch_utils import ( WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE, WEBSITE_ITEM_INDEX, WEBSITE_ITEM_NAME_AUTOCOMPLETE, is_redisearch_enabled, - make_key, ) from erpnext.e_commerce.shopping_cart.product_info import set_product_info_for_website from erpnext.setup.doctype.item_group.item_group import get_item_for_list_in_html @@ -88,15 +87,17 @@ def product_search(query, limit=10, fuzzy_search=True): if not query: return search_results - red = frappe.cache() + redis = frappe.cache() query = clean_up_query(query) # TODO: Check perf/correctness with Suggestions & Query vs only Query # TODO: Use Levenshtein Distance in Query (max=3) - ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=red) - client = Client(make_key(WEBSITE_ITEM_INDEX), conn=red) - suggestions = ac.get_suggestions( - query, num=limit, fuzzy=fuzzy_search and len(query) > 3 # Fuzzy on length < 3 can be real slow + redisearch = redis.ft(WEBSITE_ITEM_INDEX) + suggestions = redisearch.sugget( + WEBSITE_ITEM_NAME_AUTOCOMPLETE, + query, + num=limit, + fuzzy=fuzzy_search and len(query) > 3, ) # Build a query @@ -106,8 +107,8 @@ def product_search(query, limit=10, fuzzy_search=True): query_string += f"|('{clean_up_query(s.string)}')" q = Query(query_string) + results = redisearch.search(q) - results = client.search(q) search_results["results"] = list(map(convert_to_dict, results.docs)) search_results["results"] = sorted( search_results["results"], key=lambda k: frappe.utils.cint(k["ranking"]), reverse=True @@ -141,8 +142,8 @@ def get_category_suggestions(query): if not query: return search_results - ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=frappe.cache()) - suggestions = ac.get_suggestions(query, num=10, with_payloads=True) + ac = frappe.cache().ft() + suggestions = ac.sugget(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE, query, num=10, with_payloads=True) results = [json.loads(s.payload) for s in suggestions] diff --git a/pyproject.toml b/pyproject.toml index 5acfd392726..14684f34911 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ dependencies = [ "pycountry~=20.7.3", "python-stdnum~=1.16", "Unidecode~=1.2.0", - "redisearch~=2.1.0", # integration dependencies "gocardless-pro~=1.22.0", From 30039e8e624f341f1f32b845094d1a1e96e29799 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 31 Aug 2022 15:38:05 +0530 Subject: [PATCH 13/13] fix: encode thumbnail URL If it contains space the URL won't load --- erpnext/e_commerce/product_ui/search.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/e_commerce/product_ui/search.js b/erpnext/e_commerce/product_ui/search.js index 61922459e56..1688cc1fb66 100644 --- a/erpnext/e_commerce/product_ui/search.js +++ b/erpnext/e_commerce/product_ui/search.js @@ -200,7 +200,7 @@ erpnext.ProductSearch = class { let thumbnail = res.thumbnail || '/assets/erpnext/images/ui-states/cart-empty-state.png'; html += `