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():
"""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.
"""Wipe everything in frappe.local.message_log that piled up during the
setup_complete request. We need the entire log gone, not just alerts:
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:
log = list(getattr(frappe.local, "message_log", []) or [])
frappe.clear_messages()
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))
frappe.local.message_log = []
except Exception:
return False
frappe.local.message_log = [m for m in log if not _is_alert(m)]
pass
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
// 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.15";
const JEY_WIZARD_VERSION = "0.1.16";
// 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.
@ -415,11 +415,11 @@ frappe.setup.SetupWizard = class JeySetupWizard {
<div style="display:flex;gap:16px;max-width:420px">
<div style="flex:1">
<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 style="flex:1">
<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 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>
`);
$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-accounting-method").val(this.data.accounting_method || "Hesablama metodu");
// Show why the default landed where it did, only when the auto-pick
@ -676,8 +693,10 @@ frappe.setup.SetupWizard = class JeySetupWizard {
return false;
}
this.data.company_abbr = abbr;
const fyStart = (this.$wrap.find(".jey-fy-start").val() || "").trim();
const fyEnd = (this.$wrap.find(".jey-fy-end").val() || "").trim();
// Frappe Date controls return the value as YYYY-MM-DD or "" when
// 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) {
this.$wrap.find(".jey-status").text(__("Fiscal year start and end dates are required"));
return false;
@ -1322,35 +1341,75 @@ frappe.setup.SetupWizard = class JeySetupWizard {
}
finalize() {
this.$wrap.find(".jey-body").html(
`<p>${__("Setting up your system. This can take a minute...")}</p>`
);
// Render an inline progress UI in place of the wizard form. We don't
// 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-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();
frappe.call({
method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete",
args: { args: values },
freeze: true,
callback: (r) => {
const m = r.message || {};
if (m.status === "ok") {
this.$wrap.find(".jey-body").html(
`<p>${__("Setup complete! Redirecting...")}</p>`
);
setProgress(100, __("Setup complete! Redirecting..."));
cleanup();
setTimeout(() => {
window.location.href = "/app";
}, 1500);
}, 800);
} else if (m.status === "registered") {
cleanup();
this.show_error(
__("Setup is running in background mode, not supported by skeleton"),
JSON.stringify(m, null, 2)
);
} else if (m.fail !== undefined) {
cleanup();
this.show_error(m.fail, JSON.stringify(m, null, 2));
} else {
cleanup();
this.show_error(
__("Setup returned unexpected response"),
JSON.stringify(m, null, 2)
@ -1358,6 +1417,7 @@ frappe.setup.SetupWizard = class JeySetupWizard {
}
},
error: () => {
cleanup();
const resp = frappe.last_response || {};
this.show_error(
resp.setup_wizard_failure_message || __("Setup failed"),