fix(regional): survive a failed invoice during Import Supplier Invoice on Postgres

create_purchase_invoice caught its own failure and then ran frappe.db.set_value + log_error in the SAME transaction. On Postgres a failed insert/save aborts the whole transaction, so the error-marking died with InFailedSqlTransaction and the failure cascaded through prepare_data_for_import's per-file loop, killing the entire import; MariaDB recovers per-statement and continues.

Let create_purchase_invoice raise, and wrap each call in prepare_data_for_import in frappe.db.savepoint + rollback(save_point=...). On failure the savepoint rollback un-poisons the transaction, the error is logged, and the per-file status is set to Error and committed (self.db_set(commit=True), matching the existing process_file_data status commit) so an interrupted import durably reflects Error instead of staying at the already-committed Processing File Data; the loop then continues to the next file. The savepoint is taken after create_supplier/create_address so those are preserved exactly as before.

Behaviour change (MariaDB): a failed invoice's partially-created draft Purchase Invoice is now rolled back on BOTH engines instead of being left as an orphan draft on MariaDB. Deliberate and more correct - a failed import should not leave a partial invoice; release-note worthy.
This commit is contained in:
Mihir Kandoi
2026-06-29 21:33:15 +05:30
parent 6f97c7199c
commit 65539d44b8

View File

@@ -95,20 +95,31 @@ class ImportSupplierInvoice(Document):
supplier_name = create_supplier(self.supplier_group, supp_dict)
create_address(supplier_name, supp_dict)
pi_name = create_purchase_invoice(supplier_name, file_name, invoices_args, self.name)
self.file_count += 1
if pi_name:
self.purchase_invoices_count += 1
file_doc = frappe.new_doc("File")
file_doc.file_name = file_name
file_doc.attached_to_doctype = "Purchase Invoice"
file_doc.attached_to_name = pi_name
file_doc.content = encoded_content
file_doc.decode = False
file_doc.is_private = False
file_doc.insert(ignore_permissions=True)
frappe.db.savepoint("import_invoice")
try:
pi_name = create_purchase_invoice(supplier_name, file_name, invoices_args, self.name)
except Exception:
frappe.db.rollback(save_point="import_invoice")
frappe.log_error(
"Unable to create Purchase Invoice",
reference_doctype=self.doctype,
reference_name=self.name,
)
self.db_set("status", "Error", commit=True)
continue
self.purchase_invoices_count += 1
file_doc = frappe.new_doc("File")
file_doc.file_name = file_name
file_doc.attached_to_doctype = "Purchase Invoice"
file_doc.attached_to_name = pi_name
file_doc.content = encoded_content
file_doc.decode = False
file_doc.is_private = False
file_doc.insert(ignore_permissions=True)
def prepare_items_for_invoice(self, file_content, invoices_args):
qty = 1
@@ -374,41 +385,36 @@ def create_purchase_invoice(supplier_name, file_name, args, name):
}
)
try:
pi.set_missing_values()
pi.insert(ignore_mandatory=True)
pi.set_missing_values()
pi.insert(ignore_mandatory=True)
# if discount exists in file, apply any discount on grand total
if args.total_discount > 0:
pi.apply_discount_on = "Grand Total"
pi.discount_amount = args.total_discount
pi.save()
# adjust payment amount to match with grand total calculated
calc_total = 0
adj = 0
for term in args.terms:
calc_total += flt(term["payment_amount"])
if flt(calc_total - flt(pi.grand_total)) != 0:
adj = calc_total - flt(pi.grand_total)
pi.payment_schedule = []
for term in args.terms:
pi.append(
"payment_schedule",
{
"mode_of_payment_code": term["mode_of_payment_code"],
"bank_account_iban": term["bank_account_iban"],
"due_date": term["due_date"],
"payment_amount": flt(term["payment_amount"]) - adj,
},
)
adj = 0
pi.imported_grand_total = calc_total
# if discount exists in file, apply any discount on grand total
if args.total_discount > 0:
pi.apply_discount_on = "Grand Total"
pi.discount_amount = args.total_discount
pi.save()
return pi.name
except Exception:
frappe.db.set_value("Import Supplier Invoice", name, "status", "Error")
pi.log_error("Unable to create Puchase Invoice")
return None
# adjust payment amount to match with grand total calculated
calc_total = 0
adj = 0
for term in args.terms:
calc_total += flt(term["payment_amount"])
if flt(calc_total - flt(pi.grand_total)) != 0:
adj = calc_total - flt(pi.grand_total)
pi.payment_schedule = []
for term in args.terms:
pi.append(
"payment_schedule",
{
"mode_of_payment_code": term["mode_of_payment_code"],
"bank_account_iban": term["bank_account_iban"],
"due_date": term["due_date"],
"payment_amount": flt(term["payment_amount"]) - adj,
},
)
adj = 0
pi.imported_grand_total = calc_total
pi.save()
return pi.name
def get_country(code):