diff --git a/jey_wizard/__init__.py b/jey_wizard/__init__.py index a73339b..00ec2dc 100644 --- a/jey_wizard/__init__.py +++ b/jey_wizard/__init__.py @@ -1 +1 @@ -__version__ = "0.0.8" +__version__ = "0.0.9" diff --git a/jey_wizard/api.py b/jey_wizard/api.py index 9a8f17f..bfef8ba 100644 --- a/jey_wizard/api.py +++ b/jey_wizard/api.py @@ -61,6 +61,45 @@ def get_company_from_cert(asan_login_name): } +@frappe.whitelist() +def reset_company_selection(asan_login_name): + """Roll back after a wrong certificate pick: wipe main_token / selected cert on + Asan Login and clear the wizard cache (both e-taxes and AMAS) so the user can + pick a different taxpayer and re-fetch from scratch. Certificates list itself + is not touched — it's still valid for the same Asan ID. + """ + _only_admin() + + doc = frappe.get_doc("Asan Login", asan_login_name) + doc.main_token = "" + doc.selected_certificate = "" + doc.selected_certificate_index = "" + doc.selected_certificate_json = "" + doc.choose_taxpayer_response = "" + doc.flags.ignore_permissions = True + doc.save() + + cache = frappe.get_single("Jey Wizard Etaxes Cache") + for field in ( + "objects_json", + "cash_registers_json", + "pos_terminals_json", + "bank_accounts_json", + "obligation_pacts_json", + "presented_certs_json", + "amas_selected_employees_json", + "last_summary", + "last_error", + ): + cache.set(field, "") + cache.fetched_at = None + cache.flags.ignore_permissions = True + cache.save() + frappe.db.commit() + + return {"ok": True} + + def _only_admin(): if frappe.session.user != "Administrator": frappe.throw(_("Only Administrator may run setup"), frappe.PermissionError) diff --git a/jey_wizard/public/js/jey_setup.js b/jey_wizard/public/js/jey_setup.js index dcc64e2..1010f70 100644 --- a/jey_wizard/public/js/jey_setup.js +++ b/jey_wizard/public/js/jey_setup.js @@ -10,7 +10,7 @@ frappe.provide("jey_wizard"); // 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.8"; +const JEY_WIZARD_VERSION = "0.0.9"; // 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. @@ -41,6 +41,8 @@ frappe.setup.SetupWizard = class JeySetupWizard { const year = new Date().getFullYear(); this.data = { language: "az", + asan_prefix: "", + asan_local_number: "", asan_phone: "", asan_user_id: "", asan_login_name: "", @@ -143,11 +145,23 @@ frappe.setup.SetupWizard = class JeySetupWizard { render_asan($body) { if (this.asan_state === "input") { + // +994 is hardcoded (every Azerbaijani mobile uses it); the visible + // parts are the carrier prefix (dropdown) and the 7-digit local number. + const prefixOptions = ["50", "51", "10", "55", "60", "70", "77", "99"] + .map((p) => ``) + .join(""); $body.html(`

${__("Asan Imza authentication")}

- +
+ +994 + + +
@@ -155,8 +169,14 @@ frappe.setup.SetupWizard = class JeySetupWizard {
`); - $body.find(".jey-asan-phone").val(this.data.asan_phone); + $body.find(".jey-asan-prefix").val(this.data.asan_prefix || ""); + $body.find(".jey-asan-phone").val(this.data.asan_local_number || ""); $body.find(".jey-asan-userid").val(this.data.asan_user_id); + $body.find(".jey-asan-phone").on("input", (e) => { + // Strip anything that isn't a digit as the user types. + const cleaned = String($(e.currentTarget).val() || "").replace(/\D/g, "").slice(0, 7); + $(e.currentTarget).val(cleaned); + }); $body.find(".jey-asan-auth").on("click", () => this.start_asan_auth()); } else if (this.asan_state === "polling") { const code = this.asan_verification_code || ""; @@ -206,7 +226,7 @@ frappe.setup.SetupWizard = class JeySetupWizard { `); $body.find(".jey-cert-pick").on("click", (e) => { const idx = parseInt($(e.currentTarget).attr("data-idx"), 10); - this.pick_certificate(idx); + this.confirm_and_pick_certificate(idx); }); } else if (this.asan_state === "fetching") { $body.html(` @@ -251,7 +271,11 @@ frappe.setup.SetupWizard = class JeySetupWizard {
${__("Cached datasets")}
+
+ +
`); + $body.find(".jey-change-company").on("click", () => this.reset_company_selection()); } } @@ -264,19 +288,17 @@ frappe.setup.SetupWizard = class JeySetupWizard {
${__("Heads up:")} ${__("You will receive a second confirmation request on your phone from ASAN Sign / MyGovID. This is separate from the one you just approved for the tax portal.")}
-

${__("This step is optional. Skip it if your company has no employees or is not registered with ƏMAS.")}

+

${__("This step is optional. Use Next to skip if your company has no employees or is not registered with ƏMAS.")}

${alreadyLoaded ? `
- ${__("Already selected:")} ${alreadyLoaded} ${__("employee(s)")}. + ${__("Already loaded:")} ${alreadyLoaded} ${__("employee(s)")}.
` : ""} -
- - +
+
`); $body.find(".jey-amas-load").on("click", () => this.amas_start_auth()); - $body.find(".jey-amas-skip").on("click", () => this.amas_skip()); } else if (this.amas_state === "auth") { const code = this.amas_verification_code || ""; $body.html(` @@ -355,14 +377,8 @@ frappe.setup.SetupWizard = class JeySetupWizard { (${__("new")}: ${newCount < 0 ? n : newCount}, ${__("already in system")}: ${existingCount})

${__("All employees will be imported into ƏMAS Employees and the standard Employee doctype after the wizard finishes.")}

-
- -
`); - $body.find(".jey-amas-back-intro").on("click", () => { - this.amas_state = "intro"; - this.show_current_step(); - }); + // No extra buttons here — Back/Next in nav cover everything. } } @@ -437,9 +453,10 @@ frappe.setup.SetupWizard = class JeySetupWizard { // surfaces a standard Next. showStdNext = this.asan_state === "done"; } else if (step === "employees") { - // On intro the user uses explicit Skip / Load buttons, not Next. - // Only the "done" sub-state surfaces a standard Next to move on. - showStdNext = this.amas_state === "done"; + // Next is visible on intro (acts as "skip this step") and on done + // (move on with the loaded selection). During auth/loading/etc it + // stays hidden so the flow can't be short-circuited mid-request. + showStdNext = this.amas_state === "intro" || this.amas_state === "done"; } else { showStdNext = true; } @@ -488,6 +505,24 @@ frappe.setup.SetupWizard = class JeySetupWizard { next() { if (!this.capture_current()) return; + const step = this.steps[this.current_step]; + // Skip-by-Next on employees/intro with no prior selection: clear any stale + // cache so materialize_after_setup doesn't re-import a previous run's data. + if ( + step === "employees" && + this.amas_state === "intro" && + (this.data.amas_selected_employees || []).length === 0 + ) { + this.data.amas_skipped = true; + frappe.call({ + method: "jey_wizard.amas.clear_amas_cache", + callback: () => { + this.current_step++; + this.show_current_step(); + }, + }); + return; + } this.current_step++; this.show_current_step(); } @@ -536,14 +571,26 @@ frappe.setup.SetupWizard = class JeySetupWizard { // ---------- asan flow ---------- start_asan_auth() { - const phone = (this.$wrap.find(".jey-asan-phone").val() || "").trim(); + const prefix = (this.$wrap.find(".jey-asan-prefix").val() || "").trim(); + const local = (this.$wrap.find(".jey-asan-phone").val() || "").replace(/\D/g, ""); const userId = (this.$wrap.find(".jey-asan-userid").val() || "").trim(); - if (!phone || !userId) { - this.$wrap.find(".jey-status").text(__("Phone and User ID are required")); + if (!prefix) { + this.$wrap.find(".jey-status").text(__("Select a phone prefix")); return; } - this.data.asan_phone = phone; + if (local.length !== 7) { + this.$wrap.find(".jey-status").text(__("Phone number must be 7 digits")); + return; + } + if (!userId) { + this.$wrap.find(".jey-status").text(__("User ID is required")); + return; + } + this.data.asan_prefix = prefix; + this.data.asan_local_number = local; + this.data.asan_phone = `+994${prefix}${local}`; this.data.asan_user_id = userId; + const phone = this.data.asan_phone; this.$wrap.find(".jey-status").empty(); frappe.call({ @@ -656,6 +703,21 @@ frappe.setup.SetupWizard = class JeySetupWizard { }); } + confirm_and_pick_certificate(idx) { + const cert = this.asan_certificates[idx]; + if (!cert) return; + const legal = cert.legalInfo || {}; + const certName = `${legal.name || ""} (${legal.voen || legal.tin || ""})`; + // Cert selection triggers a cascade (select_taxpayer → fetch_etaxes_data) that + // writes a lot of cached data. Picking the wrong cert is reversible but costs + // another phone tap, so confirm explicitly first. + frappe.confirm( + __("You selected {0}. Is this the right company? Switching later will require clearing loaded data and re-authenticating.", [frappe.utils.escape_html(certName)]), + () => this.pick_certificate(idx), + () => {} + ); + } + pick_certificate(idx) { const cert = this.asan_certificates[idx]; if (!cert) return; @@ -714,6 +776,44 @@ frappe.setup.SetupWizard = class JeySetupWizard { }); } + reset_company_selection() { + // Reached from asan/done when the user realizes they picked the wrong cert. + // Warn explicitly — we wipe both the e-taxes cache and the AMAS selection, + // and clear the certificate/main_token on Asan Login. Cert list stays in + // this.asan_certificates so the user can pick again without re-polling. + frappe.confirm( + __("This will delete all loaded tax portal data and any ƏMAS employee selection. You will need to pick a certificate again. Continue?"), + () => { + frappe.call({ + method: "jey_wizard.api.reset_company_selection", + args: { asan_login_name: this.data.asan_login_name }, + freeze: true, + freeze_message: __("Clearing..."), + callback: (r) => { + const m = r.message || {}; + if (!m.ok) { + this.$wrap.find(".jey-status").text(__("Failed to clear data.")); + return; + } + this.data.company_name = ""; + this.data.asan_voen = ""; + this.data.amas_selected_employees = []; + this.data.amas_skipped = false; + this.data.amas_organization_name = ""; + this.amas_employees = []; + this.amas_existing_map = {}; + this.amas_state = "intro"; + this.asan_fetch_summary = {}; + this.asan_fetch_errors = {}; + this.asan_state = "certs"; + this.show_current_step(); + }, + }); + }, + () => {} + ); + } + fetch_etaxes_data() { frappe.call({ method: "jey_wizard.etaxes.fetch_all_etaxes", @@ -735,20 +835,6 @@ frappe.setup.SetupWizard = class JeySetupWizard { // ---------- AMAS flow ---------- - amas_skip() { - this.data.amas_skipped = true; - this.data.amas_selected_employees = []; - frappe.call({ - method: "jey_wizard.amas.clear_amas_cache", - freeze: true, - freeze_message: __("Skipping..."), - callback: () => { - this.current_step++; - this.show_current_step(); - }, - }); - } - amas_start_auth() { if (!this.data.asan_login_name) { this.$wrap.find(".jey-status").text(__("Finish Asan Imza authentication first.")); diff --git a/jey_wizard/translations/az.csv b/jey_wizard/translations/az.csv index 9a77170..4394ae2 100644 --- a/jey_wizard/translations/az.csv +++ b/jey_wizard/translations/az.csv @@ -4,6 +4,18 @@ Wizard & site language,Sihirbaz və sayt dili Asan Imza authentication,Asan İmza ilə giriş Phone number,Telefon nömrəsi e.g. 0501234567,məsələn 0501234567 +Prefix,Prefiks +7 digits,7 rəqəm +Select a phone prefix,Telefon prefiksini seçin +Phone number must be 7 digits,Telefon nömrəsi 7 rəqəmdən ibarət olmalıdır +User ID is required,User ID tələb olunur +"You selected {0}. Is this the right company? Switching later will require clearing loaded data and re-authenticating.","Siz {0} seçdiniz. Bu, düzgün şirkətdir? Sonradan dəyişmək üçün yüklənmiş məlumatları silmək və yenidən giriş etmək lazım olacaq." +"This will delete all loaded tax portal data and any ƏMAS employee selection. You will need to pick a certificate again. Continue?","Vergi portalından yüklənmiş bütün məlumatlar və seçilmiş ƏMAS işçiləri silinəcək. Sertifikatı yenidən seçmək lazım gələcək. Davam edilsin?" +Clearing...,Silinir... +Failed to clear data.,Məlumatı silmək alınmadı. +Wrong company? Change,Yanlış şirkət? Dəyiş +Use Next to skip if your company has no employees or is not registered with ƏMAS.,"Şirkətinizdə işçi yoxdursa və ya ƏMAS-da qeydiyyatda deyilsə, «İrəli» düyməsi ilə ötürün." +Already loaded:,Artıq yükləndi: User ID,İstifadəçi ID ASAN ID,ASAN ID Send request to Asan Imza,Asan İmza-ya sorğu göndər diff --git a/jey_wizard/translations/ru.csv b/jey_wizard/translations/ru.csv index 97acbdc..b012aa4 100644 --- a/jey_wizard/translations/ru.csv +++ b/jey_wizard/translations/ru.csv @@ -4,6 +4,18 @@ Wizard & site language,Язык мастера и сайта Asan Imza authentication,Аутентификация Asan İmza Phone number,Номер телефона e.g. 0501234567,например 0501234567 +Prefix,Префикс +7 digits,7 цифр +Select a phone prefix,Выберите префикс номера +Phone number must be 7 digits,Номер телефона должен содержать 7 цифр +User ID is required,User ID обязателен +"You selected {0}. Is this the right company? Switching later will require clearing loaded data and re-authenticating.","Вы выбрали {0}. Это нужная компания? Смена выбора позже потребует удаления загруженных данных и повторной авторизации." +"This will delete all loaded tax portal data and any ƏMAS employee selection. You will need to pick a certificate again. Continue?","Будут удалены все загруженные данные с налогового портала и выбранные сотрудники ƏMAS. Вам потребуется выбрать сертификат заново. Продолжить?" +Clearing...,Очистка... +Failed to clear data.,Не удалось очистить данные. +Wrong company? Change,Не та компания? Сменить +Use Next to skip if your company has no employees or is not registered with ƏMAS.,"Нажмите «Далее», чтобы пропустить, если у вашей компании нет сотрудников или она не зарегистрирована в ƏMAS." +Already loaded:,Уже загружено: User ID,User ID ASAN ID,ASAN ID Send request to Asan Imza,Отправить запрос в Asan İmza