fix(master-data): create Items like fixtures (ignore_links) + re-sync after setup wizard
Two problems on image-based deployments where `bench migrate` runs only at
build time (before the setup wizard) and never again:
1. Item insert hit _validate_links and crashed migrate when standard UOMs
('Litre'/'Nos') weren't loaded yet. validate() itself passes — only the
link check fails — so set doc.flags.ignore_links = True, exactly as Frappe's
fixture import does (import_file.py). The referenced data always exists in a
real running program, so no dangling links in practice.
2. ERPNext creates the Item Group root with the *translated* name in the setup
wizard (e.g. Azerbaijani "Bütün Element Qrupları"), after migrate. Groups
created at build time were parented to the non-existent "All Item Groups"
and stayed orphaned/invisible in the tree.
Add a setup_wizard_complete hook (after_setup_wizard) that re-runs both syncs
once the wizard has created the real root and standard data: groups get
reparented under the real root and skipped items get created. It never raises
(a throwing task would fail the whole wizard), logging and swallowing instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ae1d0a9782
commit
3044e8ae8a
|
|
@ -25,6 +25,12 @@ after_migrate = [
|
||||||
"taxes_az.setup_accounts.check_and_create_accounts"
|
"taxes_az.setup_accounts.check_and_create_accounts"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# On image-based deployments migrate runs only at build time, before the setup
|
||||||
|
# wizard has created the Item Group root and standard UOMs. Re-sync once the
|
||||||
|
# wizard completes so the groups nest under the real (localized) root and the
|
||||||
|
# items resolve their UOMs. See sync.after_setup_wizard.
|
||||||
|
setup_wizard_complete = "taxes_az.master_data.sync.after_setup_wizard"
|
||||||
|
|
||||||
# Bəyannamə düsturlarının runtime-ı. Generasiya olunmuş Client Script-lər yalnız
|
# Bəyannamə düsturlarının runtime-ı. Generasiya olunmuş Client Script-lər yalnız
|
||||||
# CONFIG daşıyır və bu runtime-ı çağırır — formula_editor tətbiqinə heç bir
|
# CONFIG daşıyır və bu runtime-ı çağırır — formula_editor tətbiqinə heç bir
|
||||||
# runtime asılılığı qalmır, ona görə onu iş maşınlarından silmək olar.
|
# runtime asılılığı qalmır, ona görə onu iş maşınlarından silmək olar.
|
||||||
|
|
|
||||||
|
|
@ -282,12 +282,18 @@ def sync_items() -> dict:
|
||||||
missing_group += 1
|
missing_group += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Isolate each insert in a savepoint: on a bare/half-provisioned site a
|
# Isolate each insert in a savepoint so nothing can abort `bench migrate`
|
||||||
# missing link (e.g. UOM 'Litre') raises LinkValidationError, and an
|
# — on these deployments migrate runs only once (during image build /
|
||||||
# unhandled exception here would abort the whole `bench migrate`. Master
|
# setup) and never again, so a crash here would permanently leave the
|
||||||
# data must never do that — roll back just this item and carry on. The
|
# site without these items.
|
||||||
# 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.
|
# ignore_links mirrors how Frappe imports fixtures (see
|
||||||
|
# frappe/modules/import_file.py: `doc.flags.ignore_links = True`): it
|
||||||
|
# skips _validate_links(), which is the *only* thing that fails when a
|
||||||
|
# referenced record (e.g. UOM 'Litre') is not loaded yet at build time.
|
||||||
|
# validate() itself passes, so the item is created now and its links
|
||||||
|
# resolve once the standard data is in place — which it always is in a
|
||||||
|
# real running program.
|
||||||
frappe.db.savepoint("taxes_az_item")
|
frappe.db.savepoint("taxes_az_item")
|
||||||
try:
|
try:
|
||||||
doc = frappe.new_doc("Item")
|
doc = frappe.new_doc("Item")
|
||||||
|
|
@ -296,6 +302,7 @@ def sync_items() -> dict:
|
||||||
doc.name = name
|
doc.name = name
|
||||||
doc.flags.name_set = True
|
doc.flags.name_set = True
|
||||||
doc.flags.ignore_permissions = True
|
doc.flags.ignore_permissions = True
|
||||||
|
doc.flags.ignore_links = True
|
||||||
doc.insert(ignore_if_duplicate=True)
|
doc.insert(ignore_if_duplicate=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.db.rollback(save_point="taxes_az_item")
|
frappe.db.rollback(save_point="taxes_az_item")
|
||||||
|
|
@ -358,3 +365,33 @@ def sync_item_groups() -> dict:
|
||||||
f"(root={root!r})"
|
f"(root={root!r})"
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def after_setup_wizard(args=None) -> None:
|
||||||
|
"""Re-run the master-data sync once the setup wizard has completed.
|
||||||
|
|
||||||
|
On image-based deployments `bench migrate` runs only during image build —
|
||||||
|
*before* the setup wizard — so ERPNext has not yet created the Item Group
|
||||||
|
tree root or the standard UOMs. Groups created at build time are therefore
|
||||||
|
parented to a root that does not exist yet (ERPNext names the root with the
|
||||||
|
*translated* "All Item Groups", e.g. the Azerbaijani "Bütün Element
|
||||||
|
Qrupları"), which leaves them orphaned and invisible in the tree, and the
|
||||||
|
fuel items cannot resolve their UOMs.
|
||||||
|
|
||||||
|
The wizard fires ``setup_wizard_complete`` after it has created the root and
|
||||||
|
the standard data, so a second sync here reparents the groups under the real
|
||||||
|
root and creates any items skipped at build time. Both syncs are idempotent,
|
||||||
|
so this is safe on sites where migrate did run after setup too.
|
||||||
|
|
||||||
|
Never raises: a task that throws here would make the whole setup wizard fail
|
||||||
|
(process_setup_stages rolls back and re-raises), so any error is logged and
|
||||||
|
swallowed — completing setup matters more than this master-data refresh.
|
||||||
|
"""
|
||||||
|
print("[taxes_az] setup_wizard_complete: re-syncing master data")
|
||||||
|
try:
|
||||||
|
sync_item_groups()
|
||||||
|
sync_items()
|
||||||
|
except Exception:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(title="[taxes_az] after_setup_wizard failed")
|
||||||
|
print("[taxes_az] setup_wizard_complete: sync FAILED (logged, setup not blocked)")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue