feat(master-data): resolve Item Group root for groups too, and log results visibly
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) <noreply@anthropic.com>
This commit is contained in:
parent
9b1f2b79e0
commit
e4ac3e328f
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue