diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 83ab5badb50..ab4ee51eb6f 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -582,6 +582,7 @@ def make_gl_entries( frappe.db.commit() except Exception as e: if frappe.in_test: + frappe.db.rollback() doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}") raise e else: diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py index c2bac737a78..2f88410fc26 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py @@ -47,6 +47,7 @@ def create_bank_entries(columns: str, data: str | list, bank_account: str): for key, value in header_map.items(): fields.update({key: d[int(value) - 1]}) + frappe.db.savepoint("bank_entry") try: bank_transaction = frappe.get_doc({"doctype": "Bank Transaction"}) bank_transaction.update(fields) @@ -56,7 +57,8 @@ def create_bank_entries(columns: str, data: str | list, bank_account: str): bank_transaction.submit() success += 1 except Exception: - bank_transaction.log_error("Bank entry creation failed") + frappe.db.rollback(save_point="bank_entry") + frappe.log_error(title="Bank entry creation failed") errors += 1 return {"success": success, "errors": errors} diff --git a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py index a219e21526d..cd574eafaa3 100644 --- a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py +++ b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py @@ -65,6 +65,7 @@ def start_merge(docname): total = len(ledger_merge.merge_accounts) for row in ledger_merge.merge_accounts: if not row.merged: + frappe.db.savepoint("ledger_merge_row") try: merge_account( row.account, @@ -79,8 +80,7 @@ def start_merge(docname): {"ledger_merge": ledger_merge.name, "current": successful_merges, "total": total}, ) except Exception: - if not frappe.in_test: - frappe.db.rollback() + frappe.db.rollback(save_point="ledger_merge_row") ledger_merge.log_error("Ledger merge failed") finally: if successful_merges == total: diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 3b6cb7920b9..b4005436ec0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -514,10 +514,12 @@ class PaymentEntry(AccountsController): invoice_names.add((ref.reference_doctype, ref.reference_name)) for doctype, name in invoice_names: + frappe.db.savepoint("subscription_update") try: doc = frappe.get_doc(doctype, name) doc.refresh_subscription_status() except Exception: + frappe.db.rollback(save_point="subscription_update") frappe.log_error(_("Failed to update subscription status for {0} {1}").format(doctype, name)) def set_missing_values(self): diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 762ed796056..a954d1c9981 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -187,6 +187,7 @@ def make_depreciation_entry( for d in depr_schedule_doc.get("depreciation_schedule")[ (sch_start_idx or 0) : (sch_end_idx or len(depr_schedule_doc.get("depreciation_schedule"))) ]: + frappe.db.savepoint("depr_entry") try: _make_journal_entry_for_depreciation( depr_schedule_doc, @@ -202,6 +203,7 @@ def make_depreciation_entry( accounting_dimensions, ) except Exception as e: + frappe.db.rollback(save_point="depr_entry") depr_posting_error = e asset.reload() diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py index 7a1db02082e..8c835a29912 100644 --- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py @@ -405,16 +405,10 @@ def get_default_scorecard_standing(): def make_default_records(): install_variable_docs = get_default_scorecard_variables() for d in install_variable_docs: - try: - d["doctype"] = "Supplier Scorecard Variable" - frappe.get_doc(d).insert() - except frappe.NameError: - pass + d["doctype"] = "Supplier Scorecard Variable" + frappe.get_doc(d).insert(ignore_if_duplicate=True) install_standing_docs = get_default_scorecard_standing() for d in install_standing_docs: - try: - d["doctype"] = "Supplier Scorecard Standing" - frappe.get_doc(d).insert() - except frappe.NameError: - pass + d["doctype"] = "Supplier Scorecard Standing" + frappe.get_doc(d).insert(ignore_if_duplicate=True) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index db1227e29b2..9cdc0a07cd5 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -7,7 +7,7 @@ import frappe from frappe import _, bold from frappe.model.meta import get_field_precision from frappe.query_builder import DocType -from frappe.query_builder.functions import Abs, Sum +from frappe.query_builder.functions import Abs, NullIf, Sum from frappe.utils import cint, flt, format_datetime, get_datetime import erpnext @@ -766,7 +766,7 @@ def get_rate_for_return( select_field = "incoming_rate" else: StockLedgerEntry = frappe.qb.DocType("Stock Ledger Entry") - select_field = Abs(StockLedgerEntry.stock_value_difference / StockLedgerEntry.actual_qty) + select_field = Abs(StockLedgerEntry.stock_value_difference / NullIf(StockLedgerEntry.actual_qty, 0)) item_details = frappe.get_cached_value("Item", item_code, ["has_batch_no", "has_expiry_date"], as_dict=1) set_zero_rate_for_expired_batch = frappe.db.get_single_value( diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.py b/erpnext/crm/doctype/email_campaign/email_campaign.py index 4454ede5310..bf0379b8e32 100644 --- a/erpnext/crm/doctype/email_campaign/email_campaign.py +++ b/erpnext/crm/doctype/email_campaign/email_campaign.py @@ -174,6 +174,7 @@ def send_mail(entry, email_campaign): subject = frappe.render_template(email_template.get("subject"), context) content = frappe.render_template(email_template.response_, context) + frappe.db.savepoint("email_campaign_send") try: comm = make( doctype="Email Campaign", @@ -197,6 +198,7 @@ def send_mail(entry, email_campaign): queue_separately=True, ) except Exception: + frappe.db.rollback(save_point="email_campaign_send") frappe.log_error(title="Email Campaign Failed.") return comm diff --git a/erpnext/crm/frappe_crm_api.py b/erpnext/crm/frappe_crm_api.py index 0230dda7925..a13109181a1 100644 --- a/erpnext/crm/frappe_crm_api.py +++ b/erpnext/crm/frappe_crm_api.py @@ -26,6 +26,7 @@ def create_prospect_against_crm_deal(): prospect.insert() prospect_name = prospect.name except Exception: + frappe.db.rollback() frappe.log_error( frappe.get_traceback(), f"Error while creating prospect against CRM Deal: {frappe.form_dict.get('crm_deal_id')}", @@ -70,6 +71,7 @@ def create_address(doctype, docname, address): if not address: return address = frappe.parse_json(address) + frappe.db.savepoint("crm_create_address") try: _address = frappe.db.exists("Address", address.get("name")) if not _address: @@ -97,6 +99,7 @@ def create_address(doctype, docname, address): address.save(ignore_permissions=True) return address.name except Exception: + frappe.db.rollback(save_point="crm_create_address") frappe.log_error(frappe.get_traceback(), f"Error while creating address for {docname}") @@ -157,6 +160,7 @@ def create_customer(customer_data: dict | None = None): create_address("Customer", customer_name, customer_data.get("address")) return customer_name except Exception: + frappe.db.rollback() frappe.log_error(frappe.get_traceback(), "Error while creating customer against Frappe CRM Deal") pass diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index a4113dfcab4..25d5a861a4b 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -69,6 +69,7 @@ def add_institution(token: str, response: str | dict): ) bank.insert() except Exception: + frappe.db.rollback() frappe.log_error("Plaid Link Error") else: bank = frappe.get_doc("Bank", response["institution"]["name"]) @@ -154,6 +155,7 @@ def add_bank_accounts(response: str | dict, bank: str | dict, company: str): ) else: + frappe.db.savepoint("plaid_update_account") try: existing_account = frappe.get_doc("Bank Account", existing_bank_account) existing_account.update( @@ -169,6 +171,7 @@ def add_bank_accounts(response: str | dict, bank: str | dict, company: str): existing_account.save() result.append(existing_bank_account) except Exception: + frappe.db.rollback(save_point="plaid_update_account") frappe.log_error("Plaid Link Error") frappe.throw( _("There was an error updating Bank Account {0} while linking with Plaid.").format( diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py index 531fe9826f9..84f10f1c1ee 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py @@ -313,6 +313,7 @@ class BOMCreator(Document): frappe.msgprint(_("BOMs created successfully")) except Exception: + frappe.db.rollback() traceback = frappe.get_traceback(with_context=True) self.db_set( { diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 59064847173..5bd0ee104f0 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -856,6 +856,7 @@ def install_country_fixtures(company, country): except ImportError: pass except Exception: + frappe.db.rollback() frappe.log_error("Unable to set country fixtures") frappe.throw( _("Failed to setup defaults for country {0}. Please contact support.").format( diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 5de54ddf53f..d3b7e2a03fd 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -120,6 +120,7 @@ def from_detailed_data(company_name, data): def update_regional_tax_settings(country, company): path = frappe.get_app_path("erpnext", "regional", frappe.scrub(country)) if os.path.exists(path.encode("utf-8")): + frappe.db.savepoint("regional_tax_settings") try: module_name = f"erpnext.regional.{frappe.scrub(country)}.setup.update_regional_tax_settings" frappe.get_attr(module_name)(country, company) @@ -127,6 +128,7 @@ def update_regional_tax_settings(country, company): pass except Exception: # Log error and ignore if failed to setup regional tax settings + frappe.db.rollback(save_point="regional_tax_settings") frappe.log_error("Unable to setup regional tax settings") diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index 2fbddcec948..3e0decd8f16 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -95,7 +95,11 @@ def get_exchange_rate( # cksgb 19/09/2016: get last entry in Currency Exchange with from_currency and to_currency. entries = frappe.get_all( - "Currency Exchange", fields=["exchange_rate"], filters=filters, order_by="date desc", limit=1 + "Currency Exchange", + fields=["exchange_rate"], + filters=filters, + order_by="date desc, name desc", + limit=1, ) if entries: return flt(entries[0].exchange_rate) diff --git a/erpnext/stock/doctype/stock_closing_entry/stock_closing_entry.py b/erpnext/stock/doctype/stock_closing_entry/stock_closing_entry.py index 106983efc9d..00a3b0204c4 100644 --- a/erpnext/stock/doctype/stock_closing_entry/stock_closing_entry.py +++ b/erpnext/stock/doctype/stock_closing_entry/stock_closing_entry.py @@ -152,6 +152,7 @@ def prepare_closing_stock_balance(name): doc.create_stock_closing_balance_entries() doc.db_set("status", "Completed") except Exception: + frappe.db.rollback() doc.db_set("status", "Failed") doc.log_error(title="Stock Closing Entry Failed") diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py index 5c668fa8a8d..dc6168f52ac 100644 --- a/erpnext/stock/reorder_item.py +++ b/erpnext/stock/reorder_item.py @@ -216,6 +216,7 @@ def create_material_request(material_requests): company_wise_mr = frappe._dict({}) for request_type in material_requests: for company in material_requests[request_type]: + frappe.db.savepoint("reorder_mr") try: items = material_requests[request_type][company] if not items: @@ -287,8 +288,9 @@ def create_material_request(material_requests): company_wise_mr.setdefault(company, []).append(mr) except Exception as exception: + frappe.db.rollback(save_point="reorder_mr") exceptions_list.append(exception) - mr.log_error("Unable to create material request") + frappe.log_error(title="Unable to create material request") if company_wise_mr: if getattr(frappe.local, "reorder_email_notify", None) is None: diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py index e295c0cb659..28308609f2f 100644 --- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py +++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py @@ -202,6 +202,7 @@ def create_reposting_entries(rows: str | list, company: str): for key, sle in item_wh.items(): item_code, warehouse = key + frappe.db.savepoint("repost_value_comparison") try: doc = frappe.get_doc( { @@ -219,7 +220,7 @@ def create_reposting_entries(rows: str | list, company: str): entries.append(get_link_to_form("Repost Item Valuation", doc.name)) except frappe.DuplicateEntryError: - pass + frappe.db.rollback(save_point="repost_value_comparison") if entries: entries = ", ".join(entries) diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py index edfcde2de2c..137feb5a34c 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py @@ -304,6 +304,7 @@ def create_reposting_entries(rows: str | list, item_code: str | None = None, war for row in rows: row = frappe._dict(row) + frappe.db.savepoint("repost_invariant_check") try: doc = frappe.get_doc( { @@ -320,6 +321,7 @@ def create_reposting_entries(rows: str | list, item_code: str | None = None, war entries.append(get_link_to_form("Repost Item Valuation", doc.name)) except frappe.DuplicateEntryError: + frappe.db.rollback(save_point="repost_invariant_check") continue if entries: diff --git a/erpnext/telephony/doctype/call_log/call_log.py b/erpnext/telephony/doctype/call_log/call_log.py index 9c00e5aeb6d..c932eb515db 100644 --- a/erpnext/telephony/doctype/call_log/call_log.py +++ b/erpnext/telephony/doctype/call_log/call_log.py @@ -163,6 +163,7 @@ def link_existing_conversations(doc, state): return if doc.doctype != "Contact": return + frappe.db.savepoint("link_call_logs") try: numbers = [d.phone for d in doc.phone_nos] @@ -196,6 +197,7 @@ def link_existing_conversations(doc, state): if not frappe.in_test: frappe.db.commit() except Exception: + frappe.db.rollback(save_point="link_call_logs") frappe.log_error(title=_("Error during caller information update"))