6.5 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this app does
jey_wizard replaces Frappe's stock setup wizard with a fixed flow tailored for Azerbaijani tenants. The flow is hard-wired to: language → asan → employees → company → confirm. Steps past Asan Imza authentication assume the company is Azerbaijani (country/currency/timezone/CoA are hardcoded in build_setup_args) and that the installer wants e-taxes + ƏMAS data pulled and materialised as part of setup.
Depends on invoice_az, declared in hooks.py:required_apps. The wizard does not re-implement e-taxes or ƏMAS integration — it thinly wraps invoice_az.auth.*, invoice_az.amas_api.* and invoice_az.company_api.*. Read those modules first before touching the flow.
Architecture
Stock wizard override (one big trick)
hooks.py:setup_wizard_requires loads jey_wizard/public/js/jey_setup.js inside /app/setup-wizard. At the top of that file we overwrite frappe.setup.SetupWizard (the class) and wipe frappe.setup.slides / slides_settings / events so ERPNext's default before_load/after_load hooks don't mutate a wizard that is no longer slide-based. Frappe's page handler then calls new frappe.setup.SetupWizard(wizard_settings) and gets our class instead. There is no slide system — we drive navigation manually with a current_step index and per-step sub-state machines (asan_state, amas_state).
On finalize we call the unmodified frappe.desk.page.setup_wizard.setup_wizard.setup_complete with a full args dict (see build_setup_args in jey_setup.js). Staying on that codepath keeps typing_validations / sanitize_input / setup_complete hooks working.
Two authentication systems, two phone taps
e-taxes and ƏMAS are separate auth flows — this is not a limitation of our code, it's how the Azerbaijani services work:
- e-taxes (Asan Imza direct):
asanImza/start→ SMS → user tap →bearer_token→chooseTaxpayerconsumes bearer, returnsmain_token. Thebearer_tokenis single-use per chooseTaxpayer call. Switching taxpayer requires another phone tap (auth.py:526returns 401 otherwise).invoice_az.client.etaxes_common.jsfollows the same pattern. - ƏMAS (via MyGovID):
asanSignLogin→ SMS → user tap →mygovid_token(JWT) → OAuth code exchange → cookie session. AMAS requires a fresh CSRF token before every request;invoice_az.amas_api.make_amas_requesthandles that.
The UI warns about the second tap on the employees step intro. Do not try to unify the two — there is no e-taxes MyGovID OAuth path in the current code.
Cache doctype: Jey Wizard Etaxes Cache
Single doctype that carries state between wizard steps and setup_wizard_complete:
objects_json/cash_registers_json/pos_terminals_json/bank_accounts_json/presented_certs_json— written byetaxes.py:fetch_all_etaxesduring the Asan step, not read back on finalize (see next point).amas_selected_employees_json— written byamas.py:cache_selected_employees, read byetaxes.py:_materialize_amas_employeesin thesetup_wizard_completehook.last_summary/last_error/fetched_at— displayed on the Asan done screen.
Schema changes to this doctype require bench migrate.
Two-phase e-taxes materialisation
During Asan step we fetch the 6 e-taxes lists into the cache doctype. On setup_wizard_complete (etaxes.py:materialize_after_setup) we call invoice_az.company_api.load_company_* — these re-fetch from e-taxes and write to real DocTypes (E-Taxes Object, E-Taxes Cash Register, …) linked to the freshly-created Company. The mid-wizard cache exists so future wizard steps can consume the data before Company exists; the loaders are invoice_az's authoritative path. Don't try to read from the cache on finalize.
AMAS employees go through invoice_az.amas_api.import_bulk_employees which frappe.enqueues a background job with realtime progress (events amas_import_progress / amas_import_complete). The wizard fires-and-forgets — the user lands in /app while employees continue importing.
"Wrong company? Change" rollback
Clicking rollback on asan/done calls api.py:reset_company_selection which wipes bearer_token, main_token, certificates_json, selected_certificate*, verification_code, sets auth_status="Not Authenticated", and empties the cache doctype. The JS then calls trigger_asan_authentication again, which sends a fresh SMS. Re-using the consumed bearer_token is what caused the historical 401 "Authentication required" bug — do not skip the backend reset.
Common commands
From any bench site
bench --site <site> migrate # pick up schema changes to Jey Wizard Etaxes Cache
bench build --app jey_wizard # rebuild /assets/jey_wizard/js/jey_setup.js
bench restart # reload Python changes
Pre-commit (from apps/jey_wizard)
pre-commit install
pre-commit run --all-files
Configured tools: ruff, eslint, prettier, pyupgrade.
Version badge
jey_wizard/__init__.py:__version__ and JEY_WIZARD_VERSION in jey_setup.js are kept in sync and bumped on every commit that changes wizard code. The badge in the top-right of the wizard UI reads this string — it's how you tell at a glance which revision is actually loaded on a given site.
Translations
translations/ru.csv and translations/az.csv (plain two-column CSV, quote fields with commas). Switching language on step 1 calls frappe.desk.page.setup_wizard.setup_wizard.load_messages, which repopulates frappe._messages so subsequent __() calls render in the new language. The constructor does this automatically on boot if frappe.boot.lang differs from the default (az).
Gotchas
- Admin-only.
_only_admin()inapi.py/etaxes.py/amas.pythrows unlessfrappe.session.user == "Administrator". The wizard is not designed for any other user. - Phone input. UI collects a 2-digit carrier prefix (values
50/51/10/55/60/70/77/99) + 7-digit local number. On submit we assemble+994{prefix}{local}. Don't strip the+— MyGovID accepts it; invoice_az stores it verbatim. - Back navigation. Within a sub-state machine, Back rewinds to intro/input first; only Back from intro/done crosses a wizard-step boundary. See
prev()injey_setup.js. - Placement of JS.
public/js/jey_setup.jsis served raw viasetup_wizard_requires. There is no bundler;bench buildjust symlinkspublic/tosites/assets/jey_wizard/.