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:
parent
7a9c25ba35
commit
40f65ffd90
|
|
@ -1 +1 @@
|
||||||
__version__ = "0.1.15"
|
__version__ = "0.1.16"
|
||||||
|
|
|
||||||
|
|
@ -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():
|
||||||
|
|
|
||||||
|
|
@ -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"),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue