From e4ac3e328f69a34635d1fe4375409cb24106783e Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Tue, 21 Jul 2026 09:52:48 +0000 Subject: [PATCH] feat(master-data): resolve Item Group root for groups too, and log results visibly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sync_item_groups now resolves the real tree root once and remaps the shipped "All Item Groups" parent to it (via a new transform hook on sync_master_data), so on localized sites (e.g. root "Bütün Element Qrupları") the tax groups nest under the actual root instead of being orphaned. Both sync_item_groups and sync_items now print explicit, always-visible lines (resolved root, per-item creation, and a created/existed/skipped summary) so a migrate run clearly shows whether the groups and items were created. Co-Authored-By: Claude Opus 4.8 (1M context) --- taxes_az/master_data/sync.py | 68 +++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/taxes_az/master_data/sync.py b/taxes_az/master_data/sync.py index 9db84ed..143550f 100644 --- a/taxes_az/master_data/sync.py +++ b/taxes_az/master_data/sync.py @@ -17,7 +17,7 @@ from __future__ import annotations import json import os -from collections.abc import Iterable +from collections.abc import Callable, Iterable import frappe @@ -77,6 +77,7 @@ def sync_master_data( *, is_tree: bool = False, manage_deletes: bool = True, + transform: Callable[[dict], dict] | None = None, ) -> dict: """Reconcile a doctype against a JSON file, applying only the delta. @@ -89,6 +90,10 @@ def sync_master_data( manage_deletes: when False, rows present in the DB but not in the JSON are left untouched. Use False when the JSON is only a subset of the table (e.g. Item Group also holds ERPNext and user-created groups). + transform: optional callback applied in-place to every loaded record + before the diff. Use it to rewrite site-dependent references (e.g. + remap a localized Item Group tree root) so both the insert and the + update-diff see the resolved values. """ if not os.path.exists(json_path): frappe.logger().warning(f"[taxes_az] master data file not found: {json_path}") @@ -98,6 +103,10 @@ def sync_master_data( if not target: return {"empty_file": True} + if transform is not None: + for key, rec in target.items(): + target[key] = transform(rec) + meta = frappe.get_meta(doctype) # value-bearing fields actually present in the JSON, minus the key itself sample = next(iter(target.values())) @@ -253,12 +262,15 @@ def sync_items() -> dict: records = json.load(f) root = _resolve_item_group_root() - inserted = skipped = 0 + print(f"[taxes_az] Item: resolved tree root = {root!r}, {len(records)} shipped item(s)") + inserted = existed = missing_group = 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 + if not name: + continue + if frappe.db.exists("Item", name): + existed += 1 continue group = rec.get("item_group") @@ -266,10 +278,8 @@ def sync_items() -> dict: 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 + print(f"[taxes_az] Item '{name}': Item Group {group!r} not found — SKIPPED") + missing_group += 1 continue doc = frappe.new_doc("Item") @@ -279,12 +289,21 @@ def sync_items() -> dict: doc.flags.name_set = True doc.flags.ignore_permissions = True doc.insert(ignore_if_duplicate=True) + print(f"[taxes_az] Item: created {name!r} ({rec.get('item_name')}) in {group!r}") inserted += 1 if inserted: frappe.db.commit() - print(f"[taxes_az] Item: +{inserted} (skipped {skipped})") - return {"doctype": "Item", "inserted": inserted, "skipped": skipped} + print( + f"[taxes_az] Item: created {inserted}, already existed {existed}, " + f"skipped (missing group) {missing_group}" + ) + return { + "doctype": "Item", + "inserted": inserted, + "existed": existed, + "missing_group": missing_group, + } def sync_item_groups() -> dict: @@ -293,8 +312,33 @@ def sync_item_groups() -> dict: Item Group is a tree doctype shared with ERPNext and user-created groups, so the JSON is only a subset: manage_deletes=False keeps everything else untouched, and is_tree=True rebuilds lft/rgt after the diff. + + The shipped JSON parents its top-level groups under ERPNext's "All Item + Groups". On localized sites the root is renamed (e.g. "Bütün Element + Qrupları"), so we resolve the real root once and remap that parent — this + keeps the tree properly nested instead of leaving the groups orphaned. """ path = frappe.get_app_path("taxes_az", "master_data", "item_group.json") - return sync_master_data( - "Item Group", path, key_field="name", is_tree=True, manage_deletes=False + + root = _resolve_item_group_root() + print(f"[taxes_az] Item Group: resolved tree root = {root!r}") + + def _remap_root_parent(rec: dict) -> dict: + if rec.get("parent_item_group") in _ROOT_ALIASES and root: + rec["parent_item_group"] = root + return rec + + result = sync_master_data( + "Item Group", + path, + key_field="name", + is_tree=True, + manage_deletes=False, + transform=_remap_root_parent, ) + print( + f"[taxes_az] Item Group: created {result.get('inserted', 0)}, " + f"updated {result.get('updated', 0)} " + f"(root={root!r})" + ) + return result