diff --git a/taxes_az/hooks.py b/taxes_az/hooks.py index 66c8a73..d370abc 100644 --- a/taxes_az/hooks.py +++ b/taxes_az/hooks.py @@ -21,6 +21,7 @@ fixtures = [ after_migrate = [ "taxes_az.master_data.sync.sync_main_type_of_activity", "taxes_az.master_data.sync.sync_item_groups", + "taxes_az.master_data.sync.sync_items", "taxes_az.setup_accounts.check_and_create_accounts" ] diff --git a/taxes_az/fixtures/item.json b/taxes_az/master_data/item.json similarity index 100% rename from taxes_az/fixtures/item.json rename to taxes_az/master_data/item.json diff --git a/taxes_az/master_data/sync.py b/taxes_az/master_data/sync.py index 2ee8c02..9db84ed 100644 --- a/taxes_az/master_data/sync.py +++ b/taxes_az/master_data/sync.py @@ -205,6 +205,88 @@ def sync_main_type_of_activity() -> dict: 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: """Sync the Azerbaijan tax-specific Item Groups.