From b8da0d9334919e283724f2e02ce3dd3a9780d9a9 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Tue, 12 Mar 2024 12:52:19 +0530 Subject: [PATCH 01/65] fix: wrong buying amount if delivered and billed qty varies --- erpnext/accounts/report/gross_profit/gross_profit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 7162aef8f22..25958692a22 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -669,20 +669,20 @@ class GrossProfitGenerator(object): elif row.sales_order and row.so_detail: incoming_amount = self.get_buying_amount_from_so_dn(row.sales_order, row.so_detail, item_code) if incoming_amount: - return incoming_amount + return flt(row.qty) * incoming_amount else: return flt(row.qty) * self.get_average_buying_rate(row, item_code) return flt(row.qty) * self.get_average_buying_rate(row, item_code) def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code): - from frappe.query_builder.functions import Sum + from frappe.query_builder.functions import Avg delivery_note_item = frappe.qb.DocType("Delivery Note Item") query = ( frappe.qb.from_(delivery_note_item) - .select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty)) + .select(Avg(delivery_note_item.incoming_rate)) .where(delivery_note_item.docstatus == 1) .where(delivery_note_item.item_code == item_code) .where(delivery_note_item.against_sales_order == sales_order) From 26202d90cbba6ef9a4f1d2318531c4fcb27963b2 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 13 Mar 2024 18:17:41 +0530 Subject: [PATCH 02/65] Merge pull request #40263 from nabinhait/pi-optimization perf: Performance optmization for Purchase Invoice submission --- .../accounting_dimension.py | 16 +- .../test_accounting_dimension.py | 2 + .../accounting_dimension_filter.py | 60 ++++--- .../test_accounting_dimension_filter.py | 2 + erpnext/accounts/doctype/budget/budget.py | 11 +- .../budget_account/budget_account.json | 124 ++++--------- erpnext/accounts/doctype/gl_entry/gl_entry.py | 6 +- .../payment_ledger_entry.py | 9 +- .../purchase_invoice/purchase_invoice.py | 167 ++++++++++-------- erpnext/accounts/general_ledger.py | 45 ++--- erpnext/controllers/accounts_controller.py | 4 +- erpnext/controllers/status_updater.py | 7 +- .../stock/doctype/item_price/item_price.json | 5 +- erpnext/stock/get_item_details.py | 36 ++-- 14 files changed, 248 insertions(+), 246 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 5258214dad2..41af06f8a59 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -261,14 +261,16 @@ def get_accounting_dimensions(as_list=True, filters=None): def get_checks_for_pl_and_bs_accounts(): - dimensions = frappe.db.sql( - """SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs - FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c - WHERE p.name = c.parent""", - as_dict=1, - ) + if frappe.flags.accounting_dimensions_details is None: + # nosemgrep + frappe.flags.accounting_dimensions_details = frappe.db.sql( + """SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs + FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c + WHERE p.name = c.parent""", + as_dict=1, + ) - return dimensions + return frappe.flags.accounting_dimensions_details def get_dimension_with_children(doctype, dimensions): diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py index cb7f5f5da78..10dbe3bab0f 100644 --- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py @@ -78,6 +78,8 @@ class TestAccountingDimension(unittest.TestCase): def tearDown(self): disable_dimension() + frappe.flags.accounting_dimensions_details = None + frappe.flags.dimension_filter_map = None def create_dimension(): diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py index 01f6e60bf3b..2179a4d46c1 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py @@ -66,37 +66,39 @@ class AccountingDimensionFilter(Document): def get_dimension_filter_map(): - filters = frappe.db.sql( - """ - SELECT - a.applicable_on_account, d.dimension_value, p.accounting_dimension, - p.allow_or_restrict, a.is_mandatory - FROM - `tabApplicable On Account` a, - `tabAccounting Dimension Filter` p - LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name - WHERE - p.name = a.parent - AND p.disabled = 0 - """, - as_dict=1, - ) - - dimension_filter_map = {} - - for f in filters: - f.fieldname = scrub(f.accounting_dimension) - - build_map( - dimension_filter_map, - f.fieldname, - f.applicable_on_account, - f.dimension_value, - f.allow_or_restrict, - f.is_mandatory, + if not frappe.flags.get("dimension_filter_map"): + filters = frappe.db.sql( + """ + SELECT + a.applicable_on_account, d.dimension_value, p.accounting_dimension, + p.allow_or_restrict, a.is_mandatory + FROM + `tabApplicable On Account` a, + `tabAccounting Dimension Filter` p + LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name + WHERE + p.name = a.parent + AND p.disabled = 0 + """, + as_dict=1, ) - return dimension_filter_map + dimension_filter_map = {} + + for f in filters: + f.fieldname = scrub(f.accounting_dimension) + + build_map( + dimension_filter_map, + f.fieldname, + f.applicable_on_account, + f.dimension_value, + f.allow_or_restrict, + f.is_mandatory, + ) + frappe.flags.dimension_filter_map = dimension_filter_map + + return frappe.flags.dimension_filter_map def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory): diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py index 6aba2ab253f..3a7bf80a3d4 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py @@ -47,6 +47,8 @@ class TestAccountingDimensionFilter(unittest.TestCase): def tearDown(self): disable_dimension_filter() disable_dimension() + frappe.flags.accounting_dimensions_details = None + frappe.flags.dimension_filter_map = None for si in self.invoice_list: si.load_from_db() diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index 2cf9d97e595..aa77af6de8e 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -139,6 +139,8 @@ class Budget(Document): def validate_expense_against_budget(args, expense_amount=0): args = frappe._dict(args) + if not frappe.get_all("Budget", limit=1): + return if args.get("company") and not args.fiscal_year: args.fiscal_year = get_fiscal_year(args.get("posting_date"), company=args.get("company"))[0] @@ -146,6 +148,11 @@ def validate_expense_against_budget(args, expense_amount=0): "Company", args.get("company"), "exception_budget_approver_role" ) + if not frappe.get_cached_value( + "Budget", {"fiscal_year": args.fiscal_year, "company": args.company} + ): # nosec + return + if not args.account: args.account = args.get("expense_account") @@ -172,13 +179,13 @@ def validate_expense_against_budget(args, expense_amount=0): if ( args.get(budget_against) and args.account - and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"}) + and (frappe.get_cached_value("Account", args.account, "root_type") == "Expense") ): doctype = dimension.get("document_type") if frappe.get_cached_value("DocType", doctype, "is_tree"): - lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"]) + lft, rgt = frappe.get_cached_value(doctype, args.get(budget_against), ["lft", "rgt"]) condition = """and exists(select name from `tab%s` where lft<=%s and rgt>=%s and name=b.%s)""" % ( doctype, diff --git a/erpnext/accounts/doctype/budget_account/budget_account.json b/erpnext/accounts/doctype/budget_account/budget_account.json index ead07614a7f..c7d872647f1 100644 --- a/erpnext/accounts/doctype/budget_account/budget_account.json +++ b/erpnext/accounts/doctype/budget_account/budget_account.json @@ -1,94 +1,42 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-05-16 11:54:09.286135", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2016-05-16 11:54:09.286135", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "account", + "budget_amount" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1, + "search_index": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "budget_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Budget Amount", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "budget_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Budget Amount", + "options": "Company:company:default_currency", + "reqd": 1 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-01-02 17:02:53.339420", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Budget Account", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_seen": 0 + ], + "istable": 1, + "links": [], + "modified": "2024-03-04 15:43:27.016947", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Budget Account", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index def2838b751..3fa8710931c 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -323,7 +323,7 @@ def update_outstanding_amt( party_condition = "" if against_voucher_type == "Sales Invoice": - party_account = frappe.db.get_value(against_voucher_type, against_voucher, "debit_to") + party_account = frappe.get_cached_value(against_voucher_type, against_voucher, "debit_to") account_condition = "and account in ({0}, {1})".format( frappe.db.escape(account), frappe.db.escape(party_account) ) @@ -391,8 +391,8 @@ def update_outstanding_amt( def validate_frozen_account(account, adv_adj=None): frozen_account = frappe.get_cached_value("Account", account, "freeze_account") if frozen_account == "Yes" and not adv_adj: - frozen_accounts_modifier = frappe.db.get_single_value( - "Accounts Settings", "frozen_accounts_modifier" + frozen_accounts_modifier = frappe.get_cached_value( + "Accounts Settings", None, "frozen_accounts_modifier" ) if not frozen_accounts_modifier: diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py index e8dfda20234..3fea325a6fc 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py @@ -161,11 +161,12 @@ class PaymentLedgerEntry(Document): def on_update(self): adv_adj = self.flags.adv_adj if not self.flags.from_repost: - self.validate_account_details() - self.validate_dimensions_for_pl_and_bs() - self.validate_allowed_dimensions() - validate_balance_type(self.account, adv_adj) validate_frozen_account(self.account, adv_adj) + if not self.delinked: + self.validate_account_details() + self.validate_dimensions_for_pl_and_bs() + self.validate_allowed_dimensions() + validate_balance_type(self.account, adv_adj) # update outstanding amount if ( diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 28d4a5edcdb..3200fc57bde 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -3,7 +3,7 @@ import frappe -from frappe import _, throw +from frappe import _, qb, throw from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate @@ -742,13 +742,12 @@ class PurchaseInvoice(BuyingController): self.db_set("repost_required", self.needs_repost) def make_gl_entries(self, gl_entries=None, from_repost=False): - if not gl_entries: - gl_entries = self.get_gl_entries() + update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" + if self.docstatus == 1: + if not gl_entries: + gl_entries = self.get_gl_entries() - if gl_entries: - update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" - - if self.docstatus == 1: + if gl_entries: make_gl_entries( gl_entries, update_outstanding=update_outstanding, @@ -756,29 +755,43 @@ class PurchaseInvoice(BuyingController): from_repost=from_repost, ) self.make_exchange_gain_loss_journal() - elif self.docstatus == 2: - provisional_entries = [a for a in gl_entries if a.voucher_type == "Purchase Receipt"] - make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) - if provisional_entries: - for entry in provisional_entries: - frappe.db.set_value( - "GL Entry", - {"voucher_type": "Purchase Receipt", "voucher_detail_no": entry.voucher_detail_no}, - "is_cancelled", - 1, - ) - - if update_outstanding == "No": - update_outstanding_amt( - self.credit_to, - "Supplier", - self.supplier, - self.doctype, - self.return_against if cint(self.is_return) and self.return_against else self.name, - ) - - elif self.docstatus == 2 and cint(self.update_stock) and self.auto_accounting_for_stock: + elif self.docstatus == 2: make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) + self.cancel_provisional_entries() + + self.update_supplier_outstanding(update_outstanding) + + def cancel_provisional_entries(self): + rows = set() + purchase_receipts = set() + for d in self.items: + if d.purchase_receipt: + purchase_receipts.add(d.purchase_receipt) + rows.add(d.name) + + if rows: + # cancel gl entries + gle = qb.DocType("GL Entry") + gle_update_query = ( + qb.update(gle) + .set(gle.is_cancelled, 1) + .where( + (gle.voucher_type == "Purchase Receipt") + & (gle.voucher_no.isin(purchase_receipts)) + & (gle.voucher_detail_no.isin(rows)) + ) + ) + gle_update_query.run() + + def update_supplier_outstanding(self, update_outstanding): + if update_outstanding == "No": + update_outstanding_amt( + self.credit_to, + "Supplier", + self.supplier, + self.doctype, + self.return_against if cint(self.is_return) and self.return_against else self.name, + ) def get_gl_entries(self, warehouse_account=None): self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) @@ -891,8 +904,9 @@ class PurchaseInvoice(BuyingController): "Company", self.company, "enable_provisional_accounting_for_non_stock_items" ) ) - - purchase_receipt_doc_map = {} + self.provisional_enpenses_booked_in_pr = False + if provisional_accounting_for_non_stock_items: + self.get_provisional_accounts() for item in self.get("items"): if flt(item.base_net_amount): @@ -1029,44 +1043,7 @@ class PurchaseInvoice(BuyingController): dummy, amount = self.get_amount_and_base_amount(item, None) if provisional_accounting_for_non_stock_items: - if item.purchase_receipt: - provisional_account, pr_qty, pr_base_rate = frappe.get_cached_value( - "Purchase Receipt Item", - item.pr_detail, - ["provisional_expense_account", "qty", "base_rate"], - ) - provisional_account = provisional_account or self.get_company_default( - "default_provisional_account" - ) - purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt) - - if not purchase_receipt_doc: - purchase_receipt_doc = frappe.get_doc("Purchase Receipt", item.purchase_receipt) - purchase_receipt_doc_map[item.purchase_receipt] = purchase_receipt_doc - - # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt - expense_booked_in_pr = frappe.db.get_value( - "GL Entry", - { - "is_cancelled": 0, - "voucher_type": "Purchase Receipt", - "voucher_no": item.purchase_receipt, - "voucher_detail_no": item.pr_detail, - "account": provisional_account, - }, - "name", - ) - - if expense_booked_in_pr: - # Intentionally passing purchase invoice item to handle partial billing - purchase_receipt_doc.add_provisional_gl_entry( - item, - gl_entries, - self.posting_date, - provisional_account, - reverse=1, - item_amount=(min(item.qty, pr_qty) * pr_base_rate), - ) + self.make_provisional_gl_entry(gl_entries, item) if not self.is_internal_transfer(): gl_entries.append( @@ -1163,6 +1140,58 @@ class PurchaseInvoice(BuyingController): if item.is_fixed_asset and item.landed_cost_voucher_amount: self.update_gross_purchase_amount_for_linked_assets(item) + def get_provisional_accounts(self): + self.provisional_accounts = frappe._dict() + linked_purchase_receipts = set([d.purchase_receipt for d in self.items if d.purchase_receipt]) + pr_items = frappe.get_all( + "Purchase Receipt Item", + filters={"parent": ("in", linked_purchase_receipts)}, + fields=["name", "provisional_expense_account", "qty", "base_rate"], + ) + default_provisional_account = self.get_company_default("default_provisional_account") + for item in pr_items: + self.provisional_accounts[item.name] = { + "provisional_account": item.provisional_expense_account or default_provisional_account, + "qty": item.qty, + "base_rate": item.base_rate, + } + + def make_provisional_gl_entry(self, gl_entries, item): + if item.purchase_receipt: + if not self.provisional_enpenses_booked_in_pr: + pr_item = self.provisional_accounts.get(item.pr_detail, {}) + provisional_account = pr_item.get("provisional_account") + pr_qty = pr_item.get("qty") + pr_base_rate = pr_item.get("base_rate") + + # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt + provision_gle_against_pr = frappe.db.get_value( + "GL Entry", + { + "is_cancelled": 0, + "voucher_type": "Purchase Receipt", + "voucher_no": item.purchase_receipt, + "voucher_detail_no": item.pr_detail, + "account": provisional_account, + }, + ["name"], + ) + if provision_gle_against_pr: + self.provisional_enpenses_booked_in_pr = True + + if self.provisional_enpenses_booked_in_pr: + purchase_receipt_doc = frappe.get_cached_doc("Purchase Receipt", item.purchase_receipt) + + # Intentionally passing purchase invoice item to handle partial billing + purchase_receipt_doc.add_provisional_gl_entry( + item, + gl_entries, + self.posting_date, + provisional_account, + reverse=1, + item_amount=(min(item.qty, pr_qty) * pr_base_rate), + ) + def update_gross_purchase_amount_for_linked_assets(self, item): assets = frappe.db.get_all( "Asset", diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 2e828867559..825a01ee637 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -7,7 +7,7 @@ import copy import frappe from frappe import _ from frappe.model.meta import get_field_precision -from frappe.utils import cint, cstr, flt, formatdate, getdate, now +from frappe.utils import cint, flt, formatdate, getdate, now import erpnext from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( @@ -234,11 +234,13 @@ def get_cost_center_allocation_data(company, posting_date): def merge_similar_entries(gl_map, precision=None): merged_gl_map = [] accounting_dimensions = get_accounting_dimensions() + merge_properties = get_merge_properties(accounting_dimensions) for entry in gl_map: + entry.merge_key = get_merge_key(entry, merge_properties) # if there is already an entry in this account then just add it # to that entry - same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions) + same_head = check_if_in_list(entry, merged_gl_map) if same_head: same_head.debit = flt(same_head.debit) + flt(entry.debit) same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt( @@ -273,34 +275,35 @@ def merge_similar_entries(gl_map, precision=None): return merged_gl_map -def check_if_in_list(gle, gl_map, dimensions=None): - account_head_fieldnames = [ - "voucher_detail_no", - "party", - "against_voucher", +def get_merge_properties(dimensions=None): + merge_properties = [ + "account", "cost_center", - "against_voucher_type", + "party", "party_type", + "voucher_detail_no", + "against_voucher", + "against_voucher_type", "project", "finance_book", "voucher_no", ] - if dimensions: - account_head_fieldnames = account_head_fieldnames + dimensions + merge_properties.extend(dimensions) + return merge_properties + +def get_merge_key(entry, merge_properties): + merge_key = [] + for fieldname in merge_properties: + merge_key.append(entry.get(fieldname, "")) + + return tuple(merge_key) + + +def check_if_in_list(gle, gl_map): for e in gl_map: - same_head = True - if e.account != gle.account: - same_head = False - continue - - for fieldname in account_head_fieldnames: - if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)): - same_head = False - break - - if same_head: + if e.merge_key == gle.merge_key: return e diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b7e687dc1d8..88397730e87 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1712,8 +1712,8 @@ class AccountsController(TransactionBase): item_allowance = {} global_qty_allowance, global_amount_allowance = None, None - role_allowed_to_over_bill = frappe.db.get_single_value( - "Accounts Settings", "role_allowed_to_over_bill" + role_allowed_to_over_bill = frappe.get_cached_value( + "Accounts Settings", None, "role_allowed_to_over_bill" ) user_roles = frappe.get_roles() diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index e5f341fc257..fcbec22cf9a 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -577,6 +577,7 @@ class StatusUpdater(Document): ref_doc.set_status(update=True) +@frappe.request_cache def get_allowance_for( item_code, item_allowance=None, @@ -606,20 +607,20 @@ def get_allowance_for( global_amount_allowance, ) - qty_allowance, over_billing_allowance = frappe.db.get_value( + qty_allowance, over_billing_allowance = frappe.get_cached_value( "Item", item_code, ["over_delivery_receipt_allowance", "over_billing_allowance"] ) if qty_or_amount == "qty" and not qty_allowance: if global_qty_allowance == None: global_qty_allowance = flt( - frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance") + frappe.get_cached_value("Stock Settings", None, "over_delivery_receipt_allowance") ) qty_allowance = global_qty_allowance elif qty_or_amount == "amount" and not over_billing_allowance: if global_amount_allowance == None: global_amount_allowance = flt( - frappe.db.get_single_value("Accounts Settings", "over_billing_allowance") + frappe.get_cached_value("Accounts Settings", None, "over_billing_allowance") ) over_billing_allowance = global_amount_allowance diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json index 707f346d396..bf944a420bc 100644 --- a/erpnext/stock/doctype/item_price/item_price.json +++ b/erpnext/stock/doctype/item_price/item_price.json @@ -104,7 +104,8 @@ "in_standard_filter": 1, "label": "Price List", "options": "Price List", - "reqd": 1 + "reqd": 1, + "search_index": 1 }, { "bold": 1, @@ -220,7 +221,7 @@ "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-01-30 14:02:19.304854", + "modified": "2024-03-13 12:23:39.630290", "modified_by": "Administrator", "module": "Stock", "name": "Item Price", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 1cb10575cd1..3c8b808b58c 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -103,22 +103,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru if args.customer and cint(args.is_pos): out.update(get_pos_profile_item_details(args.company, args, update_data=True)) - if ( - args.get("doctype") == "Material Request" - and args.get("material_request_type") == "Material Transfer" - ): - out.update(get_bin_details(args.item_code, args.get("from_warehouse"))) - - elif out.get("warehouse"): - if doc and doc.get("doctype") == "Purchase Order": - # calculate company_total_stock only for po - bin_details = get_bin_details( - args.item_code, out.warehouse, args.company, include_child_warehouses=True - ) - else: - bin_details = get_bin_details(args.item_code, out.warehouse, include_child_warehouses=True) - - out.update(bin_details) + if item.is_stock_item: + update_bin_details(args, out, doc) # update args with out, if key or value not exists for key, value in out.items(): @@ -169,6 +155,24 @@ def set_valuation_rate(out, args): out.update(get_valuation_rate(args.item_code, args.company, out.get("warehouse"))) +def update_bin_details(args, out, doc): + if ( + args.get("doctype") == "Material Request" + and args.get("material_request_type") == "Material Transfer" + ): + out.update(get_bin_details(args.item_code, args.get("from_warehouse"))) + + elif out.get("warehouse"): + company = args.company if (doc and doc.get("doctype") == "Purchase Order") else None + + # calculate company_total_stock only for po + bin_details = get_bin_details( + args.item_code, out.warehouse, company, include_child_warehouses=True + ) + + out.update(bin_details) + + def process_args(args): if isinstance(args, str): args = json.loads(args) From 0d65d878deb717b80a22cae4d152d07289acae58 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 3 Feb 2024 11:58:11 +0530 Subject: [PATCH 03/65] refactor: more options for 'status' and move it to top --- .../transaction_deletion_record.json | 22 ++++++++++++++----- .../transaction_deletion_record.py | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index 23e59472a6d..8f3a5d05666 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -7,10 +7,12 @@ "engine": "InnoDB", "field_order": [ "company", + "column_break_txbg", + "status", + "section_break_tbej", "doctypes", "doctypes_to_be_ignored", - "amended_from", - "status" + "amended_from" ], "fields": [ { @@ -46,18 +48,27 @@ { "fieldname": "status", "fieldtype": "Select", - "hidden": 1, "label": "Status", - "options": "Draft\nCompleted" + "options": "Queued\nRunning\nFailed\nCompleted\nCancelled", + "read_only": 1 + }, + { + "fieldname": "column_break_txbg", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_tbej", + "fieldtype": "Section Break" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-08-04 20:15:59.071493", + "modified": "2024-02-03 12:42:21.628177", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -76,5 +87,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 88c4b078977..cbe7e05adb4 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -26,7 +26,7 @@ class TransactionDeletionRecord(Document): company: DF.Link doctypes: DF.Table[TransactionDeletionRecordItem] doctypes_to_be_ignored: DF.Table[TransactionDeletionRecordItem] - status: DF.Literal["Draft", "Completed"] + status: DF.Literal["Queued", "Running", "Completed"] # end: auto-generated types def __init__(self, *args, **kwargs): From 6fbb67b1d2da1fe9aa1879ce354f5702150584c8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 3 Feb 2024 12:46:12 +0530 Subject: [PATCH 04/65] refactor: set status and trigger job on submit --- .../transaction_deletion_record.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index cbe7e05adb4..073eec29a36 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -26,7 +26,7 @@ class TransactionDeletionRecord(Document): company: DF.Link doctypes: DF.Table[TransactionDeletionRecordItem] doctypes_to_be_ignored: DF.Table[TransactionDeletionRecordItem] - status: DF.Literal["Queued", "Running", "Completed"] + status: DF.Literal["Queued", "Running", "Failed", "Completed", "Cancelled"] # end: auto-generated types def __init__(self, *args, **kwargs): @@ -52,6 +52,16 @@ class TransactionDeletionRecord(Document): if not self.doctypes_to_be_ignored: self.populate_doctypes_to_be_ignored_table() + def before_save(self): + self.status = "" + + def on_submit(self): + self.db_set("status", "Queued") + + def on_cancel(self): + self.db_set("status", "Cancelled") + + def start_deletion_process(self): self.delete_bins() self.delete_lead_addresses() self.reset_company_values() From d0dc2c6e77c4e04e8d74a36a632ad6f2189dfedc Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 3 Feb 2024 14:29:04 +0530 Subject: [PATCH 05/65] refactor: tasks section and UI niceties --- .../transaction_deletion_record.json | 53 ++++++++++++++++++- .../transaction_deletion_record.py | 19 +++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index 8f3a5d05666..a9e04d3892e 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -9,6 +9,12 @@ "company", "column_break_txbg", "status", + "tasks_section", + "delete_bin_data", + "delete_leads_and_addresses", + "reset_company_default_values", + "clear_notifications", + "delete_transactions", "section_break_tbej", "doctypes", "doctypes_to_be_ignored", @@ -59,12 +65,57 @@ { "fieldname": "section_break_tbej", "fieldtype": "Section Break" + }, + { + "fieldname": "tasks_section", + "fieldtype": "Section Break", + "label": "Tasks" + }, + { + "default": "0", + "fieldname": "delete_bin_data", + "fieldtype": "Check", + "label": "Delete Bins", + "no_copy": 1, + "read_only": 1 + }, + { + "default": "0", + "fieldname": "delete_leads_and_addresses", + "fieldtype": "Check", + "label": "Delete Leads and Addresses", + "no_copy": 1, + "read_only": 1 + }, + { + "default": "0", + "fieldname": "clear_notifications", + "fieldtype": "Check", + "label": "Clear Notifications", + "no_copy": 1, + "read_only": 1 + }, + { + "default": "0", + "fieldname": "reset_company_default_values", + "fieldtype": "Check", + "label": "Reset Company Default Values", + "no_copy": 1, + "read_only": 1 + }, + { + "default": "0", + "fieldname": "delete_transactions", + "fieldtype": "Check", + "label": "Delete Transactions", + "no_copy": 1, + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-02-03 12:42:21.628177", + "modified": "2024-02-03 14:40:40.207482", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 073eec29a36..f1f6a5b90d3 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -23,9 +23,14 @@ class TransactionDeletionRecord(Document): ) amended_from: DF.Link | None + clear_notifications: DF.Check company: DF.Link + delete_bin_data: DF.Check + delete_leads_and_addresses: DF.Check + delete_transactions: DF.Check doctypes: DF.Table[TransactionDeletionRecordItem] doctypes_to_be_ignored: DF.Table[TransactionDeletionRecordItem] + reset_company_default_values: DF.Check status: DF.Literal["Queued", "Running", "Failed", "Completed", "Cancelled"] # end: auto-generated types @@ -52,8 +57,16 @@ class TransactionDeletionRecord(Document): if not self.doctypes_to_be_ignored: self.populate_doctypes_to_be_ignored_table() + def reset_task_flags(self): + self.clear_notifications = 0 + self.delete_bin_data = 0 + self.delete_leads_and_addresses = 0 + self.delete_transactions = 0 + self.reset_company_default_values = 0 + def before_save(self): self.status = "" + self.reset_task_flags() def on_submit(self): self.db_set("status", "Queued") @@ -61,11 +74,13 @@ class TransactionDeletionRecord(Document): def on_cancel(self): self.db_set("status", "Cancelled") + @frappe.whitelist() def start_deletion_process(self): self.delete_bins() self.delete_lead_addresses() self.reset_company_values() clear_notifications() + self.db_set("clear_notifications", 1) self.delete_company_transactions() def populate_doctypes_to_be_ignored_table(self): @@ -79,6 +94,7 @@ class TransactionDeletionRecord(Document): (select name from tabWarehouse where company=%s)""", self.company, ) + self.db_set("delete_bin_data", 1) def delete_lead_addresses(self): """Delete addresses to which leads are linked""" @@ -117,12 +133,14 @@ class TransactionDeletionRecord(Document): leads=",".join(leads) ) ) + self.db_set("delete_leads_and_addresses", 1) def reset_company_values(self): company_obj = frappe.get_doc("Company", self.company) company_obj.total_monthly_sales = 0 company_obj.sales_monthly_history = None company_obj.save() + self.db_set("reset_company_default_values", 1) def delete_company_transactions(self): doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list() @@ -156,6 +174,7 @@ class TransactionDeletionRecord(Document): if naming_series: if "#" in naming_series: self.update_naming_series(naming_series, docfield["parent"]) + self.db_set("delete_transactions", 1) def get_doctypes_to_be_ignored_list(self): singles = frappe.get_all("DocType", filters={"issingle": 1}, pluck="name") From 8944ab8b6ad1da2acdd5a852a17fc72e11d84695 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 3 Feb 2024 14:49:18 +0530 Subject: [PATCH 06/65] refactor: UI trigger --- .../transaction_deletion_record.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js index 527c753d6a9..671f927106d 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js @@ -24,6 +24,17 @@ frappe.ui.form.on("Transaction Deletion Record", { refresh: function (frm) { frm.fields_dict["doctypes_to_be_ignored"].grid.set_column_disp("no_of_docs", false); frm.refresh_field("doctypes_to_be_ignored"); + + if (frm.doc.docstatus==1 && ['Queued', 'Failed'].find(x => x == frm.doc.status)) { + let execute_btn = __("Start / Resume") + + frm.add_custom_button(execute_btn, () => { + frm.call({ + method: 'start_deletion_process', + doc: frm.doc + }); + }); + } }, }); From 6a77d86a53580b670937b92bcbde69ea920dbb9e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 3 Feb 2024 20:21:30 +0530 Subject: [PATCH 07/65] refactor: use flags to decide on current stage --- .../transaction_deletion_record.js | 12 +- .../transaction_deletion_record.json | 10 +- .../transaction_deletion_record.py | 196 ++++++++++-------- .../transaction_deletion_record_item.json | 19 +- .../transaction_deletion_record_item.py | 2 + 5 files changed, 141 insertions(+), 98 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js index 671f927106d..1a8b52f46bd 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js @@ -16,9 +16,15 @@ frappe.ui.form.on("Transaction Deletion Record", { }); } - frm.get_field("doctypes_to_be_ignored").grid.cannot_add_rows = true; - frm.fields_dict["doctypes_to_be_ignored"].grid.set_column_disp("no_of_docs", false); - frm.refresh_field("doctypes_to_be_ignored"); + + frm.get_field('doctypes_to_be_ignored').grid.cannot_add_rows = true; + frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false); + frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('done', false); + frm.refresh_field('doctypes_to_be_ignored'); + + frm.get_field('doctypes').grid.cannot_add_rows = true; + frm.fields_dict['doctypes'].grid.set_column_disp('no_of_docs', true); + frm.refresh_field('doctypes'); }, refresh: function (frm) { diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index a9e04d3892e..6a848413ffc 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -15,6 +15,7 @@ "reset_company_default_values", "clear_notifications", "delete_transactions", + "initialize_doctypes_table", "section_break_tbej", "doctypes", "doctypes_to_be_ignored", @@ -110,12 +111,19 @@ "label": "Delete Transactions", "no_copy": 1, "read_only": 1 + }, + { + "default": "0", + "fieldname": "initialize_doctypes_table", + "fieldtype": "Check", + "label": "Initialize Summary Table", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-02-03 14:40:40.207482", + "modified": "2024-02-03 20:48:34.107577", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index f1f6a5b90d3..5a5a827d304 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -30,6 +30,7 @@ class TransactionDeletionRecord(Document): delete_transactions: DF.Check doctypes: DF.Table[TransactionDeletionRecordItem] doctypes_to_be_ignored: DF.Table[TransactionDeletionRecordItem] + initialize_doctypes_table: DF.Check reset_company_default_values: DF.Check status: DF.Literal["Queued", "Running", "Failed", "Completed", "Cancelled"] # end: auto-generated types @@ -79,8 +80,10 @@ class TransactionDeletionRecord(Document): self.delete_bins() self.delete_lead_addresses() self.reset_company_values() - clear_notifications() - self.db_set("clear_notifications", 1) + if not self.clear_notifications: + clear_notifications() + self.db_set("clear_notifications", 1) + self.initialize_doctypes_to_be_deleted_table() self.delete_company_transactions() def populate_doctypes_to_be_ignored_table(self): @@ -89,92 +92,108 @@ class TransactionDeletionRecord(Document): self.append("doctypes_to_be_ignored", {"doctype_name": doctype}) def delete_bins(self): - frappe.db.sql( - """delete from `tabBin` where warehouse in - (select name from tabWarehouse where company=%s)""", - self.company, - ) - self.db_set("delete_bin_data", 1) + if not self.delete_bin_data: + frappe.db.sql( + """delete from `tabBin` where warehouse in + (select name from tabWarehouse where company=%s)""", + self.company, + ) + self.db_set("delete_bin_data", 1) def delete_lead_addresses(self): """Delete addresses to which leads are linked""" - leads = frappe.get_all("Lead", filters={"company": self.company}) - leads = ["'%s'" % row.get("name") for row in leads] - addresses = [] - if leads: - addresses = frappe.db.sql_list( - """select parent from `tabDynamic Link` where link_name - in ({leads})""".format( - leads=",".join(leads) - ) - ) - - if addresses: - addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] - - frappe.db.sql( - """delete from `tabAddress` where name in ({addresses}) and - name not in (select distinct dl1.parent from `tabDynamic Link` dl1 - inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent - and dl1.link_doctype<>dl2.link_doctype)""".format( - addresses=",".join(addresses) - ) - ) - - frappe.db.sql( - """delete from `tabDynamic Link` where link_doctype='Lead' - and parenttype='Address' and link_name in ({leads})""".format( + if not self.delete_leads_and_addresses: + leads = frappe.get_all("Lead", filters={"company": self.company}) + leads = ["'%s'" % row.get("name") for row in leads] + addresses = [] + if leads: + addresses = frappe.db.sql_list( + """select parent from `tabDynamic Link` where link_name + in ({leads})""".format( leads=",".join(leads) ) ) - frappe.db.sql( - """update `tabCustomer` set lead_name=NULL where lead_name in ({leads})""".format( - leads=",".join(leads) + if addresses: + addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] + + frappe.db.sql( + """delete from `tabAddress` where name in ({addresses}) and + name not in (select distinct dl1.parent from `tabDynamic Link` dl1 + inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent + and dl1.link_doctype<>dl2.link_doctype)""".format( + addresses=",".join(addresses) + ) + ) + + frappe.db.sql( + """delete from `tabDynamic Link` where link_doctype='Lead' + and parenttype='Address' and link_name in ({leads})""".format( + leads=",".join(leads) + ) + ) + + frappe.db.sql( + """update `tabCustomer` set lead_name=NULL where lead_name in ({leads})""".format( + leads=",".join(leads) + ) ) - ) - self.db_set("delete_leads_and_addresses", 1) + self.db_set("delete_leads_and_addresses", 1) def reset_company_values(self): - company_obj = frappe.get_doc("Company", self.company) - company_obj.total_monthly_sales = 0 - company_obj.sales_monthly_history = None - company_obj.save() - self.db_set("reset_company_default_values", 1) + if not self.reset_company_default_values: + company_obj = frappe.get_doc("Company", self.company) + company_obj.total_monthly_sales = 0 + company_obj.sales_monthly_history = None + company_obj.save() + self.db_set("reset_company_default_values", 1) + + def initialize_doctypes_to_be_deleted_table(self): + if not self.initialize_doctypes_table: + doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list() + docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list) + tables = self.get_all_child_doctypes() + for docfield in docfields: + if docfield["parent"] != self.doctype: + no_of_docs = self.get_number_of_docs_linked_with_specified_company( + docfield["parent"], docfield["fieldname"] + ) + if no_of_docs > 0: + # Initialize + self.populate_doctypes_table(tables, docfield["parent"], docfield["fieldname"], 0) + self.db_set("initialize_doctypes_table", 1) def delete_company_transactions(self): - doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list() - docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list) + if not self.delete_transactions: + doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list() + docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list) - tables = self.get_all_child_doctypes() - for docfield in docfields: - if docfield["parent"] != self.doctype: - no_of_docs = self.get_number_of_docs_linked_with_specified_company( - docfield["parent"], docfield["fieldname"] - ) - - if no_of_docs > 0: - self.delete_version_log(docfield["parent"], docfield["fieldname"]) - - reference_docs = frappe.get_all( - docfield["parent"], filters={docfield["fieldname"]: self.company} + tables = self.get_all_child_doctypes() + for docfield in self.doctypes: + if docfield.doctype_name != self.doctype: + no_of_docs = self.get_number_of_docs_linked_with_specified_company( + docfield.doctype_name, docfield.docfield_name ) - reference_doc_names = [r.name for r in reference_docs] + if no_of_docs > 0: + reference_docs = frappe.get_all( + docfield.doctype_name, filters={docfield.docfield_name: self.company}, limit=self.batch_size + ) + reference_doc_names = [r.name for r in reference_docs] - self.delete_communications(docfield["parent"], reference_doc_names) - self.delete_comments(docfield["parent"], reference_doc_names) - self.unlink_attachments(docfield["parent"], reference_doc_names) + self.delete_version_log(docfield.doctype_name, reference_doc_names) + self.delete_communications(docfield.doctype_name, reference_doc_names) + self.delete_comments(docfield.doctype_name, reference_doc_names) + self.unlink_attachments(docfield.doctype_name, reference_doc_names) - self.populate_doctypes_table(tables, docfield["parent"], no_of_docs) + self.delete_child_tables(docfield.doctype_name, reference_doc_names) + self.delete_docs_linked_with_specified_company(docfield.doctype_name, docfield.docfield_name) - self.delete_child_tables(docfield["parent"], docfield["fieldname"]) - self.delete_docs_linked_with_specified_company(docfield["parent"], docfield["fieldname"]) - - naming_series = frappe.db.get_value("DocType", docfield["parent"], "autoname") - if naming_series: - if "#" in naming_series: - self.update_naming_series(naming_series, docfield["parent"]) - self.db_set("delete_transactions", 1) + naming_series = frappe.db.get_value("DocType", docfield.doctype_name, "autoname") + # TODO: do this at the end of each doctype + if naming_series: + if "#" in naming_series: + self.update_naming_series(naming_series, docfield.doctype_name) + self.db_set("delete_transactions", 1) def get_doctypes_to_be_ignored_list(self): singles = frappe.get_all("DocType", filters={"issingle": 1}, pluck="name") @@ -203,22 +222,21 @@ class TransactionDeletionRecord(Document): def get_number_of_docs_linked_with_specified_company(self, doctype, company_fieldname): return frappe.db.count(doctype, {company_fieldname: self.company}) - def populate_doctypes_table(self, tables, doctype, no_of_docs): + def populate_doctypes_table(self, tables, doctype, fieldname, no_of_docs): + self.flags.ignore_validate_update_after_submit = True if doctype not in tables: - self.append("doctypes", {"doctype_name": doctype, "no_of_docs": no_of_docs}) - - def delete_child_tables(self, doctype, company_fieldname): - parent_docs_to_be_deleted = frappe.get_all( - doctype, {company_fieldname: self.company}, pluck="name" - ) + self.append( + "doctypes", {"doctype_name": doctype, "docfield_name": fieldname, "no_of_docs": no_of_docs} + ) + self.save(ignore_permissions=True) + def delete_child_tables(self, doctype, reference_doc_names): child_tables = frappe.get_all( "DocField", filters={"fieldtype": "Table", "parent": doctype}, pluck="options" ) - for batch in create_batch(parent_docs_to_be_deleted, self.batch_size): - for table in child_tables: - frappe.db.delete(table, {"parent": ["in", batch]}) + for table in child_tables: + frappe.db.delete(table, {"parent": ["in", reference_doc_names]}) def delete_docs_linked_with_specified_company(self, doctype, company_fieldname): frappe.db.delete(doctype, {company_fieldname: self.company}) @@ -242,17 +260,11 @@ class TransactionDeletionRecord(Document): frappe.db.sql("""update `tabSeries` set current = %s where name=%s""", (last, prefix)) - def delete_version_log(self, doctype, company_fieldname): - dt = qb.DocType(doctype) - names = qb.from_(dt).select(dt.name).where(dt[company_fieldname] == self.company).run(as_list=1) - names = [x[0] for x in names] - - if names: - versions = qb.DocType("Version") - for batch in create_batch(names, self.batch_size): - qb.from_(versions).delete().where( - (versions.ref_doctype == doctype) & (versions.docname.isin(batch)) - ).run() + def delete_version_log(self, doctype, docnames): + versions = qb.DocType("Version") + qb.from_(versions).delete().where( + (versions.ref_doctype == doctype) & (versions.docname.isin(docnames)) + ).run() def delete_communications(self, doctype, reference_doc_names): communications = frappe.get_all( diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json index be0be945c4e..4e5e1846999 100644 --- a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json +++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json @@ -6,7 +6,9 @@ "engine": "InnoDB", "field_order": [ "doctype_name", - "no_of_docs" + "docfield_name", + "no_of_docs", + "done" ], "fields": [ { @@ -22,12 +24,24 @@ "fieldtype": "Data", "in_list_view": 1, "label": "Number of Docs" + }, + { + "default": "0", + "fieldname": "done", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Done" + }, + { + "fieldname": "docfield_name", + "fieldtype": "Data", + "label": "DocField Name" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-05-08 23:10:46.166744", + "modified": "2024-02-03 21:06:32.274445", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record Item", @@ -35,5 +49,6 @@ "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py index f154cdb2474..ce716ac477c 100644 --- a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py +++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py @@ -15,7 +15,9 @@ class TransactionDeletionRecordItem(Document): if TYPE_CHECKING: from frappe.types import DF + docfield_name: DF.Data | None doctype_name: DF.Link + done: DF.Check no_of_docs: DF.Data | None parent: DF.Data parentfield: DF.Data From cccb2d5141e30234dd9d3f7ff877c9aaaa44879e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 4 Feb 2024 08:09:53 +0530 Subject: [PATCH 08/65] refactor: reorder flags in Tasks section --- .../transaction_deletion_record.json | 4 ++-- .../transaction_deletion_record.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index 6a848413ffc..dc35fe59553 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -14,8 +14,8 @@ "delete_leads_and_addresses", "reset_company_default_values", "clear_notifications", - "delete_transactions", "initialize_doctypes_table", + "delete_transactions", "section_break_tbej", "doctypes", "doctypes_to_be_ignored", @@ -123,7 +123,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-02-03 20:48:34.107577", + "modified": "2024-02-04 08:09:26.784109", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 5a5a827d304..96e5bf97891 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -170,7 +170,7 @@ class TransactionDeletionRecord(Document): tables = self.get_all_child_doctypes() for docfield in self.doctypes: - if docfield.doctype_name != self.doctype: + if docfield.doctype_name != self.doctype and not docfield.done: no_of_docs = self.get_number_of_docs_linked_with_specified_company( docfield.doctype_name, docfield.docfield_name ) @@ -193,6 +193,9 @@ class TransactionDeletionRecord(Document): if naming_series: if "#" in naming_series: self.update_naming_series(naming_series, docfield.doctype_name) + + else: + frappe.db.set_value(docfield.doctype, docfield.name, "done", 1) self.db_set("delete_transactions", 1) def get_doctypes_to_be_ignored_list(self): From b12ca65fcc76c85c9a26fb9d6d2e252ede4a3e70 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 4 Feb 2024 08:25:18 +0530 Subject: [PATCH 09/65] refactor: chained callback --- .../transaction_deletion_record.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 96e5bf97891..9911d44fc3c 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -75,22 +75,37 @@ class TransactionDeletionRecord(Document): def on_cancel(self): self.db_set("status", "Cancelled") + def chain_callback(self, method): + frappe.enqueue( + "frappe.utils.background_jobs.run_doc_method", + doctype=self.doctype, + name=self.name, + doc_method=method, + queue="long", + enqueue_after_commit=True, + ) + @frappe.whitelist() def start_deletion_process(self): self.delete_bins() self.delete_lead_addresses() self.reset_company_values() + self.delete_notifications() + self.initialize_doctypes_to_be_deleted_table() + self.delete_company_transactions() + + def delete_notifications(self): if not self.clear_notifications: clear_notifications() self.db_set("clear_notifications", 1) - self.initialize_doctypes_to_be_deleted_table() - self.delete_company_transactions() + self.chain_callback("initialize_doctypes_to_be_deleted_table") def populate_doctypes_to_be_ignored_table(self): doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() for doctype in doctypes_to_be_ignored_list: self.append("doctypes_to_be_ignored", {"doctype_name": doctype}) + @frappe.whitelist() def delete_bins(self): if not self.delete_bin_data: frappe.db.sql( @@ -99,6 +114,7 @@ class TransactionDeletionRecord(Document): self.company, ) self.db_set("delete_bin_data", 1) + self.chain_callback(method="delete_lead_addresses") def delete_lead_addresses(self): """Delete addresses to which leads are linked""" @@ -139,6 +155,7 @@ class TransactionDeletionRecord(Document): ) ) self.db_set("delete_leads_and_addresses", 1) + self.chain_callback(method="reset_company_values") def reset_company_values(self): if not self.reset_company_default_values: @@ -147,6 +164,7 @@ class TransactionDeletionRecord(Document): company_obj.sales_monthly_history = None company_obj.save() self.db_set("reset_company_default_values", 1) + self.chain_callback(method="delete_notifications") def initialize_doctypes_to_be_deleted_table(self): if not self.initialize_doctypes_table: @@ -162,6 +180,7 @@ class TransactionDeletionRecord(Document): # Initialize self.populate_doctypes_table(tables, docfield["parent"], docfield["fieldname"], 0) self.db_set("initialize_doctypes_table", 1) + self.chain_callback(method="delete_company_transactions") def delete_company_transactions(self): if not self.delete_transactions: @@ -194,6 +213,7 @@ class TransactionDeletionRecord(Document): if "#" in naming_series: self.update_naming_series(naming_series, docfield.doctype_name) + self.chain_callback(method="delete_company_transactions") else: frappe.db.set_value(docfield.doctype, docfield.name, "done", 1) self.db_set("delete_transactions", 1) From 49d3bcbc8df8fe9457cce55793896146705b50e5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 4 Feb 2024 10:57:34 +0530 Subject: [PATCH 10/65] refactor: use separate child table for summary --- .../__init__.py | 0 .../transaction_deletion_record_details.json | 59 +++++++++++++++++++ .../transaction_deletion_record_details.py | 26 ++++++++ .../transaction_deletion_record.js | 12 ++++ .../transaction_deletion_record.json | 4 +- .../transaction_deletion_record.py | 43 ++++++++------ .../transaction_deletion_record_item.json | 25 +------- .../transaction_deletion_record_item.py | 3 - 8 files changed, 126 insertions(+), 46 deletions(-) create mode 100644 erpnext/accounts/doctype/transaction_deletion_record_details/__init__.py create mode 100644 erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json create mode 100644 erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.py diff --git a/erpnext/accounts/doctype/transaction_deletion_record_details/__init__.py b/erpnext/accounts/doctype/transaction_deletion_record_details/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json new file mode 100644 index 00000000000..e8a5eb6c432 --- /dev/null +++ b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json @@ -0,0 +1,59 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-02-04 10:53:32.307930", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "doctype_name", + "docfield_name", + "no_of_docs", + "done" + ], + "fields": [ + { + "fieldname": "doctype_name", + "fieldtype": "Link", + "in_list_view": 1, + "label": "DocType", + "options": "DocType", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "docfield_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "DocField", + "read_only": 1 + }, + { + "fieldname": "no_of_docs", + "fieldtype": "Int", + "in_list_view": 1, + "label": "No of Docs", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "done", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Done", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-02-04 10:55:52.060417", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Transaction Deletion Record Details", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.py b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.py new file mode 100644 index 00000000000..bc5b5c41fdd --- /dev/null +++ b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.py @@ -0,0 +1,26 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class TransactionDeletionRecordDetails(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + docfield_name: DF.Data | None + doctype_name: DF.Link + done: DF.Check + no_of_docs: DF.Int + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + # end: auto-generated types + + pass diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js index 1a8b52f46bd..c6bb3781bfc 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js @@ -41,6 +41,18 @@ frappe.ui.form.on("Transaction Deletion Record", { }); }); } + + if (frm.doc.docstatus==1 && ['Queued', 'Failed'].find(x => x == frm.doc.status)) { + let execute_btn = __("Start Chain of Events") + + frm.add_custom_button(execute_btn, () => { + frm.call({ + method: 'delete_bins', + doc: frm.doc + }); + }); + } + }, }); diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index dc35fe59553..bbc571a0816 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -34,7 +34,7 @@ "fieldname": "doctypes", "fieldtype": "Table", "label": "Summary", - "options": "Transaction Deletion Record Item", + "options": "Transaction Deletion Record Details", "read_only": 1 }, { @@ -123,7 +123,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-02-04 08:09:26.784109", + "modified": "2024-02-04 10:55:09.430373", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 9911d44fc3c..60976aa572b 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -18,6 +18,9 @@ class TransactionDeletionRecord(Document): if TYPE_CHECKING: from frappe.types import DF + from erpnext.accounts.doctype.transaction_deletion_record_details.transaction_deletion_record_details import ( + TransactionDeletionRecordDetails, + ) from erpnext.setup.doctype.transaction_deletion_record_item.transaction_deletion_record_item import ( TransactionDeletionRecordItem, ) @@ -28,7 +31,7 @@ class TransactionDeletionRecord(Document): delete_bin_data: DF.Check delete_leads_and_addresses: DF.Check delete_transactions: DF.Check - doctypes: DF.Table[TransactionDeletionRecordItem] + doctypes: DF.Table[TransactionDeletionRecordDetails] doctypes_to_be_ignored: DF.Table[TransactionDeletionRecordItem] initialize_doctypes_table: DF.Check reset_company_default_values: DF.Check @@ -37,7 +40,7 @@ class TransactionDeletionRecord(Document): def __init__(self, *args, **kwargs): super(TransactionDeletionRecord, self).__init__(*args, **kwargs) - self.batch_size = 5000 + self.batch_size = 5 def validate(self): frappe.only_for("System Manager") @@ -75,7 +78,7 @@ class TransactionDeletionRecord(Document): def on_cancel(self): self.db_set("status", "Cancelled") - def chain_callback(self, method): + def chain_call(self, method): frappe.enqueue( "frappe.utils.background_jobs.run_doc_method", doctype=self.doctype, @@ -98,7 +101,7 @@ class TransactionDeletionRecord(Document): if not self.clear_notifications: clear_notifications() self.db_set("clear_notifications", 1) - self.chain_callback("initialize_doctypes_to_be_deleted_table") + self.chain_call("initialize_doctypes_to_be_deleted_table") def populate_doctypes_to_be_ignored_table(self): doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() @@ -114,7 +117,7 @@ class TransactionDeletionRecord(Document): self.company, ) self.db_set("delete_bin_data", 1) - self.chain_callback(method="delete_lead_addresses") + self.chain_call(method="delete_lead_addresses") def delete_lead_addresses(self): """Delete addresses to which leads are linked""" @@ -155,7 +158,7 @@ class TransactionDeletionRecord(Document): ) ) self.db_set("delete_leads_and_addresses", 1) - self.chain_callback(method="reset_company_values") + self.chain_call(method="reset_company_values") def reset_company_values(self): if not self.reset_company_default_values: @@ -164,7 +167,7 @@ class TransactionDeletionRecord(Document): company_obj.sales_monthly_history = None company_obj.save() self.db_set("reset_company_default_values", 1) - self.chain_callback(method="delete_notifications") + self.chain_call(method="delete_notifications") def initialize_doctypes_to_be_deleted_table(self): if not self.initialize_doctypes_table: @@ -180,7 +183,7 @@ class TransactionDeletionRecord(Document): # Initialize self.populate_doctypes_table(tables, docfield["parent"], docfield["fieldname"], 0) self.db_set("initialize_doctypes_table", 1) - self.chain_callback(method="delete_company_transactions") + self.chain_call(method="delete_company_transactions") def delete_company_transactions(self): if not self.delete_transactions: @@ -203,20 +206,24 @@ class TransactionDeletionRecord(Document): self.delete_communications(docfield.doctype_name, reference_doc_names) self.delete_comments(docfield.doctype_name, reference_doc_names) self.unlink_attachments(docfield.doctype_name, reference_doc_names) - self.delete_child_tables(docfield.doctype_name, reference_doc_names) - self.delete_docs_linked_with_specified_company(docfield.doctype_name, docfield.docfield_name) - + self.delete_docs_linked_with_specified_company(docfield.doctype_name, reference_doc_names) + processed = int(docfield.no_of_docs) + len(reference_doc_names) + frappe.db.set_value(docfield.doctype, docfield.name, "no_of_docs", processed) + else: naming_series = frappe.db.get_value("DocType", docfield.doctype_name, "autoname") - # TODO: do this at the end of each doctype if naming_series: if "#" in naming_series: self.update_naming_series(naming_series, docfield.doctype_name) - - self.chain_callback(method="delete_company_transactions") - else: frappe.db.set_value(docfield.doctype, docfield.name, "done", 1) - self.db_set("delete_transactions", 1) + + pending_doctypes = frappe.db.get_all( + docfield.doctype, filters={"parent": self.name, "done": 0}, pluck="doctype_name" + ) + if pending_doctypes: + self.chain_call(method="delete_company_transactions") + else: + self.db_set("delete_transactions", 1) def get_doctypes_to_be_ignored_list(self): singles = frappe.get_all("DocType", filters={"issingle": 1}, pluck="name") @@ -261,8 +268,8 @@ class TransactionDeletionRecord(Document): for table in child_tables: frappe.db.delete(table, {"parent": ["in", reference_doc_names]}) - def delete_docs_linked_with_specified_company(self, doctype, company_fieldname): - frappe.db.delete(doctype, {company_fieldname: self.company}) + def delete_docs_linked_with_specified_company(self, doctype, reference_doc_names): + frappe.db.delete(doctype, {"name": ("in", reference_doc_names)}) def update_naming_series(self, naming_series, doctype_name): if "." in naming_series: diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json index 4e5e1846999..89db63694c2 100644 --- a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json +++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json @@ -5,10 +5,7 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "doctype_name", - "docfield_name", - "no_of_docs", - "done" + "doctype_name" ], "fields": [ { @@ -18,30 +15,12 @@ "label": "DocType", "options": "DocType", "reqd": 1 - }, - { - "fieldname": "no_of_docs", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Number of Docs" - }, - { - "default": "0", - "fieldname": "done", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Done" - }, - { - "fieldname": "docfield_name", - "fieldtype": "Data", - "label": "DocField Name" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-02-03 21:06:32.274445", + "modified": "2024-02-04 10:56:27.413691", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record Item", diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py index ce716ac477c..906660739e4 100644 --- a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py +++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py @@ -15,10 +15,7 @@ class TransactionDeletionRecordItem(Document): if TYPE_CHECKING: from frappe.types import DF - docfield_name: DF.Data | None doctype_name: DF.Link - done: DF.Check - no_of_docs: DF.Data | None parent: DF.Data parentfield: DF.Data parenttype: DF.Data From b98a5e4edcd896b05e4e7fd5874c56ceb9d95bf0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 4 Feb 2024 15:26:33 +0530 Subject: [PATCH 11/65] chore: remove unwanted UI code --- .../transaction_deletion_record.js | 29 +++---------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js index c6bb3781bfc..027bbcb4b20 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js @@ -10,40 +10,19 @@ frappe.ui.form.on("Transaction Deletion Record", { callback: function (r) { doctypes_to_be_ignored_array = r.message; populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm); - frm.fields_dict["doctypes_to_be_ignored"].grid.set_column_disp("no_of_docs", false); - frm.refresh_field("doctypes_to_be_ignored"); - }, + frm.refresh_field('doctypes_to_be_ignored'); + } }); } frm.get_field('doctypes_to_be_ignored').grid.cannot_add_rows = true; - frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false); - frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('done', false); - frm.refresh_field('doctypes_to_be_ignored'); - frm.get_field('doctypes').grid.cannot_add_rows = true; - frm.fields_dict['doctypes'].grid.set_column_disp('no_of_docs', true); - frm.refresh_field('doctypes'); }, - refresh: function (frm) { - frm.fields_dict["doctypes_to_be_ignored"].grid.set_column_disp("no_of_docs", false); - frm.refresh_field("doctypes_to_be_ignored"); - + refresh: function(frm) { if (frm.doc.docstatus==1 && ['Queued', 'Failed'].find(x => x == frm.doc.status)) { - let execute_btn = __("Start / Resume") - - frm.add_custom_button(execute_btn, () => { - frm.call({ - method: 'start_deletion_process', - doc: frm.doc - }); - }); - } - - if (frm.doc.docstatus==1 && ['Queued', 'Failed'].find(x => x == frm.doc.status)) { - let execute_btn = __("Start Chain of Events") + let execute_btn = __("Start Deletion") frm.add_custom_button(execute_btn, () => { frm.call({ From 7c4cff2649daa52d6cabf8a046b3867ab60e0da6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 4 Feb 2024 15:29:39 +0530 Subject: [PATCH 12/65] refactor: make Excluded doctype table read only --- .../transaction_deletion_record.js | 4 +--- .../transaction_deletion_record.json | 5 +++-- .../transaction_deletion_record.py | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js index 027bbcb4b20..ed70ebb5f70 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js @@ -15,9 +15,6 @@ frappe.ui.form.on("Transaction Deletion Record", { }); } - - frm.get_field('doctypes_to_be_ignored').grid.cannot_add_rows = true; - }, refresh: function(frm) { @@ -25,6 +22,7 @@ frappe.ui.form.on("Transaction Deletion Record", { let execute_btn = __("Start Deletion") frm.add_custom_button(execute_btn, () => { + // Entry point for chain of events frm.call({ method: 'delete_bins', doc: frm.doc diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index bbc571a0816..bd45b1c109d 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -41,7 +41,8 @@ "fieldname": "doctypes_to_be_ignored", "fieldtype": "Table", "label": "Excluded DocTypes", - "options": "Transaction Deletion Record Item" + "options": "Transaction Deletion Record Item", + "read_only": 1 }, { "fieldname": "amended_from", @@ -123,7 +124,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-02-04 10:55:09.430373", + "modified": "2024-02-04 15:28:29.532826", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 60976aa572b..0d553ca19e1 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -110,6 +110,7 @@ class TransactionDeletionRecord(Document): @frappe.whitelist() def delete_bins(self): + # This methid is the entry point for the chain of events that follow if not self.delete_bin_data: frappe.db.sql( """delete from `tabBin` where warehouse in From 86b5e2e2779eb498a8525ca07c6e25335e6f2f5d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 4 Feb 2024 15:37:21 +0530 Subject: [PATCH 13/65] refactor: validate status before running events --- .../transaction_deletion_record.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 0d553ca19e1..63b28017685 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -6,7 +6,7 @@ import frappe from frappe import _, qb from frappe.desk.notifications import clear_notifications from frappe.model.document import Document -from frappe.utils import cint, create_batch +from frappe.utils import cint, create_batch, get_link_to_form class TransactionDeletionRecord(Document): @@ -40,7 +40,7 @@ class TransactionDeletionRecord(Document): def __init__(self, *args, **kwargs): super(TransactionDeletionRecord, self).__init__(*args, **kwargs) - self.batch_size = 5 + self.batch_size = 5000 def validate(self): frappe.only_for("System Manager") @@ -98,6 +98,7 @@ class TransactionDeletionRecord(Document): self.delete_company_transactions() def delete_notifications(self): + self.validate_doc_status() if not self.clear_notifications: clear_notifications() self.db_set("clear_notifications", 1) @@ -108,9 +109,19 @@ class TransactionDeletionRecord(Document): for doctype in doctypes_to_be_ignored_list: self.append("doctypes_to_be_ignored", {"doctype_name": doctype}) + def validate_doc_status(self): + if self.status != "Running": + frappe.throw( + _("{0} is not running. Cannot trigger events for this Document").format( + get_link_to_form("Transaction Deletion Record", self.name) + ) + ) + @frappe.whitelist() def delete_bins(self): # This methid is the entry point for the chain of events that follow + self.db_set("status", "Running") + if not self.delete_bin_data: frappe.db.sql( """delete from `tabBin` where warehouse in @@ -122,6 +133,7 @@ class TransactionDeletionRecord(Document): def delete_lead_addresses(self): """Delete addresses to which leads are linked""" + self.validate_doc_status() if not self.delete_leads_and_addresses: leads = frappe.get_all("Lead", filters={"company": self.company}) leads = ["'%s'" % row.get("name") for row in leads] @@ -162,6 +174,7 @@ class TransactionDeletionRecord(Document): self.chain_call(method="reset_company_values") def reset_company_values(self): + self.validate_doc_status() if not self.reset_company_default_values: company_obj = frappe.get_doc("Company", self.company) company_obj.total_monthly_sales = 0 @@ -171,6 +184,7 @@ class TransactionDeletionRecord(Document): self.chain_call(method="delete_notifications") def initialize_doctypes_to_be_deleted_table(self): + self.validate_doc_status() if not self.initialize_doctypes_table: doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list() docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list) @@ -187,6 +201,7 @@ class TransactionDeletionRecord(Document): self.chain_call(method="delete_company_transactions") def delete_company_transactions(self): + self.validate_doc_status() if not self.delete_transactions: doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list() docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list) @@ -212,6 +227,7 @@ class TransactionDeletionRecord(Document): processed = int(docfield.no_of_docs) + len(reference_doc_names) frappe.db.set_value(docfield.doctype, docfield.name, "no_of_docs", processed) else: + # reset naming series naming_series = frappe.db.get_value("DocType", docfield.doctype_name, "autoname") if naming_series: if "#" in naming_series: @@ -224,6 +240,7 @@ class TransactionDeletionRecord(Document): if pending_doctypes: self.chain_call(method="delete_company_transactions") else: + self.db_set("status", "Completed") self.db_set("delete_transactions", 1) def get_doctypes_to_be_ignored_list(self): From 1014940953ed54f31a170e0e99d03c1c5f1cd022 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 4 Feb 2024 16:11:42 +0530 Subject: [PATCH 14/65] chore: show correct status in list view --- .../transaction_deletion_record_list.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js index 08a35df2c17..7c7b8ff25a7 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js @@ -1,12 +1,16 @@ // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.listview_settings["Transaction Deletion Record"] = { - get_indicator: function (doc) { - if (doc.docstatus == 0) { - return [__("Draft"), "red"]; - } else { - return [__("Completed"), "green"]; - } +frappe.listview_settings['Transaction Deletion Record'] = { + add_fields: ["status"], + get_indicator: function(doc) { + let colors = { + 'Queued': 'orange', + 'Completed': 'green', + 'Running': 'blue', + 'Failed': 'red', + }; + let status = doc.status; + return [__(status), colors[status], 'status,=,'+status]; }, }; From 2dbe68a09d0bf9a13ef0d3bb8f071a7634291766 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Feb 2024 10:21:12 +0530 Subject: [PATCH 15/65] refactor: reset all flags and remove unwanted code --- .../transaction_deletion_record.json | 4 +++- .../transaction_deletion_record.py | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index bd45b1c109d..aa06d14b170 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -57,6 +57,7 @@ "fieldname": "status", "fieldtype": "Select", "label": "Status", + "no_copy": 1, "options": "Queued\nRunning\nFailed\nCompleted\nCancelled", "read_only": 1 }, @@ -118,13 +119,14 @@ "fieldname": "initialize_doctypes_table", "fieldtype": "Check", "label": "Initialize Summary Table", + "no_copy": 1, "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-02-04 15:28:29.532826", + "modified": "2024-02-05 10:25:28.462255", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 63b28017685..5f430b85f58 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -58,6 +58,15 @@ class TransactionDeletionRecord(Document): ) def before_submit(self): + if queued_docs := frappe.db.get_all( + "Transaction Deletion Record", filters={"company": self.company, "status": "Queued"} + ): + frappe.throw( + _("There is another document: {0} Queued. Cannot queue multi docs for one company.").format( + self.queued_docs + ) + ) + if not self.doctypes_to_be_ignored: self.populate_doctypes_to_be_ignored_table() @@ -66,6 +75,7 @@ class TransactionDeletionRecord(Document): self.delete_bin_data = 0 self.delete_leads_and_addresses = 0 self.delete_transactions = 0 + self.initialize_doctypes_table = 0 self.reset_company_default_values = 0 def before_save(self): @@ -88,15 +98,6 @@ class TransactionDeletionRecord(Document): enqueue_after_commit=True, ) - @frappe.whitelist() - def start_deletion_process(self): - self.delete_bins() - self.delete_lead_addresses() - self.reset_company_values() - self.delete_notifications() - self.initialize_doctypes_to_be_deleted_table() - self.delete_company_transactions() - def delete_notifications(self): self.validate_doc_status() if not self.clear_notifications: From 55e93b3fe14b492e2f648b6ee860e902d87013e6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Feb 2024 10:42:59 +0530 Subject: [PATCH 16/65] refactor: no copy on summary table and more validations --- .../transaction_deletion_record.json | 3 ++- .../transaction_deletion_record.py | 14 ++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index aa06d14b170..6e057ace4a6 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -34,6 +34,7 @@ "fieldname": "doctypes", "fieldtype": "Table", "label": "Summary", + "no_copy": 1, "options": "Transaction Deletion Record Details", "read_only": 1 }, @@ -126,7 +127,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-02-05 10:25:28.462255", + "modified": "2024-02-05 10:36:34.229864", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 5f430b85f58..c2c173e0514 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -6,7 +6,7 @@ import frappe from frappe import _, qb from frappe.desk.notifications import clear_notifications from frappe.model.document import Document -from frappe.utils import cint, create_batch, get_link_to_form +from frappe.utils import cint, comma_and, create_batch, get_link_to_form class TransactionDeletionRecord(Document): @@ -59,11 +59,16 @@ class TransactionDeletionRecord(Document): def before_submit(self): if queued_docs := frappe.db.get_all( - "Transaction Deletion Record", filters={"company": self.company, "status": "Queued"} + "Transaction Deletion Record", + filters={"company": self.company, "status": ("in", ["Running", "Queued"]), "docstatus": 1}, + pluck="name", ): frappe.throw( - _("There is another document: {0} Queued. Cannot queue multi docs for one company.").format( - self.queued_docs + _( + "Cannot queue multi docs for one company. {0} is already queued/running for company: {1}" + ).format( + comma_and([get_link_to_form("Transaction Deletion Record", x) for x in queued_docs]), + frappe.bold(self.company), ) ) @@ -80,6 +85,7 @@ class TransactionDeletionRecord(Document): def before_save(self): self.status = "" + self.doctypes.clear() self.reset_task_flags() def on_submit(self): From 31a2da552b0773f8b7caf510fa06e54cdc1f2e9c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Feb 2024 11:52:00 +0530 Subject: [PATCH 17/65] refactor: validations to prevent duplicate jobs --- .../transaction_deletion_record.py | 75 +++++++++++++++---- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index c2c173e0514..71892bbba1a 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -7,6 +7,7 @@ from frappe import _, qb from frappe.desk.notifications import clear_notifications from frappe.model.document import Document from frappe.utils import cint, comma_and, create_batch, get_link_to_form +from frappe.utils.background_jobs import get_job, is_job_enqueued class TransactionDeletionRecord(Document): @@ -41,6 +42,15 @@ class TransactionDeletionRecord(Document): def __init__(self, *args, **kwargs): super(TransactionDeletionRecord, self).__init__(*args, **kwargs) self.batch_size = 5000 + # Tasks are listged by their execution order + self.task_to_internal_method_map = { + "Delete Bins": "delete_bins", + "Delete Leads and Addresses": "delete_lead_addresses", + "Reset Company Values": "reset_company_values", + "Clear Notifications": "delete_notifications", + "Initialize Summary Table": "initialize_doctypes_to_be_deleted_table", + "Delete Transactions": "delete_company_transactions", + } def validate(self): frappe.only_for("System Manager") @@ -57,6 +67,16 @@ class TransactionDeletionRecord(Document): title=_("Not Allowed"), ) + def generate_job_name_for_task(self, task=None): + method = self.task_to_internal_method_map[task] + return f"{self.name}_{method}" + + def generate_job_name_for_all_tasks(self): + job_names = [] + for method in self.task_to_internal_method_map.values(): + job_names.append(self.generate_job_name_for_task) + return job_names + def before_submit(self): if queued_docs := frappe.db.get_all( "Transaction Deletion Record", @@ -65,7 +85,7 @@ class TransactionDeletionRecord(Document): ): frappe.throw( _( - "Cannot queue multi docs for one company. {0} is already queued/running for company: {1}" + "Cannot enqueue multi docs for one company. {0} is already queued/running for company: {1}" ).format( comma_and([get_link_to_form("Transaction Deletion Record", x) for x in queued_docs]), frappe.bold(self.company), @@ -94,28 +114,47 @@ class TransactionDeletionRecord(Document): def on_cancel(self): self.db_set("status", "Cancelled") - def chain_call(self, method): - frappe.enqueue( - "frappe.utils.background_jobs.run_doc_method", - doctype=self.doctype, - name=self.name, - doc_method=method, - queue="long", - enqueue_after_commit=True, - ) + def chain_call(self, task=None): + if task and task in self.task_to_internal_method_map: + method = self.task_to_internal_method_map[task] + job_id = self.generate_job_name_for_task(task) + + frappe.enqueue( + "frappe.utils.background_jobs.run_doc_method", + doctype=self.doctype, + name=self.name, + doc_method=method, + job_id=job_id, + queue="long", + enqueue_after_commit=True, + ) def delete_notifications(self): self.validate_doc_status() if not self.clear_notifications: clear_notifications() self.db_set("clear_notifications", 1) - self.chain_call("initialize_doctypes_to_be_deleted_table") + self.chain_call(task="Initialize Summary Table") def populate_doctypes_to_be_ignored_table(self): doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() for doctype in doctypes_to_be_ignored_list: self.append("doctypes_to_be_ignored", {"doctype_name": doctype}) + def validate_running_task_for_doc(self, job_names: list = None): + # at most only one task should be runnning + running_tasks = [] + for x in job_names: + if is_job_enqueued(x): + running_tasks.append(get_job(x).get_id()) + + if running_tasks: + frappe.throw( + _("{0} is already running for {1}").format( + comma_and([get_link_to_form("RQ Job", x) for x in running_tasks]), self.name + ) + ) + def validate_doc_status(self): if self.status != "Running": frappe.throw( @@ -123,6 +162,9 @@ class TransactionDeletionRecord(Document): get_link_to_form("Transaction Deletion Record", self.name) ) ) + # make sure that job none of tasks are already running + job_names = self.generate_job_name_for_all_tasks() + self.validate_running_task_for_doc(job_names=job_names) @frappe.whitelist() def delete_bins(self): @@ -136,7 +178,7 @@ class TransactionDeletionRecord(Document): self.company, ) self.db_set("delete_bin_data", 1) - self.chain_call(method="delete_lead_addresses") + self.chain_call(task="Delete Leads and Addresses") def delete_lead_addresses(self): """Delete addresses to which leads are linked""" @@ -178,7 +220,7 @@ class TransactionDeletionRecord(Document): ) ) self.db_set("delete_leads_and_addresses", 1) - self.chain_call(method="reset_company_values") + self.chain_call(task="Reset Company Values") def reset_company_values(self): self.validate_doc_status() @@ -188,7 +230,7 @@ class TransactionDeletionRecord(Document): company_obj.sales_monthly_history = None company_obj.save() self.db_set("reset_company_default_values", 1) - self.chain_call(method="delete_notifications") + self.chain_call(task="Clear Notifications") def initialize_doctypes_to_be_deleted_table(self): self.validate_doc_status() @@ -205,7 +247,7 @@ class TransactionDeletionRecord(Document): # Initialize self.populate_doctypes_table(tables, docfield["parent"], docfield["fieldname"], 0) self.db_set("initialize_doctypes_table", 1) - self.chain_call(method="delete_company_transactions") + self.chain_call(task="Delete Transactions") def delete_company_transactions(self): self.validate_doc_status() @@ -245,7 +287,8 @@ class TransactionDeletionRecord(Document): docfield.doctype, filters={"parent": self.name, "done": 0}, pluck="doctype_name" ) if pending_doctypes: - self.chain_call(method="delete_company_transactions") + # as method is enqueued after commit, calling itself will not make validate_doc_status to throw + self.chain_call(task="Delete Transactions") else: self.db_set("status", "Completed") self.db_set("delete_transactions", 1) From 98afb4d468145ac2270362841468f2ccb05b501f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Feb 2024 17:35:14 +0530 Subject: [PATCH 18/65] chore: hide docfield in list view --- .../transaction_deletion_record_details.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json index e8a5eb6c432..fe4b0852ac1 100644 --- a/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json +++ b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json @@ -24,7 +24,6 @@ { "fieldname": "docfield_name", "fieldtype": "Data", - "in_list_view": 1, "label": "DocField", "read_only": 1 }, @@ -47,7 +46,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-02-04 10:55:52.060417", + "modified": "2024-02-05 17:35:09.556054", "modified_by": "Administrator", "module": "Accounts", "name": "Transaction Deletion Record Details", From 78c9cc63b1011761dd3fed84edb5c0b41ff9f3f5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Feb 2024 20:35:29 +0530 Subject: [PATCH 19/65] refactor: make sure only one task is running for doc --- .../transaction_deletion_record.js | 2 +- .../transaction_deletion_record.py | 48 ++++++++++++------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js index ed70ebb5f70..ccf09a6c38b 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js @@ -24,7 +24,7 @@ frappe.ui.form.on("Transaction Deletion Record", { frm.add_custom_button(execute_btn, () => { // Entry point for chain of events frm.call({ - method: 'delete_bins', + method: 'process_tasks', doc: frm.doc }); }); diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 71892bbba1a..be161e30322 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -1,6 +1,7 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +from collections import OrderedDict import frappe from frappe import _, qb @@ -42,15 +43,17 @@ class TransactionDeletionRecord(Document): def __init__(self, *args, **kwargs): super(TransactionDeletionRecord, self).__init__(*args, **kwargs) self.batch_size = 5000 - # Tasks are listged by their execution order - self.task_to_internal_method_map = { - "Delete Bins": "delete_bins", - "Delete Leads and Addresses": "delete_lead_addresses", - "Reset Company Values": "reset_company_values", - "Clear Notifications": "delete_notifications", - "Initialize Summary Table": "initialize_doctypes_to_be_deleted_table", - "Delete Transactions": "delete_company_transactions", - } + # Tasks are listed by their execution order + self.task_to_internal_method_map = OrderedDict( + { + "Delete Bins": "delete_bins", + "Delete Leads and Addresses": "delete_lead_addresses", + "Reset Company Values": "reset_company_values", + "Clear Notifications": "delete_notifications", + "Initialize Summary Table": "initialize_doctypes_to_be_deleted_table", + "Delete Transactions": "delete_company_transactions", + } + ) def validate(self): frappe.only_for("System Manager") @@ -71,10 +74,19 @@ class TransactionDeletionRecord(Document): method = self.task_to_internal_method_map[task] return f"{self.name}_{method}" + def generate_job_name_for_next_tasks(self, task=None): + job_names = [] + current_task_idx = list(self.task_to_internal_method_map).index(task) + for idx, task in enumerate(self.task_to_internal_method_map.keys(), 0): + # generate job_name for next tasks + if idx > current_task_idx: + job_names.append(self.generate_job_name_for_task(task)) + return job_names + def generate_job_name_for_all_tasks(self): job_names = [] - for method in self.task_to_internal_method_map.values(): - job_names.append(self.generate_job_name_for_task) + for task in self.task_to_internal_method_map.keys(): + job_names.append(self.generate_job_name_for_task(task)) return job_names def before_submit(self): @@ -116,6 +128,10 @@ class TransactionDeletionRecord(Document): def chain_call(self, task=None): if task and task in self.task_to_internal_method_map: + # make sure that none of next tasks are already running + job_names = self.generate_job_name_for_next_tasks(task=task) + self.validate_running_task_for_doc(job_names=job_names) + method = self.task_to_internal_method_map[task] job_id = self.generate_job_name_for_task(task) @@ -162,15 +178,15 @@ class TransactionDeletionRecord(Document): get_link_to_form("Transaction Deletion Record", self.name) ) ) - # make sure that job none of tasks are already running - job_names = self.generate_job_name_for_all_tasks() - self.validate_running_task_for_doc(job_names=job_names) @frappe.whitelist() - def delete_bins(self): - # This methid is the entry point for the chain of events that follow + def process_tasks(self): + # This method is the entry point for the chain of events that follow self.db_set("status", "Running") + self.chain_call(task="Delete Bins") + def delete_bins(self): + self.validate_doc_status() if not self.delete_bin_data: frappe.db.sql( """delete from `tabBin` where warehouse in From ec194ef076037b16edea68728069b3308a9a4216 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 7 Feb 2024 20:54:29 +0530 Subject: [PATCH 20/65] refactor: barebones hook on all doctypes with 'company' field --- erpnext/hooks.py | 5 ++++- .../transaction_deletion_record.py | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 308e6ca011e..492ae701d52 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -309,7 +309,10 @@ period_closing_doctypes = [ doc_events = { "*": { - "validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply", + "validate": [ + "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply", + "erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.check_for_running_deletion_job", + ], }, tuple(period_closing_doctypes): { "validate": "erpnext.accounts.doctype.accounting_period.accounting_period.validate_accounting_period_on_doc_save", diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index be161e30322..5a49a7d1d7f 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -450,3 +450,24 @@ def get_doctypes_to_be_ignored(): doctypes_to_be_ignored.extend(frappe.get_hooks("company_data_to_be_ignored") or []) return doctypes_to_be_ignored + + +def check_for_running_deletion_job(doc, method=None): + df = qb.DocType("DocField") + if ( + not_allowed := qb.from_(df) + .select(df.parent) + .where((df.fieldname == "company") & (df.parent == doc.doctype)) + .run() + ): + if running_deletion_jobs := frappe.db.get_all( + "Transaction Deletion Record", + filters={"docstatus": 1, "company": doc.company, "status": "Running"}, + ): + frappe.throw( + _( + "Transaction Deletion job {0} is running for this Company. Cannot make any transactions until the deletion job is completed" + ).format( + get_link_to_form("Transaction Deletion Record", running_deletion_jobs[0].name) + ) + ) From 30463657bf7f44321b690a79c94e24d6286d486e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 8 Feb 2024 16:08:59 +0530 Subject: [PATCH 21/65] refactor: better method naming --- .../transaction_deletion_record.py | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 5a49a7d1d7f..170b1bfe72d 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -126,31 +126,40 @@ class TransactionDeletionRecord(Document): def on_cancel(self): self.db_set("status", "Cancelled") - def chain_call(self, task=None): + def enqueue_task(self, task: str | None = None): if task and task in self.task_to_internal_method_map: # make sure that none of next tasks are already running job_names = self.generate_job_name_for_next_tasks(task=task) self.validate_running_task_for_doc(job_names=job_names) - method = self.task_to_internal_method_map[task] + # method = self.task_to_internal_method_map[task] + # Generate Job Id to uniquely identify each task for this document job_id = self.generate_job_name_for_task(task) frappe.enqueue( "frappe.utils.background_jobs.run_doc_method", doctype=self.doctype, name=self.name, - doc_method=method, + doc_method="execute_task", job_id=job_id, queue="long", enqueue_after_commit=True, + task_to_execute=task, ) + def execute_task(self, task_to_execute: str | None = None): + if task_to_execute: + pass + method = self.task_to_internal_method_map[task_to_execute] + if task := getattr(self, method, None): + task() + def delete_notifications(self): self.validate_doc_status() if not self.clear_notifications: clear_notifications() self.db_set("clear_notifications", 1) - self.chain_call(task="Initialize Summary Table") + self.enqueue_task(task="Initialize Summary Table") def populate_doctypes_to_be_ignored_table(self): doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() @@ -183,7 +192,7 @@ class TransactionDeletionRecord(Document): def process_tasks(self): # This method is the entry point for the chain of events that follow self.db_set("status", "Running") - self.chain_call(task="Delete Bins") + self.enqueue_task(task="Delete Bins") def delete_bins(self): self.validate_doc_status() @@ -194,7 +203,7 @@ class TransactionDeletionRecord(Document): self.company, ) self.db_set("delete_bin_data", 1) - self.chain_call(task="Delete Leads and Addresses") + self.enqueue_task(task="Delete Leads and Addresses") def delete_lead_addresses(self): """Delete addresses to which leads are linked""" @@ -236,7 +245,7 @@ class TransactionDeletionRecord(Document): ) ) self.db_set("delete_leads_and_addresses", 1) - self.chain_call(task="Reset Company Values") + self.enqueue_task(task="Reset Company Values") def reset_company_values(self): self.validate_doc_status() @@ -246,7 +255,7 @@ class TransactionDeletionRecord(Document): company_obj.sales_monthly_history = None company_obj.save() self.db_set("reset_company_default_values", 1) - self.chain_call(task="Clear Notifications") + self.enqueue_task(task="Clear Notifications") def initialize_doctypes_to_be_deleted_table(self): self.validate_doc_status() @@ -263,7 +272,7 @@ class TransactionDeletionRecord(Document): # Initialize self.populate_doctypes_table(tables, docfield["parent"], docfield["fieldname"], 0) self.db_set("initialize_doctypes_table", 1) - self.chain_call(task="Delete Transactions") + self.enqueue_task(task="Delete Transactions") def delete_company_transactions(self): self.validate_doc_status() @@ -304,7 +313,8 @@ class TransactionDeletionRecord(Document): ) if pending_doctypes: # as method is enqueued after commit, calling itself will not make validate_doc_status to throw - self.chain_call(task="Delete Transactions") + # recursively call this task to delete all transactions + self.enqueue_task(task="Delete Transactions") else: self.db_set("status", "Completed") self.db_set("delete_transactions", 1) From 8b81274769f13a5b7ec91d79f4d2a13d48faeb9c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 18 Mar 2024 08:22:28 +0530 Subject: [PATCH 22/65] refactor: toggle between 'http' and 'https' on exchange rate API --- .../currency_exchange_settings.js | 46 ++++++++++++------- .../currency_exchange_settings.json | 10 +++- .../currency_exchange_settings.py | 21 ++++++++- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js index d931f627dbd..ad68352c2a4 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js @@ -3,22 +3,36 @@ frappe.ui.form.on("Currency Exchange Settings", { service_provider: function (frm) { - if (frm.doc.service_provider == "exchangerate.host") { - let result = ["result"]; - let params = { - date: "{transaction_date}", - from: "{from_currency}", - to: "{to_currency}", - }; - add_param(frm, "https://api.exchangerate.host/convert", params, result); - } else if (frm.doc.service_provider == "frankfurter.app") { - let result = ["rates", "{to_currency}"]; - let params = { - base: "{from_currency}", - symbols: "{to_currency}", - }; - add_param(frm, "https://frankfurter.app/{transaction_date}", params, result); - } + frm.call({ + method: "erpnext.accounts.doctype.currency_exchange_settings.currency_exchange_settings.get_api_endpoint", + args: { + service_provider: frm.doc.service_provider, + use_http: frm.doc.use_http, + }, + callback: function (r) { + if (r && r.message) { + if (frm.doc.service_provider == "exchangerate.host") { + let result = ["result"]; + let params = { + date: "{transaction_date}", + from: "{from_currency}", + to: "{to_currency}", + }; + add_param(frm, r.message, params, result); + } else if (frm.doc.service_provider == "frankfurter.app") { + let result = ["rates", "{to_currency}"]; + let params = { + base: "{from_currency}", + symbols: "{to_currency}", + }; + add_param(frm, r.message, params, result); + } + } + }, + }); + }, + use_http: function (frm) { + frm.trigger("service_provider"); }, }); diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json index df232a5848c..bd90b8add80 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json @@ -9,6 +9,7 @@ "disabled", "service_provider", "api_endpoint", + "use_http", "access_key", "url", "column_break_3", @@ -91,12 +92,19 @@ "fieldname": "access_key", "fieldtype": "Data", "label": "Access Key" + }, + { + "default": "0", + "depends_on": "eval: doc.service_provider != \"Custom\"", + "fieldname": "use_http", + "fieldtype": "Check", + "label": "Use HTTP Protocol" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-10-04 15:30:25.333860", + "modified": "2024-03-18 08:32:26.895076", "modified_by": "Administrator", "module": "Accounts", "name": "Currency Exchange Settings", diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py index 3393d4170bc..b8817c60572 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py @@ -31,6 +31,7 @@ class CurrencyExchangeSettings(Document): result_key: DF.Table[CurrencyExchangeSettingsResult] service_provider: DF.Literal["frankfurter.app", "exchangerate.host", "Custom"] url: DF.Data | None + use_http: DF.Check # end: auto-generated types def validate(self): @@ -53,7 +54,7 @@ class CurrencyExchangeSettings(Document): self.set("result_key", []) self.set("req_params", []) - self.api_endpoint = "https://api.exchangerate.host/convert" + self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http) self.append("result_key", {"key": "result"}) self.append("req_params", {"key": "access_key", "value": self.access_key}) self.append("req_params", {"key": "amount", "value": "1"}) @@ -64,7 +65,7 @@ class CurrencyExchangeSettings(Document): self.set("result_key", []) self.set("req_params", []) - self.api_endpoint = "https://frankfurter.app/{transaction_date}" + self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http) self.append("result_key", {"key": "rates"}) self.append("result_key", {"key": "{to_currency}"}) self.append("req_params", {"key": "base", "value": "{from_currency}"}) @@ -103,3 +104,19 @@ class CurrencyExchangeSettings(Document): frappe.throw(_("Returned exchange rate is neither integer not float.")) self.url = response.url + + +@frappe.whitelist() +def get_api_endpoint(service_provider: str = None, use_http: bool = False): + if service_provider and service_provider in ["exchangerate.host", "frankfurter.app"]: + if service_provider == "exchangerate.host": + api = "api.exchangerate.host/convert" + elif service_provider == "frankfurter.app": + api = "frankfurter.app/{transaction_date}" + + protocol = "https://" + if use_http: + protocol = "http://" + + return protocol + api + return None From 00de529a1ec033037ab2d5f1e5347deac272cb25 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 18 Mar 2024 12:41:43 +0530 Subject: [PATCH 23/65] fix: balance qty in the stock ledger report (#40506) --- erpnext/stock/report/stock_ledger/stock_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 2e4b08c3ea5..e98351a0a8b 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -60,6 +60,7 @@ def execute(filters=None): if filters.get("batch_no") or inventory_dimension_filters_applied: actual_qty += flt(sle.actual_qty, precision) stock_value += sle.stock_value_difference + batch_balance_dict[sle.batch_no] += sle.actual_qty if sle.voucher_type == "Stock Reconciliation" and not sle.actual_qty: actual_qty = sle.qty_after_transaction From 7695759f3ce9c3acb1db986c6cd3a82d520289c5 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 18 Mar 2024 15:09:55 +0530 Subject: [PATCH 24/65] fix: not able to delete cancelled delivery note (#40508) --- .../doctype/work_order/work_order.js | 2 ++ .../delivery_note/test_delivery_note.py | 21 +++++++++++++++++++ erpnext/stock/doctype/pick_list/pick_list.py | 6 +++++- .../serial_and_batch_bundle.py | 4 ++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 42f69438aef..70e803dbba1 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -9,6 +9,8 @@ frappe.ui.form.on("Work Order", { "Job Card": "Create Job Card", }; + frm.ignore_doctypes_on_cancel_all = ["Serial and Batch Bundle"]; + // Set query for warehouses frm.set_query("wip_warehouse", function () { return { diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 434e00194d6..905287d55e8 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1108,9 +1108,30 @@ class TestDeliveryNote(FrappeTestCase): dn.load_from_db() batch_no = get_batch_from_bundle(dn.packed_items[0].serial_and_batch_bundle) + packed_name = dn.packed_items[0].name self.assertTrue(batch_no) + dn.cancel() + + # Cancel the reposting entry + reposting_entries = frappe.get_all("Repost Item Valuation", filters={"docstatus": 1}) + for entry in reposting_entries: + doc = frappe.get_doc("Repost Item Valuation", entry.name) + doc.cancel() + doc.delete() + + frappe.db.set_single_value("Accounts Settings", "delete_linked_ledger_entries", 1) + + dn.reload() + dn.delete() + + bundle = frappe.db.get_value( + "Serial and Batch Bundle", {"voucher_detail_no": packed_name}, "name" + ) + self.assertFalse(bundle) + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + frappe.db.set_single_value("Accounts Settings", "delete_linked_ledger_entries", 0) def test_payment_terms_are_fetched_when_creating_sales_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 8a1f79d4a27..627520c1dcd 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -184,7 +184,11 @@ class PickList(Document): def delink_serial_and_batch_bundle(self): for row in self.locations: - if row.serial_and_batch_bundle: + if ( + row.serial_and_batch_bundle + and frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "docstatus") + == 1 + ): frappe.db.set_value( "Serial and Batch Bundle", row.serial_and_batch_bundle, diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 9a7395fc667..1fb4969e306 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -778,6 +778,10 @@ class SerialandBatchBundle(Document): or_filters=or_filters, ) + if not vouchers and self.voucher_type == "Delivery Note": + frappe.db.set_value("Packed Item", self.voucher_detail_no, "serial_and_batch_bundle", None) + return + for voucher in vouchers: if voucher.get("current_serial_and_batch_bundle"): frappe.db.set_value(self.child_table, voucher.name, "current_serial_and_batch_bundle", None) From 3d5dba6976e7aee6e9a291a951e8367e1b8f36af Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 18 Mar 2024 15:34:21 +0530 Subject: [PATCH 25/65] fix: invalid exchange loss booking on invoice againts base accounts --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 8a5d2c6c690..b4d1d39595e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -489,7 +489,9 @@ class PaymentEntry(AccountsController): ref_details = get_reference_details( d.reference_doctype, d.reference_name, self.party_account_currency ) - if ref_exchange_rate: + + # Only update exchange rate when the reference is Journal Entry + if ref_exchange_rate and d.reference_doctype == "Journal Entry": ref_details.update({"exchange_rate": ref_exchange_rate}) for field, value in ref_details.items(): From e1c2d006caa634f52661026fdc4e82762256251c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 18 Mar 2024 20:24:05 +0530 Subject: [PATCH 26/65] refactor(test): ensure Exchange gain/loss journals aren't created --- .../test_payment_reconciliation.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 301e6ef625c..1d20a5b954d 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -1130,6 +1130,17 @@ class TestPaymentReconciliation(FrappeTestCase): self.assertEqual(pr.allocation[0].allocated_amount, 85) self.assertEqual(pr.allocation[0].difference_amount, 0) + pr.reconcile() + si.reload() + self.assertEqual(si.outstanding_amount, 0) + # No Exchange Gain/Loss journal should be generated + exc_gain_loss_journals = frappe.db.get_all( + "Journal Entry Account", + filters={"reference_type": si.doctype, "reference_name": si.name, "docstatus": 1}, + fields=["parent"], + ) + self.assertEqual(exc_gain_loss_journals, []) + def test_reconciliation_purchase_invoice_against_return(self): self.supplier = "_Test Supplier USD" pi = self.create_purchase_invoice(qty=5, rate=50, do_not_submit=True) From e43beac086efb6e36d7cc691342b9c4b67120b22 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 18 Mar 2024 22:07:12 +0530 Subject: [PATCH 27/65] fix: provisional entry for non-stock item --- .../doctype/purchase_invoice/purchase_invoice.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 3200fc57bde..75f0d082ea0 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1158,12 +1158,12 @@ class PurchaseInvoice(BuyingController): def make_provisional_gl_entry(self, gl_entries, item): if item.purchase_receipt: - if not self.provisional_enpenses_booked_in_pr: - pr_item = self.provisional_accounts.get(item.pr_detail, {}) - provisional_account = pr_item.get("provisional_account") - pr_qty = pr_item.get("qty") - pr_base_rate = pr_item.get("base_rate") + pr_item = self.provisional_accounts.get(item.pr_detail, {}) + provisional_account = pr_item.get("provisional_account") + pr_qty = pr_item.get("qty") + pr_base_rate = pr_item.get("base_rate") + if not self.provisional_enpenses_booked_in_pr: # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt provision_gle_against_pr = frappe.db.get_value( "GL Entry", From 2f3d6cd3045775c6228cb93ae462ab56df1e6a3a Mon Sep 17 00:00:00 2001 From: David Date: Mon, 18 Mar 2024 19:52:24 +0100 Subject: [PATCH 28/65] fix: permissions for selling settings --- erpnext/selling/doctype/sales_order/sales_order.py | 7 +++++++ .../selling/doctype/sales_order/sales_order_list.js | 11 ++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 4956f29eff1..8896e2960d3 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -753,6 +753,13 @@ def get_list_context(context=None): return list_context +@frappe.whitelist() +def is_enable_cutoff_date_on_bulk_delivery_note_creation(): + return frappe.db.get_single_value( + "Accounts Settings", "enable_cutoff_date_on_bulk_delivery_note_creation" + ) + + @frappe.whitelist() def close_or_unclose_sales_orders(names, status): if not frappe.has_permission("Sales Order", "write"): diff --git a/erpnext/selling/doctype/sales_order/sales_order_list.js b/erpnext/selling/doctype/sales_order/sales_order_list.js index 14ba051fc25..65301750c04 100644 --- a/erpnext/selling/doctype/sales_order/sales_order_list.js +++ b/erpnext/selling/doctype/sales_order/sales_order_list.js @@ -68,10 +68,10 @@ frappe.listview_settings["Sales Order"] = { }); listview.page.add_action_item(__("Delivery Note"), () => { - frappe.db - .get_single_value("Selling Settings", "enable_cutoff_date_on_bulk_delivery_note_creation") - .then((value) => { - if (value) { + frappe.call({ + method: "erpnext.selling.doctype.sales_order.sales_order.is_enable_cutoff_date_on_bulk_delivery_note_creation", + callback: (r) => { + if (r.message) { var dialog = new frappe.ui.Dialog({ title: __("Select Items up to Delivery Date"), fields: [ @@ -98,7 +98,8 @@ frappe.listview_settings["Sales Order"] = { } else { erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Delivery Note"); } - }); + }, + }); }); listview.page.add_action_item(__("Advance Payment"), () => { From 49dd4c1ef3fcb6f710c185c50188d0bee4ea2dcd Mon Sep 17 00:00:00 2001 From: David Date: Mon, 18 Mar 2024 23:05:58 +0100 Subject: [PATCH 29/65] fix: permissions during bulk transaction logs --- erpnext/utilities/bulk_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index 3538c24aeba..343a88116fa 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -176,7 +176,7 @@ def create_log(doc_name, e, from_doctype, to_doctype, status, log_date=None, res transaction_log.from_doctype = from_doctype transaction_log.to_doctype = to_doctype transaction_log.retried = restarted - transaction_log.save() + transaction_log.save(ignore_permissions=True) def show_job_status(fail_count, deserialized_data_count, to_doctype): From 8d1b530070288e2eee3cc0a140274483bbcdf030 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 19 Mar 2024 02:22:52 +0100 Subject: [PATCH 30/65] fix(RFQ): load up to 100 suppliers --- .../request_for_quotation.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 272d077b1e3..6b10df885c3 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -518,16 +518,15 @@ erpnext.buying.RequestforQuotationController = class RequestforQuotationControll callback: load_suppliers, }); } else if (args.supplier_group) { - return frappe.call({ - method: "frappe.client.get_list", - args: { - doctype: "Supplier", + frappe.db + .get_list("Supplier", { + filters: { supplier_group: args.supplier_group }, + limit: 100, order_by: "name", - fields: ["name"], - filters: [["Supplier", "supplier_group", "=", args.supplier_group]], - }, - callback: load_suppliers, - }); + }) + .then((r) => { + load_suppliers({ message: r }); + }); } }, }); From eea260b9f9c4a4a3a5a716b1d74a45c7569d4f98 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 19 Mar 2024 11:18:11 +0530 Subject: [PATCH 31/65] chore: code cleanup --- .../transaction_deletion_record.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 170b1bfe72d..039d9a58413 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -132,7 +132,6 @@ class TransactionDeletionRecord(Document): job_names = self.generate_job_name_for_next_tasks(task=task) self.validate_running_task_for_doc(job_names=job_names) - # method = self.task_to_internal_method_map[task] # Generate Job Id to uniquely identify each task for this document job_id = self.generate_job_name_for_task(task) @@ -149,7 +148,6 @@ class TransactionDeletionRecord(Document): def execute_task(self, task_to_execute: str | None = None): if task_to_execute: - pass method = self.task_to_internal_method_map[task_to_execute] if task := getattr(self, method, None): task() @@ -309,7 +307,9 @@ class TransactionDeletionRecord(Document): frappe.db.set_value(docfield.doctype, docfield.name, "done", 1) pending_doctypes = frappe.db.get_all( - docfield.doctype, filters={"parent": self.name, "done": 0}, pluck="doctype_name" + "Transaction Deletion Record Details", + filters={"parent": self.name, "done": 0}, + pluck="doctype_name", ) if pending_doctypes: # as method is enqueued after commit, calling itself will not make validate_doc_status to throw From 09ea7edb863dce39e28c799ea8e4dc0b12a3c69a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 19 Mar 2024 12:10:24 +0530 Subject: [PATCH 32/65] fix: Update Existing Price List Rate not working (#40333) --- .../doctype/sales_order/test_sales_order.py | 34 +++++++++++++++++++ erpnext/setup/demo_data/item.json | 10 ++++++ erpnext/stock/get_item_details.py | 4 ++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index f7e65e0b39a..7327fdefd42 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2141,6 +2141,40 @@ class TestSalesOrder(FrappeTestCase): dn.submit() dn.reload() + def test_auto_update_price_list(self): + item = make_item( + "_Test Auto Update Price List Item", + ) + + frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 1) + so = make_sales_order( + item_code=item.name, currency="USD", qty=1, rate=100, price_list_rate=100, do_not_submit=True + ) + so.save() + + item_price = frappe.db.get_value("Item Price", {"item_code": item.name}, "price_list_rate") + self.assertEqual(item_price, 100) + + so = make_sales_order( + item_code=item.name, currency="USD", qty=1, rate=200, price_list_rate=100, do_not_submit=True + ) + so.save() + + item_price = frappe.db.get_value("Item Price", {"item_code": item.name}, "price_list_rate") + self.assertEqual(item_price, 100) + + frappe.db.set_single_value("Stock Settings", "update_existing_price_list_rate", 1) + so = make_sales_order( + item_code=item.name, currency="USD", qty=1, rate=200, price_list_rate=200, do_not_submit=True + ) + so.save() + + item_price = frappe.db.get_value("Item Price", {"item_code": item.name}, "price_list_rate") + self.assertEqual(item_price, 200) + + frappe.db.set_single_value("Stock Settings", "update_existing_price_list_rate", 0) + frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0) + def automatically_fetch_payment_terms(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") diff --git a/erpnext/setup/demo_data/item.json b/erpnext/setup/demo_data/item.json index 330e114dd53..17024341225 100644 --- a/erpnext/setup/demo_data/item.json +++ b/erpnext/setup/demo_data/item.json @@ -4,6 +4,7 @@ "item_group": "Demo Item Group", "item_code": "SKU001", "item_name": "T-shirt", + "valuation_rate": 400.0, "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/1484808/pexels-photo-1484808.jpeg" }, @@ -11,6 +12,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU002", + "valuation_rate": 300.0, "item_name": "Laptop", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/3999538/pexels-photo-3999538.jpeg" @@ -19,6 +21,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU003", + "valuation_rate": 523.0, "item_name": "Book", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/2422178/pexels-photo-2422178.jpeg" @@ -27,6 +30,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU004", + "valuation_rate": 725.0, "item_name": "Smartphone", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/1647976/pexels-photo-1647976.jpeg" @@ -35,6 +39,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU005", + "valuation_rate": 222.0, "item_name": "Sneakers", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/1598505/pexels-photo-1598505.jpeg" @@ -43,6 +48,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU006", + "valuation_rate": 420.0, "item_name": "Coffee Mug", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/585753/pexels-photo-585753.jpeg" @@ -51,6 +57,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU007", + "valuation_rate": 375.0, "item_name": "Television", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/8059376/pexels-photo-8059376.jpeg" @@ -59,6 +66,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU008", + "valuation_rate": 333.0, "item_name": "Backpack", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/3731256/pexels-photo-3731256.jpeg" @@ -67,6 +75,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU009", + "valuation_rate": 700.0, "item_name": "Headphones", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/3587478/pexels-photo-3587478.jpeg" @@ -75,6 +84,7 @@ "doctype": "Item", "item_group": "Demo Item Group", "item_code": "SKU010", + "valuation_rate": 500.0, "item_name": "Camera", "gst_hsn_code": "999512", "image": "https://images.pexels.com/photos/51383/photo-camera-subject-photographer-51383.jpeg" diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 1cb10575cd1..85da0348265 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -816,7 +816,9 @@ def get_price_list_rate(args, item_doc, out=None): price_list_rate = get_price_list_rate_for(args, item_doc.variant_of) # insert in database - if price_list_rate is None: + if price_list_rate is None or frappe.db.get_single_value( + "Stock Settings", "update_existing_price_list_rate" + ): if args.price_list and args.rate: insert_item_price(args) return out From 643cc022fdada0839e3c55d7c56aa475fdc4de58 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 19 Mar 2024 12:12:26 +0530 Subject: [PATCH 33/65] fix: missing range for ageing summary --- .../process_statement_of_accounts.html | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 5307ccb1931..81ebf9744c4 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -89,10 +89,11 @@ - - - - + + + + + @@ -101,6 +102,7 @@ +
30 Days60 Days90 Days120 Days0 - 30 Days30 - 60 Days60 - 90 Days90 - 120 DaysAbove 120 Days
{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }} {{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }} {{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}
From a64c2ecf396a009bfcaa1e58ad34c61d1b671fe6 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 19 Mar 2024 14:54:17 +0530 Subject: [PATCH 34/65] fix: stock reco negative batch (#40533) --- .../serial_and_batch_bundle/serial_and_batch_bundle.py | 7 +++++++ .../doctype/stock_reconciliation/stock_reconciliation.py | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 1fb4969e306..58971e8f19d 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -832,6 +832,13 @@ class SerialandBatchBundle(Document): if not self.has_batch_no: return + if ( + self.voucher_type == "Stock Reconciliation" + and self.type_of_transaction == "Outward" + and frappe.db.get_value("Stock Reconciliation Item", self.voucher_detail_no, "qty") > 0 + ): + return + batches = [d.batch_no for d in self.entries if d.batch_no] if not batches: return diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 3356ad5f300..0311481b6ca 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -154,7 +154,6 @@ class StockReconciliation(StockController): { "current_serial_and_batch_bundle": sn_doc.name, "current_serial_no": "", - "batch_no": "", } ) From 81369544840c4eded80bf40fd3e45ed0eb6f52b1 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 19 Mar 2024 15:26:09 +0530 Subject: [PATCH 35/65] fix: rate changing while making PR (negative discount) (#40539) --- erpnext/public/js/controllers/taxes_and_totals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 30991846c45..4a525b2c5ee 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -26,7 +26,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { item.discount_amount = flt(item.rate_with_margin) * flt(item.discount_percentage) / 100; } - if (item.discount_amount) { + if (item.discount_amount > 0) { item_rate = flt((item.rate_with_margin) - (item.discount_amount), precision('rate', item)); item.discount_percentage = 100 * flt(item.discount_amount) / flt(item.rate_with_margin); } From d238751e6ba0666b4098f1db9be38e9bd8163770 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 19 Mar 2024 12:03:36 +0100 Subject: [PATCH 36/65] refactor: usage of in_list --- .../doctype/journal_entry/journal_entry.js | 6 ++--- .../doctype/payment_entry/payment_entry.js | 16 +++++++------- .../payment_request/payment_request.js | 2 +- erpnext/assets/doctype/asset/asset.js | 6 ++--- .../doctype/purchase_order/purchase_order.js | 4 ++-- erpnext/manufacturing/doctype/bom/bom.js | 2 +- .../production_plan/production_plan.js | 2 +- .../doctype/work_order/work_order.js | 4 ++-- erpnext/public/js/communication.js | 2 +- erpnext/public/js/controllers/accounts.js | 2 +- erpnext/public/js/controllers/buying.js | 2 +- .../public/js/controllers/taxes_and_totals.js | 22 +++++++++---------- erpnext/public/js/controllers/transaction.js | 20 ++++++++--------- erpnext/public/js/payment/payments.js | 2 +- erpnext/public/js/sms_manager.js | 4 ++-- erpnext/public/js/utils/party.js | 4 ++-- erpnext/public/js/utils/sales_common.js | 6 ++--- .../import_supplier_invoice.js | 2 +- .../point_of_sale/pos_past_order_summary.js | 2 +- .../closing_stock_balance.js | 2 +- .../delivery_trip/delivery_trip_list.js | 4 ++-- erpnext/templates/form_grid/item_grid.html | 2 +- 22 files changed, 59 insertions(+), 59 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index f6d35fe2bba..3186d07adcc 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -196,7 +196,7 @@ frappe.ui.form.on("Journal Entry", { !(frm.doc.accounts || []).length || ((frm.doc.accounts || []).length === 1 && !frm.doc.accounts[0].account) ) { - if (in_list(["Bank Entry", "Cash Entry"], frm.doc.voucher_type)) { + if (["Bank Entry", "Cash Entry"].includes(frm.doc.voucher_type)) { return frappe.call({ type: "GET", method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account", @@ -308,7 +308,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro filters: [[jvd.reference_type, "docstatus", "=", 1]], }; - if (in_list(["Sales Invoice", "Purchase Invoice"], jvd.reference_type)) { + if (["Sales Invoice", "Purchase Invoice"].includes(jvd.reference_type)) { out.filters.push([jvd.reference_type, "outstanding_amount", "!=", 0]); // Filter by cost center if (jvd.cost_center) { @@ -320,7 +320,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]); } - if (in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) { + if (["Sales Order", "Purchase Order"].includes(jvd.reference_type)) { // party_type and party mandatory frappe.model.validate_missing(jvd, "party_type"); frappe.model.validate_missing(jvd, "party"); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 961ee204d47..0cb1a3d4997 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -32,7 +32,7 @@ frappe.ui.form.on("Payment Entry", { frm.set_query("paid_from", function () { frm.events.validate_company(frm); - var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) + var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type) ? ["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]]; return { @@ -87,7 +87,7 @@ frappe.ui.form.on("Payment Entry", { frm.set_query("paid_to", function () { frm.events.validate_company(frm); - var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) + var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type) ? ["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]]; return { @@ -134,7 +134,7 @@ frappe.ui.form.on("Payment Entry", { frm.set_query("payment_term", "references", function (frm, cdt, cdn) { const child = locals[cdt][cdn]; if ( - in_list(["Purchase Invoice", "Sales Invoice"], child.reference_doctype) && + ["Purchase Invoice", "Sales Invoice"].includes(child.reference_doctype) && child.reference_name ) { return { @@ -623,7 +623,7 @@ frappe.ui.form.on("Payment Entry", { if (frm.doc.paid_from_account_currency == company_currency) { frm.set_value("source_exchange_rate", 1); } else if (frm.doc.paid_from) { - if (in_list(["Internal Transfer", "Pay"], frm.doc.payment_type)) { + if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) { let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; frappe.call({ method: "erpnext.setup.utils.get_exchange_rate", @@ -1042,7 +1042,7 @@ frappe.ui.form.on("Payment Entry", { } var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding; - } else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) { + } else if (["Customer", "Supplier"].includes(frm.doc.party_type)) { total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount")); if (paid_amount > total_negative_outstanding) { if (total_negative_outstanding == 0) { @@ -1213,7 +1213,7 @@ frappe.ui.form.on("Payment Entry", { if ( frm.doc.party_type == "Customer" && - !in_list(["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"], row.reference_doctype) + !["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"].includes(row.reference_doctype) ) { frappe.model.set_value(row.doctype, row.name, "reference_doctype", null); frappe.msgprint( @@ -1227,7 +1227,7 @@ frappe.ui.form.on("Payment Entry", { if ( frm.doc.party_type == "Supplier" && - !in_list(["Purchase Order", "Purchase Invoice", "Journal Entry"], row.reference_doctype) + !["Purchase Order", "Purchase Invoice", "Journal Entry"].includes(row.reference_doctype) ) { frappe.model.set_value(row.doctype, row.name, "against_voucher_type", null); frappe.msgprint( @@ -1323,7 +1323,7 @@ frappe.ui.form.on("Payment Entry", { bank_account: function (frm) { const field = frm.doc.payment_type == "Pay" ? "paid_from" : "paid_to"; - if (frm.doc.bank_account && in_list(["Pay", "Receive"], frm.doc.payment_type)) { + if (frm.doc.bank_account && ["Pay", "Receive"].includes(frm.doc.payment_type)) { frappe.call({ method: "erpnext.accounts.doctype.bank_account.bank_account.get_bank_account_details", args: { diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js index d07f8249fca..f12facfbf5a 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.js +++ b/erpnext/accounts/doctype/payment_request/payment_request.js @@ -32,7 +32,7 @@ frappe.ui.form.on("Payment Request", "refresh", function (frm) { if ( frm.doc.payment_request_type == "Inward" && frm.doc.payment_channel !== "Phone" && - !in_list(["Initiated", "Paid"], frm.doc.status) && + !["Initiated", "Paid"].includes(frm.doc.status) && !frm.doc.__islocal && frm.doc.docstatus == 1 ) { diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 6dbb53a078f..0f71e5d6f60 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -79,7 +79,7 @@ frappe.ui.form.on("Asset", { frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1); if (frm.doc.docstatus == 1) { - if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) { + if (["Submitted", "Partially Depreciated", "Fully Depreciated"].includes(frm.doc.status)) { frm.add_custom_button( __("Transfer Asset"), function () { @@ -365,7 +365,7 @@ frappe.ui.form.on("Asset", { if (v.journal_entry) { asset_values.push(asset_value); } else { - if (in_list(["Scrapped", "Sold"], frm.doc.status)) { + if (["Scrapped", "Sold"].includes(frm.doc.status)) { asset_values.push(null); } else { asset_values.push(asset_value); @@ -400,7 +400,7 @@ frappe.ui.form.on("Asset", { }); } - if (in_list(["Scrapped", "Sold"], frm.doc.status)) { + if (["Scrapped", "Sold"].includes(frm.doc.status)) { x_intervals.push(frappe.format(frm.doc.disposal_date, { fieldtype: "Date" })); asset_values.push(0); } diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index e62d22bd15b..cf383021b06 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -291,7 +291,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( this.frm.fields_dict.items_section.wrapper.removeClass("hide-border"); } - if (!in_list(["Closed", "Delivered"], doc.status)) { + if (!["Closed", "Delivered"].includes(doc.status)) { if ( this.frm.doc.status !== "Closed" && flt(this.frm.doc.per_received, 2) < 100 && @@ -336,7 +336,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( this.frm.page.set_inner_btn_group_as_primary(__("Status")); } - } else if (in_list(["Closed", "Delivered"], doc.status)) { + } else if (["Closed", "Delivered"].includes(doc.status)) { if (this.frm.has_perm("submit")) { this.frm.add_custom_button( __("Re-open"), diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 2ac28ea885f..6267ee4d029 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -400,7 +400,7 @@ frappe.ui.form.on("BOM", { }, rm_cost_as_per(frm) { - if (in_list(["Valuation Rate", "Last Purchase Rate"], frm.doc.rm_cost_as_per)) { + if (["Valuation Rate", "Last Purchase Rate"].includes(frm.doc.rm_cost_as_per)) { frm.set_value("plc_conversion_rate", 1.0); } }, diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 54d1414c814..6db901c71a4 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -129,7 +129,7 @@ frappe.ui.form.on("Production Plan", { if ( frm.doc.mr_items && frm.doc.mr_items.length && - !in_list(["Material Requested", "Closed"], frm.doc.status) + !["Material Requested", "Closed"].includes(frm.doc.status) ) { frm.add_custom_button( __("Material Request"), diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 70e803dbba1..1da33f0ad9b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -196,7 +196,7 @@ frappe.ui.form.on("Work Order", { }, add_custom_button_to_return_components: function (frm) { - if (frm.doc.docstatus === 1 && in_list(["Closed", "Completed"], frm.doc.status)) { + if (frm.doc.docstatus === 1 && ["Closed", "Completed"].includes(frm.doc.status)) { let non_consumed_items = frm.doc.required_items.filter((d) => { return flt(d.consumed_qty) < flt(d.transferred_qty - d.returned_qty); }); @@ -596,7 +596,7 @@ erpnext.work_order = { ); } - if (doc.docstatus === 1 && !in_list(["Closed", "Completed"], doc.status)) { + if (doc.docstatus === 1 && !["Closed", "Completed"].includes(doc.status)) { if (doc.status != "Stopped" && doc.status != "Completed") { frm.add_custom_button( __("Stop"), diff --git a/erpnext/public/js/communication.js b/erpnext/public/js/communication.js index d9187f8b678..c8905e14af2 100644 --- a/erpnext/public/js/communication.js +++ b/erpnext/public/js/communication.js @@ -20,7 +20,7 @@ frappe.ui.form.on("Communication", { ); } - if (!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) { + if (!["Lead", "Opportunity"].includes(frm.doc.reference_doctype)) { frm.add_custom_button( __("Lead"), () => { diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index f0d8cbb99ca..964a175ac9f 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -11,7 +11,7 @@ erpnext.accounts.taxes = { setup: function(frm) { // set conditional display for rate column in taxes $(frm.wrapper).on('grid-row-render', function(e, grid_row) { - if(in_list(['Sales Taxes and Charges', 'Purchase Taxes and Charges'], grid_row.doc.doctype)) { + if(['Sales Taxes and Charges', 'Purchase Taxes and Charges'].includes(grid_row.doc.doctype)) { me.set_conditional_mandatory_rate_or_amount(grid_row); } }); diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 934becf6641..1e94c0032ab 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -129,7 +129,7 @@ erpnext.buying = { } toggle_subcontracting_fields() { - if (in_list(['Purchase Receipt', 'Purchase Invoice'], this.frm.doc.doctype)) { + if (['Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doc.doctype)) { this.frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty', 'read_only', this.frm.doc.__onload && this.frm.doc.__onload.backflush_based_on === 'BOM'); diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 30991846c45..b614bd99649 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -9,7 +9,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { apply_pricing_rule_on_item(item) { let effective_item_rate = item.price_list_rate; let item_rate = item.rate; - if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) { + if (["Sales Order", "Quotation"].includes(item.parenttype) && item.blanket_order_rate) { effective_item_rate = item.blanket_order_rate; } if (item.margin_type == "Percentage") { @@ -52,7 +52,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { // Advance calculation applicable to Sales/Purchase Invoice if ( - in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype) + ["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.docstatus < 2 && !this.frm.doc.is_return ) { @@ -60,7 +60,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } if ( - in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) + ["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.is_pos && this.frm.doc.is_return ) { @@ -69,7 +69,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } // Sales person's commission - if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) { + if (["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"].includes(this.frm.doc.doctype)) { this.calculate_commission(); this.calculate_contribution(); } @@ -575,7 +575,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { ? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.grand_total_diff) : this.frm.doc.net_total); - if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) { + if(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) { this.frm.doc.base_grand_total = (this.frm.doc.total_taxes_and_charges) ? flt(this.frm.doc.grand_total * this.frm.doc.conversion_rate) : this.frm.doc.base_net_total; } else { @@ -583,7 +583,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.frm.doc.taxes_and_charges_added = this.frm.doc.taxes_and_charges_deducted = 0.0; if(tax_count) { $.each(this.frm.doc["taxes"] || [], function(i, tax) { - if (in_list(["Valuation and Total", "Total"], tax.category)) { + if (["Valuation and Total", "Total"].includes(tax.category)) { if(tax.add_deduct_tax == "Add") { me.frm.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount); } else { @@ -729,7 +729,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { var actual_taxes_dict = {}; $.each(this.frm.doc["taxes"] || [], function(i, tax) { - if (in_list(["Actual", "On Item Quantity"], tax.charge_type)) { + if (["Actual", "On Item Quantity"].includes(tax.charge_type)) { var tax_amount = (tax.category == "Valuation") ? 0.0 : tax.tax_amount; tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0; actual_taxes_dict[tax.idx] = tax_amount; @@ -774,7 +774,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { // NOTE: // paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice // total_advance is only for non POS Invoice - if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_return){ + if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.is_return){ this.calculate_paid_amount(); } @@ -782,7 +782,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]); - if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)) { + if(["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)) { let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total; let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total; @@ -805,7 +805,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.frm.refresh_field("base_paid_amount"); } - if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) { + if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) { let total_amount_for_payment = (this.frm.doc.redeem_loyalty_points && this.frm.doc.loyalty_amount) ? flt(total_amount_to_pay - this.frm.doc.loyalty_amount, precision("base_grand_total")) : total_amount_to_pay; @@ -909,7 +909,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { calculate_change_amount(){ this.frm.doc.change_amount = 0.0; this.frm.doc.base_change_amount = 0.0; - if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) + if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return) { var payment_types = $.map(this.frm.doc.payments, function(d) { return d.type; }); diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 8135bb2404a..e6fa055707b 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -315,7 +315,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } setup_quality_inspection() { - if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"], this.frm.doc.doctype)) { + if(!["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(this.frm.doc.doctype)) { return; } @@ -327,7 +327,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe this.frm.page.set_inner_btn_group_as_primary(__('Create')); } - const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"], this.frm.doc.doctype) + const inspection_type = ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(this.frm.doc.doctype) ? "Incoming" : "Outgoing"; let quality_inspection_field = this.frm.get_docfield("items", "quality_inspection"); @@ -359,7 +359,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe make_payment_request() { let me = this; - const payment_request_type = (in_list(['Sales Order', 'Sales Invoice'], this.frm.doc.doctype)) + const payment_request_type = (['Sales Order', 'Sales Invoice'].includes(this.frm.doc.doctype)) ? "Inward" : "Outward"; frappe.call({ @@ -474,7 +474,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe setup_sms() { var me = this; let blacklist = ['Purchase Invoice', 'BOM']; - if(this.frm.doc.docstatus===1 && !in_list(["Lost", "Stopped", "Closed"], this.frm.doc.status) + if(this.frm.doc.docstatus===1 && !["Lost", "Stopped", "Closed"].includes(this.frm.doc.status) && !blacklist.includes(this.frm.doctype)) { this.frm.page.add_menu_item(__('Send SMS'), function() { me.send_sms(); }); } @@ -760,7 +760,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } on_submit() { - if (in_list(["Purchase Invoice", "Sales Invoice"], this.frm.doc.doctype) + if (["Purchase Invoice", "Sales Invoice"].includes(this.frm.doc.doctype) && !this.frm.doc.update_stock) { return; } @@ -864,7 +864,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } var set_party_account = function(set_pricing) { - if (in_list(["Sales Invoice", "Purchase Invoice"], me.frm.doc.doctype)) { + if (["Sales Invoice", "Purchase Invoice"].includes(me.frm.doc.doctype)) { if(me.frm.doc.doctype=="Sales Invoice") { var party_type = "Customer"; var party_account_field = 'debit_to'; @@ -899,7 +899,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") && - in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) { + ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doctype)) { erpnext.utils.get_shipping_address(this.frm, function() { set_party_account(set_pricing); }); @@ -1620,7 +1620,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe "doctype": me.frm.doc.doctype, "name": me.frm.doc.name, "is_return": cint(me.frm.doc.is_return), - "update_stock": in_list(['Sales Invoice', 'Purchase Invoice'], me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0, + "update_stock": ['Sales Invoice', 'Purchase Invoice'].includes(me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0, "conversion_factor": me.frm.doc.conversion_factor, "pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '', "coupon_code": me.frm.doc.coupon_code @@ -2266,7 +2266,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe get_method_for_payment() { var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"; if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){ - if(in_list(['Sales Invoice', 'Purchase Invoice'], cur_frm.doc.doctype)){ + if(['Sales Invoice', 'Purchase Invoice'].includes( cur_frm.doc.doctype)){ method = "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_invoice"; }else { method= "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_order"; @@ -2506,7 +2506,7 @@ erpnext.show_serial_batch_selector = function (frm, item_row, callback, on_close } frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() { - if (in_list(["Sales Invoice", "Delivery Note"], frm.doc.doctype)) { + if (["Sales Invoice", "Delivery Note"].includes(frm.doc.doctype)) { item_row.type_of_transaction = frm.doc.is_return ? "Inward" : "Outward"; } else { item_row.type_of_transaction = frm.doc.is_return ? "Outward" : "Inward"; diff --git a/erpnext/public/js/payment/payments.js b/erpnext/public/js/payment/payments.js index 0e584205396..c91bb046a52 100644 --- a/erpnext/public/js/payment/payments.js +++ b/erpnext/public/js/payment/payments.js @@ -218,7 +218,7 @@ erpnext.payments = class payments extends erpnext.stock.StockController { update_paid_amount(update_write_off) { var me = this; - if (in_list(["change_amount", "write_off_amount"], this.idx)) { + if (["change_amount", "write_off_amount"].includes(this.idx)) { var value = me.selected_mode.val(); if (me.idx == "change_amount") { me.change_amount(value); diff --git a/erpnext/public/js/sms_manager.js b/erpnext/public/js/sms_manager.js index d3147bb4600..63833da5af3 100644 --- a/erpnext/public/js/sms_manager.js +++ b/erpnext/public/js/sms_manager.js @@ -28,11 +28,11 @@ erpnext.SMSManager = function SMSManager(doc) { "Purchase Receipt": "Items has been received against purchase receipt: " + doc.name, }; - if (in_list(["Sales Order", "Delivery Note", "Sales Invoice"], doc.doctype)) + if (["Sales Order", "Delivery Note", "Sales Invoice"].includes(doc.doctype)) this.show(doc.contact_person, "Customer", doc.customer, "", default_msg[doc.doctype]); else if (doc.doctype === "Quotation") this.show(doc.contact_person, "Customer", doc.party_name, "", default_msg[doc.doctype]); - else if (in_list(["Purchase Order", "Purchase Receipt"], doc.doctype)) + else if (["Purchase Order", "Purchase Receipt"].includes(doc.doctype)) this.show(doc.contact_person, "Supplier", doc.supplier, "", default_msg[doc.doctype]); else if (doc.doctype == "Lead") this.show("", "", "", doc.mobile_no, default_msg[doc.doctype]); else if (doc.doctype == "Opportunity") diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 801376b2ed7..623941755d1 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -14,10 +14,10 @@ erpnext.utils.get_party_details = function (frm, method, args, callback) { if (!args) { if ( (frm.doctype != "Purchase Order" && frm.doc.customer) || - (frm.doc.party_name && in_list(["Quotation", "Opportunity"], frm.doc.doctype)) + (frm.doc.party_name && ["Quotation", "Opportunity"].includes(frm.doc.doctype)) ) { let party_type = "Customer"; - if (frm.doc.quotation_to && in_list(["Lead", "Prospect"], frm.doc.quotation_to)) { + if (frm.doc.quotation_to && ["Lead", "Prospect"].includes(frm.doc.quotation_to)) { party_type = frm.doc.quotation_to; } diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index f2b7331cf3b..00df1c5c191 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -303,7 +303,7 @@ erpnext.sales_common = { if ((doc.packed_items || []).length) { $(this.frm.fields_dict.packing_list.row.wrapper).toggle(true); - if (in_list(["Delivery Note", "Sales Invoice"], doc.doctype)) { + if (["Delivery Note", "Sales Invoice"].includes(doc.doctype)) { var help_msg = "
" + __( @@ -315,7 +315,7 @@ erpnext.sales_common = { } } else { $(this.frm.fields_dict.packing_list.row.wrapper).toggle(false); - if (in_list(["Delivery Note", "Sales Invoice"], doc.doctype)) { + if (["Delivery Note", "Sales Invoice"].includes(doc.doctype)) { frappe.meta.get_docfield(doc.doctype, "product_bundle_help", doc.name).options = ""; } } @@ -416,7 +416,7 @@ erpnext.sales_common = { project() { let me = this; - if (in_list(["Delivery Note", "Sales Invoice", "Sales Order"], this.frm.doc.doctype)) { + if (["Delivery Note", "Sales Invoice", "Sales Order"].includes(this.frm.doc.doctype)) { if (this.frm.doc.project) { frappe.call({ method: "erpnext.projects.doctype.project.project.get_cost_center_name", diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js index 5fbb5cb7e01..7aa8012f0b6 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js @@ -34,7 +34,7 @@ frappe.ui.form.on("Import Supplier Invoice", { }, toggle_read_only_fields: function (frm) { - if (in_list(["File Import Completed", "Processing File Data"], frm.doc.status)) { + if (["File Import Completed", "Processing File Data"].includes(frm.doc.status)) { cur_frm.set_read_only(); cur_frm.refresh_fields(); frm.set_df_property("import_invoices", "hidden", 1); diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index 448dbcab43a..c399005643c 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -73,7 +73,7 @@ erpnext.PointOfSale.PastOrderSummary = class { const { status } = doc; let indicator_color = ""; - in_list(["Paid", "Consolidated"], status) && (indicator_color = "green"); + ["Paid", "Consolidated"].includes(status) && (indicator_color = "green"); status === "Draft" && (indicator_color = "red"); status === "Return" && (indicator_color = "grey"); diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.js b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.js index 0f0221fa562..aec752aec77 100644 --- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.js +++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.js @@ -8,7 +8,7 @@ frappe.ui.form.on("Closing Stock Balance", { }, generate_closing_balance(frm) { - if (in_list(["Queued", "Failed"], frm.doc.status)) { + if (["Queued", "Failed"].includes(frm.doc.status)) { frm.add_custom_button(__("Generate Closing Stock Balance"), () => { frm.call({ method: "enqueue_job", diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip_list.js b/erpnext/stock/doctype/delivery_trip/delivery_trip_list.js index 230107caadb..65a1be33224 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip_list.js +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip_list.js @@ -1,9 +1,9 @@ frappe.listview_settings["Delivery Trip"] = { add_fields: ["status"], get_indicator: function (doc) { - if (in_list(["Cancelled", "Draft"], doc.status)) { + if (["Cancelled", "Draft"].includes(doc.status)) { return [__(doc.status), "red", "status,=," + doc.status]; - } else if (in_list(["In Transit", "Scheduled"], doc.status)) { + } else if (["In Transit", "Scheduled"].includes(doc.status)) { return [__(doc.status), "orange", "status,=," + doc.status]; } else if (doc.status === "Completed") { return [__(doc.status), "green", "status,=," + doc.status]; diff --git a/erpnext/templates/form_grid/item_grid.html b/erpnext/templates/form_grid/item_grid.html index 027046fd92f..72db6c8e653 100644 --- a/erpnext/templates/form_grid/item_grid.html +++ b/erpnext/templates/form_grid/item_grid.html @@ -18,7 +18,7 @@ actual_qty = (frm.doc.doctype==="Sales Order" ? doc.projected_qty : doc.actual_qty); if(flt(frm.doc.per_delivered, 2) < 100 - && in_list(["Sales Order Item", "Delivery Note Item"], doc.doctype)) { + && ["Sales Order Item", "Delivery Note Item"].includes(doc.doctype)) { if(actual_qty != undefined) { if(actual_qty >= doc.qty) { var color = "green"; From 775e8db04ed353339b797285a53135e9ff019097 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 19 Mar 2024 12:26:50 +0100 Subject: [PATCH 37/65] refactor: reduce usage of `cur_frm` --- .../exchange_rate_revaluation.js | 4 +- .../invoice_discounting.js | 2 +- .../doctype/journal_entry/journal_entry.js | 16 ++--- .../doctype/payment_entry/payment_entry.js | 4 +- .../doctype/pos_invoice/pos_invoice.js | 4 +- .../purchase_invoice/purchase_invoice.js | 8 +-- .../doctype/sales_invoice/sales_invoice.js | 63 +++++++++---------- erpnext/assets/doctype/asset/asset.js | 14 ++--- .../doctype/purchase_order/purchase_order.js | 36 +++++------ .../supplier_quotation/supplier_quotation.js | 10 +-- erpnext/crm/doctype/campaign/campaign.js | 2 +- erpnext/crm/doctype/lead/lead.js | 25 ++++---- .../crm/doctype/opportunity/opportunity.js | 4 +- erpnext/public/js/projects/timer.js | 2 +- 14 files changed, 95 insertions(+), 99 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js index 741039acae6..7d467cb085c 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js @@ -57,13 +57,13 @@ frappe.ui.form.on("Exchange Rate Revaluation", { get_entries: function (frm, account) { frappe.call({ method: "get_accounts_data", - doc: cur_frm.doc, + doc: frm.doc, account: account, callback: function (r) { frappe.model.clear_table(frm.doc, "accounts"); if (r.message) { r.message.forEach((d) => { - cur_frm.add_child("accounts", d); + frm.add_child("accounts", d); }); frm.events.get_total_gain_loss(frm); refresh_field("accounts"); diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js index 6d90c26415b..f65f0c274c0 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js @@ -189,7 +189,7 @@ frappe.ui.form.on("Invoice Discounting", { show_general_ledger: (frm) => { if (frm.doc.docstatus > 0) { - cur_frm.add_custom_button( + frm.add_custom_button( __("Accounting Ledger"), function () { frappe.route_options = { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 3186d07adcc..ed134bab879 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -255,7 +255,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro } onload_post_render() { - cur_frm.get_field("accounts").grid.set_multiple_add("account"); + this.frm.get_field("accounts").grid.set_multiple_add("account"); } load_defaults() { @@ -402,7 +402,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro row.debit = -doc.difference; } } - cur_frm.cscript.update_totals(doc); + this.frm.cscript.update_totals(doc); erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, "accounts"); } @@ -469,11 +469,11 @@ frappe.ui.form.on("Journal Entry Account", { }, debit: function (frm, dt, dn) { - cur_frm.cscript.update_totals(frm.doc); + frm.cscript.update_totals(frm.doc); }, credit: function (frm, dt, dn) { - cur_frm.cscript.update_totals(frm.doc); + frm.cscript.update_totals(frm.doc); }, exchange_rate: function (frm, cdt, cdn) { @@ -489,7 +489,7 @@ frappe.ui.form.on("Journal Entry Account", { }); frappe.ui.form.on("Journal Entry Account", "accounts_remove", function (frm) { - cur_frm.cscript.update_totals(frm.doc); + frm.cscript.update_totals(frm.doc); }); $.extend(erpnext.journal_entry, { @@ -531,7 +531,7 @@ $.extend(erpnext.journal_entry, { flt(flt(row.credit_in_account_currency) * row.exchange_rate, precision("credit", row)) ); - cur_frm.cscript.update_totals(frm.doc); + frm.cscript.update_totals(frm.doc); }, set_exchange_rate: function (frm, cdt, cdn) { @@ -673,10 +673,10 @@ $.extend(erpnext.journal_entry, { return { filters: filters }; }, - reverse_journal_entry: function () { + reverse_journal_entry: function (frm) { frappe.model.open_mapped_doc({ method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry", - frm: cur_frm, + frm: frm, }); }, }); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 0cb1a3d4997..781d9f52467 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -322,13 +322,13 @@ frappe.ui.form.on("Payment Entry", { "references" ); - cur_frm.set_df_property( + frm.set_df_property( "source_exchange_rate", "description", "1 " + frm.doc.paid_from_account_currency + " = [?] " + company_currency ); - cur_frm.set_df_property( + frm.set_df_property( "target_exchange_rate", "description", "1 " + frm.doc.paid_to_account_currency + " = [?] " + company_currency diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js index a6e8bfa6286..52c770667cd 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js @@ -193,7 +193,7 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex make_sales_return() { frappe.model.open_mapped_doc({ method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.make_sales_return", - frm: cur_frm, + frm: this.frm, }); } }; @@ -293,7 +293,7 @@ frappe.ui.form.on("POS Invoice", { }); } else if (frappe.dom.freeze_count != 0) { frappe.dom.unfreeze(); - cur_frm.reload_doc(); + frm.reload_doc(); cur_pos.payment.events.submit_invoice(); frappe.show_alert({ diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 957611f7858..2bd39a59778 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -131,17 +131,17 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. if (doc.docstatus == 1 && doc.outstanding_amount != 0 && !doc.on_hold) { this.frm.add_custom_button(__("Payment"), () => this.make_payment_entry(), __("Create")); - cur_frm.page.set_inner_btn_group_as_primary(__("Create")); + this.frm.page.set_inner_btn_group_as_primary(__("Create")); } if (!doc.is_return && doc.docstatus == 1) { if (doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) { - cur_frm.add_custom_button(__("Return / Debit Note"), this.make_debit_note, __("Create")); + this.frm.add_custom_button(__("Return / Debit Note"), this.make_debit_note, __("Create")); } } if (doc.outstanding_amount > 0 && !cint(doc.is_return) && !doc.on_hold) { - cur_frm.add_custom_button( + this.frm.add_custom_button( __("Payment Request"), function () { me.make_payment_request(); @@ -462,7 +462,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. make_debit_note() { frappe.model.open_mapped_doc({ method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_debit_note", - frm: cur_frm, + frm: this.frm, }); } }; diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index c7505ce007d..cf01bdd28e4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -61,9 +61,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( refresh(doc, dt, dn) { const me = this; super.refresh(); - if (cur_frm.msgbox && cur_frm.msgbox.$wrapper.is(":visible")) { + if (this.frm.msgbox && this.frm.msgbox.$wrapper.is(":visible")) { // hide new msgbox - cur_frm.msgbox.hide(); + this.frm.msgbox.hide(); } this.frm.toggle_reqd("due_date", !this.frm.doc.is_return); @@ -113,33 +113,33 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( if (doc.docstatus == 1 && !doc.is_return) { var is_delivered_by_supplier = false; - is_delivered_by_supplier = cur_frm.doc.items.some(function (item) { + is_delivered_by_supplier = this.frm.doc.items.some(function (item) { return item.is_delivered_by_supplier ? true : false; }); if (doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) { - cur_frm.add_custom_button(__("Return / Credit Note"), this.make_sales_return, __("Create")); - cur_frm.page.set_inner_btn_group_as_primary(__("Create")); + this.frm.add_custom_button(__("Return / Credit Note"), this.make_sales_return, __("Create")); + this.frm.page.set_inner_btn_group_as_primary(__("Create")); } if (cint(doc.update_stock) != 1) { // show Make Delivery Note button only if Sales Invoice is not created from Delivery Note var from_delivery_note = false; - from_delivery_note = cur_frm.doc.items.some(function (item) { + from_delivery_note = this.frm.doc.items.some(function (item) { return item.delivery_note ? true : false; }); if (!from_delivery_note && !is_delivered_by_supplier) { - cur_frm.add_custom_button( + this.frm.add_custom_button( __("Delivery"), - cur_frm.cscript["Make Delivery Note"], + this.frm.cscript["Make Delivery Note"], __("Create") ); } } if (doc.outstanding_amount > 0) { - cur_frm.add_custom_button( + this.frm.add_custom_button( __("Payment Request"), function () { me.make_payment_request(); @@ -147,10 +147,10 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( __("Create") ); - cur_frm.add_custom_button( + this.frm.add_custom_button( __("Invoice Discounting"), function () { - cur_frm.events.create_invoice_discounting(cur_frm); + this.frm.events.create_invoice_discounting(this.frm); }, __("Create") ); @@ -171,10 +171,10 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( } if (doc.docstatus === 1) { - cur_frm.add_custom_button( + this.frm.add_custom_button( __("Maintenance Schedule"), function () { - cur_frm.cscript.make_maintenance_schedule(); + this.frm.cscript.make_maintenance_schedule(); }, __("Create") ); @@ -182,7 +182,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( } // Show buttons only when pos view is active - if (cint(doc.docstatus == 0) && cur_frm.page.current_view_name !== "pos" && !doc.is_return) { + if (cint(doc.docstatus == 0) && this.frm.page.current_view_name !== "pos" && !doc.is_return) { this.frm.cscript.sales_order_btn(); this.frm.cscript.delivery_note_btn(); this.frm.cscript.quotation_btn(); @@ -213,7 +213,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( make_maintenance_schedule() { frappe.model.open_mapped_doc({ method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule", - frm: cur_frm, + frm: this.frm, }); } @@ -232,28 +232,27 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( set_default_print_format() { // set default print format to POS type or Credit Note - if (cur_frm.doc.is_pos) { - if (cur_frm.pos_print_format) { - cur_frm.meta._default_print_format = cur_frm.meta.default_print_format; - cur_frm.meta.default_print_format = cur_frm.pos_print_format; + if (this.frm.doc.is_pos) { + if (this.frm.pos_print_format) { + this.frm.meta._default_print_format = this.frm.meta.default_print_format; + this.frm.meta.default_print_format = this.frm.pos_print_format; } - } else if (cur_frm.doc.is_return && !cur_frm.meta.default_print_format) { - if (cur_frm.return_print_format) { - cur_frm.meta._default_print_format = cur_frm.meta.default_print_format; - cur_frm.meta.default_print_format = cur_frm.return_print_format; + } else if (this.frm.doc.is_return && !this.frm.meta.default_print_format) { + if (this.frm.return_print_format) { + this.frm.meta._default_print_format = this.frm.meta.default_print_format; + this.frm.meta.default_print_format = this.frm.return_print_format; } } else { - if (cur_frm.meta._default_print_format) { - cur_frm.meta.default_print_format = cur_frm.meta._default_print_format; - cur_frm.meta._default_print_format = null; + if (this.frm.meta._default_print_format) { + this.frm.meta.default_print_format = this.frm.meta._default_print_format; + this.frm.meta._default_print_format = null; } else if ( - in_list( - [cur_frm.pos_print_format, cur_frm.return_print_format], - cur_frm.meta.default_print_format + [this.frm.pos_print_format, this.frm.return_print_format].includes( + this.frm.meta.default_print_format ) ) { - cur_frm.meta.default_print_format = null; - cur_frm.meta._default_print_format = null; + this.frm.meta.default_print_format = null; + this.frm.meta._default_print_format = null; } } } @@ -465,7 +464,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( make_sales_return() { frappe.model.open_mapped_doc({ method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_sales_return", - frm: cur_frm, + frm: this.frm, }); } diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 0f71e5d6f60..b4fd73956e0 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -48,7 +48,7 @@ frappe.ui.form.on("Asset", { method: "erpnext.assets.doctype.asset.asset.make_asset_movement", freeze: true, args: { - assets: [{ name: cur_frm.doc.name }], + assets: [{ name: frm.doc.name }], }, callback: function (r) { if (r.message) { @@ -791,9 +791,7 @@ erpnext.asset.scrap_asset = function (frm) { asset_name: frm.doc.name, }, method: "erpnext.assets.doctype.asset.depreciation.scrap_asset", - callback: function (r) { - cur_frm.reload_doc(); - }, + callback: (r) => frm.reload_doc(), }); }); }; @@ -805,19 +803,17 @@ erpnext.asset.restore_asset = function (frm) { asset_name: frm.doc.name, }, method: "erpnext.assets.doctype.asset.depreciation.restore_asset", - callback: function (r) { - cur_frm.reload_doc(); - }, + callback: (r) => frm.reload_doc(), }); }); }; -erpnext.asset.transfer_asset = function () { +erpnext.asset.transfer_asset = function (frm) { frappe.call({ method: "erpnext.assets.doctype.asset.asset.make_asset_movement", freeze: true, args: { - assets: [{ name: cur_frm.doc.name }], + assets: [{ name: frm.doc.name }], purpose: "Transfer", }, callback: function (r) { diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index cf383021b06..c3a155a0133 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -86,7 +86,7 @@ frappe.ui.form.on("Purchase Order", { args: { subcontract_order: frm.doc.name, rm_details: po_details, - order_doctype: cur_frm.doc.doctype, + order_doctype: frm.doc.doctype, }, callback: function (r) { if (r && r.message) { @@ -270,8 +270,8 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( var allow_receipt = false; var is_drop_ship = false; - for (var i in cur_frm.doc.items) { - var item = cur_frm.doc.items[i]; + for (var i in this.frm.doc.items) { + var item = this.frm.doc.items[i]; if (item.delivered_by_supplier !== 1) { allow_receipt = true; } else { @@ -348,7 +348,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( if (doc.status != "Closed") { if (doc.status != "On Hold") { if (flt(doc.per_received, 2) < 100 && allow_receipt) { - cur_frm.add_custom_button( + this.frm.add_custom_button( __("Purchase Receipt"), this.make_purchase_receipt, __("Create") @@ -356,7 +356,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( if (doc.is_subcontracted) { if (doc.is_old_subcontracting_flow) { if (me.has_unsupplied_items()) { - cur_frm.add_custom_button( + this.frm.add_custom_button( __("Material to Supplier"), function () { me.make_stock_entry(); @@ -365,7 +365,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( ); } } else { - cur_frm.add_custom_button( + this.frm.add_custom_button( __("Subcontracting Order"), this.make_subcontracting_order, __("Create") @@ -374,7 +374,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( } } if (flt(doc.per_billed, 2) < 100) - cur_frm.add_custom_button( + this.frm.add_custom_button( __("Purchase Invoice"), this.make_purchase_invoice, __("Create") @@ -418,10 +418,10 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( } } - cur_frm.page.set_inner_btn_group_as_primary(__("Create")); + this.frm.page.set_inner_btn_group_as_primary(__("Create")); } } else if (doc.docstatus === 0) { - cur_frm.cscript.add_from_mappers(); + this.frm.cscript.add_from_mappers(); } } @@ -458,8 +458,8 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( frappe.call({ method: "erpnext.controllers.subcontracting_controller.make_rm_stock_entry", args: { - subcontract_order: cur_frm.doc.name, - order_doctype: cur_frm.doc.doctype, + subcontract_order: this.frm.doc.name, + order_doctype: this.frm.doc.doctype, }, callback: function (r) { var doclist = frappe.model.sync(r.message); @@ -478,7 +478,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( make_purchase_receipt() { frappe.model.open_mapped_doc({ method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt", - frm: cur_frm, + frm: this.frm, freeze_message: __("Creating Purchase Receipt ..."), }); } @@ -486,14 +486,14 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( make_purchase_invoice() { frappe.model.open_mapped_doc({ method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice", - frm: cur_frm, + frm: this.frm, }); } make_subcontracting_order() { frappe.model.open_mapped_doc({ method: "erpnext.buying.doctype.purchase_order.purchase_order.make_subcontracting_order", - frm: cur_frm, + frm: this.frm, freeze_message: __("Creating Subcontracting Order ..."), }); } @@ -652,7 +652,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( } unhold_purchase_order() { - cur_frm.cscript.update_status("Resume", "Draft"); + this.frm.cscript.update_status("Resume", "Draft"); } hold_purchase_order() { @@ -692,15 +692,15 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( } unclose_purchase_order() { - cur_frm.cscript.update_status("Re-open", "Submitted"); + this.frm.cscript.update_status("Re-open", "Submitted"); } close_purchase_order() { - cur_frm.cscript.update_status("Close", "Closed"); + this.frm.cscript.update_status("Close", "Closed"); } delivered_by_supplier() { - cur_frm.cscript.update_status("Deliver", "Delivered"); + this.frm.cscript.update_status("Deliver", "Delivered"); } items_on_form_rendered() { diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js index c1698710135..abb57023103 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js @@ -22,9 +22,9 @@ erpnext.buying.SupplierQuotationController = class SupplierQuotationController e this.frm.set_value("valid_till", frappe.datetime.add_months(this.frm.doc.transaction_date, 1)); } if (this.frm.doc.docstatus === 1) { - cur_frm.add_custom_button(__("Purchase Order"), this.make_purchase_order, __("Create")); - cur_frm.page.set_inner_btn_group_as_primary(__("Create")); - cur_frm.add_custom_button(__("Quotation"), this.make_quotation, __("Create")); + this.frm.add_custom_button(__("Purchase Order"), this.make_purchase_order, __("Create")); + this.frm.page.set_inner_btn_group_as_primary(__("Create")); + this.frm.add_custom_button(__("Quotation"), this.make_quotation, __("Create")); } else if (this.frm.doc.docstatus === 0) { this.frm.add_custom_button( __("Material Request"), @@ -87,13 +87,13 @@ erpnext.buying.SupplierQuotationController = class SupplierQuotationController e make_purchase_order() { frappe.model.open_mapped_doc({ method: "erpnext.buying.doctype.supplier_quotation.supplier_quotation.make_purchase_order", - frm: cur_frm, + frm: this.frm, }); } make_quotation() { frappe.model.open_mapped_doc({ method: "erpnext.buying.doctype.supplier_quotation.supplier_quotation.make_quotation", - frm: cur_frm, + frm: this.frm, }); } }; diff --git a/erpnext/crm/doctype/campaign/campaign.js b/erpnext/crm/doctype/campaign/campaign.js index 9e4a0a95362..219e8cf3e4f 100644 --- a/erpnext/crm/doctype/campaign/campaign.js +++ b/erpnext/crm/doctype/campaign/campaign.js @@ -11,7 +11,7 @@ frappe.ui.form.on("Campaign", { frappe.boot.sysdefaults.campaign_naming_by == "Naming Series" ); } else { - cur_frm.add_custom_button( + frm.add_custom_button( __("View Leads"), function () { frappe.route_options = { source: "Campaign", campaign_name: frm.doc.name }; diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 609eab7f9a2..848d697461a 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -89,32 +89,33 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller make_customer() { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.lead.lead.make_customer", - frm: cur_frm, + frm: this.frm, }); } make_quotation() { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.lead.lead.make_quotation", - frm: cur_frm, + frm: this.frm, }); } make_prospect() { + const me = this; frappe.model.with_doctype("Prospect", function () { let prospect = frappe.model.get_new_doc("Prospect"); - prospect.company_name = cur_frm.doc.company_name; - prospect.no_of_employees = cur_frm.doc.no_of_employees; - prospect.industry = cur_frm.doc.industry; - prospect.market_segment = cur_frm.doc.market_segment; - prospect.territory = cur_frm.doc.territory; - prospect.fax = cur_frm.doc.fax; - prospect.website = cur_frm.doc.website; - prospect.prospect_owner = cur_frm.doc.lead_owner; - prospect.notes = cur_frm.doc.notes; + prospect.company_name = me.frm.doc.company_name; + prospect.no_of_employees = me.frm.doc.no_of_employees; + prospect.industry = me.frm.doc.industry; + prospect.market_segment = me.frm.doc.market_segment; + prospect.territory = me.frm.doc.territory; + prospect.fax = me.frm.doc.fax; + prospect.website = me.frm.doc.website; + prospect.prospect_owner = me.frm.doc.lead_owner; + prospect.notes = me.frm.doc.notes; let leads_row = frappe.model.add_child(prospect, "leads"); - leads_row.lead = cur_frm.doc.name; + leads_row.lead = me.frm.doc.name; frappe.set_route("Form", "Prospect", prospect.name); }); diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index 1c8a80a0ed6..c7063579d8f 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -318,14 +318,14 @@ erpnext.crm.Opportunity = class Opportunity extends frappe.ui.form.Controller { create_quotation() { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.opportunity.opportunity.make_quotation", - frm: cur_frm, + frm: this.frm, }); } make_customer() { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.opportunity.opportunity.make_customer", - frm: cur_frm, + frm: this.frm, }); } diff --git a/erpnext/public/js/projects/timer.js b/erpnext/public/js/projects/timer.js index 8370cc6ffff..10f06272189 100644 --- a/erpnext/public/js/projects/timer.js +++ b/erpnext/public/js/projects/timer.js @@ -110,7 +110,7 @@ erpnext.timesheet.control_timer = function (frm, dialog, row, timestamp = 0) { // Stop the timer and update the time logged by the timer on click of 'Complete' button $btn_complete.click(function () { - var grid_row = cur_frm.fields_dict["time_logs"].grid.get_row(row.idx - 1); + var grid_row = frm.fields_dict["time_logs"].grid.get_row(row.idx - 1); var args = dialog.get_values(); grid_row.doc.completed = 1; grid_row.doc.activity_type = args.activity_type; From e9e985aea013c80ddfa1aa270f46e4c821300ff1 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 10 Mar 2024 16:19:59 +0100 Subject: [PATCH 38/65] refactor: use this.frm instead of cur_frm --- erpnext/public/js/controllers/transaction.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index e6fa055707b..c756d537cb8 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2264,9 +2264,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } get_method_for_payment() { - var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"; - if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){ - if(['Sales Invoice', 'Purchase Invoice'].includes( cur_frm.doc.doctype)){ + let method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"; + if(this.frm.doc.__onload && this.frm.doc.__onload.make_payment_via_journal_entry){ + if(['Sales Invoice', 'Purchase Invoice'].includes( this.frm.doc.doctype)){ method = "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_invoice"; }else { method= "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_order"; From 4cfe99d74f2390a87410723aaedcc3d72dab1545 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 19 Mar 2024 12:29:50 +0100 Subject: [PATCH 39/65] fix: translatability of bom configurator labels --- .../js/bom_configurator/bom_configurator.bundle.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/public/js/bom_configurator/bom_configurator.bundle.js b/erpnext/public/js/bom_configurator/bom_configurator.bundle.js index 454a2a4709f..5061be9d20a 100644 --- a/erpnext/public/js/bom_configurator/bom_configurator.bundle.js +++ b/erpnext/public/js/bom_configurator/bom_configurator.bundle.js @@ -107,7 +107,7 @@ class BOMConfigurator { this.frm?.doc.docstatus === 0 ? [ { - label: __(frappe.utils.icon("edit", "sm") + " Qty"), + label: `${frappe.utils.icon("edit", "sm")} ${__("Qty")}`, click: function (node) { let view = frappe.views.trees["BOM Configurator"]; view.events.edit_qty(node, view); @@ -115,7 +115,7 @@ class BOMConfigurator { btnClass: "hidden-xs", }, { - label: __(frappe.utils.icon("add", "sm") + " Raw Material"), + label: `${frappe.utils.icon("add", "sm")} ${__("Raw Material")}`, click: function (node) { let view = frappe.views.trees["BOM Configurator"]; view.events.add_item(node, view); @@ -126,7 +126,7 @@ class BOMConfigurator { btnClass: "hidden-xs", }, { - label: __(frappe.utils.icon("add", "sm") + " Sub Assembly"), + label: `${frappe.utils.icon("add", "sm")} ${__("Sub Assembly")}`, click: function (node) { let view = frappe.views.trees["BOM Configurator"]; view.events.add_sub_assembly(node, view); @@ -156,7 +156,7 @@ class BOMConfigurator { btnClass: "hidden-xs expand-all-btn", }, { - label: __(frappe.utils.icon("move", "sm") + " Sub Assembly"), + label: `${frappe.utils.icon("move", "sm")} ${__("Sub Assembly")}`, click: function (node) { let view = frappe.views.trees["BOM Configurator"]; view.events.convert_to_sub_assembly(node, view); @@ -167,7 +167,7 @@ class BOMConfigurator { btnClass: "hidden-xs", }, { - label: __(frappe.utils.icon("delete", "sm") + __(" Item")), + label: `${frappe.utils.icon("delete", "sm")} ${__("Item")}`, click: function (node) { let view = frappe.views.trees["BOM Configurator"]; view.events.delete_node(node, view); From 87e36d290e90fd5d2f958075273edaf99c827126 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 19 Mar 2024 12:34:23 +0100 Subject: [PATCH 40/65] fix(Supplier Quotation Comparison): group by options --- .../supplier_quotation_comparison.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js index c109abd8146..f7d0d947b61 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js @@ -77,7 +77,10 @@ frappe.query_reports["Supplier Quotation Comparison"] = { fieldname: "group_by", label: __("Group by"), fieldtype: "Select", - options: [__("Group by Supplier"), __("Group by Item")], + options: [ + { label: __("Group by Supplier"), value: "Group by Supplier" }, + { label: __("Group by Item"), value: "Group by Item" }, + ], default: __("Group by Supplier"), }, { From 4a55240e630fedf1590a2c324528fd5934066d37 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 19 Mar 2024 17:39:20 +0530 Subject: [PATCH 41/65] refactor: exception propogation --- .../transaction_deletion_record.json | 9 ++++++++- .../transaction_deletion_record.py | 13 ++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index 6e057ace4a6..8291a4ffc24 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -10,6 +10,7 @@ "column_break_txbg", "status", "tasks_section", + "error_log", "delete_bin_data", "delete_leads_and_addresses", "reset_company_default_values", @@ -122,12 +123,18 @@ "label": "Initialize Summary Table", "no_copy": 1, "read_only": 1 + }, + { + "depends_on": "eval: doc.error_log", + "fieldname": "error_log", + "fieldtype": "Long Text", + "label": "Error Log" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-02-05 10:36:34.229864", + "modified": "2024-03-19 17:04:49.369734", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 039d9a58413..b1e75117ad4 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -35,6 +35,7 @@ class TransactionDeletionRecord(Document): delete_transactions: DF.Check doctypes: DF.Table[TransactionDeletionRecordDetails] doctypes_to_be_ignored: DF.Table[TransactionDeletionRecordItem] + error_log: DF.LongText | None initialize_doctypes_table: DF.Check reset_company_default_values: DF.Check status: DF.Literal["Queued", "Running", "Failed", "Completed", "Cancelled"] @@ -146,11 +147,21 @@ class TransactionDeletionRecord(Document): task_to_execute=task, ) + # todo: add a non-background job based approach as well + def execute_task(self, task_to_execute: str | None = None): if task_to_execute: method = self.task_to_internal_method_map[task_to_execute] if task := getattr(self, method, None): - task() + try: + task() + except Exception as err: + frappe.db.rollback() + traceback = frappe.get_traceback(with_context=True) + if traceback: + message = "Traceback:
" + traceback + frappe.db.set_value(self.doctype, self.name, "error_log", message) + frappe.db.set_value(self.doctype, self.name, "status", "Failed") def delete_notifications(self): self.validate_doc_status() From 0455d0c46c8a171c0bbbd6f1730f671263cfedc4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 19 Mar 2024 17:41:37 +0530 Subject: [PATCH 42/65] refactor: minor UI tweaks --- .../transaction_deletion_record.js | 16 +++++++--------- .../transaction_deletion_record.py | 1 + 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js index ccf09a6c38b..d543baa3cf6 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js @@ -10,26 +10,24 @@ frappe.ui.form.on("Transaction Deletion Record", { callback: function (r) { doctypes_to_be_ignored_array = r.message; populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm); - frm.refresh_field('doctypes_to_be_ignored'); - } + frm.refresh_field("doctypes_to_be_ignored"); + }, }); } - }, - refresh: function(frm) { - if (frm.doc.docstatus==1 && ['Queued', 'Failed'].find(x => x == frm.doc.status)) { - let execute_btn = __("Start Deletion") + refresh: function (frm) { + if (frm.doc.docstatus == 1 && ["Queued", "Failed"].find((x) => x == frm.doc.status)) { + let execute_btn = frm.doc.status == "Queued" ? __("Start Deletion") : __("Retry"); frm.add_custom_button(execute_btn, () => { // Entry point for chain of events frm.call({ - method: 'process_tasks', - doc: frm.doc + method: "process_tasks", + doc: frm.doc, }); }); } - }, }); diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index b1e75117ad4..1959a7d86d9 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -329,6 +329,7 @@ class TransactionDeletionRecord(Document): else: self.db_set("status", "Completed") self.db_set("delete_transactions", 1) + self.db_set("error_log", None) def get_doctypes_to_be_ignored_list(self): singles = frappe.get_all("DocType", filters={"issingle": 1}, pluck="name") From 3cec62d4f872a66557e1b9e56a2207f194e5439f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 19 Mar 2024 17:49:05 +0530 Subject: [PATCH 43/65] chore: move status and error log to their own section --- .../transaction_deletion_record.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index 8291a4ffc24..688b14a808a 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -7,10 +7,10 @@ "engine": "InnoDB", "field_order": [ "company", - "column_break_txbg", + "section_break_qpwb", "status", - "tasks_section", "error_log", + "tasks_section", "delete_bin_data", "delete_leads_and_addresses", "reset_company_default_values", @@ -63,10 +63,6 @@ "options": "Queued\nRunning\nFailed\nCompleted\nCancelled", "read_only": 1 }, - { - "fieldname": "column_break_txbg", - "fieldtype": "Column Break" - }, { "fieldname": "section_break_tbej", "fieldtype": "Section Break" @@ -129,12 +125,16 @@ "fieldname": "error_log", "fieldtype": "Long Text", "label": "Error Log" + }, + { + "fieldname": "section_break_qpwb", + "fieldtype": "Section Break" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-03-19 17:04:49.369734", + "modified": "2024-03-19 17:47:04.490196", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", From 9996d6b7e6c8276ed42314356d3b7fd17c2f0643 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 19 Mar 2024 18:44:13 +0530 Subject: [PATCH 44/65] fix: provisional entry for non-stock item --- .../accounts/doctype/gl_entry/gl_entry.json | 7 +-- erpnext/accounts/doctype/gl_entry/gl_entry.py | 2 - .../purchase_invoice/purchase_invoice.py | 44 +++++++++---------- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index c071193739c..991a08bec26 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -179,7 +179,8 @@ "fieldname": "voucher_detail_no", "fieldtype": "Data", "label": "Voucher Detail No", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "project", @@ -290,7 +291,7 @@ "idx": 1, "in_create": 1, "links": [], - "modified": "2023-09-26 12:03:23.031733", + "modified": "2024-03-19 18:43:42.235373", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", @@ -325,4 +326,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 3fa8710931c..a6f6d4eb565 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -32,8 +32,6 @@ class GLEntry(Document): account: DF.Link | None account_currency: DF.Link | None against: DF.Text | None - against_link: DF.DynamicLink | None - against_type: DF.Link | None against_voucher: DF.DynamicLink | None against_voucher_type: DF.Link | None company: DF.Link | None diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 75f0d082ea0..126eef04771 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -904,7 +904,6 @@ class PurchaseInvoice(BuyingController): "Company", self.company, "enable_provisional_accounting_for_non_stock_items" ) ) - self.provisional_enpenses_booked_in_pr = False if provisional_accounting_for_non_stock_items: self.get_provisional_accounts() @@ -1149,37 +1148,34 @@ class PurchaseInvoice(BuyingController): fields=["name", "provisional_expense_account", "qty", "base_rate"], ) default_provisional_account = self.get_company_default("default_provisional_account") + provisional_accounts = [ + d.provisional_expense_account if d.provisional_expense_account else default_provisional_account + for d in pr_items + ] + + provisional_gl_entries = frappe.get_all( + "GL Entry", + filters={ + "voucher_type": "Purchase Receipt", + "voucher_no": ("in", linked_purchase_receipts), + "account": ("in", provisional_accounts), + "is_cancelled": 0, + }, + fields=["voucher_detail_no"], + ) + rows_with_provisional_entries = [d.voucher_detail_no for d in provisional_gl_entries] for item in pr_items: self.provisional_accounts[item.name] = { "provisional_account": item.provisional_expense_account or default_provisional_account, "qty": item.qty, "base_rate": item.base_rate, + "has_provisional_entry": item.name in rows_with_provisional_entries, } def make_provisional_gl_entry(self, gl_entries, item): if item.purchase_receipt: pr_item = self.provisional_accounts.get(item.pr_detail, {}) - provisional_account = pr_item.get("provisional_account") - pr_qty = pr_item.get("qty") - pr_base_rate = pr_item.get("base_rate") - - if not self.provisional_enpenses_booked_in_pr: - # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt - provision_gle_against_pr = frappe.db.get_value( - "GL Entry", - { - "is_cancelled": 0, - "voucher_type": "Purchase Receipt", - "voucher_no": item.purchase_receipt, - "voucher_detail_no": item.pr_detail, - "account": provisional_account, - }, - ["name"], - ) - if provision_gle_against_pr: - self.provisional_enpenses_booked_in_pr = True - - if self.provisional_enpenses_booked_in_pr: + if pr_item.get("has_provisional_entry"): purchase_receipt_doc = frappe.get_cached_doc("Purchase Receipt", item.purchase_receipt) # Intentionally passing purchase invoice item to handle partial billing @@ -1187,9 +1183,9 @@ class PurchaseInvoice(BuyingController): item, gl_entries, self.posting_date, - provisional_account, + pr_item.get("provisional_account"), reverse=1, - item_amount=(min(item.qty, pr_qty) * pr_base_rate), + item_amount=(min(item.qty, pr_item.get("qty")) * pr_item.get("base_rate")), ) def update_gross_purchase_amount_for_linked_assets(self, item): From 0725707cb12e4431703273d12f4e39c6201a47ba Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 19 Mar 2024 20:15:36 +0530 Subject: [PATCH 45/65] fix: currency symbol for Landed Cost Voucher Amount (#40550) --- .../doctype/purchase_invoice_item/purchase_invoice_item.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 3ee4214ae71..66df76a3af0 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -745,6 +745,7 @@ "fieldtype": "Currency", "label": "Landed Cost Voucher Amount", "no_copy": 1, + "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1 }, @@ -938,7 +939,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2024-02-04 14:11:52.742228", + "modified": "2024-03-19 19:09:47.210965", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", From b73f51a9bdf6532a06c80b768991a7fd7f5d31f7 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 19 Mar 2024 21:05:22 +0530 Subject: [PATCH 46/65] fix: get unique provisional accounts --- .../doctype/purchase_invoice/purchase_invoice.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 126eef04771..382c034c0f5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1148,10 +1148,12 @@ class PurchaseInvoice(BuyingController): fields=["name", "provisional_expense_account", "qty", "base_rate"], ) default_provisional_account = self.get_company_default("default_provisional_account") - provisional_accounts = [ - d.provisional_expense_account if d.provisional_expense_account else default_provisional_account - for d in pr_items - ] + provisional_accounts = set( + [ + d.provisional_expense_account if d.provisional_expense_account else default_provisional_account + for d in pr_items + ] + ) provisional_gl_entries = frappe.get_all( "GL Entry", From 1a9ae33110371655e9ef88d6904c0c03f985ec90 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 20 Mar 2024 13:18:23 +0530 Subject: [PATCH 47/65] fix: pick list not picked qty less than 1 --- erpnext/stock/doctype/pick_list/pick_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 8a1f79d4a27..18d530f12af 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -774,7 +774,7 @@ def get_available_item_locations( if picked_item_details: for location in list(locations): - if location["qty"] < 1: + if location["qty"] < 0: locations.remove(location) total_qty_available = sum(location.get("qty") for location in locations) From 5fe0b20be108ce55a9519af5c953f7aebeaca93b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 20 Mar 2024 14:10:08 +0530 Subject: [PATCH 48/65] chore: rename entry point --- .../transaction_deletion_record/transaction_deletion_record.js | 2 +- .../transaction_deletion_record/transaction_deletion_record.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js index d543baa3cf6..9aa02784165 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js @@ -23,7 +23,7 @@ frappe.ui.form.on("Transaction Deletion Record", { frm.add_custom_button(execute_btn, () => { // Entry point for chain of events frm.call({ - method: "process_tasks", + method: "start_deletion_tasks", doc: frm.doc, }); }); diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 1959a7d86d9..e8ffe55a2b7 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -198,7 +198,7 @@ class TransactionDeletionRecord(Document): ) @frappe.whitelist() - def process_tasks(self): + def start_deletion_tasks(self): # This method is the entry point for the chain of events that follow self.db_set("status", "Running") self.enqueue_task(task="Delete Bins") From 5a3afea8c772b7167839593ba88f3582a381259c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 20 Mar 2024 14:32:27 +0530 Subject: [PATCH 49/65] refactor: link running doc validation to company master --- erpnext/setup/doctype/company/company.js | 2 +- erpnext/setup/doctype/company/company.py | 41 ++++++------------- .../transaction_deletion_record.py | 32 ++++++++++----- 3 files changed, 34 insertions(+), 41 deletions(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 39170053838..9b1a41a0b77 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -168,7 +168,7 @@ frappe.ui.form.on("Company", { delete_company_transactions: function (frm) { frappe.call({ - method: "erpnext.setup.doctype.company.company.is_deletion_job_running", + method: "erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.is_deletion_doc_running", args: { company: frm.doc.name, }, diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 876b6a4ac80..3ca14e65fdd 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -12,7 +12,6 @@ from frappe.contacts.address_and_contact import load_address_and_contact from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.desk.page.setup_wizard.setup_wizard import make_records from frappe.utils import cint, formatdate, get_link_to_form, get_timestamp, today -from frappe.utils.background_jobs import get_job, is_job_enqueued from frappe.utils.nestedset import NestedSet, rebuild_tree from erpnext.accounts.doctype.account.account import get_account_currency @@ -901,37 +900,21 @@ def get_default_company_address(name, sort_key="is_primary_address", existing_ad return None -def generate_id_for_deletion_job(company): - return "delete_company_transactions_" + company - - -@frappe.whitelist() -def is_deletion_job_running(company): - job_id = generate_id_for_deletion_job(company) - if is_job_enqueued(job_id): - job_name = get_job(job_id).get_id() # job name will have site prefix - frappe.throw( - _("A Transaction Deletion Job: {0} is already running for {1}").format( - frappe.bold(get_link_to_form("RQ Job", job_name)), frappe.bold(company) - ) - ) - - @frappe.whitelist() def create_transaction_deletion_request(company): - is_deletion_job_running(company) - job_id = generate_id_for_deletion_job(company) + from erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record import ( + is_deletion_doc_running, + ) + + is_deletion_doc_running(company) tdr = frappe.get_doc({"doctype": "Transaction Deletion Record", "company": company}) - tdr.insert() + tdr.submit() + tdr.start_deletion_tasks() - frappe.enqueue( - "frappe.utils.background_jobs.run_doc_method", - doctype=tdr.doctype, - name=tdr.name, - doc_method="submit", - job_id=job_id, - queue="long", - enqueue_after_commit=True, + frappe.msgprint( + _("A Transaction Deletion Document: {0} is triggered for {0}").format( + get_link_to_form("Transaction Deletion Record", tdr.name) + ), + frappe.bold(company), ) - frappe.msgprint(_("A Transaction Deletion Job is triggered for {0}").format(frappe.bold(company))) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index e8ffe55a2b7..4ee91306ee9 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -474,7 +474,25 @@ def get_doctypes_to_be_ignored(): return doctypes_to_be_ignored +@frappe.whitelist() +def is_deletion_doc_running(company: str | None = None, err_msg: str | None = None): + if company: + if running_deletion_jobs := frappe.db.get_all( + "Transaction Deletion Record", + filters={"docstatus": 1, "company": company, "status": "Running"}, + ): + if not err_msg: + err_msg = "" + frappe.throw( + title=_("Deletion in Progress!"), + msg=_("Transaction Deletion Document: {0} is running for this Company. {1}").format( + get_link_to_form("Transaction Deletion Record", running_deletion_jobs[0].name), err_msg + ), + ) + + def check_for_running_deletion_job(doc, method=None): + # Check if DocType has 'company' field df = qb.DocType("DocField") if ( not_allowed := qb.from_(df) @@ -482,14 +500,6 @@ def check_for_running_deletion_job(doc, method=None): .where((df.fieldname == "company") & (df.parent == doc.doctype)) .run() ): - if running_deletion_jobs := frappe.db.get_all( - "Transaction Deletion Record", - filters={"docstatus": 1, "company": doc.company, "status": "Running"}, - ): - frappe.throw( - _( - "Transaction Deletion job {0} is running for this Company. Cannot make any transactions until the deletion job is completed" - ).format( - get_link_to_form("Transaction Deletion Record", running_deletion_jobs[0].name) - ) - ) + is_deletion_doc_running( + doc.company, _("Cannot make any transactions until the deletion job is completed") + ) From ac92a229149292f41dfc5f9f96e3e9d4bbce8b64 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 20 Mar 2024 14:56:07 +0530 Subject: [PATCH 50/65] fix: Cannot read properties of undefined (reading 'rate') --- erpnext/public/js/controllers/transaction.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 8135bb2404a..1f85eb60d41 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1276,8 +1276,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe calculate_stock_uom_rate(doc, cdt, cdn) { let item = frappe.get_doc(cdt, cdn); - item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor); - refresh_field("stock_uom_rate", item.name, item.parentfield); + + if (item?.rate) { + item.stock_uom_rate = flt(item.rate) / flt(item.conversion_factor); + refresh_field("stock_uom_rate", item.name, item.parentfield); + } } service_stop_date(frm, cdt, cdn) { var child = locals[cdt][cdn]; From a158b825d7eb359a66743b3e6972aa1b81389df0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 20 Mar 2024 14:59:19 +0530 Subject: [PATCH 51/65] refactor: ability to process in single transaction --- .../transaction_deletion_record.json | 11 ++++++-- .../transaction_deletion_record.py | 26 ++++++++++--------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index 688b14a808a..e03e1695e0e 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -20,7 +20,8 @@ "section_break_tbej", "doctypes", "doctypes_to_be_ignored", - "amended_from" + "amended_from", + "process_in_single_transaction" ], "fields": [ { @@ -129,12 +130,18 @@ { "fieldname": "section_break_qpwb", "fieldtype": "Section Break" + }, + { + "default": "0", + "fieldname": "process_in_single_transaction", + "fieldtype": "Check", + "label": "Process in Single Transaction" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-03-19 17:47:04.490196", + "modified": "2024-03-20 14:58:15.086360", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 4ee91306ee9..00fad5f0fa6 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -37,6 +37,7 @@ class TransactionDeletionRecord(Document): doctypes_to_be_ignored: DF.Table[TransactionDeletionRecordItem] error_log: DF.LongText | None initialize_doctypes_table: DF.Check + process_in_single_transaction: DF.Check reset_company_default_values: DF.Check status: DF.Literal["Queued", "Running", "Failed", "Completed", "Cancelled"] # end: auto-generated types @@ -136,18 +137,19 @@ class TransactionDeletionRecord(Document): # Generate Job Id to uniquely identify each task for this document job_id = self.generate_job_name_for_task(task) - frappe.enqueue( - "frappe.utils.background_jobs.run_doc_method", - doctype=self.doctype, - name=self.name, - doc_method="execute_task", - job_id=job_id, - queue="long", - enqueue_after_commit=True, - task_to_execute=task, - ) - - # todo: add a non-background job based approach as well + if self.process_in_single_transaction: + self.execute_task(task_to_execute=task) + else: + frappe.enqueue( + "frappe.utils.background_jobs.run_doc_method", + doctype=self.doctype, + name=self.name, + doc_method="execute_task", + job_id=job_id, + queue="long", + enqueue_after_commit=True, + task_to_execute=task, + ) def execute_task(self, task_to_execute: str | None = None): if task_to_execute: From 81309576b0cb41ac2c91cf1abbf79b4655c7697d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 20 Mar 2024 15:16:33 +0530 Subject: [PATCH 52/65] refactor(test): test cases modified to handle new approach --- erpnext/setup/demo.py | 2 ++ .../test_transaction_deletion_record.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py index 688d45a5a77..f48402e175b 100644 --- a/erpnext/setup/demo.py +++ b/erpnext/setup/demo.py @@ -181,8 +181,10 @@ def get_random_date(start_date, start_range, end_range): def create_transaction_deletion_record(company): transaction_deletion_record = frappe.new_doc("Transaction Deletion Record") transaction_deletion_record.company = company + transaction_deletion_record.process_in_single_transaction = True transaction_deletion_record.save(ignore_permissions=True) transaction_deletion_record.submit() + transaction_deletion_record.start_deletion_tasks() def clear_masters(): diff --git a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py index 844e7865e3e..432438bcfee 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py @@ -29,6 +29,7 @@ class TestTransactionDeletionRecord(FrappeTestCase): for i in range(5): create_task("Dunder Mifflin Paper Co") tdr = create_transaction_deletion_doc("Dunder Mifflin Paper Co") + tdr.reload() for doctype in tdr.doctypes: if doctype.doctype_name == "Task": self.assertEqual(doctype.no_of_docs, 5) @@ -60,7 +61,9 @@ def create_company(company_name): def create_transaction_deletion_doc(company): tdr = frappe.get_doc({"doctype": "Transaction Deletion Record", "company": company}) tdr.insert() + tdr.process_in_single_transaction = True tdr.submit() + tdr.start_deletion_tasks() return tdr From 02c522b7cddf0b1ae4bcc1d05e156a5b7aa09f2f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 20 Mar 2024 16:01:09 +0530 Subject: [PATCH 53/65] chore: fix linting issue in JS --- .../transaction_deletion_record_list.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js index 7c7b8ff25a7..285cb6dd221 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js @@ -1,16 +1,16 @@ // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.listview_settings['Transaction Deletion Record'] = { +frappe.listview_settings["Transaction Deletion Record"] = { add_fields: ["status"], - get_indicator: function(doc) { + get_indicator: function (doc) { let colors = { - 'Queued': 'orange', - 'Completed': 'green', - 'Running': 'blue', - 'Failed': 'red', + Queued: "orange", + Completed: "green", + Running: "blue", + Failed: "red", }; let status = doc.status; - return [__(status), colors[status], 'status,=,'+status]; + return [__(status), colors[status], "status,=," + status]; }, }; From 1c639838736dee633c435ec728e4b7319cbd9748 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 20 Mar 2024 16:09:14 +0530 Subject: [PATCH 54/65] fix: use Text Editor for rendering tax breakup table --- erpnext/accounts/doctype/pos_invoice/pos_invoice.json | 4 ++-- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 2 +- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 4 ++-- .../accounts/doctype/purchase_invoice/purchase_invoice.py | 2 +- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 6 +++--- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- erpnext/buying/doctype/purchase_order/purchase_order.json | 6 +++--- erpnext/buying/doctype/purchase_order/purchase_order.py | 3 ++- .../doctype/supplier_quotation/supplier_quotation.json | 4 ++-- .../buying/doctype/supplier_quotation/supplier_quotation.py | 2 +- erpnext/selling/doctype/quotation/quotation.json | 4 ++-- erpnext/selling/doctype/quotation/quotation.py | 2 +- erpnext/selling/doctype/sales_order/sales_order.json | 6 +++--- erpnext/selling/doctype/sales_order/sales_order.py | 4 +++- erpnext/stock/doctype/delivery_note/delivery_note.json | 4 ++-- erpnext/stock/doctype/delivery_note/delivery_note.py | 4 ++-- .../stock/doctype/purchase_receipt/purchase_receipt.json | 4 ++-- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 2 +- 18 files changed, 34 insertions(+), 31 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index 955b66a1b81..d7b173667ec 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -775,7 +775,7 @@ }, { "fieldname": "other_charges_calculation", - "fieldtype": "Long Text", + "fieldtype": "Text Editor", "label": "Taxes and Charges Calculation", "no_copy": 1, "oldfieldtype": "HTML", @@ -1563,7 +1563,7 @@ "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2023-11-20 12:27:12.848149", + "modified": "2024-03-20 16:00:34.268756", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 1a7eef6b02b..5db3da1f5ea 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -109,7 +109,7 @@ class POSInvoice(SalesInvoice): loyalty_redemption_cost_center: DF.Link | None naming_series: DF.Literal["ACC-PSINV-.YYYY.-"] net_total: DF.Currency - other_charges_calculation: DF.LongText | None + other_charges_calculation: DF.TextEditor | None outstanding_amount: DF.Currency packed_items: DF.Table[PackedItem] paid_amount: DF.Currency diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 22f2d13649c..44d3d48933f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -760,7 +760,7 @@ }, { "fieldname": "other_charges_calculation", - "fieldtype": "Long Text", + "fieldtype": "Text Editor", "label": "Taxes and Charges Calculation", "no_copy": 1, "oldfieldtype": "HTML", @@ -1638,7 +1638,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2024-03-11 14:46:30.298184", + "modified": "2024-03-20 15:57:00.736868", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 382c034c0f5..19b7092afdf 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -147,7 +147,7 @@ class PurchaseInvoice(BuyingController): net_total: DF.Currency on_hold: DF.Check only_include_allocated_payments: DF.Check - other_charges_calculation: DF.LongText | None + other_charges_calculation: DF.TextEditor | None outstanding_amount: DF.Currency paid_amount: DF.Currency party_account_currency: DF.Link | None diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 37b2752ed44..1d8983e8eef 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -946,7 +946,7 @@ }, { "fieldname": "other_charges_calculation", - "fieldtype": "Long Text", + "fieldtype": "Text Editor", "hide_days": 1, "hide_seconds": 1, "label": "Taxes and Charges Calculation", @@ -2193,7 +2193,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2024-03-15 16:44:17.778370", + "modified": "2024-03-20 16:02:52.237732", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", @@ -2248,4 +2248,4 @@ "title_field": "title", "track_changes": 1, "track_seen": 1 -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index bf50e77f7f9..1228bbb677f 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -146,7 +146,7 @@ class SalesInvoice(SellingController): naming_series: DF.Literal["ACC-SINV-.YYYY.-", "ACC-SINV-RET-.YYYY.-"] net_total: DF.Currency only_include_allocated_payments: DF.Check - other_charges_calculation: DF.LongText | None + other_charges_calculation: DF.TextEditor | None outstanding_amount: DF.Currency packed_items: DF.Table[PackedItem] paid_amount: DF.Currency diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 9da49a79ee2..0916ff5f8d0 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -642,7 +642,7 @@ }, { "fieldname": "other_charges_calculation", - "fieldtype": "Long Text", + "fieldtype": "Text Editor", "label": "Taxes and Charges Calculation", "no_copy": 1, "oldfieldtype": "HTML", @@ -1288,7 +1288,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-10-10 13:37:40.158761", + "modified": "2024-03-20 16:03:31.611808", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", @@ -1343,4 +1343,4 @@ "timeline_field": "supplier", "title_field": "supplier_name", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 4d948689f63..4f24ec2acc2 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -57,6 +57,7 @@ class PurchaseOrder(BuyingController): additional_discount_percentage: DF.Float address_display: DF.SmallText | None advance_paid: DF.Currency + advance_payment_status: DF.Literal["Not Initiated", "Initiated", "Partially Paid", "Fully Paid"] amended_from: DF.Link | None apply_discount_on: DF.Literal["", "Grand Total", "Net Total"] apply_tds: DF.Check @@ -109,7 +110,7 @@ class PurchaseOrder(BuyingController): net_total: DF.Currency order_confirmation_date: DF.Date | None order_confirmation_no: DF.Data | None - other_charges_calculation: DF.LongText | None + other_charges_calculation: DF.TextEditor | None party_account_currency: DF.Link | None payment_schedule: DF.Table[PaymentSchedule] payment_terms_template: DF.Link | None diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 18912610ce0..09be247b363 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -462,7 +462,7 @@ }, { "fieldname": "other_charges_calculation", - "fieldtype": "Long Text", + "fieldtype": "Markdown Editor", "label": "Taxes and Charges Calculation", "no_copy": 1, "oldfieldtype": "HTML", @@ -928,7 +928,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-11-17 12:34:30.083077", + "modified": "2024-03-20 16:03:59.069145", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py index e2b737ba6fe..b716f7f0427 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py @@ -71,7 +71,7 @@ class SupplierQuotation(BuyingController): naming_series: DF.Literal["PUR-SQTN-.YYYY.-"] net_total: DF.Currency opportunity: DF.Link | None - other_charges_calculation: DF.LongText | None + other_charges_calculation: DF.MarkdownEditor | None plc_conversion_rate: DF.Float price_list_currency: DF.Link | None pricing_rules: DF.Table[PricingRuleDetail] diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index 8c816cf6e4e..982e7326775 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -557,7 +557,7 @@ }, { "fieldname": "other_charges_calculation", - "fieldtype": "Long Text", + "fieldtype": "Text Editor", "label": "Taxes and Charges Calculation", "no_copy": 1, "oldfieldtype": "HTML", @@ -1073,7 +1073,7 @@ "idx": 82, "is_submittable": 1, "links": [], - "modified": "2023-06-03 16:21:04.980033", + "modified": "2024-03-20 16:04:21.567847", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 13d17d66fe2..633e5f56a05 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -78,7 +78,7 @@ class Quotation(SellingController): opportunity: DF.Link | None order_lost_reason: DF.SmallText | None order_type: DF.Literal["", "Sales", "Maintenance", "Shopping Cart"] - other_charges_calculation: DF.LongText | None + other_charges_calculation: DF.TextEditor | None packed_items: DF.Table[PackedItem] party_name: DF.DynamicLink | None payment_schedule: DF.Table[PaymentSchedule] diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 3c516d0ea37..1fb1ae0c040 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -777,7 +777,7 @@ }, { "fieldname": "other_charges_calculation", - "fieldtype": "Long Text", + "fieldtype": "Text Editor", "hide_days": 1, "hide_seconds": 1, "label": "Taxes and Charges Calculation", @@ -1657,7 +1657,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-10-18 12:41:54.813462", + "modified": "2024-03-20 16:04:43.627183", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", @@ -1735,4 +1735,4 @@ "title_field": "customer_name", "track_changes": 1, "track_seen": 1 -} +} \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 4956f29eff1..79df4d343b3 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -66,6 +66,7 @@ class SalesOrder(SellingController): additional_discount_percentage: DF.Float address_display: DF.SmallText | None advance_paid: DF.Currency + advance_payment_status: DF.Literal["Not Requested", "Requested", "Partially Paid", "Fully Paid"] amended_from: DF.Link | None amount_eligible_for_commission: DF.Currency apply_discount_on: DF.Literal["", "Grand Total", "Net Total"] @@ -122,7 +123,7 @@ class SalesOrder(SellingController): naming_series: DF.Literal["SAL-ORD-.YYYY.-"] net_total: DF.Currency order_type: DF.Literal["", "Sales", "Maintenance", "Shopping Cart"] - other_charges_calculation: DF.LongText | None + other_charges_calculation: DF.TextEditor | None packed_items: DF.Table[PackedItem] party_account_currency: DF.Link | None payment_schedule: DF.Table[PaymentSchedule] @@ -155,6 +156,7 @@ class SalesOrder(SellingController): "", "Draft", "On Hold", + "To Pay", "To Deliver and Bill", "To Bill", "To Deliver", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index d07a825331d..87c333370b2 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -680,7 +680,7 @@ }, { "fieldname": "other_charges_calculation", - "fieldtype": "Long Text", + "fieldtype": "Text Editor", "label": "Taxes and Charges Calculation", "no_copy": 1, "oldfieldtype": "HTML", @@ -1397,7 +1397,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2024-03-05 11:58:47.784349", + "modified": "2024-03-20 16:05:02.854990", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 2f52f218ee9..e17a0a2307f 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -76,7 +76,7 @@ class DeliveryNote(SellingController): ignore_pricing_rule: DF.Check in_words: DF.Data | None incoterm: DF.Link | None - installation_status: DF.Literal + installation_status: DF.Literal[None] instructions: DF.Text | None inter_company_reference: DF.Link | None is_internal_customer: DF.Check @@ -90,7 +90,7 @@ class DeliveryNote(SellingController): named_place: DF.Data | None naming_series: DF.Literal["MAT-DN-.YYYY.-", "MAT-DN-RET-.YYYY.-"] net_total: DF.Currency - other_charges_calculation: DF.LongText | None + other_charges_calculation: DF.TextEditor | None packed_items: DF.Table[PackedItem] per_billed: DF.Percent per_installed: DF.Percent diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index a1810221219..b926e9826eb 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -651,7 +651,7 @@ }, { "fieldname": "other_charges_calculation", - "fieldtype": "Long Text", + "fieldtype": "Text Editor", "label": "Taxes and Charges Calculation", "no_copy": 1, "oldfieldtype": "HTML", @@ -1252,7 +1252,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2023-12-18 17:26:41.279663", + "modified": "2024-03-20 16:05:31.713453", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 2eec58f8e54..034dd0a799c 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -87,7 +87,7 @@ class PurchaseReceipt(BuyingController): named_place: DF.Data | None naming_series: DF.Literal["MAT-PRE-.YYYY.-", "MAT-PR-RET-.YYYY.-"] net_total: DF.Currency - other_charges_calculation: DF.LongText | None + other_charges_calculation: DF.TextEditor | None per_billed: DF.Percent per_returned: DF.Percent plc_conversion_rate: DF.Float From 967540da18351705dfdcf7bebae62d5445eec7e2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 20 Mar 2024 16:56:30 +0530 Subject: [PATCH 55/65] fix: style for tax breakup --- erpnext/public/scss/erpnext.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/public/scss/erpnext.scss b/erpnext/public/scss/erpnext.scss index e45ae5038f5..03dd31104e1 100644 --- a/erpnext/public/scss/erpnext.scss +++ b/erpnext/public/scss/erpnext.scss @@ -548,3 +548,7 @@ body[data-route="pos"] { align-items: center; justify-content: center; } + +.frappe-control[data-fieldname="other_charges_calculation"] .ql-editor { + white-space: normal; +} From a1d108c062831b2301b2b62b09dad35e8bbd1959 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 20 Mar 2024 18:45:04 +0530 Subject: [PATCH 56/65] fix: validate gl for previous fiscal year --- .../doctype/period_closing_voucher/period_closing_voucher.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 2a84d971f44..76c0a093861 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -141,7 +141,8 @@ class PeriodClosingVoucher(AccountsController): previous_fiscal_year = get_fiscal_year(last_year_closing, company=self.company, boolean=True) if previous_fiscal_year and not frappe.db.exists( - "GL Entry", {"posting_date": ("<=", last_year_closing), "company": self.company} + "GL Entry", + {"posting_date": ("<=", last_year_closing), "company": self.company, "is_cancelled": 0}, ): return From 4ba67fb3ec9668dc743e24aad43ed17f286f48de Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 21 Mar 2024 10:29:56 +0530 Subject: [PATCH 57/65] refactor: config changes in Transaction Deletion Record --- .../transaction_deletion_record.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index e03e1695e0e..b9f911dbe8c 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -135,13 +135,16 @@ "default": "0", "fieldname": "process_in_single_transaction", "fieldtype": "Check", - "label": "Process in Single Transaction" + "hidden": 1, + "label": "Process in Single Transaction", + "no_copy": 1, + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-03-20 14:58:15.086360", + "modified": "2024-03-21 10:29:19.456413", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", From cbcc47b5c4ae190f3951a772bb88f81acfa8fc3a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:57:22 +0100 Subject: [PATCH 58/65] refactor(Item Price): validate dates --- erpnext/stock/doctype/item_price/item_price.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index de2add64ef7..25a28b436ff 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -7,7 +7,6 @@ from frappe import _, bold from frappe.model.document import Document from frappe.query_builder import Criterion from frappe.query_builder.functions import Cast_ -from frappe.utils import getdate class ItemPriceDuplicateItem(frappe.ValidationError): @@ -46,7 +45,7 @@ class ItemPrice(Document): def validate(self): self.validate_item() - self.validate_dates() + self.validate_from_to_dates("valid_from", "valid_upto") self.update_price_list_details() self.update_item_details() self.check_duplicates() @@ -56,11 +55,6 @@ class ItemPrice(Document): if not frappe.db.exists("Item", self.item_code): frappe.throw(_("Item {0} not found.").format(self.item_code)) - def validate_dates(self): - if self.valid_from and self.valid_upto: - if getdate(self.valid_from) > getdate(self.valid_upto): - frappe.throw(_("Valid From Date must be lesser than Valid Up To Date.")) - def update_price_list_details(self): if self.price_list: price_list_details = frappe.db.get_value( From ccb51ded957fb4fd16f4dd7ce7c7e285f6ef1a81 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 21 Mar 2024 20:56:52 +0530 Subject: [PATCH 59/65] test: buying amt calculation if DN and SI differ in qty --- .../report/gross_profit/test_gross_profit.py | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py index 82fe1a0ba12..aa820aa4c73 100644 --- a/erpnext/accounts/report/gross_profit/test_gross_profit.py +++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py @@ -460,3 +460,95 @@ class TestGrossProfit(FrappeTestCase): } gp_entry = [x for x in data if x.parent_invoice == sinv.name] self.assertDictContainsSubset(expected_entry, gp_entry[0]) + + def test_different_rates_in_si_and_dn(self): + from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order + + """ + Test gp calculation when invoice and delivery note differ in qty and aren't connected + SO -- INV + | + DN + """ + se = make_stock_entry( + company=self.company, + item_code=self.item, + target=self.warehouse, + qty=3, + basic_rate=700, + do_not_submit=True, + ) + item = se.items[0] + se.append( + "items", + { + "item_code": item.item_code, + "s_warehouse": item.s_warehouse, + "t_warehouse": item.t_warehouse, + "qty": 10, + "basic_rate": 700, + "conversion_factor": item.conversion_factor or 1.0, + "transfer_qty": flt(item.qty) * (flt(item.conversion_factor) or 1.0), + "serial_no": item.serial_no, + "batch_no": item.batch_no, + "cost_center": item.cost_center, + "expense_account": item.expense_account, + }, + ) + se = se.save().submit() + + so = make_sales_order( + customer=self.customer, + company=self.company, + warehouse=self.warehouse, + item=self.item, + rate=800, + qty=10, + do_not_save=False, + do_not_submit=False, + ) + + from erpnext.selling.doctype.sales_order.sales_order import ( + make_delivery_note, + make_sales_invoice, + ) + + dn1 = make_delivery_note(so.name) + dn1.items[0].qty = 4 + dn1.items[0].rate = 800 + dn1.save().submit() + + dn2 = make_delivery_note(so.name) + dn2.items[0].qty = 6 + dn2.items[0].rate = 800 + dn2.save().submit() + + sinv = make_sales_invoice(so.name) + sinv.items[0].qty = 4 + sinv.items[0].rate = 800 + sinv.save().submit() + + filters = frappe._dict( + company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice" + ) + + columns, data = execute(filters=filters) + expected_entry = { + "parent_invoice": sinv.name, + "currency": "INR", + "sales_invoice": self.item, + "customer": self.customer, + "posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()), + "item_code": self.item, + "item_name": self.item, + "warehouse": "Stores - _GP", + "qty": 4.0, + "avg._selling_rate": 800.0, + "valuation_rate": 700.0, + "selling_amount": 3200.0, + "buying_amount": 2800.0, + "gross_profit": 400.0, + "gross_profit_%": 12.5, + } + gp_entry = [x for x in data if x.parent_invoice == sinv.name] + self.assertDictContainsSubset(expected_entry, gp_entry[0]) From d893a465d79b2e301b49b093f05d21d9eb196c9d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 21 Mar 2024 21:04:03 +0530 Subject: [PATCH 60/65] fix: rate not fetching from the item price --- erpnext/stock/get_item_details.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 12049f12dd1..c6f8d62f16e 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -825,7 +825,6 @@ def get_price_list_rate(args, item_doc, out=None): ): if args.price_list and args.rate: insert_item_price(args) - return out out.price_list_rate = ( flt(price_list_rate) * flt(args.plc_conversion_rate) / flt(args.conversion_rate) From 1cd38c860a5da2ab33d4da0a5f29bf95c788e59a Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Fri, 22 Mar 2024 08:47:03 +0530 Subject: [PATCH 61/65] perf: add in some indexes (#40590) `Sales Invoice Item.purchase_order` `Delivery Note Item.purchase_order` Signed-off-by: Akhil Narang --- .../selling/doctype/sales_order_item/sales_order_item.json | 5 +++-- .../stock/doctype/delivery_note_item/delivery_note_item.json | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 9599980418f..d451768eaab 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -834,7 +834,8 @@ "label": "Purchase Order", "options": "Purchase Order", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "column_break_89", @@ -909,7 +910,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2024-01-25 14:24:00.330219", + "modified": "2024-03-21 18:15:56.625005", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index 247672fe126..b8164b25753 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -796,7 +796,8 @@ "label": "Purchase Order", "options": "Purchase Order", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "column_break_82", @@ -912,7 +913,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-02-04 14:10:31.750340", + "modified": "2024-03-21 18:15:07.603672", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", From f30e5eab111e536000020924d608a07b7a5f0931 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 22 Mar 2024 09:55:47 +0530 Subject: [PATCH 62/65] fix: query 'Selling Settings' --- erpnext/selling/doctype/sales_order/sales_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 8896e2960d3..74fa4c7b281 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -756,7 +756,7 @@ def get_list_context(context=None): @frappe.whitelist() def is_enable_cutoff_date_on_bulk_delivery_note_creation(): return frappe.db.get_single_value( - "Accounts Settings", "enable_cutoff_date_on_bulk_delivery_note_creation" + "Selling Settings", "enable_cutoff_date_on_bulk_delivery_note_creation" ) From 6821baa850e4f820bdd274b5113a34e3fea3daba Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 22 Mar 2024 15:03:38 +0530 Subject: [PATCH 63/65] fix: rate reset to zero --- erpnext/stock/get_item_details.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index c6f8d62f16e..ebbc18d1bdc 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -826,6 +826,9 @@ def get_price_list_rate(args, item_doc, out=None): if args.price_list and args.rate: insert_item_price(args) + if not price_list_rate: + return out + out.price_list_rate = ( flt(price_list_rate) * flt(args.plc_conversion_rate) / flt(args.conversion_rate) ) From a88bf8419e7c9987d81b7d0c1d05f45be26d65cc Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 22 Mar 2024 16:11:05 +0530 Subject: [PATCH 64/65] refactor: reset flag for old records --- erpnext/patches.txt | 2 +- erpnext/patches/v14_0/update_flag_for_return_invoices.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 15dfc368915..7c2c439546f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -356,7 +356,7 @@ erpnext.patches.v14_0.update_total_asset_cost_field erpnext.patches.v15_0.create_advance_payment_status erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool -erpnext.patches.v14_0.update_flag_for_return_invoices +erpnext.patches.v14_0.update_flag_for_return_invoices #2024-03-22 # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 diff --git a/erpnext/patches/v14_0/update_flag_for_return_invoices.py b/erpnext/patches/v14_0/update_flag_for_return_invoices.py index feb43beacf8..bea99575425 100644 --- a/erpnext/patches/v14_0/update_flag_for_return_invoices.py +++ b/erpnext/patches/v14_0/update_flag_for_return_invoices.py @@ -12,6 +12,10 @@ def execute(): creation_date = "2024-01-25" si = qb.DocType("Sales Invoice") + + # unset flag, as migration would have set it for all records, as the field was introduced with default '1' + qb.update(si).set(si.update_outstanding_for_self, False).run() + if cr_notes := ( qb.from_(si) .select(si.name) @@ -37,6 +41,10 @@ def execute(): ).run() pi = qb.DocType("Purchase Invoice") + + # unset flag, as migration would have set it for all records, as the field was introduced with default '1' + qb.update(pi).set(pi.update_outstanding_for_self, False).run() + if dr_notes := ( qb.from_(pi) .select(pi.name) From fdcdc8a56ea8892f197dd2a45b175d20137953f6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 22 Mar 2024 17:50:51 +0530 Subject: [PATCH 65/65] refactor: hide on print formats --- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 1d8983e8eef..ab4152b56a5 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -2180,7 +2180,8 @@ "fieldname": "update_outstanding_for_self", "fieldtype": "Check", "label": "Update Outstanding for Self", - "no_copy": 1 + "no_copy": 1, + "print_hide": 1 } ], "icon": "fa fa-file-text", @@ -2193,7 +2194,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2024-03-20 16:02:52.237732", + "modified": "2024-03-22 17:50:34.395602", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice",