diff --git a/jey_wizard/__init__.py b/jey_wizard/__init__.py index 0c5c300..74acd0e 100644 --- a/jey_wizard/__init__.py +++ b/jey_wizard/__init__.py @@ -1 +1 @@ -__version__ = "0.1.11" +__version__ = "0.1.12" diff --git a/jey_wizard/etaxes.py b/jey_wizard/etaxes.py index 8253e63..d713e2a 100644 --- a/jey_wizard/etaxes.py +++ b/jey_wizard/etaxes.py @@ -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) diff --git a/jey_wizard/public/js/jey_setup.js b/jey_wizard/public/js/jey_setup.js index 82e2a48..75b6754 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.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(` +

${__("Administrator account")}

+

${__("Create the day-to-day admin account. The default Administrator login is preserved as a fallback.")}

+
+ + +
+
+ + +
+
+ + +
${__("Minimum 8 characters.")}
+
+
+ + +
+ `); + $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 { : ""}
  • ${__("Fiscal Year")}: ${frappe.utils.escape_html(this.data.fy_start_date)} — ${frappe.utils.escape_html(this.data.fy_end_date)}
  • ${__("Valuation method")}: ${this.data.valuation_method === "Moving Average" ? __("Moving Average") : "FIFO"}
  • +
  • ${__("Admin user")}: ${frappe.utils.escape_html(this.data.user_full_name)} (${frappe.utils.escape_html(this.data.user_email)})
  • ${__("ƏMAS employees")}: ${empRow}
  • `); @@ -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",