diff --git a/jey_wizard/__init__.py b/jey_wizard/__init__.py index 81f0fde..b1a19e3 100644 --- a/jey_wizard/__init__.py +++ b/jey_wizard/__init__.py @@ -1 +1 @@ -__version__ = "0.0.4" +__version__ = "0.0.5" diff --git a/jey_wizard/api.py b/jey_wizard/api.py index 60b6dac..9a8f17f 100644 --- a/jey_wizard/api.py +++ b/jey_wizard/api.py @@ -1,4 +1,66 @@ -# The skeleton wizard now calls frappe.desk.page.setup_wizard.setup_wizard.setup_complete -# directly from the client, so no server-side finalize shim is needed here. -# Real integrations (tax API, session-backed steps, rollback) will live in this module -# once the skeleton is verified. +import json + +import frappe +from frappe import _ + + +@frappe.whitelist() +def ensure_asan_login(phone, user_id): + """Reuse the single Asan Login record (or create one) and reset its auth state. + + The wizard is one-shot, so we keep at most one record. If the user goes back to the + credentials step and changes phone/user_id, we wipe the previous tokens so a fresh + auth round can run cleanly. + """ + _only_admin() + + existing = frappe.get_all("Asan Login", limit=1, fields=["name"]) + if existing: + doc = frappe.get_doc("Asan Login", existing[0].name) + else: + doc = frappe.new_doc("Asan Login") + + doc.phone = phone + doc.user_id = user_id + doc.auth_status = "Not Authenticated" + doc.bearer_token = "" + doc.main_token = "" + doc.certificates_json = "" + doc.selected_certificate = "" + doc.selected_certificate_index = "" + doc.selected_certificate_json = "" + doc.choose_taxpayer_response = "" + doc.verification_code = "" + doc.is_default = 1 + doc.flags.ignore_permissions = True + doc.save() if existing else doc.insert(ignore_permissions=True) + + return {"name": doc.name} + + +@frappe.whitelist() +def get_company_from_cert(asan_login_name): + """After select_taxpayer ran, pull the company name + VÖEN from the chosen + certificate. The cert payload from the tax portal already carries everything we need + for the wizard's company step — no extra HTTP call required. + """ + _only_admin() + + doc = frappe.get_doc("Asan Login", asan_login_name) + cert = json.loads(doc.selected_certificate_json or "{}") + + legal = cert.get("legalInfo") or {} + # Wizard is legal-only by user requirement; we still defensively fall back to + # individualInfo so the JS never crashes if a stray individual cert sneaks in. + individual = cert.get("individualInfo") or {} + + return { + "taxpayer_type": cert.get("taxpayerType") or "", + "company_name": legal.get("name") or individual.get("name") or "", + "voen": legal.get("voen") or legal.get("tin") or individual.get("fin") or "", + } + + +def _only_admin(): + if frappe.session.user != "Administrator": + frappe.throw(_("Only Administrator may run setup"), frappe.PermissionError) diff --git a/jey_wizard/hooks.py b/jey_wizard/hooks.py index 3ea1677..37b9d26 100644 --- a/jey_wizard/hooks.py +++ b/jey_wizard/hooks.py @@ -8,7 +8,9 @@ app_license = "unlicense" # Apps # ------------------ -# required_apps = [] +# Asan Imza authentication in the wizard reuses invoice_az's Asan Login DocType and +# its auth.* whitelisted methods directly. The wizard cannot run without invoice_az. +required_apps = ["invoice_az"] # Each item in the list will be shown as an app in the apps page # add_to_apps_screen = [ diff --git a/jey_wizard/public/js/jey_setup.js b/jey_wizard/public/js/jey_setup.js index 6e1343a..b0cd1a8 100644 --- a/jey_wizard/public/js/jey_setup.js +++ b/jey_wizard/public/js/jey_setup.js @@ -7,10 +7,10 @@ frappe.provide("jey_wizard"); -// Bump this string in every commit that changes wizard code. Displayed in the footer so +// Bump this string in every commit that changes wizard code. Displayed in the badge so // we can tell at a glance which version is actually running on a given machine. Kept in // sync with __version__ in jey_wizard/__init__.py. -const JEY_WIZARD_VERSION = "0.0.4"; +const JEY_WIZARD_VERSION = "0.0.5"; // Wipe Frappe + ERPNext default slides so their `before_load`/`after_load` listeners // don't try to mutate a wizard that isn't slide-based anymore. @@ -18,16 +18,32 @@ frappe.setup.slides = []; frappe.setup.slides_settings = []; frappe.setup.events = {}; +// Asan polling: 20 attempts × 5s = ~100s window for the user to confirm on phone. +// Mirrors invoice_az/asan_login.js timing. +const ASAN_POLL_INTERVAL_MS = 5000; +const ASAN_POLL_MAX_ATTEMPTS = 20; + frappe.setup.SetupWizard = class JeySetupWizard { constructor(settings = {}) { this.parent = settings.parent; this.values = {}; this.data = { language: "az", - company_name: "Test Company AZ", + asan_phone: "", + asan_user_id: "", + asan_login_name: "", + asan_voen: "", + company_name: "", }; + // Sub-state for the asan step: input -> polling -> certs -> done + this.asan_state = "input"; + this.asan_certificates = []; + this.asan_bearer_token = ""; + this.asan_poll_attempts = 0; + this.asan_poll_timer = null; + this.current_step = 0; - this.steps = ["language", "company", "confirm"]; + this.steps = ["language", "asan", "company", "confirm"]; this.render(); } @@ -37,7 +53,7 @@ frappe.setup.SetupWizard = class JeySetupWizard { render() { const $parent = $(this.parent).empty(); this.$wrap = $(` -
Open the Asan Imza app on your phone and confirm the request.
+ ${code + ? `| Company | +VÖEN | ++ |
|---|
No legal-entity certificates available on this Asan ID. Use a phone/ID linked to a company.
`} + `); + $body.find(".jey-cert-pick").on("click", (e) => { + const idx = parseInt($(e.currentTarget).attr("data-idx"), 10); + this.pick_certificate(idx); + }); + } else if (this.asan_state === "done") { + $body.html(` +Pulled from the tax portal:
Click Next to continue — you'll be able to edit the company name on the next step.
`); } + } + + render_company($body) { + $body.html(` + + + ${this.data.asan_voen + ? `