mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-15 15:38:39 +00:00
fix: removing the document naming series dialog and moving to framework
(cherry picked from commit aa7402b1e3)
This commit is contained in:
@@ -10,7 +10,7 @@ frappe.ui.form.on("Accounts Settings", {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
if (!frm.naming_controller) frm.naming_controller = new erpnext.NamingSeriesController(frm);
|
if (!frm.naming_controller) frm.naming_controller = new frappe.ui.NamingSeriesController(frm);
|
||||||
|
|
||||||
frm.naming_controller.render_table("transaction_naming_html", get_transactions(frm));
|
frm.naming_controller.render_table("transaction_naming_html", get_transactions(frm));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
frappe.ui.form.on("Buying Settings", {
|
frappe.ui.form.on("Buying Settings", {
|
||||||
refresh(frm) {
|
refresh(frm) {
|
||||||
if (!frm.naming_controller) frm.naming_controller = new erpnext.NamingSeriesController(frm);
|
if (!frm.naming_controller) frm.naming_controller = new frappe.ui.NamingSeriesController(frm);
|
||||||
|
|
||||||
const display = frm.doc.supp_master_name === "Naming Series";
|
const display = frm.doc.supp_master_name === "Naming Series";
|
||||||
frm.set_df_property("naming_series_details", "hidden", !display);
|
frm.set_df_property("naming_series_details", "hidden", !display);
|
||||||
|
|||||||
@@ -37,6 +37,5 @@ import "./utils/demo.js";
|
|||||||
import "./financial_statements.js";
|
import "./financial_statements.js";
|
||||||
import "./sales_trends_filters.js";
|
import "./sales_trends_filters.js";
|
||||||
import "./purchase_trends_filters.js";
|
import "./purchase_trends_filters.js";
|
||||||
import "./utils/naming_series.js";
|
|
||||||
|
|
||||||
// import { sum } from 'frappe/public/utils/util.js'
|
// import { sum } from 'frappe/public/utils/util.js'
|
||||||
|
|||||||
@@ -1,417 +0,0 @@
|
|||||||
frappe.provide("erpnext");
|
|
||||||
|
|
||||||
erpnext.NamingSeriesDialog = class NamingSeriesDialog {
|
|
||||||
constructor(opts = {}) {
|
|
||||||
this.opts = Object.assign(
|
|
||||||
{
|
|
||||||
title: __("Document Naming"),
|
|
||||||
single_doctype: "Document Naming Settings",
|
|
||||||
},
|
|
||||||
opts
|
|
||||||
);
|
|
||||||
|
|
||||||
this.current_doctype = null;
|
|
||||||
this.loaded = false;
|
|
||||||
this.make_dialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
make_dialog() {
|
|
||||||
this.dialog = new frappe.ui.Dialog({
|
|
||||||
title: this.opts.title,
|
|
||||||
size: "medium",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
fieldtype: "Table",
|
|
||||||
fieldname: "naming_series_options",
|
|
||||||
label: __("Add Series Prefix"),
|
|
||||||
reqd: 1,
|
|
||||||
in_place_edit: true,
|
|
||||||
data: [],
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
fieldtype: "Data",
|
|
||||||
fieldname: "series",
|
|
||||||
label: __("Series"),
|
|
||||||
in_list_view: 1,
|
|
||||||
change: async function () {
|
|
||||||
const preview = await this.grid_row.grid._naming_dialog.get_series_preview(
|
|
||||||
this.doc.series
|
|
||||||
);
|
|
||||||
this.doc.preview = preview;
|
|
||||||
this.grid_row.refresh_field("preview");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldtype: "Data",
|
|
||||||
fieldname: "preview",
|
|
||||||
label: __("Preview"),
|
|
||||||
in_list_view: 1,
|
|
||||||
placeholder: " ",
|
|
||||||
read_only: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ fieldtype: "Section Break", label: __("Rules for configuring series"), collapsible: 1 },
|
|
||||||
{
|
|
||||||
fieldtype: "HTML",
|
|
||||||
fieldname: "naming_series_description",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
primary_action_label: __("Update"),
|
|
||||||
primary_action: () => this.save(),
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dialog.fields_dict.naming_series_options.grid._naming_dialog = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
async show() {
|
|
||||||
this.dialog.show();
|
|
||||||
this.render_help();
|
|
||||||
|
|
||||||
if (this.opts.doctype && !this.loaded) {
|
|
||||||
await this.get_transaction(this.opts.doctype);
|
|
||||||
this.loaded = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render_help() {
|
|
||||||
this.dialog.get_field("naming_series_description").$wrapper.html(`
|
|
||||||
<ul>
|
|
||||||
<li>${__("Allowed special characters are '/' and '-'")}</li>
|
|
||||||
<li>
|
|
||||||
${__(
|
|
||||||
"Optionally, set the number of digits in the series using dot (.) followed by hashes (#). For example, '.####' means that the series will have four digits. Default is five digits."
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
<li> ${__("You can also use variables in the series name by putting them between (.) dots")}
|
|
||||||
<br>
|
|
||||||
${__("Supported Variables:")}
|
|
||||||
<ul>
|
|
||||||
<li><code>.YYYY.</code> - ${__("Year in 4 digits")}</li>
|
|
||||||
<li><code>.YY.</code> - ${__("Year in 2 digits")}</li>
|
|
||||||
<li><code>.MM.</code> - ${__("Month")}</li>
|
|
||||||
<li><code>.DD.</code> - ${__("Day of month")}</li>
|
|
||||||
<li><code>.WW.</code> - ${__("Week of the year")}</li>
|
|
||||||
<li>
|
|
||||||
<code>.{fieldname}.</code> - ${__("fieldname on the document e.g.")}
|
|
||||||
<code>branch</code>
|
|
||||||
</li>
|
|
||||||
<li><code>.FY.</code> - ${__("Fiscal Year (requires ERPNext to be installed)")}</li>
|
|
||||||
<li><code>.ABBR.</code> - ${__("Company Abbreviation (requires ERPNext to be installed)")}</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
Examples:
|
|
||||||
<ul>
|
|
||||||
<li>INV-</li>
|
|
||||||
<li>INV-10-</li>
|
|
||||||
<li>INVK-</li>
|
|
||||||
<li>INV-.YYYY.-._{branch}.-.MM.-.####</li>
|
|
||||||
</ul>
|
|
||||||
<br>`);
|
|
||||||
}
|
|
||||||
|
|
||||||
get_series_preview(series) {
|
|
||||||
if (!series) return "";
|
|
||||||
|
|
||||||
return this.get_document_naming_doc().then((doc) => {
|
|
||||||
doc.try_naming_series = series;
|
|
||||||
doc.transaction_type = this.current_doctype;
|
|
||||||
return frappe
|
|
||||||
.call({
|
|
||||||
doc: doc,
|
|
||||||
method: "preview_series",
|
|
||||||
freeze: true,
|
|
||||||
})
|
|
||||||
.then((r) => (r.message || "").split("\n")[0] || "");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get_document_naming_doc() {
|
|
||||||
const dt = this.opts.single_doctype;
|
|
||||||
return frappe.model.with_doc(dt, dt).then(() => {
|
|
||||||
return frappe.model.get_doc(dt, dt);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async get_transaction(doctype) {
|
|
||||||
this.current_doctype = doctype;
|
|
||||||
|
|
||||||
await frappe.model.with_doctype(doctype, async () => {
|
|
||||||
const meta = frappe.get_meta(doctype);
|
|
||||||
const naming_df = (meta?.fields || []).find((df) => df.fieldname === "naming_series");
|
|
||||||
const series_list = (naming_df?.options || "").split("\n").filter(Boolean);
|
|
||||||
const rows = await Promise.all(
|
|
||||||
series_list.map(async (series) => ({
|
|
||||||
series: series,
|
|
||||||
preview: await this.get_series_preview(series),
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
this.dialog.fields_dict.naming_series_options.df.data = rows;
|
|
||||||
this.dialog.fields_dict.naming_series_options.grid.refresh();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
save() {
|
|
||||||
const rows = this.dialog.fields_dict.naming_series_options.grid.get_data();
|
|
||||||
const naming_series_options = rows
|
|
||||||
.map((r) => (r.series || "").trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
if (!this.current_doctype) {
|
|
||||||
frappe.msgprint(__("Please select a transaction."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!naming_series_options) {
|
|
||||||
frappe.msgprint(__("Please add at least one naming series."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.get_document_naming_doc().then((doc) => {
|
|
||||||
doc.transaction_type = this.current_doctype;
|
|
||||||
doc.naming_series_options = naming_series_options;
|
|
||||||
|
|
||||||
frappe.call({
|
|
||||||
doc: doc,
|
|
||||||
method: "update_series",
|
|
||||||
freeze: true,
|
|
||||||
callback: async () => {
|
|
||||||
const updated_rows = await Promise.all(
|
|
||||||
naming_series_options
|
|
||||||
.split("\n")
|
|
||||||
.filter(Boolean)
|
|
||||||
.map(async (series) => ({
|
|
||||||
series: series,
|
|
||||||
preview: await this.get_series_preview(series),
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
this.dialog.fields_dict.naming_series_options.df.data = updated_rows;
|
|
||||||
this.dialog.fields_dict.naming_series_options.grid.refresh();
|
|
||||||
|
|
||||||
frappe.show_alert({ message: __("Naming Series updated"), indicator: "green" });
|
|
||||||
this.dialog.hide();
|
|
||||||
this.opts.on_update?.({ doctype: this.current_doctype, naming_series_options });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
erpnext.NamingSeriesTable = class NamingSeriesTable {
|
|
||||||
constructor(opts = {}) {
|
|
||||||
this.frm = opts.frm;
|
|
||||||
this.transactions = opts.transactions || [];
|
|
||||||
this.$wrapper = opts.frm.get_field(opts.fieldname).$wrapper;
|
|
||||||
this.theme_observer = null;
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
this.$wrapper.html(`
|
|
||||||
<div class="form-grid" style="margin-bottom: 24px;">
|
|
||||||
<table class="table" style="margin: 0;">
|
|
||||||
<thead class="grid-heading-row" style="background-color: var(--subtle-fg);">
|
|
||||||
<tr>
|
|
||||||
<td style="width: 25%; padding: 8px 12px; text-align: left;">
|
|
||||||
${__("Transaction")}
|
|
||||||
</td>
|
|
||||||
<td colspan="2"
|
|
||||||
style="width: 75%; padding: 8px 12px; text-align: left; border-left: 1px solid var(--border-color);">
|
|
||||||
${__("Current Series")}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="naming-series-table-rows"></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
|
|
||||||
const $rows = this.$wrapper.find(".naming-series-table-rows");
|
|
||||||
this.map_configure_button($rows);
|
|
||||||
this.get_row_data($rows);
|
|
||||||
|
|
||||||
if (this.theme_observer) {
|
|
||||||
this.theme_observer.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
const badge_class = this.get_current_badge_class();
|
|
||||||
|
|
||||||
this.$wrapper.find(".badge").removeClass("badge-light badge-dark").addClass(badge_class);
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(document.documentElement, {
|
|
||||||
attributes: true,
|
|
||||||
attributeFilter: ["data-theme"],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
map_configure_button($rows) {
|
|
||||||
$rows.on("click", ".configure-btn", (e) => {
|
|
||||||
const $btn = $(e.currentTarget);
|
|
||||||
const doctype = $btn.data("doctype");
|
|
||||||
const label = $btn.data("label");
|
|
||||||
|
|
||||||
if (!this.frm._naming_dialogs) this.frm._naming_dialogs = {};
|
|
||||||
|
|
||||||
if (!this.frm._naming_dialogs[doctype]) {
|
|
||||||
this.frm._naming_dialogs[doctype] = new erpnext.NamingSeriesDialog({
|
|
||||||
doctype: doctype,
|
|
||||||
title: __("{0} Naming Series", [__(label)]),
|
|
||||||
on_update: ({ naming_series_options }) => {
|
|
||||||
const series = naming_series_options.split("\n").filter(Boolean);
|
|
||||||
this.$wrapper
|
|
||||||
.find(`.series-cell-${frappe.scrub(doctype)}`)
|
|
||||||
.html(this.series_list_background(series));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.frm._naming_dialogs[doctype].show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async get_row_data($rows) {
|
|
||||||
const rows = await Promise.all(
|
|
||||||
this.transactions.map(async (t) => {
|
|
||||||
await new Promise((resolve) => frappe.model.with_doctype(t.doctype, resolve));
|
|
||||||
|
|
||||||
const meta = frappe.get_meta(t.doctype);
|
|
||||||
const naming_df = (meta?.fields || []).find((df) => df.fieldname === "naming_series");
|
|
||||||
const series = (naming_df?.options || "")
|
|
||||||
.split("\n")
|
|
||||||
.map((s) => s.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
return this.make_row(t, series);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
$rows.empty();
|
|
||||||
rows.forEach(($row) => $rows.append($row));
|
|
||||||
}
|
|
||||||
|
|
||||||
make_row(t, series) {
|
|
||||||
return $(`
|
|
||||||
<tr>
|
|
||||||
<td style="width: 25%; padding: 8px 12px; vertical-align: top; background-color: var(--card-bg);">
|
|
||||||
${frappe.utils.escape_html(t.label)}
|
|
||||||
</td>
|
|
||||||
<td class="series-cell-${frappe.scrub(t.doctype)}"
|
|
||||||
style="width: 70%; padding: 8px 12px; border-left: 1px solid var(--border-color); white-space: normal; vertical-align: top; background-color: var(--card-bg);">
|
|
||||||
${this.series_list_background(series)}
|
|
||||||
</td>
|
|
||||||
<td class="text-center"
|
|
||||||
style="width: 5%; padding: 8px 12px; border-left: 1px solid var(--border-color); vertical-align: middle; background-color: var(--card-bg);">
|
|
||||||
<a class="btn-link configure-btn"
|
|
||||||
data-doctype="${frappe.utils.escape_html(t.doctype)}"
|
|
||||||
data-label="${frappe.utils.escape_html(t.label)}"
|
|
||||||
style="cursor: pointer; color: var(--text-muted);">
|
|
||||||
${frappe.utils.icon("edit", "sm")}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
get_current_badge_class() {
|
|
||||||
return document.documentElement.getAttribute("data-theme") === "dark" ? "badge-dark" : "badge-light";
|
|
||||||
}
|
|
||||||
|
|
||||||
series_list_background(series_list) {
|
|
||||||
if (!series_list.length) {
|
|
||||||
return `<span class="text-muted">${__("Not configured")}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const badge_class = this.get_current_badge_class();
|
|
||||||
|
|
||||||
return series_list
|
|
||||||
.map(
|
|
||||||
(s) => `<span class="badge ${badge_class}"
|
|
||||||
style="margin: 2px; font-family: monospace; font-weight: normal;">
|
|
||||||
${frappe.utils.escape_html(s)}
|
|
||||||
</span>`
|
|
||||||
)
|
|
||||||
.join("");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} frm - Frappe form instance.
|
|
||||||
*/
|
|
||||||
erpnext.NamingSeriesController = class NamingSeriesController {
|
|
||||||
constructor(frm) {
|
|
||||||
this.frm = frm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the naming series table in the given field.
|
|
||||||
*
|
|
||||||
* @param {string} fieldname - Fieldname where the table should be rendered.
|
|
||||||
* @param {Array<{doctype: string, label: string}>} [transactions=[]] - Transactions to display.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
render_table(fieldname, transactions = []) {
|
|
||||||
this.frm._naming_series_table = new erpnext.NamingSeriesTable({
|
|
||||||
frm: this.frm,
|
|
||||||
fieldname: fieldname,
|
|
||||||
transactions: transactions,
|
|
||||||
});
|
|
||||||
this.frm._naming_series_table.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads naming series from the given master doctype into a field.
|
|
||||||
*
|
|
||||||
* @param {string} doctype - Master doctype name.
|
|
||||||
* @param {string} field - Fieldname where series should be shown.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
load_master_series(doctype, field) {
|
|
||||||
frappe.model.with_doctype(doctype, () => {
|
|
||||||
const meta = frappe.get_meta(doctype);
|
|
||||||
const naming_df = (meta?.fields || []).find((df) => df.fieldname === "naming_series");
|
|
||||||
const options = naming_df?.options || "";
|
|
||||||
const series_list = options
|
|
||||||
.split("\n")
|
|
||||||
.map((s) => s.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
this.frm.doc[field] = series_list.length
|
|
||||||
? series_list.join("\n")
|
|
||||||
: __("No naming series defined");
|
|
||||||
this.frm.refresh_field(field);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the naming series dialog for a doctype.
|
|
||||||
*
|
|
||||||
* @param {string} doctype - Transaction doctype.
|
|
||||||
* @param {Function} [on_update] - Called after series are updated.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
show_naming_series_dialog(doctype, on_update) {
|
|
||||||
if (!this.frm._naming_dialogs) this.frm._naming_dialogs = {};
|
|
||||||
|
|
||||||
if (!this.frm._naming_dialogs[doctype]) {
|
|
||||||
this.frm._naming_dialogs[doctype] = new erpnext.NamingSeriesDialog({
|
|
||||||
doctype: doctype,
|
|
||||||
title: __("{0} Naming Series", [__(doctype)]),
|
|
||||||
on_update: ({ naming_series_options }) => {
|
|
||||||
const series = naming_series_options.split("\n").filter(Boolean);
|
|
||||||
this.frm
|
|
||||||
.get_field(this.opts.table_field)
|
|
||||||
.$wrapper.find(`.series-cell-${frappe.scrub(doctype)}`)
|
|
||||||
.html(this.frm._naming_series_table?.series_list_background(series));
|
|
||||||
on_update?.({ doctype, naming_series_options });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.frm._naming_dialogs[doctype].show();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
frappe.ui.form.on("Selling Settings", {
|
frappe.ui.form.on("Selling Settings", {
|
||||||
refresh(frm) {
|
refresh(frm) {
|
||||||
if (!frm.naming_controller) frm.naming_controller = new erpnext.NamingSeriesController(frm);
|
if (!frm.naming_controller) frm.naming_controller = new frappe.ui.NamingSeriesController(frm);
|
||||||
|
|
||||||
const display = frm.doc.cust_master_name === "Naming Series";
|
const display = frm.doc.cust_master_name === "Naming Series";
|
||||||
frm.set_df_property("naming_series_details", "hidden", !display);
|
frm.set_df_property("naming_series_details", "hidden", !display);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ frappe.ui.form.on("Stock Settings", {
|
|||||||
frm.set_query("default_warehouse", filters);
|
frm.set_query("default_warehouse", filters);
|
||||||
frm.set_query("sample_retention_warehouse", filters);
|
frm.set_query("sample_retention_warehouse", filters);
|
||||||
|
|
||||||
if (!frm.naming_controller) frm.naming_controller = new erpnext.NamingSeriesController(frm);
|
if (!frm.naming_controller) frm.naming_controller = new frappe.ui.NamingSeriesController(frm);
|
||||||
const item_display = frm.doc.item_naming_by === "Naming Series";
|
const item_display = frm.doc.item_naming_by === "Naming Series";
|
||||||
const serial_and_batch_naming_display =
|
const serial_and_batch_naming_display =
|
||||||
frm.doc.set_serial_and_batch_bundle_naming_based_on_naming_series;
|
frm.doc.set_serial_and_batch_bundle_naming_based_on_naming_series;
|
||||||
|
|||||||
Reference in New Issue
Block a user