fix(master-data): import shipped Items ourselves, resolving the Item Group root by name

fixtures/item.json was imported blindly by Frappe's import_fixtures on every
migrate and hard-failed (skipping the whole file) on sites whose Item Group
tree root is not literally "All Item Groups" — e.g. the Azerbaijani-localized
"Bütün Element Qrupları" — so the fuel Items never got created and the reports
relying on them came up empty.

Move the file to master_data/ and import it via a new sync_items() in
after_migrate (after sync_item_groups). It resolves the root Item Group by
either known name (falling back to the structural root), remaps the shipped
root reference to whatever this site calls its root, and inserts only missing
items — existing items and their report data are never touched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ali 2026-07-21 09:43:43 +00:00
parent 5c96fe7836
commit 9b1f2b79e0
3 changed files with 83 additions and 0 deletions

View File

@ -21,6 +21,7 @@ fixtures = [
after_migrate = [ after_migrate = [
"taxes_az.master_data.sync.sync_main_type_of_activity", "taxes_az.master_data.sync.sync_main_type_of_activity",
"taxes_az.master_data.sync.sync_item_groups", "taxes_az.master_data.sync.sync_item_groups",
"taxes_az.master_data.sync.sync_items",
"taxes_az.setup_accounts.check_and_create_accounts" "taxes_az.setup_accounts.check_and_create_accounts"
] ]

View File

@ -205,6 +205,88 @@ def sync_main_type_of_activity() -> dict:
return sync_master_data("Main type of activity", path, key_field="name") return sync_master_data("Main type of activity", path, key_field="name")
# Item Group names that all mean "the tree root". The shipped item.json refers
# to the root by ERPNext's English name, but localized installs rename it.
_ROOT_ALIASES = ("All Item Groups", "Bütün Element Qrupları")
def _resolve_item_group_root() -> str | None:
"""Find this site's Item Group tree root by any of its known names.
ERPNext ships the root as "All Item Groups", but localized installs rename
it (e.g. the Azerbaijani "Bütün Element Qrupları"). Try the known names in
order, then fall back to the structural root (a group with no parent).
"""
for candidate in _ROOT_ALIASES:
if frappe.db.exists("Item Group", candidate):
return candidate
roots = frappe.get_all(
"Item Group",
filters={"is_group": 1, "parent_item_group": ("in", ("", None))},
pluck="name",
limit=1,
)
return roots[0] if roots else None
def sync_items() -> dict:
"""Create the fuel Items the app ships, resolving the Item Group root name.
These records used to live in ``fixtures/item.json``, which Frappe's
``import_fixtures`` imports blindly on every migrate. That import hard-fails
and skips the *whole* file on sites whose Item Group root is not literally
named "All Item Groups" (e.g. the localized "Bütün Element Qrupları"), so the
items never get created and the reports that rely on them come up empty.
We import the same records ourselves instead: the root reference is remapped
to whatever this site actually calls its root, and only *missing* items are
inserted existing items (and any report data on them) are never touched.
Must run after :func:`sync_item_groups` so the "Yanacaq növləri" group exists.
"""
path = frappe.get_app_path("taxes_az", "master_data", "item.json")
if not os.path.exists(path):
frappe.logger().warning(f"[taxes_az] master data file not found: {path}")
return {"skipped": True}
with open(path, encoding="utf-8") as f:
records = json.load(f)
root = _resolve_item_group_root()
inserted = skipped = 0
for rec in records:
name = rec.get("name") or rec.get("item_code")
if not name or frappe.db.exists("Item", name):
skipped += 1
continue
group = rec.get("item_group")
# Remap the ERPNext root name to whatever this site calls its root.
if group in _ROOT_ALIASES and root:
group = root
if not group or not frappe.db.exists("Item Group", group):
frappe.logger().warning(
f"[taxes_az] Item '{name}': Item Group '{group}' not found — skipped"
)
skipped += 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)
inserted += 1
if inserted:
frappe.db.commit()
print(f"[taxes_az] Item: +{inserted} (skipped {skipped})")
return {"doctype": "Item", "inserted": inserted, "skipped": skipped}
def sync_item_groups() -> dict: def sync_item_groups() -> dict:
"""Sync the Azerbaijan tax-specific Item Groups. """Sync the Azerbaijan tax-specific Item Groups.