Merge pull request #56630 from mihir-kandoi/pg-convergence-fixes

fix: Postgres transaction-abort savepoints + div0/tiebreaker convergence fixes
This commit is contained in:
Mihir Kandoi
2026-06-30 13:40:07 +05:30
committed by GitHub
19 changed files with 44 additions and 18 deletions

View File

@@ -582,6 +582,7 @@ def make_gl_entries(
frappe.db.commit() frappe.db.commit()
except Exception as e: except Exception as e:
if frappe.in_test: if frappe.in_test:
frappe.db.rollback()
doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}") doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}")
raise e raise e
else: else:

View File

@@ -47,6 +47,7 @@ def create_bank_entries(columns: str, data: str | list, bank_account: str):
for key, value in header_map.items(): for key, value in header_map.items():
fields.update({key: d[int(value) - 1]}) fields.update({key: d[int(value) - 1]})
frappe.db.savepoint("bank_entry")
try: try:
bank_transaction = frappe.get_doc({"doctype": "Bank Transaction"}) bank_transaction = frappe.get_doc({"doctype": "Bank Transaction"})
bank_transaction.update(fields) bank_transaction.update(fields)
@@ -56,7 +57,8 @@ def create_bank_entries(columns: str, data: str | list, bank_account: str):
bank_transaction.submit() bank_transaction.submit()
success += 1 success += 1
except Exception: 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 errors += 1
return {"success": success, "errors": errors} return {"success": success, "errors": errors}

View File

@@ -65,6 +65,7 @@ def start_merge(docname):
total = len(ledger_merge.merge_accounts) total = len(ledger_merge.merge_accounts)
for row in ledger_merge.merge_accounts: for row in ledger_merge.merge_accounts:
if not row.merged: if not row.merged:
frappe.db.savepoint("ledger_merge_row")
try: try:
merge_account( merge_account(
row.account, row.account,
@@ -79,8 +80,7 @@ def start_merge(docname):
{"ledger_merge": ledger_merge.name, "current": successful_merges, "total": total}, {"ledger_merge": ledger_merge.name, "current": successful_merges, "total": total},
) )
except Exception: except Exception:
if not frappe.in_test: frappe.db.rollback(save_point="ledger_merge_row")
frappe.db.rollback()
ledger_merge.log_error("Ledger merge failed") ledger_merge.log_error("Ledger merge failed")
finally: finally:
if successful_merges == total: if successful_merges == total:

View File

@@ -514,10 +514,12 @@ class PaymentEntry(AccountsController):
invoice_names.add((ref.reference_doctype, ref.reference_name)) invoice_names.add((ref.reference_doctype, ref.reference_name))
for doctype, name in invoice_names: for doctype, name in invoice_names:
frappe.db.savepoint("subscription_update")
try: try:
doc = frappe.get_doc(doctype, name) doc = frappe.get_doc(doctype, name)
doc.refresh_subscription_status() doc.refresh_subscription_status()
except Exception: except Exception:
frappe.db.rollback(save_point="subscription_update")
frappe.log_error(_("Failed to update subscription status for {0} {1}").format(doctype, name)) frappe.log_error(_("Failed to update subscription status for {0} {1}").format(doctype, name))
def set_missing_values(self): def set_missing_values(self):

View File

@@ -187,6 +187,7 @@ def make_depreciation_entry(
for d in depr_schedule_doc.get("depreciation_schedule")[ 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"))) (sch_start_idx or 0) : (sch_end_idx or len(depr_schedule_doc.get("depreciation_schedule")))
]: ]:
frappe.db.savepoint("depr_entry")
try: try:
_make_journal_entry_for_depreciation( _make_journal_entry_for_depreciation(
depr_schedule_doc, depr_schedule_doc,
@@ -202,6 +203,7 @@ def make_depreciation_entry(
accounting_dimensions, accounting_dimensions,
) )
except Exception as e: except Exception as e:
frappe.db.rollback(save_point="depr_entry")
depr_posting_error = e depr_posting_error = e
asset.reload() asset.reload()

View File

@@ -405,16 +405,10 @@ def get_default_scorecard_standing():
def make_default_records(): def make_default_records():
install_variable_docs = get_default_scorecard_variables() install_variable_docs = get_default_scorecard_variables()
for d in install_variable_docs: for d in install_variable_docs:
try: d["doctype"] = "Supplier Scorecard Variable"
d["doctype"] = "Supplier Scorecard Variable" frappe.get_doc(d).insert(ignore_if_duplicate=True)
frappe.get_doc(d).insert()
except frappe.NameError:
pass
install_standing_docs = get_default_scorecard_standing() install_standing_docs = get_default_scorecard_standing()
for d in install_standing_docs: for d in install_standing_docs:
try: d["doctype"] = "Supplier Scorecard Standing"
d["doctype"] = "Supplier Scorecard Standing" frappe.get_doc(d).insert(ignore_if_duplicate=True)
frappe.get_doc(d).insert()
except frappe.NameError:
pass

View File

@@ -7,7 +7,7 @@ import frappe
from frappe import _, bold from frappe import _, bold
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
from frappe.query_builder import DocType 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 from frappe.utils import cint, flt, format_datetime, get_datetime
import erpnext import erpnext
@@ -766,7 +766,7 @@ def get_rate_for_return(
select_field = "incoming_rate" select_field = "incoming_rate"
else: else:
StockLedgerEntry = frappe.qb.DocType("Stock Ledger Entry") 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) 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( set_zero_rate_for_expired_batch = frappe.db.get_single_value(

View File

@@ -174,6 +174,7 @@ def send_mail(entry, email_campaign):
subject = frappe.render_template(email_template.get("subject"), context) subject = frappe.render_template(email_template.get("subject"), context)
content = frappe.render_template(email_template.response_, context) content = frappe.render_template(email_template.response_, context)
frappe.db.savepoint("email_campaign_send")
try: try:
comm = make( comm = make(
doctype="Email Campaign", doctype="Email Campaign",
@@ -197,6 +198,7 @@ def send_mail(entry, email_campaign):
queue_separately=True, queue_separately=True,
) )
except Exception: except Exception:
frappe.db.rollback(save_point="email_campaign_send")
frappe.log_error(title="Email Campaign Failed.") frappe.log_error(title="Email Campaign Failed.")
return comm return comm

View File

@@ -26,6 +26,7 @@ def create_prospect_against_crm_deal():
prospect.insert() prospect.insert()
prospect_name = prospect.name prospect_name = prospect.name
except Exception: except Exception:
frappe.db.rollback()
frappe.log_error( frappe.log_error(
frappe.get_traceback(), frappe.get_traceback(),
f"Error while creating prospect against CRM Deal: {frappe.form_dict.get('crm_deal_id')}", 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: if not address:
return return
address = frappe.parse_json(address) address = frappe.parse_json(address)
frappe.db.savepoint("crm_create_address")
try: try:
_address = frappe.db.exists("Address", address.get("name")) _address = frappe.db.exists("Address", address.get("name"))
if not _address: if not _address:
@@ -97,6 +99,7 @@ def create_address(doctype, docname, address):
address.save(ignore_permissions=True) address.save(ignore_permissions=True)
return address.name return address.name
except Exception: except Exception:
frappe.db.rollback(save_point="crm_create_address")
frappe.log_error(frappe.get_traceback(), f"Error while creating address for {docname}") 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")) create_address("Customer", customer_name, customer_data.get("address"))
return customer_name return customer_name
except Exception: except Exception:
frappe.db.rollback()
frappe.log_error(frappe.get_traceback(), "Error while creating customer against Frappe CRM Deal") frappe.log_error(frappe.get_traceback(), "Error while creating customer against Frappe CRM Deal")
pass pass

View File

@@ -69,6 +69,7 @@ def add_institution(token: str, response: str | dict):
) )
bank.insert() bank.insert()
except Exception: except Exception:
frappe.db.rollback()
frappe.log_error("Plaid Link Error") frappe.log_error("Plaid Link Error")
else: else:
bank = frappe.get_doc("Bank", response["institution"]["name"]) 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: else:
frappe.db.savepoint("plaid_update_account")
try: try:
existing_account = frappe.get_doc("Bank Account", existing_bank_account) existing_account = frappe.get_doc("Bank Account", existing_bank_account)
existing_account.update( existing_account.update(
@@ -169,6 +171,7 @@ def add_bank_accounts(response: str | dict, bank: str | dict, company: str):
existing_account.save() existing_account.save()
result.append(existing_bank_account) result.append(existing_bank_account)
except Exception: except Exception:
frappe.db.rollback(save_point="plaid_update_account")
frappe.log_error("Plaid Link Error") frappe.log_error("Plaid Link Error")
frappe.throw( frappe.throw(
_("There was an error updating Bank Account {0} while linking with Plaid.").format( _("There was an error updating Bank Account {0} while linking with Plaid.").format(

View File

@@ -313,6 +313,7 @@ class BOMCreator(Document):
frappe.msgprint(_("BOMs created successfully")) frappe.msgprint(_("BOMs created successfully"))
except Exception: except Exception:
frappe.db.rollback()
traceback = frappe.get_traceback(with_context=True) traceback = frappe.get_traceback(with_context=True)
self.db_set( self.db_set(
{ {

View File

@@ -856,6 +856,7 @@ def install_country_fixtures(company, country):
except ImportError: except ImportError:
pass pass
except Exception: except Exception:
frappe.db.rollback()
frappe.log_error("Unable to set country fixtures") frappe.log_error("Unable to set country fixtures")
frappe.throw( frappe.throw(
_("Failed to setup defaults for country {0}. Please contact support.").format( _("Failed to setup defaults for country {0}. Please contact support.").format(

View File

@@ -120,6 +120,7 @@ def from_detailed_data(company_name, data):
def update_regional_tax_settings(country, company): def update_regional_tax_settings(country, company):
path = frappe.get_app_path("erpnext", "regional", frappe.scrub(country)) path = frappe.get_app_path("erpnext", "regional", frappe.scrub(country))
if os.path.exists(path.encode("utf-8")): if os.path.exists(path.encode("utf-8")):
frappe.db.savepoint("regional_tax_settings")
try: try:
module_name = f"erpnext.regional.{frappe.scrub(country)}.setup.update_regional_tax_settings" module_name = f"erpnext.regional.{frappe.scrub(country)}.setup.update_regional_tax_settings"
frappe.get_attr(module_name)(country, company) frappe.get_attr(module_name)(country, company)
@@ -127,6 +128,7 @@ def update_regional_tax_settings(country, company):
pass pass
except Exception: except Exception:
# Log error and ignore if failed to setup regional tax settings # 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") frappe.log_error("Unable to setup regional tax settings")

View File

@@ -95,7 +95,11 @@ def get_exchange_rate(
# cksgb 19/09/2016: get last entry in Currency Exchange with from_currency and to_currency. # cksgb 19/09/2016: get last entry in Currency Exchange with from_currency and to_currency.
entries = frappe.get_all( 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: if entries:
return flt(entries[0].exchange_rate) return flt(entries[0].exchange_rate)

View File

@@ -152,6 +152,7 @@ def prepare_closing_stock_balance(name):
doc.create_stock_closing_balance_entries() doc.create_stock_closing_balance_entries()
doc.db_set("status", "Completed") doc.db_set("status", "Completed")
except Exception: except Exception:
frappe.db.rollback()
doc.db_set("status", "Failed") doc.db_set("status", "Failed")
doc.log_error(title="Stock Closing Entry Failed") doc.log_error(title="Stock Closing Entry Failed")

View File

@@ -216,6 +216,7 @@ def create_material_request(material_requests):
company_wise_mr = frappe._dict({}) company_wise_mr = frappe._dict({})
for request_type in material_requests: for request_type in material_requests:
for company in material_requests[request_type]: for company in material_requests[request_type]:
frappe.db.savepoint("reorder_mr")
try: try:
items = material_requests[request_type][company] items = material_requests[request_type][company]
if not items: if not items:
@@ -287,8 +288,9 @@ def create_material_request(material_requests):
company_wise_mr.setdefault(company, []).append(mr) company_wise_mr.setdefault(company, []).append(mr)
except Exception as exception: except Exception as exception:
frappe.db.rollback(save_point="reorder_mr")
exceptions_list.append(exception) 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 company_wise_mr:
if getattr(frappe.local, "reorder_email_notify", None) is None: if getattr(frappe.local, "reorder_email_notify", None) is None:

View File

@@ -202,6 +202,7 @@ def create_reposting_entries(rows: str | list, company: str):
for key, sle in item_wh.items(): for key, sle in item_wh.items():
item_code, warehouse = key item_code, warehouse = key
frappe.db.savepoint("repost_value_comparison")
try: try:
doc = frappe.get_doc( 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)) entries.append(get_link_to_form("Repost Item Valuation", doc.name))
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass frappe.db.rollback(save_point="repost_value_comparison")
if entries: if entries:
entries = ", ".join(entries) entries = ", ".join(entries)

View File

@@ -304,6 +304,7 @@ def create_reposting_entries(rows: str | list, item_code: str | None = None, war
for row in rows: for row in rows:
row = frappe._dict(row) row = frappe._dict(row)
frappe.db.savepoint("repost_invariant_check")
try: try:
doc = frappe.get_doc( 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)) entries.append(get_link_to_form("Repost Item Valuation", doc.name))
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
frappe.db.rollback(save_point="repost_invariant_check")
continue continue
if entries: if entries:

View File

@@ -163,6 +163,7 @@ def link_existing_conversations(doc, state):
return return
if doc.doctype != "Contact": if doc.doctype != "Contact":
return return
frappe.db.savepoint("link_call_logs")
try: try:
numbers = [d.phone for d in doc.phone_nos] numbers = [d.phone for d in doc.phone_nos]
@@ -196,6 +197,7 @@ def link_existing_conversations(doc, state):
if not frappe.in_test: if not frappe.in_test:
frappe.db.commit() frappe.db.commit()
except Exception: except Exception:
frappe.db.rollback(save_point="link_call_logs")
frappe.log_error(title=_("Error during caller information update")) frappe.log_error(title=_("Error during caller information update"))