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()
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:

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():
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}

View File

@@ -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:

View File

@@ -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):

View File

@@ -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()

View File

@@ -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)

View File

@@ -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(

View File

@@ -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

View File

@@ -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

View File

@@ -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(

View File

@@ -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(
{

View File

@@ -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(

View File

@@ -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")

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.
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)

View File

@@ -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")

View File

@@ -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:

View File

@@ -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)

View File

@@ -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:

View File

@@ -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"))