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.
This commit is contained in:
Ali 2026-04-28 12:44:45 +00:00
parent b739f06c98
commit d93a024ba8
3 changed files with 105 additions and 7 deletions

View File

@ -1 +1 @@
__version__ = "0.1.11"
__version__ = "0.1.12"

View File

@ -260,6 +260,14 @@ def materialize_after_setup(args):
_materialize_amas_employees(company_name)
# Drop toast-style messages that piled up during the various install/translate
# stages — notably az_locale's "Sənəd X yenidən adlandırıldı" notifications
# fired when fixtures get renamed to Azerbaijani. These are cosmetic and would
# spam the post-setup screen. Modal warnings (non-alert msgprints — e.g. HRMS
# Salary Component "accounts not assigned") stay so genuine open items remain
# visible to the user.
_drop_alert_messages()
def _materialize_native_banks(company):
"""Create ERPNext-native Bank / GL Account / Bank Account for each non-closed
@ -758,6 +766,27 @@ def _find_default_warehouse(company):
)
def _drop_alert_messages():
"""Remove every entry from frappe.local.message_log that was queued via
frappe.msgprint(alert=True) (a.k.a. toast). Keeps non-alert msgprints so
the user still sees real warnings.
"""
try:
log = list(getattr(frappe.local, "message_log", []) or [])
except Exception:
return
if not log:
return
def _is_alert(m):
try:
if isinstance(m, dict):
return bool(m.get("alert"))
return bool(getattr(m, "alert", 0))
except Exception:
return False
frappe.local.message_log = [m for m in log if not _is_alert(m)]
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.1.11";
const JEY_WIZARD_VERSION = "0.1.12";
// 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.
@ -53,6 +53,10 @@ frappe.setup.SetupWizard = class JeySetupWizard {
fy_start_date: `${year}-01-01`,
fy_end_date: `${year}-12-31`,
valuation_method: "FIFO",
// Admin user (fresh user step — Administrator stays untouched)
user_full_name: "",
user_email: "",
user_password: "",
// AMAS
amas_skipped: false,
amas_selected_employees: [],
@ -75,7 +79,7 @@ frappe.setup.SetupWizard = class JeySetupWizard {
this.amas_verification_code = "";
this.current_step = 0;
this.steps = ["language", "asan", "employees", "company", "confirm"];
this.steps = ["language", "asan", "employees", "company", "user", "confirm"];
this.render();
// Session arrives in whatever language Frappe booted in (usually English for
@ -121,6 +125,7 @@ frappe.setup.SetupWizard = class JeySetupWizard {
else if (step === "asan") this.render_asan($body);
else if (step === "employees") this.render_employees($body);
else if (step === "company") this.render_company($body);
else if (step === "user") this.render_user($body);
else if (step === "confirm") this.render_confirm($body);
this.render_nav($nav, step);
@ -514,6 +519,38 @@ frappe.setup.SetupWizard = class JeySetupWizard {
return initialsOf(clean, 4).slice(0, 10);
}
render_user($body) {
// Collect a real admin user separate from Frappe's Administrator. We
// pass full_name/email/password into setup_complete; Frappe will create
// this user during run_setup_success. Administrator itself stays as
// the system-of-last-resort fallback login.
$body.html(`
<h3 style="margin-bottom:8px">${__("Administrator account")}</h3>
<p style="color:#888;font-size:13px;margin-bottom:16px">${__("Create the day-to-day admin account. The default Administrator login is preserved as a fallback.")}</p>
<div style="margin-bottom:12px;max-width:420px">
<label style="display:block;margin-bottom:4px">${__("Full name")}</label>
<input type="text" class="jey-user-name form-control" autocomplete="name" />
</div>
<div style="margin-bottom:12px;max-width:420px">
<label style="display:block;margin-bottom:4px">${__("Email (login)")}</label>
<input type="email" class="jey-user-email form-control" autocomplete="email" />
</div>
<div style="margin-bottom:12px;max-width:420px">
<label style="display:block;margin-bottom:4px">${__("Password")}</label>
<input type="password" class="jey-user-password form-control" autocomplete="new-password" />
<div style="color:#888;font-size:12px;margin-top:4px">${__("Minimum 8 characters.")}</div>
</div>
<div style="margin-bottom:12px;max-width:420px">
<label style="display:block;margin-bottom:4px">${__("Confirm password")}</label>
<input type="password" class="jey-user-password2 form-control" autocomplete="new-password" />
</div>
`);
$body.find(".jey-user-name").val(this.data.user_full_name);
$body.find(".jey-user-email").val(this.data.user_email);
$body.find(".jey-user-password").val(this.data.user_password);
$body.find(".jey-user-password2").val(this.data.user_password);
}
render_confirm($body) {
const empCount = (this.data.amas_selected_employees || []).length;
const empRow = this.data.amas_skipped
@ -529,6 +566,7 @@ frappe.setup.SetupWizard = class JeySetupWizard {
: ""}
<li><b>${__("Fiscal Year")}:</b> ${frappe.utils.escape_html(this.data.fy_start_date)} ${frappe.utils.escape_html(this.data.fy_end_date)}</li>
<li><b>${__("Valuation method")}:</b> ${this.data.valuation_method === "Moving Average" ? __("Moving Average") : "FIFO"}</li>
<li><b>${__("Admin user")}:</b> ${frappe.utils.escape_html(this.data.user_full_name)} <span style="color:#888">(${frappe.utils.escape_html(this.data.user_email)})</span></li>
<li><b>${__("ƏMAS employees")}:</b> ${empRow}</li>
</ul>
`);
@ -618,6 +656,35 @@ frappe.setup.SetupWizard = class JeySetupWizard {
this.data.fy_end_date = fyEnd;
const vm = (this.$wrap.find(".jey-valuation-method").val() || "FIFO").trim();
this.data.valuation_method = vm === "Moving Average" ? "Moving Average" : "FIFO";
} else if (step === "user") {
const fullName = (this.$wrap.find(".jey-user-name").val() || "").trim();
const email = (this.$wrap.find(".jey-user-email").val() || "").trim().toLowerCase();
const pw = this.$wrap.find(".jey-user-password").val() || "";
const pw2 = this.$wrap.find(".jey-user-password2").val() || "";
if (!fullName) {
this.$wrap.find(".jey-status").text(__("Full name is required"));
return false;
}
// Permissive check — Frappe revalidates on the server.
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
this.$wrap.find(".jey-status").text(__("Enter a valid email"));
return false;
}
if (email === "administrator" || email === "admin@example.com") {
this.$wrap.find(".jey-status").text(__("Pick a different email — that one is reserved"));
return false;
}
if (pw.length < 8) {
this.$wrap.find(".jey-status").text(__("Password must be at least 8 characters"));
return false;
}
if (pw !== pw2) {
this.$wrap.find(".jey-status").text(__("Passwords do not match"));
return false;
}
this.data.user_full_name = fullName;
this.data.user_email = email;
this.data.user_password = pw;
}
return true;
}
@ -1162,7 +1229,6 @@ frappe.setup.SetupWizard = class JeySetupWizard {
this.derive_abbr(company) ||
"CO"
).slice(0, 10);
const admin = frappe.boot.user || {};
return {
language: LANG_NAME[this.data.language] || "English",
@ -1170,9 +1236,12 @@ frappe.setup.SetupWizard = class JeySetupWizard {
timezone: "Asia/Baku",
currency: "AZN",
enable_telemetry: 0,
full_name: admin.full_name || "Administrator",
email: admin.email || "admin@example.com",
password: "",
// Admin user fields come from the dedicated `user` step. We never
// reuse Administrator's identity here — that's why the previous
// "username `administrator` already exists" warning showed up.
full_name: this.data.user_full_name,
email: this.data.user_email,
password: this.data.user_password,
company_name: company,
company_abbr: abbr,
chart_of_accounts: "Azerbaijan Republic",