fix(master-data): never let Item master-data abort migrate

On a bare/half-provisioned site (e.g. a distrobuilder base image where standard
UOMs like 'Litre' are not loaded yet), Item insert raised LinkValidationError
and the unhandled exception aborted the whole `bench migrate`, failing the
image build.

Isolate each Item insert in a savepoint and catch failures: roll back just that
item, log a visible SKIPPED line, and continue. Items are only inserted when
missing, so a later migrate on a fully provisioned site still creates them.
Adds a 'failed' counter to the summary line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ali 2026-07-21 15:28:55 +00:00
parent 82ae160123
commit ae1d0a9782
1 changed files with 25 additions and 9 deletions

View File

@ -263,7 +263,7 @@ def sync_items() -> dict:
root = _resolve_item_group_root()
print(f"[taxes_az] Item: resolved tree root = {root!r}, {len(records)} shipped item(s)")
inserted = existed = missing_group = 0
inserted = existed = missing_group = failed = 0
for rec in records:
name = rec.get("name") or rec.get("item_code")
@ -282,13 +282,28 @@ def sync_items() -> dict:
missing_group += 1
continue
doc = frappe.new_doc("Item")
doc.update(rec)
doc.item_group = group
doc.name = name
doc.flags.name_set = True
doc.flags.ignore_permissions = True
doc.insert(ignore_if_duplicate=True)
# Isolate each insert in a savepoint: on a bare/half-provisioned site a
# missing link (e.g. UOM 'Litre') raises LinkValidationError, and an
# unhandled exception here would abort the whole `bench migrate`. Master
# data must never do that — roll back just this item and carry on. The
# item stays missing and a later migrate (once the site is complete)
# will create it, since sync only inserts items that don't exist yet.
frappe.db.savepoint("taxes_az_item")
try:
doc = frappe.new_doc("Item")
doc.update(rec)
doc.item_group = group
doc.name = name
doc.flags.name_set = True
doc.flags.ignore_permissions = True
doc.insert(ignore_if_duplicate=True)
except Exception as e:
frappe.db.rollback(save_point="taxes_az_item")
frappe.clear_last_message()
print(f"[taxes_az] Item '{name}': insert FAILED — SKIPPED ({e})")
failed += 1
continue
frappe.db.release_savepoint("taxes_az_item")
print(f"[taxes_az] Item: created {name!r} ({rec.get('item_name')}) in {group!r}")
inserted += 1
@ -296,13 +311,14 @@ def sync_items() -> dict:
frappe.db.commit()
print(
f"[taxes_az] Item: created {inserted}, already existed {existed}, "
f"skipped (missing group) {missing_group}"
f"skipped (missing group) {missing_group}, failed {failed}"
)
return {
"doctype": "Item",
"inserted": inserted,
"existed": existed,
"missing_group": missing_group,
"failed": failed,
}