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>
This commit is contained in:
Ali 2026-05-15 16:07:21 +00:00
parent b982825c71
commit 55174552c0
10 changed files with 73 additions and 87 deletions

View File

@ -29,7 +29,7 @@ The UI warns about the second tap on the employees step intro. Do not try to uni
Single doctype that carries state between wizard steps and `setup_wizard_complete`:
- `objects_json` / `cash_registers_json` / `pos_terminals_json` / `bank_accounts_json` / `obligation_pacts_json` / `presented_certs_json` — written by `etaxes.py:fetch_all_etaxes` during the Asan step, **not read back** on finalize (see next point).
- `objects_json` / `cash_registers_json` / `pos_terminals_json` / `bank_accounts_json` / `presented_certs_json` — written by `etaxes.py:fetch_all_etaxes` during the Asan step, **not read back** on finalize (see next point).
- `amas_selected_employees_json` — written by `amas.py:cache_selected_employees`, read by `etaxes.py:_materialize_amas_employees` in the `setup_wizard_complete` hook.
- `last_summary` / `last_error` / `fetched_at` — displayed on the Asan done screen.

View File

@ -1 +1 @@
__version__ = "0.1.26"
__version__ = "0.1.27"

View File

@ -90,7 +90,6 @@ def reset_company_selection(asan_login_name):
"cash_registers_json",
"pos_terminals_json",
"bank_accounts_json",
"obligation_pacts_json",
"presented_certs_json",
"amas_selected_employees_json",
"last_summary",

View File

@ -54,13 +54,6 @@ ENDPOINTS = [
None,
False,
),
(
"obligation_pacts_json",
"GET",
f"{BASE}/api/po/edi-legal/public/v1/application/sub-contractor/obl-pact-list",
None,
False,
),
(
"presented_certs_json",
"GET",
@ -79,7 +72,6 @@ KIND_TO_FIELD = {
"cash_registers": "cash_registers_json",
"pos_terminals": "pos_terminals_json",
"bank_accounts": "bank_accounts_json",
"obligation_pacts": "obligation_pacts_json",
"presented_certs": "presented_certs_json",
}
@ -236,7 +228,6 @@ def get_preview_data():
"cash_registers": _preview_cash_registers(cache.cash_registers_json),
"pos_terminals": _preview_pos_terminals(cache.pos_terminals_json),
"bank_accounts": _preview_bank_accounts(cache.bank_accounts_json),
"obligation_pacts": _preview_obligation_pacts(cache.obligation_pacts_json),
"presented_certs": _preview_presented_certs(cache.presented_certs_json),
"profile": _preview_profile(cache.company_profile_json),
}
@ -356,34 +347,6 @@ def _preview_bank_accounts(raw):
return {"headers": headers, "rows": rows}
def _preview_obligation_pacts(raw):
headers = [_("Code"), _("Name"), _("Valid from"), _("Expires")]
data = _safe_parse(raw)
if data is None:
return _empty_preview(headers)
items = []
if isinstance(data, dict):
items = data.get("oblPacts") or []
rows = []
for item in items[:PREVIEW_ROW_LIMIT]:
if not isinstance(item, dict):
continue
pact = item.get("oblPact") or {}
name_blob = pact.get("name")
# name may be {"az": "..."} or a plain string depending on tenant.
if isinstance(name_blob, dict):
name = name_blob.get("az") or name_blob.get("en") or next(iter(name_blob.values()), "")
else:
name = name_blob or ""
rows.append([
pact.get("code") or "",
name,
(item.get("validityDate") or "")[:10],
(item.get("expireDate") or "")[:10],
])
return {"headers": headers, "rows": rows}
def _preview_presented_certs(raw):
headers = [_("Cert №"), _("Category"), _("State"), _("Counterparty"), _("VÖEN"), _("Operation date")]
data = _safe_parse(raw)
@ -569,7 +532,6 @@ def materialize_after_setup(args):
("cash_registers", company_api.load_company_cash_registers, "E-Taxes Cash Register"),
("pos_terminals", company_api.load_company_pos_terminals, "E-Taxes POS Terminal"),
("bank_accounts", company_api.load_company_bank_accounts, "E-Taxes Bank Account"),
("obligation_pacts", company_api.load_company_obligation_pacts, "E-Taxes Obligation Pact"),
("presented_certs", company_api.load_company_presented_certificates, "E-Taxes Presented Certificate"),
]
@ -1453,8 +1415,6 @@ def _purge_inactive_etaxes_records(company):
invoice_az.send_sales_api which filters status=='A' before send).
- E-Taxes Cash Register: status_code='A' means active.
- E-Taxes POS Terminal: status='A' means active.
- E-Taxes Obligation Pact: rows with expire_date in the past are
expired drop.
Presented Certificates carry historical state (cert_state) but are
primarily an audit record we leave them alone.
@ -1462,8 +1422,6 @@ def _purge_inactive_etaxes_records(company):
Only deletes rows belonging to the freshly-created Company so prior
data on the site stays untouched if anyone re-runs the materialize.
"""
from frappe.utils import getdate, today
report = {}
def _delete_where(doctype, filters, label):
@ -1516,27 +1474,6 @@ def _purge_inactive_etaxes_records(company):
"pos_terminals_inactive",
)
# Expired obligation pacts: expire_date strictly in the past
try:
expired = frappe.get_all(
"E-Taxes Obligation Pact",
filters={"company": company, "expire_date": ("<", today())},
pluck="name",
)
deleted = 0
for name in expired:
try:
frappe.delete_doc("E-Taxes Obligation Pact", name, ignore_permissions=True, delete_permanently=True)
deleted += 1
except Exception as exc:
frappe.log_error(
f"_purge_inactive_etaxes_records: delete pact/{name}: {exc}",
"Jey Wizard purge inactive",
)
report["obligation_pacts_expired"] = {"deleted": deleted, "matched": len(expired)}
except Exception as exc:
report["obligation_pacts_expired"] = {"error": str(exc)[:200]}
frappe.db.commit()
frappe.log_error(
json.dumps(report, ensure_ascii=False, indent=2),
@ -1872,7 +1809,6 @@ def _log_materialize_entry(args):
"cash_registers_json_len": len(c.cash_registers_json or ""),
"pos_terminals_json_len": len(c.pos_terminals_json or ""),
"bank_accounts_json_len": len(c.bank_accounts_json or ""),
"obligation_pacts_json_len": len(c.obligation_pacts_json or ""),
"presented_certs_json_len": len(c.presented_certs_json or ""),
"amas_selected_employees_json_len": len(c.amas_selected_employees_json or ""),
}

View File

@ -14,7 +14,6 @@
"cash_registers_json",
"pos_terminals_json",
"bank_accounts_json",
"obligation_pacts_json",
"presented_certs_json",
"company_profile_section",
"company_profile_json",
@ -79,13 +78,6 @@
"options": "JSON",
"read_only": 1
},
{
"fieldname": "obligation_pacts_json",
"fieldtype": "Code",
"label": "Obligation Pacts",
"options": "JSON",
"read_only": 1
},
{
"fieldname": "presented_certs_json",
"fieldtype": "Code",

View File

@ -4,3 +4,4 @@
[post_model_sync]
# Patches added in this section will be executed after doctypes are migrated
jey_wizard.patches.v0_1_27_remove_obligation_pacts

View File

@ -0,0 +1,56 @@
"""Drop the E-Taxes Obligation Pact doctype and its leftovers.
The doctype is gone from invoice_az on disk, so `bench migrate` would leave
the DB rows (table + DocType metadata + Custom Field rows + orphan single
values) behind. This patch cleans those up on existing sites.
"""
import frappe
def execute():
# 1. Drop the Company Custom Fields that hosted the in-form list.
for fn in ("obligation_pacts_list_section", "obligation_pacts_list_html"):
for name in frappe.get_all(
"Custom Field",
filters={"dt": "Company", "fieldname": fn},
pluck="name",
):
try:
frappe.delete_doc(
"Custom Field", name, ignore_permissions=True, delete_permanently=True
)
except Exception as exc:
frappe.log_error(
f"remove Custom Field {name}: {exc}",
"jey_wizard patch: remove obligation pacts",
)
# 2. Drop the doctype itself. delete_doc handles meta + DocField + table.
if frappe.db.exists("DocType", "E-Taxes Obligation Pact"):
try:
frappe.delete_doc(
"DocType", "E-Taxes Obligation Pact", ignore_permissions=True, force=True
)
except Exception as exc:
frappe.log_error(
f"delete DocType failed, falling back to manual cleanup: {exc}",
"jey_wizard patch: remove obligation pacts",
)
frappe.db.sql_ddl("DROP TABLE IF EXISTS `tabE-Taxes Obligation Pact`")
frappe.db.delete("DocType", {"name": "E-Taxes Obligation Pact"})
frappe.db.delete("DocField", {"parent": "E-Taxes Obligation Pact"})
else:
# DocType row already gone, make sure the table doesn't linger.
frappe.db.sql_ddl("DROP TABLE IF EXISTS `tabE-Taxes Obligation Pact`")
# 3. Orphan value left in tabSingles after we dropped the field from the
# Jey Wizard Etaxes Cache JSON. Single docs store rows as
# (doctype, field, value) — clear it so frappe.get_single doesn't surface
# the obsolete payload anywhere.
frappe.db.delete(
"Singles",
{"doctype": "Jey Wizard Etaxes Cache", "field": "obligation_pacts_json"},
)
frappe.db.commit()

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.1.26";
const JEY_WIZARD_VERSION = "0.1.27";
// 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.
@ -269,7 +269,7 @@ frappe.setup.SetupWizard = class JeySetupWizard {
} else if (this.asan_state === "fetching") {
$body.html(`
<h3 style="margin-bottom:16px">${__("Loading data from tax portal...")}</h3>
<p style="color:#666">${__("Pulling objects, cash registers, POS terminals, bank accounts, obligation pacts and presented certificates. This usually takes 10-30 seconds.")}</p>
<p style="color:#666">${__("Pulling objects, cash registers, POS terminals, bank accounts and presented certificates. This usually takes 10-30 seconds.")}</p>
<div style="display:flex;align-items:center;gap:12px;margin-top:20px">
<div class="jey-spinner" style="width:18px;height:18px;border:3px solid #ddd;border-top-color:#444;border-radius:50%;animation:jey-spin 0.8s linear infinite"></div>
<div style="color:#666">${__("Fetching...")}</div>
@ -284,9 +284,12 @@ frappe.setup.SetupWizard = class JeySetupWizard {
["cash_registers_json", "cash_registers", __("Cash registers")],
["pos_terminals_json", "pos_terminals", __("POS terminals")],
["bank_accounts_json", "bank_accounts", __("Bank accounts")],
["obligation_pacts_json", "obligation_pacts", __("Obligation pacts")],
["presented_certs_json", "presented_certs", __("Presented certificates")],
];
// Whole row is the clickable target so users don't have to aim
// at the digit. Anchor strips default underline + color, gets a
// soft hover background.
const linkStyle = "display:block;padding:4px 6px;border-radius:3px;text-decoration:none;color:inherit;cursor:pointer";
const rows = datasets.map(([field, kind, label]) => {
if (errors[field]) {
return `<li><b>${label}:</b> <span style="color:#c00">${__("failed")}</span> <span style="color:#888;font-size:12px">(${frappe.utils.escape_html(errors[field])})</span></li>`;
@ -296,17 +299,18 @@ frappe.setup.SetupWizard = class JeySetupWizard {
return `<li><b>${label}:</b> <span style="color:#888">—</span></li>`;
}
if (n > 0) {
return `<li><b>${label}:</b> <a href="#" class="jey-preview-link" data-kind="${kind}" data-title="${frappe.utils.escape_html(label)}">${n}</a> <span style="color:#888;font-size:12px">${__("(click to preview)")}</span></li>`;
return `<li style="list-style:none;margin-left:-20px"><a href="#" class="jey-preview-link" data-kind="${kind}" data-title="${frappe.utils.escape_html(label)}" style="${linkStyle}"><b>${label}:</b> ${n} <span style="color:#888;font-size:12px">${__("(click to preview)")}</span></a></li>`;
}
return `<li><b>${label}:</b> ${n}</li>`;
}).join("");
$body.html(`
<style>.jey-preview-link:hover{background:#eef3fb;color:inherit;text-decoration:none}</style>
<h3 style="margin-bottom:16px;color:#080"> ${__("Authenticated & data loaded")}</h3>
<p>${__("Pulled from the tax portal:")}</p>
<ul style="line-height:1.8">
<li><b>${__("Company")}:</b> <a href="#" class="jey-preview-link" data-kind="profile" data-title="${frappe.utils.escape_html(__("Company profile"))}">${frappe.utils.escape_html(this.data.company_name)}</a> <span style="color:#888;font-size:12px">${__("(click to preview)")}</span></li>
<li><b>${__("VÖEN")}:</b> ${frappe.utils.escape_html(this.data.asan_voen)}</li>
<ul style="line-height:1.8;list-style:none;padding-left:0">
<li><a href="#" class="jey-preview-link" data-kind="profile" data-title="${frappe.utils.escape_html(__("Company profile"))}" style="${linkStyle}"><b>${__("Company")}:</b> ${frappe.utils.escape_html(this.data.company_name)} <span style="color:#888;font-size:12px">${__("(click to preview)")}</span></a></li>
<li style="padding:4px 6px"><b>${__("VÖEN")}:</b> ${frappe.utils.escape_html(this.data.asan_voen)}</li>
</ul>
<div style="margin-top:16px;padding:12px;background:#f7f7f7;border-radius:6px">
<div style="font-weight:600;margin-bottom:6px">${__("Loaded datasets")}</div>

View File

@ -31,7 +31,7 @@ Company,Şirkət
VÖEN,VÖEN
"No legal-entity certificates available on this Asan ID. Use a phone/ID linked to a company.","Bu Asan ID üzrə hüquqi şəxs sertifikatı yoxdur. Şirkətə bağlı telefon/ID istifadə edin."
Loading data from tax portal...,Vergi portalından məlumat yüklənir...
"Pulling objects, cash registers, POS terminals, bank accounts, obligation pacts and presented certificates. This usually takes 10-30 seconds.","Obyektlər, kassa aparatları, POS terminallar, bank hesabları, öhdəlik razılaşmaları və təqdim edilmiş sertifikatlar yüklənir. Adətən 1030 saniyə çəkir."
"Pulling objects, cash registers, POS terminals, bank accounts and presented certificates. This usually takes 10-30 seconds.","Obyektlər, kassa aparatları, POS terminallar, bank hesabları və təqdim edilmiş sertifikatlar yüklənir. Adətən 1030 saniyə çəkir."
Fetching...,Yüklənir...
Authenticated & data loaded,"Giriş edildi, məlumat yükləndi"
Pulled from the tax portal:,Vergi portalından yükləndi:
@ -39,7 +39,6 @@ Objects,Obyektlər
Cash registers,Kassa aparatları
POS terminals,POS terminallar
Bank accounts,Bank hesabları
Obligation pacts,Öhdəlik razılaşmaları
Presented certificates,Təqdim edilmiş sertifikatlar
failed,uğursuz
Cached datasets,Keşlənmiş məlumatlar

1 Step {0} of {1} Addım {0} / {1}
31 VÖEN VÖEN
32 No legal-entity certificates available on this Asan ID. Use a phone/ID linked to a company. Bu Asan ID üzrə hüquqi şəxs sertifikatı yoxdur. Şirkətə bağlı telefon/ID istifadə edin.
33 Loading data from tax portal... Vergi portalından məlumat yüklənir...
34 Pulling objects, cash registers, POS terminals, bank accounts, obligation pacts and presented certificates. This usually takes 10-30 seconds. Pulling objects, cash registers, POS terminals, bank accounts and presented certificates. This usually takes 10-30 seconds. Obyektlər, kassa aparatları, POS terminallar, bank hesabları, öhdəlik razılaşmaları və təqdim edilmiş sertifikatlar yüklənir. Adətən 10–30 saniyə çəkir. Obyektlər, kassa aparatları, POS terminallar, bank hesabları və təqdim edilmiş sertifikatlar yüklənir. Adətən 10–30 saniyə çəkir.
35 Fetching... Yüklənir...
36 Authenticated & data loaded Giriş edildi, məlumat yükləndi
37 Pulled from the tax portal: Vergi portalından yükləndi:
39 Cash registers Kassa aparatları
40 POS terminals POS terminallar
41 Bank accounts Bank hesabları
Obligation pacts Öhdəlik razılaşmaları
42 Presented certificates Təqdim edilmiş sertifikatlar
43 failed uğursuz
44 Cached datasets Keşlənmiş məlumatlar

View File

@ -31,7 +31,7 @@ Company,Компания
VÖEN,ВÖЕН
"No legal-entity certificates available on this Asan ID. Use a phone/ID linked to a company.","На этом Asan ID нет сертификатов юр. лиц. Используйте телефон/ID привязанный к компании."
Loading data from tax portal...,Загрузка данных с налогового портала...
"Pulling objects, cash registers, POS terminals, bank accounts, obligation pacts and presented certificates. This usually takes 10-30 seconds.","Загружаем объекты, кассовые аппараты, POS-терминалы, банковские счета, договоры и представленные сертификаты. Обычно занимает 1030 секунд."
"Pulling objects, cash registers, POS terminals, bank accounts and presented certificates. This usually takes 10-30 seconds.","Загружаем объекты, кассовые аппараты, POS-терминалы, банковские счета и представленные сертификаты. Обычно занимает 1030 секунд."
Fetching...,Загрузка...
Authenticated & data loaded,"Аутентификация пройдена, данные загружены"
Pulled from the tax portal:,Загружено с налогового портала:
@ -39,7 +39,6 @@ Objects,Объекты
Cash registers,Кассовые аппараты
POS terminals,POS-терминалы
Bank accounts,Банковские счета
Obligation pacts,Договоры-обязательства
Presented certificates,Представленные сертификаты
failed,ошибка
Cached datasets,Закэшированные данные

1 Step {0} of {1} Шаг {0} из {1}
31 VÖEN ВÖЕН
32 No legal-entity certificates available on this Asan ID. Use a phone/ID linked to a company. На этом Asan ID нет сертификатов юр. лиц. Используйте телефон/ID привязанный к компании.
33 Loading data from tax portal... Загрузка данных с налогового портала...
34 Pulling objects, cash registers, POS terminals, bank accounts, obligation pacts and presented certificates. This usually takes 10-30 seconds. Pulling objects, cash registers, POS terminals, bank accounts and presented certificates. This usually takes 10-30 seconds. Загружаем объекты, кассовые аппараты, POS-терминалы, банковские счета, договоры и представленные сертификаты. Обычно занимает 10–30 секунд. Загружаем объекты, кассовые аппараты, POS-терминалы, банковские счета и представленные сертификаты. Обычно занимает 10–30 секунд.
35 Fetching... Загрузка...
36 Authenticated & data loaded Аутентификация пройдена, данные загружены
37 Pulled from the tax portal: Загружено с налогового портала:
39 Cash registers Кассовые аппараты
40 POS terminals POS-терминалы
41 Bank accounts Банковские счета
Obligation pacts Договоры-обязательства
42 Presented certificates Представленные сертификаты
43 failed ошибка
44 Cached datasets Закэшированные данные