diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py index 553f8d8d9d5..f376047c37b 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py @@ -107,6 +107,9 @@ def auto_create_fiscal_year(): ) for d in fiscal_year: + # savepoint so a duplicate-year INSERT (Fiscal Year autoname=field:year) that aborts the + # statement doesn't poison the whole scheduler transaction on Postgres and kill the next iteration + frappe.db.savepoint("auto_create_fiscal_year") try: current_fy = frappe.get_doc("Fiscal Year", d[0]) @@ -127,7 +130,7 @@ def auto_create_fiscal_year(): new_fy.insert(ignore_permissions=True) except frappe.NameError: - pass + frappe.db.rollback(save_point="auto_create_fiscal_year") def get_from_and_to_date(fiscal_year): diff --git a/erpnext/crm/frappe_crm_api.py b/erpnext/crm/frappe_crm_api.py index a13109181a1..bbb0b8e5215 100644 --- a/erpnext/crm/frappe_crm_api.py +++ b/erpnext/crm/frappe_crm_api.py @@ -154,15 +154,26 @@ def create_customer(customer_data: dict | None = None): customer.set(field, customer_data.get(field)) customer.insert(ignore_permissions=True) customer_name = customer.name - - contacts = frappe.parse_json(customer_data.get("contacts")) - create_contacts(contacts, customer_name, "Customer", customer_name) - 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 + return + + # Link contacts/address under a savepoint so a failure here does NOT discard the Customer just + # created (a full rollback would; MariaDB kept it pre-migration). Linking is best-effort. + frappe.db.savepoint("crm_customer_links") + try: + contacts = frappe.parse_json(customer_data.get("contacts")) + create_contacts(contacts, customer_name, "Customer", customer_name) + create_address("Customer", customer_name, customer_data.get("address")) + except Exception: + frappe.db.rollback(save_point="crm_customer_links") + frappe.log_error(frappe.get_traceback(), "Error while linking contacts/address to new Customer") + # keep the Customer, but preserve the pre-existing contract of returning None on a linking failure + # so CRM callers still see the failure signal + return + + return customer_name def validate_frappe_crm_sync(): diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 25d5a861a4b..bcab065bba4 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -215,7 +215,14 @@ def sync_transactions(bank, bank_account): result = [] if transactions: for transaction in reversed(transactions): - result += new_bank_transaction(transaction) + # per-transaction savepoint: a failed insert/submit must not discard the Bank + # Transactions already synced this run (MariaDB keeps them) nor poison the txn on Postgres + frappe.db.savepoint("plaid_sync_txn") + try: + result += new_bank_transaction(transaction) + except Exception: + frappe.db.rollback(save_point="plaid_sync_txn") + raise if result: last_transaction_date = frappe.db.get_value("Bank Transaction", result.pop(), "date")