Commit Graph

45 Commits

Author SHA1 Message Date
Ali 997a085274 Reword Asan confirmation heading
"Confirm on your phone" → "Confirm the Asan Imza request" (source),
"Asan İmza sorğusunu təsdiqləyin" (az), "Подтвердите запрос Asan İmza" (ru).
Replaces the awkward "Puş bildirişini təsdiqləyin". POT regenerated,
mirrored to az_locale. Bump version 0.1.31 -> 0.1.32.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 13:33:02 +00:00
Ali ea445d65c0 Polish az translations: polite imperatives, simpler wording
- Informal imperatives → polite -in/-yin form on buttons: Seç→Seçin,
  Tamamla→Tamamlayın, yüklə→yükləyin, göndər→göndərin, Dəyiş→Dəyişin
  (and update the «Tamamla»→«Tamamlayın» button reference in the import copy).
- "Confirm on your phone": Telefonunuzda təsdiqləyin → Puş bildirişini
  təsdiqləyin (az), Подтвердите на телефоне → Подтвердите пуш (ru).
- Fix Turkish-ism "isteğe bağlı" → "məcburi deyil" (az).
- Drop "main token" jargon from the taxpayer-selection status (az + ru).

Mirrored to az_locale. Bump version 0.1.30 -> 0.1.31.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 13:19:39 +00:00
Ali a163b7f50e Migrate translations CSV → PO; complete az/ru coverage
- Move from legacy translations/{az,ru}.csv to gettext PO under locale/
  (main.pot + az.po + ru.po). Frappe v16 reads both, PO/MO overrides CSV.
- Translate the ~100 previously-untranslated strings (preview headers,
  admin-account step, employee-import copy, error/status messages, Etaxes
  Cache DocType labels) in both az and ru. Proper nouns (IBAN, ƏMAS,
  Stores, System Manager) intentionally fall back to source.
- Master copies added to the az_locale app (translations_{az,ru}/jey_wizard/),
  which copies them into locale/ and force-compiles the .mo on bench migrate.
- Document the PO + az_locale workflow in CLAUDE.md.

Bump version 0.1.29 -> 0.1.30.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 13:04:43 +00:00
Ali e1ea661855 Hide & exclude inactive e-taxes rows; drop Status column
Inactive rows (objects/cash registers/POS with status != A; bank accounts
with status = C) are no longer counted, previewed, or kept in the DB.

- Single active-status source of truth: _extract_items / _is_active /
  _active_items, mirroring _purge_inactive_etaxes_records.
- fetch_all_etaxes summary counts active rows only (drop _count_items).
- _preview_* filter inactive and remove the Status column (4 lists).
- Presented certificates (audit records) left untouched.

DB side was already covered by the post-load purge; this makes the
wizard list/counter consistent with what survives finalize.

Bump version 0.1.28 -> 0.1.29.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:43:21 +00:00
Ali 68ea3dce38 Revert clickable area: only digit + hint stay linked
Wrapping the whole row turned the bold label into a link too, which
read like a heading-as-button. Keep label plain, put the digit and
the "(click to preview)" hint inside one anchor so they share a
clickable target without hijacking the label.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:16:27 +00:00
Ali 55174552c0 Remove Obligation Pacts from wizard + widen click-to-preview hit area
- Drop obligation_pacts from the Asan-step fetch list, cache doctype,
  preview endpoint, finalize-time loader/purge, and translations.
- v0_1_27 patch cleans up the doctype, its Custom Fields on Company,
  and the orphan Singles row on existing sites.
- Asan/done counters now sit inside full-row anchors with a hover
  highlight — clicking anywhere on the line opens the preview modal,
  no more aiming at the digit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:07:21 +00:00
Ali b982825c71 Add click-to-preview for fetched e-taxes data on Asan/done
Counter rows and the company name on the post-Asan screen open a modal
with the actual records (6 e-taxes lists + company profile fields), so
users can sanity-check what landed before finalizing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:42:00 +00:00
Ali 6ba1bdf53d Pre-seed Gender / Employment Type before bulk ƏMAS import
Diagnosed cause of "wizard says employees will load, I see 0":
the bulk import worker dies on the first emp.insert() with
LinkValidationError("Could not find Gender: Male"). Frappe ships
Gender records (Male/Female/Other/...) and HRMS ships Employment
Type rows (Full-time/Part-time/Contract/...) via fixtures, but on
a freshly-finalized site those fixtures may not have landed by the
time _materialize_amas_employees fires. Every employee then fails
identically and the import job finishes with 0 created.

Fix: _seed_amas_employee_dependencies() runs right before
import_bulk_employees and idempotently inserts the standard rows
for both doctypes if missing. Per-row insert failures are logged
to "Jey Wizard seed amas deps" and don't block the rest of the
seed loop. Idempotent — safe on reruns.

Sample output of the existing "Jey Wizard trace: amas outcome"
will now show employees_in_db_for_company > 0 instead of always 0.
2026-05-04 10:35:33 +00:00
Ali 1f32d0b07d One-stop ƏMAS-import diagnostics in Error Log
Goal: stop asking the user to run console snippets every time the
import looks broken. After finalize the wizard now writes two
self-contained Error Log rows that together answer "what did the
wizard ask for, and what actually happened":

1. "Jey Wizard trace: amas dispatch" — written immediately when
   _materialize_amas_employees calls import_bulk_employees. JSON
   payload covers: how many employees we passed, the result dict
   from import_bulk_employees, parallel_module_available + bulk_
   parallelism (was the worker code refactored?), full Asan Login
   state with import lock + session/csrf lengths, wizard cache
   shape (employee count, first-row keys, doc_oid presence,
   contract_status distribution), recent RQ Job rows for the
   bulk import worker.

2. "Jey Wizard trace: amas outcome" — enqueued to the long queue
   right after dispatch. Polls RQ for the bulk import job (up to
   ~90s) until finished/failed/stopped/cancelled, then writes:
   final RQ status + truncated exc_info, employee count in DB for
   the new company, AMAS Employees count, post-run Asan Login
   state (lock cleared? session still alive?), and the last 10 min
   of AMAS-tagged Error Log rows excluding our own traces.

Together that's enough to localise any of the failure modes I'd
otherwise investigate one-by-one (CSRF abort, expired session,
stale lock, parallelism module mismatch, all-employees-filtered).
The user only needs to grep `Jey Wizard trace: amas*` and paste
the two rows back.
2026-05-01 16:41:33 +00:00
Ali 98681daf2f Drop 'cache' from user-visible strings
User-facing reword only — server-side method names (cache_selected_
employees, clear_amas_cache) stay as-is since they're not visible.

ƏMAS done state: "Cached and ready" → "Ready to import",
"Employees cached" → "Employees found", body line dropped the
"cached on the server" lead-in.

ƏMAS error strings: "Failed to cache" / "Network error while
caching" → "Failed to save" / "Network error while saving".

Asan done state: "Cached datasets" → "Loaded datasets".
2026-05-01 16:25:01 +00:00
Ali 8c178524d9 ƏMAS step: surface failure reason on intro, clearer 'cached' state
Symptom: after the second phone tap the wizard appeared to drop the
user back to the ƏMAS intro screen with no indication of what
happened — they couldn't tell if employees had been cached, the
import had run, or anything had failed.

Root cause was a UX bug in the JS layer: every error path in the
ƏMAS sub-flow (connect_amas, change_amas_account, get_employees_
report, cache_selected_employees) did

    this.$wrap.find(".jey-status").text(error_message);
    this.amas_state = "intro";
    this.show_current_step();

but show_current_step() unconditionally calls .jey-status.empty()
at the top of the render, so the message was wiped before the user
could read it. They saw a clean intro screen and assumed nothing
ran or that it silently succeeded.

Fix:

- New persistent fields data.amas_last_error / data.asan_last_error
  that survive show_current_step re-renders. Every ƏMAS error path
  now writes to data.amas_last_error before flipping state.
- intro renderer reads data.amas_last_error and shows a red
  "Previous attempt failed: <message>" banner above the usual
  copy when set. amas_start_auth clears it on retry.
- Added .error callbacks to the four frappe.call sites that didn't
  have one — network errors used to drop the user to intro with no
  message at all.

Also rewrote the success "done" screen so it can't be misread:
heading is now "Cached and ready", body explicitly says "Cached on
the server. Actual import into Employee / ƏMAS Employees runs as a
background job right after you click Finish — you can keep working
in /app while it runs." Same data, just unmistakable.
2026-05-01 16:22:20 +00:00
Ali 29f277a7bf Trace whether amas_api worker module has the parallel import code
Reported symptom: ƏMAS employees imported via the wizard appear to
trickle in one-by-one (old sequential _process_bulk_employees_import),
while invoice_az's in-app "Load from ƏMAS" button finishes the same
volume in seconds via the new ThreadPoolExecutor Phase A + sequential
Phase B path.

The wizard and the in-app flow both call invoice_az.amas_api.
import_bulk_employees, which frappe.enqueue's the work to a Frappe
RQ worker. Same code on disk, same enqueue, same worker queue — so
there's no place left where the wizard could pick a different code
path. The remaining plausible explanations are out-of-band:
  - parallel changes never committed/pushed to the test machine
  - workers running with stale imports because only `bench restart
    web` (or only the supervisor web group) ran, not the workers

Add `parallel_module_available` (truthy iff invoice_az.amas_api
exposes the BULK_IMPORT_PARALLELISM constant added in the parallel
refactor) and the constant value itself to the existing
"Jey Wizard trace: amas enqueued" Error Log entry. After the next
finalize on the test machine, that line will say either
  parallel_module_available: true,  bulk_parallelism: 10
or
  parallel_module_available: false, bulk_parallelism: null
which is enough to tell the source-of-truth from the in-process
state without rerunning anything.
2026-05-01 15:58:54 +00:00
Ali 8fe891d898 Make Asan certificate selection step explicit
The cert picker has been a sub-state of the asan step since day one
(asan_state === "certs"), but in the wizard it presented as "Select
your company" with a single Select button per row, which read more
like a continuation of the company step than a discrete certificate
choice. Polished it so the step is unmistakable and one-click:

- Header changed to "Choose certificate" with a count line ("Found N
  certificate(s) linked to this Asan ID. Pick the one whose VÖEN
  matches the company you want to set up.").
- Each row now carries a Legal entity / Individual tag pill so the
  user sees what type they're picking.
- Click on Select commits immediately — dropped the second
  frappe.confirm dialog. Re-picking still requires an SMS via
  reset_company_selection, so the picker itself IS the confirmation;
  a noisy confirm right after wasn't adding safety.
- Save the chosen cert label (holder + VÖEN) on this.data so the
  Confirm screen surfaces it as "Asan certificate: NAME (VÖEN)" —
  proves to the user during review that the right cert is locked in.

confirm_and_pick_certificate removed (dead now). Background ƏMAS
import behaviour unchanged: still fire-and-forget via
import_bulk_employees.
2026-05-01 15:12:56 +00:00
Ali 10c07dfc0b Track new import_bulk_employees response shape (parallel refactor)
invoice_az's amas_api was rewritten so import_bulk_employees:
  - takes a per-Asan-Login lock (Asan Login.amas_import_running)
    BEFORE enqueueing, returning {already_running: True} if the lock
    is held;
  - inside the worker, runs Phase A (parallel ThreadPool, 10 detail
    fetches concurrently) then Phase B (sequential DB writes with
    prefetched payloads), so realtime amas_import_progress now
    carries phase: "fetch" | "save".

Call signature is unchanged; only the return value differs and the
internal mechanism is faster. Wizard updates:

- _materialize_amas_employees now captures the response and dumps
  it into the "Jey Wizard trace: amas enqueued" breadcrumb (success,
  enqueued, total / already_running flag, full message). Spotting a
  stale lock at finalize-time on a fresh site is now possible from
  the Error Log alone.

- If the first call comes back with already_running: True (would be
  a stuck lock from a prior failed run — improbable on a freshly-
  dropped site but cheap to defend against), we force-clear the two
  Asan Login flags via direct db.set_value and retry once. The
  second outcome lands in the same trace.

- Comment on the function rewritten to describe the two-phase
  parallel/sequential flow so the next reader doesn't grep
  invoice_az to figure out where the work happens.

JS side: no changes — the wizard fires-and-forgets; in-app progress
UI lives in invoice_az/client/employee.js (already updated upstream).
2026-05-01 13:56:14 +00:00
Ali 38293a8de5 Allow Azerbaijani abbreviation, FY end auto-fill, drop inactive rows
Four pieces of polish requested after the wizard otherwise lands clean:

1. Abbreviation accepts any Unicode letter
   The validator regex /^[A-Z0-9 ]{1,10}$/ rejected legitimate
   Azerbaijani uppercase characters (Ə Ğ İ Ö Ş Ü Ç) the user may
   want in their abbr. Switched to /^[\p{L}\p{N} ]{1,10}$/u so any
   letter / digit goes through; toUpperCase() in modern browsers
   already handles the AZ casing correctly. Error string updated to
   match.

2. Fiscal Year end auto-fills from start
   When the Frappe Date control on the start field fires `change`,
   we set end = start + 1 year - 1 day via moment. The reverse
   trigger (end → start) is intentionally absent so editing end
   never overwrites the user's chosen start.

3. Inactive rows removed after the e-taxes loaders
   New _purge_inactive_etaxes_records(company) runs right after the
   "loaders done" trace. invoice_az writes everything, regardless
   of status; the wizard's contract is "active rows only", so we
   prune:
     - E-Taxes Bank Account where status='C' (closed)
     - E-Taxes Object where object_status not in (A,active)
     - E-Taxes Cash Register where status_code not in (A,active)
     - E-Taxes POS Terminal where status not in (A,active)
     - E-Taxes Obligation Pact where expire_date < today
   Presented Certificates carry historical state and stay. Per-row
   delete failures logged. New trace "inactive purge done" reports
   the matched/deleted counts per category.

4. ƏMAS: filter terminated/suspended at cache time
   cache_selected_employees now drops anything whose contract_status
   isn't in {qüvvədədir, aktiv, active}. Empty status passes through
   so we never silently lose someone (e.g. a row from get_employees_
   report that omitted the field). The bulk import job hits AMAS
   once per row, so cutting these here saves real wall time on the
   post-finalize background work.

Background-import architecture explained in chat — the per-employee
sync via _process_bulk_employees_import (one CSRF + AMAS detail call
per row, sequential) is by design in invoice_az; jey_wizard fires
and forgets it.
2026-04-30 14:06:32 +00:00
Ali 0e7ce08d7e Date controls: only_input mode; finalize bar: !important + label
Two corrections to v0.1.16 — your screenshot showed v0.1.8 in the
badge, so the JS bundle there is older than the Python side. Once you
rebuild and bust the browser cache, both fixes take effect:

1. Frappe Date controls now mount with only_input:true
   v0.1.16 used render_input:true without only_input, which kept the
   built-in form-group wrapper (label slot, help-box, etc.). With our
   own label rendered above, that produced a double-label layout that
   degraded the visual to look like a plain native date input. The
   only_input flag mirrors what frappe.ui.Page.add_field does — strips
   the wrapper and gives just the input + datepicker.

   Mount path is now mountDate(selector, value): clears the placeholder
   first (idempotent re-renders), constructs the control, then
   set_value with the data dict. Same get_value contract on read.

2. Finalize progress UI hardened with !important
   The reported "still a dimmed screen" was the cached older JS, but
   the new bar styles now also carry !important so any leftover wizard
   CSS or app theme rule can't collapse the bar div. Also overrides
   .jey-progress to "Finalizing" in finalize() so the step counter
   doesn't show "Adım 6 / 6" mid-progress.
2026-04-30 12:43:29 +00:00
Ali 40f65ffd90 Frappe Date controls, finalize progress bar, full message_log clear
Three UX fixes for the post-setup experience:

1. Native Frappe Date pickers on the company step
   Replaces <input type="date"> for fy_start/fy_end with
   frappe.ui.form.make_control({fieldtype:"Date"}). Same Flatpickr-
   styled control the rest of the desk uses. Values come back as
   YYYY-MM-DD via get_value(), matching the previous .val() contract.

2. Inline progress bar instead of the freeze overlay
   finalize() now renders a status line + animated bar in place of
   the wizard form, and drops freeze:true so the bar is visible.
   Subscribes to the realtime "setup_task" channel that
   process_setup_stages publishes — translates the [done, total]
   tuples into a 5%→95% bar fill, finishes at 100% on success, and
   handles the realtime "fail" case by switching to the error view.

3. Wipe all setup-time messages, not just alerts
   _drop_alert_messages → frappe.clear_messages(). The old filter
   missed non-alert msgprints, so HRMS Salary Component "Hesablar
   təyin edilməyib" warnings and Frappe's "Employee Self Service rolu
   silindi" note for the new admin user still surfaced as a modal.
   Genuine failures raise; everything queued for the response by this
   point is just noise for an automated run.
2026-04-29 16:28:55 +00:00
Ali 7a9c25ba35 Auto-pick Kassa for micro entities, Hesablama otherwise
fetch_company_profile now also returns the lower-cased
criteriaOfBusinessEntity ("micro" / "small" / "medium" / "big" / etc.)
so the JS layer can react before the user reaches the company step.

Heuristic in fetch_company_profile callback:
  criteria === "micro"  → Kassa metodu
  any other criteria    → Hesablama metodu
  unknown / fetch error → static "Hesablama metodu" default stands

The auto-pick is one-shot and respectful: once the user manually
changes the select on the company step we set
accounting_method_user_edited = true, and subsequent renders /
re-fetches no longer overwrite the choice. The hint line under the
field explains where the default came from when the heuristic ran.
2026-04-29 10:59:15 +00:00
Ali f10721302e Collect accounting_method on company step, apply to Company
Tax Policy → Uçot metodu (Accounting Method) is a Select with two
options ("Hesablama metodu" / "Kassa metodu") that the e-taxes
/v2/profile payload doesn't carry — it's a user choice. Add a Select
on the company wizard step (right under the FIFO/Moving Average
picker), default "Hesablama metodu".

Persisted by passing accounting_method through build_setup_args into
setup_complete (Frappe ignores unknown args; we read it back in
materialize_after_setup) and then a direct frappe.db.set_value on the
new Company. Done in the same block as valuation_method since both
come from the same step. _apply_company_profile won't clobber it
because the profile payload has no field for it.

Confirm screen now shows the chosen accounting method. Trace
breadcrumb "materialize start" now includes accounting_method
alongside valuation_method.
2026-04-29 10:55:14 +00:00
Ali 89ae5e237c Pull /v2/profile during Asan step + fill Company Tax Policy on finalize
Mirrors the manual "Update from E-Taxes" flow that already lives in
taxes_az/client/company.js — but unattended, as part of the wizard.

Asan step:
  fetch_company_profile() (new whitelisted method, jey_wizard.etaxes)
  wraps taxes_az.company_etaxes.get_company_profile, which hits
  /api/po/profile/public/v2/profile with the main_token. Same call
  also resolves taxAuthorityCode and propertyType to display names
  via the e-taxes dictionaries (token still fresh here — by finalize
  it may have expired). The merged blob is cached in a new
  Jey Wizard Etaxes Cache.company_profile_json field so finalize can
  apply it without another network round-trip.

  Wire-up: fetch_etaxes_data → fetch_company_profile, runs back-to-
  back during the existing "fetching" sub-state. Profile pull is
  best-effort — failure doesn't block the rest of the flow.

Finalize:
  _apply_company_profile(company) parses the cached blob, builds a
  field_updates dict mirroring the JS _parseProfileData mapping
  (BASIC, MANAGEMENT, BUSINESS/TAX INFO, VAT INFO, ORG STRUCTURE,
  REGISTRATION DATES, ADDRESSES, TAX SYSTEMS, EMPLOYER) plus the
  resolved tax_authority/property_type names. Date fields are
  normalised: YYYYMMDD compact dates → YYYY-MM-DD; ISO datetimes →
  YYYY-MM-DD prefix. Booleans → "1"/"0" so Check fields take them.

  Actual DB writes go through taxes_az.company_etaxes.force_update_
  company so the table-child handling for additionalActivityCodes
  and affiliatesNames/affiliatesTins stays in one place.

Schema: Jey Wizard Etaxes Cache picks up two new fields
  (company_profile_section, company_profile_json) — needs `bench
  migrate` on existing sites.

Trace: two new breadcrumbs in Error Log,
  "Jey Wizard trace: fetch_company_profile done" and
  "Jey Wizard trace: company profile applied" (with the list of
  fieldnames written and the force_update_company result summary).
2026-04-29 10:07:28 +00:00
Ali d93a024ba8 New 'user' wizard step + drop alert toasts after setup
Two fixes for the noisy post-finalize popup:

1. Dedicated admin-user step
   The wizard was passing Frappe's existing Administrator identity
   (full_name="Administrator", email="admin@example.com") into
   setup_complete, which made Frappe's user creation suggest a new
   username and warn that admin@example.com had no linked employee
   ("İşçinin Öz Xidməti rolu silindi"). Add a new "user" step between
   "company" and "confirm" collecting full name, email (= login), and
   password (with confirmation). build_setup_args sends those values,
   so Frappe creates a real user with no naming clash. Administrator
   stays untouched as the system-of-last-resort fallback.

   Validation: name required, basic email regex, password ≥8 chars,
   passwords must match. Email "admin@example.com" / "administrator"
   are blocked client-side as obviously-reserved.

2. Suppress alert-style toasts
   At the end of materialize_after_setup, drop every entry from
   frappe.local.message_log where alert=1. That kills the cluster of
   "Sənəd X yenidən adlandırıldı" toasts that pile up when the AZ
   translation pipeline renames default Item Group fixtures, without
   silencing the genuine modal warnings (e.g. HRMS Salary Component
   "accounts not assigned") which use plain msgprint.
2026-04-28 12:44:45 +00:00
Ali b739f06c98 Wipe placeholders unconditionally, then number real banks 223.X
Replaces the per-currency-match repurposing scheme with a clean-slate
approach that handles every shape of e-taxes data uniformly:

1. Detach Company.default_bank_account if it points at a placeholder
   (otherwise we'd hit LinkExistsError trying to delete that row).
2. Wipe every XXXX-marked Account under the bank parent. Same
   delete-or-disable fallback as before.
3. Sort the e-taxes accounts by currency preference
   (AZN→USD→EUR→RUB→GBP→others) so the resulting numbering looks
   familiar even though the source order from the API is arbitrary.
4. Allocate sequential 223.X account_numbers (next-free integer under
   the bank parent), creating each GL with an explicit account_number
   so ERPNext's autoname yields "223.X - <CUR> <IBAN> - <abbr>".
5. Repoint Company.default_bank_account to the first AZN-preferred GL
   (or first created if no preferred currency present).

Handles cleanly:
- standard 1-per-currency setups
- multiples of one currency (each gets its own 223.X)
- exotic currencies like TRY (still gets a 223.X prefix)
- zero-bank tenants (placeholders gone, default_bank_account left None)
- idempotent reruns (existing GL with matching account_name reused
  without consuming a number)

Removed now-unused helpers:
_index_placeholders_by_currency, _parse_placeholder_currency,
_claim_placeholder, _peek_placeholder, _convert_placeholder_to_real,
_delete_bank_placeholders.
2026-04-28 12:13:46 +00:00
Ali 830d621a87 Use ERPNext's update_account_number for placeholder rename + per-bank trace
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.
2026-04-28 12:00:20 +00:00
Ali 048ab04d7a Repurpose CoA bank placeholders in place + polish defaults
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.
2026-04-28 11:36:56 +00:00
Ali 1bc9fd6b13 Silence two post-setup popups from bank materialization
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.
2026-04-28 10:19:41 +00:00
Ali 322aa211b0 Trace materialize_after_setup decision points to Error Log
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.
2026-04-28 09:54:34 +00:00
Ali 3812a06c3c Read banks from wizard cache; save Stock Settings; quoted-core abbr
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.
2026-04-24 14:44:27 +00:00
Ali 4ed716637d Fix silent bank materialize, set Stock Settings too, editable abbr
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.
2026-04-24 14:09:35 +00:00
Ali 45e9e54535 Materialize ERPNext Bank / Bank Account / GL Account in wizard
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.
2026-04-24 13:10:17 +00:00
Ali 3137fc3caf Add FIFO/Moving Average picker; add CLAUDE.md
- 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).
2026-04-24 13:01:18 +00:00
Ali db43231927 Drop hardcoded rows from confirm screen
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.
2026-04-24 12:46:47 +00:00
Ali e401eb1210 Drop useless "already in system" counter on AMAS done screen
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.
2026-04-24 12:40:10 +00:00
Ali cab4131803 Drop visible +994 label, simpler cert confirm, re-auth on company rollback
- 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.
2026-04-24 12:27:35 +00:00
Ali 6422129c53 Phone prefix picker, certificate-pick confirmation & rollback, cleaner AMAS step
- 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.
2026-04-24 11:59:53 +00:00
Ali e166da49cc Auto-import all fetched ƏMAS employees; apply default language on boot
- 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.
2026-04-24 11:38:19 +00:00
Ali 3f62fea6e6 Add ƏMAS employees step, fiscal year picker, translations, full back nav
- 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.
2026-04-24 11:27:53 +00:00
Ali 2b9fba7dd7 Fetch e-taxes data after Asan auth, cache for wizard, materialize on finalize
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>
2026-04-22 15:36:02 +00:00
Ali 21981a1032 Add Asan Imza step that prefills company name from cert
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>
2026-04-22 13:56:25 +00:00
Ali f4e7d0a3b6 Make version badge visible
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>
2026-04-22 13:42:08 +00:00
Ali bce6e785e0 Pass CoA display name instead of filename
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>
2026-04-22 13:28:16 +00:00
Ali 6c687af420 Show version string in wizard footer
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>
2026-04-22 13:19:16 +00:00
Ali 7662e784b2 Call stock setup_complete endpoint directly from client
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>
2026-04-22 13:09:40 +00:00
Ali 34f0ab2d15 fixed bug with user 2026-04-22 12:59:10 +00:00
Ali 8f8a936693 first commit 2026-04-22 11:52:25 +00:00
Ali 1bb4ecfb6c feat: Initialize App 2026-04-22 10:00:16 +00:00