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.
This commit is contained in:
Ali 2026-04-24 11:59:53 +00:00
parent e166da49cc
commit 6422129c53
5 changed files with 188 additions and 39 deletions

View File

@ -1 +1 @@
__version__ = "0.0.8"
__version__ = "0.0.9"

View File

@ -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)

View File

@ -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) => `<option value="${p}">0${p}</option>`)
.join("");
$body.html(`
<h3 style="margin-bottom:16px">${__("Asan Imza authentication")}</h3>
<div style="margin-bottom:12px">
<label style="display:block;margin-bottom:4px">${__("Phone number")}</label>
<input type="text" class="jey-asan-phone form-control" style="max-width:280px" placeholder="${__("e.g. 0501234567")}" />
<div style="display:flex;align-items:center;gap:6px;max-width:320px">
<span style="padding:6px 10px;background:#f0f0f0;border:1px solid #d1d1d1;border-radius:4px;color:#555">+994</span>
<select class="jey-asan-prefix form-control" style="width:80px">
<option value="">${__("Prefix")}</option>
${prefixOptions}
</select>
<input type="text" inputmode="numeric" maxlength="7" class="jey-asan-phone form-control" style="flex:1" placeholder="${__("7 digits")}" />
</div>
</div>
<div style="margin-bottom:12px">
<label style="display:block;margin-bottom:4px">${__("User ID")}</label>
@ -155,8 +169,14 @@ frappe.setup.SetupWizard = class JeySetupWizard {
</div>
<button class="btn btn-primary jey-asan-auth" style="margin-top:8px">${__("Send request to Asan Imza")}</button>
`);
$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 {
<div style="font-weight:600;margin-bottom:6px">${__("Cached datasets")}</div>
<ul style="line-height:1.6;margin:0;padding-left:20px">${rows}</ul>
</div>
<div style="margin-top:16px;text-align:right">
<button class="btn btn-default btn-sm jey-change-company">${__("Wrong company? Change")}</button>
</div>
`);
$body.find(".jey-change-company").on("click", () => this.reset_company_selection());
}
}
@ -264,19 +288,17 @@ frappe.setup.SetupWizard = class JeySetupWizard {
<div style="margin-top:12px;padding:12px;background:#fff8e1;border-left:3px solid #e0a800;border-radius:4px">
<b>${__("Heads up:")}</b> ${__("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.")}
</div>
<p style="color:#888;margin-top:12px">${__("This step is optional. Skip it if your company has no employees or is not registered with ƏMAS.")}</p>
<p style="color:#888;margin-top:12px">${__("This step is optional. Use Next to skip if your company has no employees or is not registered with ƏMAS.")}</p>
${alreadyLoaded
? `<div style="margin-top:12px;padding:10px;background:#e8f5e9;border-radius:4px">
${__("Already selected:")} <b>${alreadyLoaded}</b> ${__("employee(s)")}.
${__("Already loaded:")} <b>${alreadyLoaded}</b> ${__("employee(s)")}.
</div>`
: ""}
<div style="margin-top:20px;display:flex;gap:12px">
<button class="btn btn-primary jey-amas-load">${alreadyLoaded ? __("Change selection") : __("Load employees")}</button>
<button class="btn btn-default jey-amas-skip">${__("Skip")}</button>
<div style="margin-top:20px">
<button class="btn btn-primary jey-amas-load">${__("Load employees")}</button>
</div>
`);
$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")}: <b>${newCount < 0 ? n : newCount}</b>, ${__("already in system")}: <b>${existingCount}</b>)
</div>
<p style="color:#666">${__("All employees will be imported into ƏMAS Employees and the standard Employee doctype after the wizard finishes.")}</p>
<div style="margin-top:16px">
<button class="btn btn-default jey-amas-back-intro">${__("Reload from ƏMAS")}</button>
</div>
`);
$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."));

View File

@ -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

1 Step {0} of {1} Addım {0} / {1}
4 Asan Imza authentication Asan İmza ilə giriş
5 Phone number Telefon nömrəsi
6 e.g. 0501234567 məsələn 0501234567
7 Prefix Prefiks
8 7 digits 7 rəqəm
9 Select a phone prefix Telefon prefiksini seçin
10 Phone number must be 7 digits Telefon nömrəsi 7 rəqəmdən ibarət olmalıdır
11 User ID is required User ID tələb olunur
12 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.
13 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?
14 Clearing... Silinir...
15 Failed to clear data. Məlumatı silmək alınmadı.
16 Wrong company? Change Yanlış şirkət? Dəyiş
17 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.
18 Already loaded: Artıq yükləndi:
19 User ID İstifadəçi ID
20 ASAN ID ASAN ID
21 Send request to Asan Imza Asan İmza-ya sorğu göndər

View File

@ -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

1 Step {0} of {1} Шаг {0} из {1}
4 Asan Imza authentication Аутентификация Asan İmza
5 Phone number Номер телефона
6 e.g. 0501234567 например 0501234567
7 Prefix Префикс
8 7 digits 7 цифр
9 Select a phone prefix Выберите префикс номера
10 Phone number must be 7 digits Номер телефона должен содержать 7 цифр
11 User ID is required User ID обязателен
12 You selected {0}. Is this the right company? Switching later will require clearing loaded data and re-authenticating. Вы выбрали {0}. Это нужная компания? Смена выбора позже потребует удаления загруженных данных и повторной авторизации.
13 This will delete all loaded tax portal data and any ƏMAS employee selection. You will need to pick a certificate again. Continue? Будут удалены все загруженные данные с налогового портала и выбранные сотрудники ƏMAS. Вам потребуется выбрать сертификат заново. Продолжить?
14 Clearing... Очистка...
15 Failed to clear data. Не удалось очистить данные.
16 Wrong company? Change Не та компания? Сменить
17 Use Next to skip if your company has no employees or is not registered with ƏMAS. Нажмите «Далее», чтобы пропустить, если у вашей компании нет сотрудников или она не зарегистрирована в ƏMAS.
18 Already loaded: Уже загружено:
19 User ID User ID
20 ASAN ID ASAN ID
21 Send request to Asan Imza Отправить запрос в Asan İmza