81 lines
6.6 KiB
Markdown
81 lines
6.6 KiB
Markdown
# 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` → `chooseTaxpayer` consumes bearer, returns `main_token`. The `bearer_token` is **single-use per chooseTaxpayer call**. Switching taxpayer requires another phone tap (`auth.py:526` returns 401 otherwise). `invoice_az.client.etaxes_common.js` follows 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_request` handles 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` / `obligation_pacts_json` / `presented_certs_json` — written by `etaxes.py:fetch_all_etaxes` during the Asan step, **not read back** on finalize (see next point).
|
|
- `amas_selected_employees_json` — written by `amas.py:cache_selected_employees`, read by `etaxes.py:_materialize_amas_employees` in the `setup_wizard_complete` hook.
|
|
- `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.enqueue`s 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
|
|
|
|
```bash
|
|
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`)
|
|
|
|
```bash
|
|
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()` in `api.py` / `etaxes.py` / `amas.py` throws unless `frappe.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()` in `jey_setup.js`.
|
|
- **Placement of JS.** `public/js/jey_setup.js` is served raw via `setup_wizard_requires`. There is no bundler; `bench build` just symlinks `public/` to `sites/assets/jey_wizard/`.
|