The 0.1.9 placeholder repurposing didn't take effect on the test machine
(account tree still showed XXXX rows even though E-Taxes Bank Account
got populated). The most likely culprit was the manual doc.save() +
frappe.rename_doc combo: doc.save runs Account.validate, which fires
validate_account_currency and validate_account_number — either can throw
on an in-place placeholder edit, leaving the account_name updated but
the doc name stale, which dodges the XXXX-pattern delete sweep too.
Switch to ERPNext's canonical update_account_number helper. It does the
rename atomically, handles the tree-doctype reparenting, and is the
exact path the in-app rename UI uses. Currency is set with a direct
db.set_value beforehand so we never go through the full validate stack.
Also expanded "Jey Wizard trace: bank native done" to a JSON dump that
records, for every bank in the cache: currency, whether a same-currency
placeholder was available, the resulting GL name, and the action taken
(reused_placeholder / created_fresh / skipped_closed / error). Plus the
placeholder_index keys/counts as seen at the start of the loop. Next
run will show exactly which step is failing if anything still goes
wrong.
Three small wins for the post-setup chart of accounts:
1. Replace placeholders, don't sit beside them
The AZ chart ships with 223.1 - AZN AZXX… - JS, 223.2 - USD AZXX…,
etc. Previously we created our real banks with auto-generated names
("AZN AZ96AIIB… - JS") and deleted the placeholders, so the chart
format flipped between numbered and unnumbered rows. Now we index
placeholders by leading-3-letter currency before the loop, and for
each materialized bank we rename the matching placeholder in place
(account_name + account_currency updated, then frappe.rename_doc to
"<account_number> - <CUR> <IBAN> - <abbr>"). Real banks inherit the
223.X numbering. Leftover placeholders (extra currencies, the Kart
row) get the same delete-or-disable treatment as before. As a bonus,
frappe.rename_doc auto-fixes Company.default_bank_account when the
AZN placeholder it pointed at is repurposed.
2. Prefer AZN for Company.default_bank_account
_repoint_company_default_bank now picks AZN → USD → EUR → RUB → GBP
→ first-created instead of always taking the first GL out of the
e-taxes response order (which was landing on RUB).
3. Set Stock Settings.default_warehouse
ERPNext's update_stock_settings looks the warehouse up by
`_("Stores")`, which at language=az is "Anbarlar" — but the warehouse
was created with the English name, so the lookup misses and the field
stays None. The Stock Settings save block in materialize_after_setup
now resolves the warehouse directly against the new Company.
Two unrelated user-visible errors appeared after a successful wizard run:
1. 'Hesab Növü: 2 tapılmadı' (×5, one per non-closed bank)
Bank Account.account_type is a Link to the Bank Account Type doctype,
which is empty on a fresh install. We were stuffing the e-taxes raw
numeric 'type' code (e.g. '2' for card) into it. Field is optional —
stop populating it, the validation error goes away.
2. 'Hesab 223.1 - AZN AZXX… ilə əlaqəli olduğu üçün silinə bilməz' (×1)
ERPNext's setup_company → set_default_accounts picks the first Bank-
typed Account and writes it to Company.default_bank_account. On the
AZ chart that's a placeholder (223.1 - AZN AZXX… - JS), so when
_delete_bank_placeholders later tries to delete it, Frappe blocks
the delete with LinkExistsError pointing back at Company.
Two-part fix: before the placeholder sweep, repoint
Company.default_bank_account to the first real bank GL we just
materialized (only when the current value is unset or a placeholder).
And as a defensive fallback, _delete_bank_placeholders now catches
LinkExistsError, clears the queued popup message, and disables the
account instead of leaving the user with a scary error.
Adds seven 'Jey Wizard trace: ...' breadcrumbs (all using frappe.log_error
so they show up in the same place actual failures already do, with a
distinct title prefix to filter them out from real errors):
- fetch_all_etaxes done — confirms the Asan-step fetch ran, with summary
and per-endpoint errors
- materialize start — args, Company existence, default Asan Login auth
state, full cache snapshot
- loaders done — per-loader before/after E-Taxes row counts (or error)
- bank native done — input rows from cache, materialised count, parent
- amas skipped / amas enqueued — which branch ran and why
Used to diagnose 'no doctypes created after setup' on a remote test
machine where I can't reproduce locally.
Materialising banks off E-Taxes Bank Account made us hostage to
invoice_az's finalize-time re-fetch. If that re-fetch 401'd or hit
a network error the doctype stayed empty, and everything downstream
(Bank / GL Account / Bank Account, placeholder cleanup) silently
no-op'd. Now we read the e-taxes response straight from
Jey Wizard Etaxes Cache.bank_accounts_json which is already
populated during the Asan step, so materialisation runs off a
guaranteed-present payload and is decoupled from the retry-prone
loader call. Logs both entry ("applying valuation_method=…") and
exit ("materialised=N skipped_closed=M parent=…") via
frappe.logger() so we can confirm from the server log that the
hook actually ran end-to-end on a given setup.
Stock Settings valuation_method write switched from
frappe.db.set_single_value to the load-and-.save() pattern that
erpnext itself uses in install_fixtures.update_stock_settings, and
followed by an explicit frappe.db.commit() — should survive any
subsequent cache fetch of the Single doc.
Abbreviation deriver rewritten around the canonical AZ registered
name shape `"<Core>" <LEGAL FORM>`. When quotes are present the core
gets initials and the legal form collapses to its short code
(MMC / ASC / QSC / SC / K / FS), joined with a space — so
"TEST SOFT" MƏHDUD MƏSULİYYƏTLİ CƏMİYYƏTİ now yields "TS MMC"
instead of "″SMM". The quote character class widened to include
U+2033 DOUBLE PRIME (the typography used on the actual reference
site) plus prime, guillemet single ‹›, and other variants. Abbr
validation relaxed to allow a single internal space so the space
between core initials and legal-form code survives setup_complete.
Bank materialize was finding no parent account and skipping everything:
the AZ CoA keeps "Bank hesablaşma hesabları" (223) as a LEAF and the
223.1/223.2/... currency placeholders as siblings under the real
group "Pul vəsaitləri və onların ekvivalentləri" (22). The finder now
walks 223 → 22 → name-prefix and lands on 22, which is where real
bank GL accounts need to go. Placeholder sweep widened from "% AZXX%"
to any leaf under the bank parent whose name contains XXXX — catches
the "Kart XXXX…AZN" template too.
Valuation was only being written to Company.valuation_method, which
wins in erpnext.stock.utils.get_valuation_method but is invisible on
the Stock Settings page users actually open to verify. Now we write
both via frappe.db.set_single_value.
Company abbreviation is editable on the company step with a live-
suggested default. Deriver strips every quote variant and collapses
the AZ legal-form boilerplate (MƏHDUD MƏSULİYYƏTLİ CƏMİYYƏTİ → MMC,
AÇIQ/QAPALI SƏHMDAR CƏMİYYƏTİ → ASC/QSC, SƏHMDAR CƏMİYYƏTİ → SC,
KOOPERATİV → K) before tokenising, so "XYZ MMC" produces XM instead
of the old deriver's M-with-a-stray-quote. Manual edits stick.
Validated A-Z0-9, 1–10 chars.
Keeps invoice_az's loader untouched (it still only fills the read-only
E-Taxes Bank Account cache) and does the ERPNext-native side here
instead. After materialize_after_setup runs the bank loader, we walk
the cache rows linked to the new Company and create a Bank (global,
one per unique bank_name), a GL Account under parent "223 Bank
hesablaşma hesabları" (account_type=Bank, account_currency from
e-taxes, named "<CUR> <IBAN>"), and a Bank Account tying them all
together. Closed accounts (status=C) are skipped entirely — cache row
still exists.
Stock AZ CoA placeholders matching "<CUR> AZXX…" under the bank group
are deleted at the end, since nothing references them on a fresh
setup and they'd otherwise clutter the chart of accounts alongside
the real entries.
Per-row failures are logged under "Jey Wizard materialize (bank …)"
and never abort the run. Idempotent on re-entry.
- Company step gains a "Stock Valuation" select (FIFO default, Moving
Average option). Value is passed through build_setup_args and applied
to Company.valuation_method in materialize_after_setup — Company-level
wins over Stock Settings in get_valuation_method, so inventory posts
use the choice immediately.
- Cannot be changed after first stock entry (helper text + ERPNext
validation), so this is the only window to set it cleanly.
- CLAUDE.md added to document the architecture for future sessions
(override trick, two separate auth flows, cache doctype, rollback
mechanics).
Country / Currency / Timezone / Chart of Accounts are fixed for every
AZ install and show no user-editable information — just noise on the
final review. Still passed to setup_complete unchanged; only the UI row
is gone.
During setup the Employee table is always empty, so the breakdown was
always "new: N, already in system: 0" — noise without signal. Removed
the counter and the get_list round-trip that fed it. The import step
still updates existing rows on FIN match if any ever appear.
- Remove the +994 badge from the phone input; it's applied silently on submit.
Placeholder simplified to "Enter number". Prefix select gets an explicit
border + white background so it stays visible under custom themes.
- Cert-pick confirm dialog shrinks to a single-line "Confirm selection: X?".
- "Wrong company? Change" now does a proper restart: the backend reset also
wipes bearer_token / certificates_json / verification_code and flips
auth_status back to "Not Authenticated", because e-taxes' bearer_token is
single-use per chooseTaxpayer. The JS immediately calls
handle_authentication to pull a fresh token — another SMS + phone tap
produces a new certs list without the 401 "Authentication required" that
was surfacing when re-using the consumed bearer_token.
- Phone input split: hardcoded +994, 8-option prefix dropdown (050/051/010/
055/060/070/077/099 shown, 2-digit values submitted), 7-digit local number
with numeric-only input filter. Full E.164 assembled on submit.
- Certificate selection now prompts a confirmation dialog before the cascade
(select_taxpayer → fetch_etaxes_data) fires. Asan/done sub-state gets a
"Wrong company? Change" button that wipes the Asan Login tokens and every
cache field (via new jey_wizard.api.reset_company_selection), then returns
the user to the certs list without another phone tap.
- Employees intro no longer shows a dedicated Skip button — Next in the nav
acts as skip (and clears any stale AMAS cache when used that way).
- Employees done no longer shows "Reload from ƏMAS" — Back in the nav is
the single way to rewind. The rogue button was sending users back into a
stale intro state with leftover buttons.
- Remove the manual employee selection UI. After loading, the wizard caches
every record returned by emasValidEmploymentContractOnline and lands on
the done sub-state. import_bulk_employees updates existing records on
FIN match, so running on a populated site is safe.
- On constructor, if the booted session lang differs from the wizard's
default (az), trigger load_messages so the UI renders in that language
without the user having to re-pick it from the dropdown.
- New employees step between asan and company: optional ƏMAS (MyGovID second
tap) load with auto-pick by VÖEN, fallback to organization list, selection
table with existing-Employee indicator, skippable.
- materialize_after_setup enqueues invoice_az.amas_api.import_bulk_employees
with the freshly-created Company so both Amas Employees and Employee
records get populated.
- Company step now has Start/End date pickers for Fiscal Year (default to
current calendar year, validated).
- All UI strings wrapped in __(); ru.csv and az.csv translation files added;
language change on step 1 reloads translations via load_messages.
- Back button works from every step and every sub-state (rewinds sub-state
first, then crosses step boundary without clobbering terminal states).
- Jey Wizard Etaxes Cache gains amas_selected_employees_json to carry the
user's selection across setup_complete; cleared on Skip.
Two-phase data loading so future wizard steps can consume tax-portal data before
Company exists, while still ending up with properly-linked E-Taxes DocType records.
Phase A (Asan step, after main_token):
- jey_wizard.etaxes.fetch_all_etaxes hits 6 endpoints (objects, cash registers,
POS terminals, bank accounts, obligation pacts, presented certs), using
invoice_az's Asan Login for the main_token. Paginated endpoints follow hasMore.
- Raw JSON per endpoint is stashed in the new single DocType
"Jey Wizard Etaxes Cache". Per-endpoint errors are logged but don't abort.
- jey_wizard.etaxes.get_cached(kind) is the read API for future wizard steps.
- UI: new "fetching" sub-state between cert pick and done. "done" now shows a
count summary of what was cached (or per-row error badge if a kind failed).
Phase B (setup_wizard_complete hook, after Company is created):
- jey_wizard.etaxes.materialize_after_setup receives setup args, reads
args.company_name, and calls invoice_az.company_api.load_company_* for each
of the six kinds. Those re-fetch and write into E-Taxes Object /
E-Taxes Cash Register / etc with the real Company link. Each loader is
best-effort; failures are logged, not raised.
Why re-fetch instead of materialising from the cache:
- invoice_az's loaders already own the ~500-line field mapping. Re-fetching
once with a still-fresh main_token beats duplicating that code.
Bump to 0.0.6.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New wizard step between language and company:
1. User enters phone + Asan user_id, hits "Send SMS"
2. ensure_asan_login (jey_wizard.api) creates/resets the single Asan Login record
3. invoice_az.auth.handle_authentication kicks off SMS, JS shows verification code
4. Polling loop calls poll_auth_status every 5s up to 20 attempts (~100s total)
5. On confirmation, get_auth_certificates fetches certs; UI filters to legal-only
6. User picks one; select_certificate + select_taxpayer run back-to-back
7. get_company_from_cert (jey_wizard.api) parses selected_certificate_json and pulls
legalInfo.name + voen — no extra API call to the tax portal needed
8. Company step is then prefilled with that name (still editable)
Test scope: legal entities only, single Asan Login reused/reset on phone change,
no rollback machinery, no own renew_token scheduler (piggybacks on invoice_az's).
required_apps now includes invoice_az since we call its auth methods directly.
Bump to 0.0.5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the version indicator to a dark badge in the top-right corner so it's obvious at
a glance which code is running. Previous muted footer was too easy to miss.
Bump to 0.0.4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
erpnext's get_chart() iterates verified/*.json and matches on the JSON's `name` field,
not the filename. az_locale ships az_chart_of_accounts.json whose `name` is
"Azerbaijan Republic", so passing the filename made get_chart() return None and the
Company on_update hook crashed with "NoneType.get". make_records swallowed the
exception, Company never inserted, and downstream setup_wizard_complete hooks failed
loudly with "Company not found".
Bump to 0.0.3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bump jey_wizard version to 0.0.2 and display it at the bottom of every wizard screen.
Prevents the old-version-on-remote-machine confusion we just hit during testing.
Rule: bump both __version__ and the JEY_WIZARD_VERSION constant on every commit that
ships wizard code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Python finalize() shim imported frappe.desk.page.setup_wizard.setup_wizard.setup_complete
and called it as a plain function, which bypassed the HTTP/whitelist code path the default
wizard uses. This made the setup_company stage silently drop the Company insert via
make_records' blanket except, so downstream setup_wizard_complete hooks blew up with
"Company Test Company AZ not found".
Build all hardcoded AZ defaults on the client and call the stock endpoint over HTTP so we
go through typing_validations, sanitize_input and parse_args exactly like the default
wizard. api.py is left as a placeholder for future integrations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>