first commit
This commit is contained in:
parent
1bb4ecfb6c
commit
8f8a936693
|
|
@ -0,0 +1,62 @@
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
|
||||||
|
LANG_CODE_TO_NAME = {
|
||||||
|
"en": "English",
|
||||||
|
"az": "Azərbaycan dili",
|
||||||
|
"ru": "Русский",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def finalize(language, company_name):
|
||||||
|
"""Run setup_complete with hardcoded AZ defaults plus two fields from the UI.
|
||||||
|
|
||||||
|
For the skeleton test only. Once the real multi-step wizard is in place, this will
|
||||||
|
grow into a Session-backed finalizer with rollback.
|
||||||
|
"""
|
||||||
|
if frappe.session.user != "Administrator":
|
||||||
|
frappe.throw(_("Only Administrator may run setup"), frappe.PermissionError)
|
||||||
|
|
||||||
|
if frappe.is_setup_complete():
|
||||||
|
return {"status": "ok", "already_complete": True}
|
||||||
|
|
||||||
|
language_name = LANG_CODE_TO_NAME.get(language, "English")
|
||||||
|
company_name = (company_name or "").strip() or "Test Company AZ"
|
||||||
|
year = date.today().year
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"language": language_name,
|
||||||
|
"country": "Azerbaijan",
|
||||||
|
"timezone": "Asia/Baku",
|
||||||
|
"currency": "AZN",
|
||||||
|
"enable_telemetry": 0,
|
||||||
|
# Leave email blank → create_or_update_user is a no-op and Administrator stays.
|
||||||
|
"full_name": "",
|
||||||
|
"email": "",
|
||||||
|
"password": "",
|
||||||
|
"company_name": company_name,
|
||||||
|
"company_abbr": _make_abbr(company_name),
|
||||||
|
"chart_of_accounts": "az_chart_of_accounts",
|
||||||
|
"fy_start_date": f"{year}-01-01",
|
||||||
|
"fy_end_date": f"{year}-12-31",
|
||||||
|
"setup_demo": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
from frappe.desk.page.setup_wizard.setup_wizard import setup_complete
|
||||||
|
|
||||||
|
return setup_complete(args)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_abbr(name: str) -> str:
|
||||||
|
parts = [p for p in name.split() if p]
|
||||||
|
if len(parts) >= 2:
|
||||||
|
abbr = "".join(p[0] for p in parts[:3])
|
||||||
|
elif parts:
|
||||||
|
abbr = parts[0][:3]
|
||||||
|
else:
|
||||||
|
abbr = "CO"
|
||||||
|
return abbr.upper()[:10]
|
||||||
|
|
@ -28,6 +28,11 @@ app_license = "unlicense"
|
||||||
# app_include_css = "/assets/jey_wizard/css/jey_wizard.css"
|
# app_include_css = "/assets/jey_wizard/css/jey_wizard.css"
|
||||||
# app_include_js = "/assets/jey_wizard/js/jey_wizard.js"
|
# app_include_js = "/assets/jey_wizard/js/jey_wizard.js"
|
||||||
|
|
||||||
|
# Replace the default setup wizard UI. This JS is loaded inside /app/setup-wizard
|
||||||
|
# page bootstrap, after frappe/setup_wizard.js but before the wizard instance is
|
||||||
|
# created — so overriding frappe.setup.SetupWizard in it swaps the UI entirely.
|
||||||
|
setup_wizard_requires = ["/assets/jey_wizard/js/jey_setup.js"]
|
||||||
|
|
||||||
# include js, css files in header of web template
|
# include js, css files in header of web template
|
||||||
# web_include_css = "/assets/jey_wizard/css/jey_wizard.css"
|
# web_include_css = "/assets/jey_wizard/css/jey_wizard.css"
|
||||||
# web_include_js = "/assets/jey_wizard/js/jey_wizard.js"
|
# web_include_js = "/assets/jey_wizard/js/jey_wizard.js"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,176 @@
|
||||||
|
// Loaded via setup_wizard_requires hook. At the time this runs:
|
||||||
|
// - frappe/setup_wizard.js has already defined frappe.setup.SetupWizard and the default
|
||||||
|
// slides_settings + the `frappe.pages["setup-wizard"].on_page_load` handler.
|
||||||
|
// - The handler called frappe.require([...this file...], cb). After we finish loading,
|
||||||
|
// cb runs and does `new frappe.setup.SetupWizard(wizard_settings)`.
|
||||||
|
// So overriding the class here replaces the default UI entirely for this pageload.
|
||||||
|
|
||||||
|
frappe.provide("jey_wizard");
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
frappe.setup.slides = [];
|
||||||
|
frappe.setup.slides_settings = [];
|
||||||
|
frappe.setup.events = {};
|
||||||
|
|
||||||
|
frappe.setup.SetupWizard = class JeySetupWizard {
|
||||||
|
constructor(settings = {}) {
|
||||||
|
this.parent = settings.parent;
|
||||||
|
this.values = {};
|
||||||
|
this.data = {
|
||||||
|
language: "az",
|
||||||
|
company_name: "Test Company AZ",
|
||||||
|
};
|
||||||
|
this.current_step = 0;
|
||||||
|
this.steps = ["language", "company", "confirm"];
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frappe's on_page_show calls frappe.wizard.show_slide — no-op for us.
|
||||||
|
show_slide() {}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const $parent = $(this.parent).empty();
|
||||||
|
this.$wrap = $(`
|
||||||
|
<div class="jey-wizard" style="max-width:560px;margin:80px auto;padding:32px;font-family:system-ui,sans-serif">
|
||||||
|
<h1 style="margin-bottom:8px">Jey Setup</h1>
|
||||||
|
<div class="jey-progress" style="color:#888;margin-bottom:24px"></div>
|
||||||
|
<div class="jey-body"></div>
|
||||||
|
<div class="jey-nav" style="margin-top:24px;display:flex;gap:8px;justify-content:flex-end"></div>
|
||||||
|
<div class="jey-status" style="margin-top:16px;color:#c00"></div>
|
||||||
|
</div>
|
||||||
|
`).appendTo($parent);
|
||||||
|
this.show_current_step();
|
||||||
|
}
|
||||||
|
|
||||||
|
show_current_step() {
|
||||||
|
const step = this.steps[this.current_step];
|
||||||
|
const $body = this.$wrap.find(".jey-body").empty();
|
||||||
|
const $nav = this.$wrap.find(".jey-nav").empty();
|
||||||
|
this.$wrap.find(".jey-progress").text(
|
||||||
|
`Step ${this.current_step + 1} of ${this.steps.length}`
|
||||||
|
);
|
||||||
|
this.$wrap.find(".jey-status").empty();
|
||||||
|
|
||||||
|
if (step === "language") {
|
||||||
|
$body.html(`
|
||||||
|
<label style="display:block;margin-bottom:8px">Wizard & site language</label>
|
||||||
|
<select class="jey-language form-control" style="max-width:280px">
|
||||||
|
<option value="az">Azərbaycan</option>
|
||||||
|
<option value="ru">Русский</option>
|
||||||
|
<option value="en">English</option>
|
||||||
|
</select>
|
||||||
|
`);
|
||||||
|
$body.find(".jey-language").val(this.data.language);
|
||||||
|
} else if (step === "company") {
|
||||||
|
$body.html(`
|
||||||
|
<label style="display:block;margin-bottom:8px">Company name</label>
|
||||||
|
<input type="text" class="jey-company-name form-control" style="max-width:380px" />
|
||||||
|
`);
|
||||||
|
$body.find(".jey-company-name").val(this.data.company_name);
|
||||||
|
} else if (step === "confirm") {
|
||||||
|
$body.html(`
|
||||||
|
<h3>Confirm</h3>
|
||||||
|
<ul style="line-height:1.8">
|
||||||
|
<li><b>Language:</b> ${this.data.language}</li>
|
||||||
|
<li><b>Company:</b> ${frappe.utils.escape_html(this.data.company_name)}</li>
|
||||||
|
<li><b>Country:</b> Azerbaijan <i>(hardcoded)</i></li>
|
||||||
|
<li><b>Currency:</b> AZN <i>(hardcoded)</i></li>
|
||||||
|
<li><b>Timezone:</b> Asia/Baku <i>(hardcoded)</i></li>
|
||||||
|
<li><b>Chart of Accounts:</b> az_chart_of_accounts <i>(hardcoded)</i></li>
|
||||||
|
<li><b>Fiscal Year:</b> current year <i>(hardcoded)</i></li>
|
||||||
|
<li><b>Telemetry:</b> off <i>(hardcoded)</i></li>
|
||||||
|
<li><b>Demo data:</b> off <i>(hardcoded)</i></li>
|
||||||
|
</ul>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.current_step > 0) {
|
||||||
|
$(`<button class="btn btn-default jey-back">Back</button>`)
|
||||||
|
.appendTo($nav)
|
||||||
|
.on("click", () => this.prev());
|
||||||
|
}
|
||||||
|
if (this.current_step < this.steps.length - 1) {
|
||||||
|
$(`<button class="btn btn-primary jey-next">Next</button>`)
|
||||||
|
.appendTo($nav)
|
||||||
|
.on("click", () => this.next());
|
||||||
|
} else {
|
||||||
|
$(`<button class="btn btn-primary jey-finish">Finish</button>`)
|
||||||
|
.appendTo($nav)
|
||||||
|
.on("click", () => this.finalize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
capture_current() {
|
||||||
|
const step = this.steps[this.current_step];
|
||||||
|
if (step === "language") {
|
||||||
|
this.data.language = this.$wrap.find(".jey-language").val();
|
||||||
|
} else if (step === "company") {
|
||||||
|
const name = (this.$wrap.find(".jey-company-name").val() || "").trim();
|
||||||
|
if (!name) {
|
||||||
|
this.$wrap.find(".jey-status").text("Company name is required");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.data.company_name = name;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
next() {
|
||||||
|
if (!this.capture_current()) return;
|
||||||
|
this.current_step++;
|
||||||
|
this.show_current_step();
|
||||||
|
}
|
||||||
|
|
||||||
|
prev() {
|
||||||
|
this.current_step--;
|
||||||
|
this.show_current_step();
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize() {
|
||||||
|
this.$wrap.find(".jey-body").html(
|
||||||
|
`<p>Setting up your system. This can take a minute...</p>`
|
||||||
|
);
|
||||||
|
this.$wrap.find(".jey-nav").empty();
|
||||||
|
this.$wrap.find(".jey-status").empty();
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: "jey_wizard.api.finalize",
|
||||||
|
args: {
|
||||||
|
language: this.data.language,
|
||||||
|
company_name: this.data.company_name,
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
callback: (r) => {
|
||||||
|
if (r.message && r.message.status === "ok") {
|
||||||
|
this.$wrap.find(".jey-body").html(
|
||||||
|
`<p>Setup complete! Redirecting...</p>`
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = "/app";
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
this.show_error(
|
||||||
|
"Setup returned unexpected response: " +
|
||||||
|
JSON.stringify(r.message || {})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
const msg =
|
||||||
|
(frappe.last_response && frappe.last_response.setup_wizard_failure_message) ||
|
||||||
|
"Setup failed — check server logs.";
|
||||||
|
this.show_error(msg);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
show_error(msg) {
|
||||||
|
this.$wrap.find(".jey-status").text(msg);
|
||||||
|
this.$wrap.find(".jey-nav")
|
||||||
|
.empty()
|
||||||
|
.html(`<button class="btn btn-default jey-retry">Retry</button>`)
|
||||||
|
.find(".jey-retry")
|
||||||
|
.on("click", () => window.location.reload());
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue