Frappe Date controls, finalize progress bar, full message_log clear

Three UX fixes for the post-setup experience:

1. Native Frappe Date pickers on the company step
   Replaces <input type="date"> for fy_start/fy_end with
   frappe.ui.form.make_control({fieldtype:"Date"}). Same Flatpickr-
   styled control the rest of the desk uses. Values come back as
   YYYY-MM-DD via get_value(), matching the previous .val() contract.

2. Inline progress bar instead of the freeze overlay
   finalize() now renders a status line + animated bar in place of
   the wizard form, and drops freeze:true so the bar is visible.
   Subscribes to the realtime "setup_task" channel that
   process_setup_stages publishes — translates the [done, total]
   tuples into a 5%→95% bar fill, finishes at 100% on success, and
   handles the realtime "fail" case by switching to the error view.

3. Wipe all setup-time messages, not just alerts
   _drop_alert_messages → frappe.clear_messages(). The old filter
   missed non-alert msgprints, so HRMS Salary Component "Hesablar
   təyin edilməyib" warnings and Frappe's "Employee Self Service rolu
   silindi" note for the new admin user still surfaced as a modal.
   Genuine failures raise; everything queued for the response by this
   point is just noise for an automated run.
This commit is contained in:
Ali 2026-04-29 16:28:55 +00:00
parent 7a9c25ba35
commit 40f65ffd90
3 changed files with 87 additions and 29 deletions

View File

@ -1 +1 @@
__version__ = "0.1.15" __version__ = "0.1.16"

View File

@ -1103,24 +1103,22 @@ def _find_default_warehouse(company):
def _drop_alert_messages(): def _drop_alert_messages():
"""Remove every entry from frappe.local.message_log that was queued via """Wipe everything in frappe.local.message_log that piled up during the
frappe.msgprint(alert=True) (a.k.a. toast). Keeps non-alert msgprints so setup_complete request. We need the entire log gone, not just alerts:
the user still sees real warnings. besides the az_locale rename toasts (alert=True), HRMS Salary Component
warnings ("Hesablar təyin edilməyib") and Frappe's own user-creation
notes ("Employee Self Service rolu silindi") come through plain
msgprints and they're noise for an automated setup. Genuine failures
would have raised an exception and been handled separately, so blanket
clearing is safe here.
""" """
try: try:
log = list(getattr(frappe.local, "message_log", []) or []) frappe.clear_messages()
except Exception: except Exception:
return
if not log:
return
def _is_alert(m):
try: try:
if isinstance(m, dict): frappe.local.message_log = []
return bool(m.get("alert"))
return bool(getattr(m, "alert", 0))
except Exception: except Exception:
return False pass
frappe.local.message_log = [m for m in log if not _is_alert(m)]
def _only_admin(): def _only_admin():

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 // 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 // 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. // sync with __version__ in jey_wizard/__init__.py.
const JEY_WIZARD_VERSION = "0.1.15"; const JEY_WIZARD_VERSION = "0.1.16";
// Wipe Frappe + ERPNext default slides so their `before_load`/`after_load` listeners // 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. // don't try to mutate a wizard that isn't slide-based anymore.
@ -415,11 +415,11 @@ frappe.setup.SetupWizard = class JeySetupWizard {
<div style="display:flex;gap:16px;max-width:420px"> <div style="display:flex;gap:16px;max-width:420px">
<div style="flex:1"> <div style="flex:1">
<label style="display:block;margin-bottom:4px">${__("Start date")}</label> <label style="display:block;margin-bottom:4px">${__("Start date")}</label>
<input type="date" class="jey-fy-start form-control" /> <div class="jey-fy-start"></div>
</div> </div>
<div style="flex:1"> <div style="flex:1">
<label style="display:block;margin-bottom:4px">${__("End date")}</label> <label style="display:block;margin-bottom:4px">${__("End date")}</label>
<input type="date" class="jey-fy-end form-control" /> <div class="jey-fy-end"></div>
</div> </div>
</div> </div>
<div style="color:#888;font-size:12px;margin-top:6px">${__("Default: current calendar year (Jan 1 Dec 31).")}</div> <div style="color:#888;font-size:12px;margin-top:6px">${__("Default: current calendar year (Jan 1 Dec 31).")}</div>
@ -443,8 +443,25 @@ frappe.setup.SetupWizard = class JeySetupWizard {
</div> </div>
`); `);
$body.find(".jey-company-name").val(this.data.company_name); $body.find(".jey-company-name").val(this.data.company_name);
$body.find(".jey-fy-start").val(this.data.fy_start_date);
$body.find(".jey-fy-end").val(this.data.fy_end_date); // Mount native Frappe Date controls (Flatpickr-styled) into the
// jey-fy-start / jey-fy-end placeholders. Plain <input type="date">
// looks dated and inconsistent with the rest of Frappe's UI. The
// controls expose set_value / get_value; we wire those into the
// usual data dict.
this.fy_start_ctrl = frappe.ui.form.make_control({
df: { fieldtype: "Date", fieldname: "fy_start_date", placeholder: "" },
parent: $body.find(".jey-fy-start")[0],
render_input: true,
});
this.fy_start_ctrl.set_value(this.data.fy_start_date || "");
this.fy_end_ctrl = frappe.ui.form.make_control({
df: { fieldtype: "Date", fieldname: "fy_end_date", placeholder: "" },
parent: $body.find(".jey-fy-end")[0],
render_input: true,
});
this.fy_end_ctrl.set_value(this.data.fy_end_date || "");
$body.find(".jey-valuation-method").val(this.data.valuation_method || "FIFO"); $body.find(".jey-valuation-method").val(this.data.valuation_method || "FIFO");
$body.find(".jey-accounting-method").val(this.data.accounting_method || "Hesablama metodu"); $body.find(".jey-accounting-method").val(this.data.accounting_method || "Hesablama metodu");
// Show why the default landed where it did, only when the auto-pick // Show why the default landed where it did, only when the auto-pick
@ -676,8 +693,10 @@ frappe.setup.SetupWizard = class JeySetupWizard {
return false; return false;
} }
this.data.company_abbr = abbr; this.data.company_abbr = abbr;
const fyStart = (this.$wrap.find(".jey-fy-start").val() || "").trim(); // Frappe Date controls return the value as YYYY-MM-DD or "" when
const fyEnd = (this.$wrap.find(".jey-fy-end").val() || "").trim(); // blank. Mirrors the previous .val() contract on <input type="date">.
const fyStart = String((this.fy_start_ctrl && this.fy_start_ctrl.get_value()) || "").trim();
const fyEnd = String((this.fy_end_ctrl && this.fy_end_ctrl.get_value()) || "").trim();
if (!fyStart || !fyEnd) { if (!fyStart || !fyEnd) {
this.$wrap.find(".jey-status").text(__("Fiscal year start and end dates are required")); this.$wrap.find(".jey-status").text(__("Fiscal year start and end dates are required"));
return false; return false;
@ -1322,35 +1341,75 @@ frappe.setup.SetupWizard = class JeySetupWizard {
} }
finalize() { finalize() {
this.$wrap.find(".jey-body").html( // Render an inline progress UI in place of the wizard form. We don't
`<p>${__("Setting up your system. This can take a minute...")}</p>` // pass freeze:true to frappe.call — the freeze overlay would dim and
); // hide the bar. process_setup_stages publishes realtime "setup_task"
// events with {progress: [idx, total], stage_status: "..."}; we
// subscribe and translate those into bar width + status text.
this.$wrap.find(".jey-body").html(`
<h3 style="margin-bottom:12px">${__("Setting up your system")}</h3>
<div class="jey-finalize-status" style="color:#666;margin-bottom:12px;min-height:1.4em">${__("Starting...")}</div>
<div style="background:#eee;border-radius:6px;height:12px;overflow:hidden">
<div class="jey-finalize-bar" style="background:#3b82f6;height:100%;width:5%;transition:width 0.4s ease"></div>
</div>
<div class="jey-finalize-pct" style="color:#888;font-size:12px;margin-top:6px;text-align:right">5%</div>
`);
this.$wrap.find(".jey-nav").empty(); this.$wrap.find(".jey-nav").empty();
this.$wrap.find(".jey-status").empty(); this.$wrap.find(".jey-status").empty();
const setProgress = (pct, status) => {
pct = Math.max(0, Math.min(100, Math.round(pct)));
this.$wrap.find(".jey-finalize-bar").css("width", pct + "%");
this.$wrap.find(".jey-finalize-pct").text(pct + "%");
if (status) this.$wrap.find(".jey-finalize-status").text(status);
};
// Realtime subscription. Each "setup_task" event carries either a
// progress tuple or a final status. Cap displayed pct at 95% during
// the run so the bar visibly completes when the HTTP response lands.
const onSetupTask = (data) => {
if (!data) return;
if (Array.isArray(data.progress)) {
const [done, total] = data.progress;
if (total > 0) {
const ratio = Math.min(done / total, 0.95);
setProgress(5 + ratio * 90, data.stage_status || __("Working..."));
}
}
if (data.status === "fail") {
this.show_error(
data.fail_msg || __("Setup failed"),
data.exc || data.exception || __("(check server logs)")
);
}
};
frappe.realtime.on("setup_task", onSetupTask);
const cleanup = () => frappe.realtime.off("setup_task", onSetupTask);
const values = this.build_setup_args(); const values = this.build_setup_args();
frappe.call({ frappe.call({
method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete", method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete",
args: { args: values }, args: { args: values },
freeze: true,
callback: (r) => { callback: (r) => {
const m = r.message || {}; const m = r.message || {};
if (m.status === "ok") { if (m.status === "ok") {
this.$wrap.find(".jey-body").html( setProgress(100, __("Setup complete! Redirecting..."));
`<p>${__("Setup complete! Redirecting...")}</p>` cleanup();
);
setTimeout(() => { setTimeout(() => {
window.location.href = "/app"; window.location.href = "/app";
}, 1500); }, 800);
} else if (m.status === "registered") { } else if (m.status === "registered") {
cleanup();
this.show_error( this.show_error(
__("Setup is running in background mode, not supported by skeleton"), __("Setup is running in background mode, not supported by skeleton"),
JSON.stringify(m, null, 2) JSON.stringify(m, null, 2)
); );
} else if (m.fail !== undefined) { } else if (m.fail !== undefined) {
cleanup();
this.show_error(m.fail, JSON.stringify(m, null, 2)); this.show_error(m.fail, JSON.stringify(m, null, 2));
} else { } else {
cleanup();
this.show_error( this.show_error(
__("Setup returned unexpected response"), __("Setup returned unexpected response"),
JSON.stringify(m, null, 2) JSON.stringify(m, null, 2)
@ -1358,6 +1417,7 @@ frappe.setup.SetupWizard = class JeySetupWizard {
} }
}, },
error: () => { error: () => {
cleanup();
const resp = frappe.last_response || {}; const resp = frappe.last_response || {};
this.show_error( this.show_error(
resp.setup_wizard_failure_message || __("Setup failed"), resp.setup_wizard_failure_message || __("Setup failed"),