mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-01 04:28:27 +00:00
Merge pull request #54635 from frappe/mergify/bp/version-16-hotfix/pr-54554
This commit is contained in:
@@ -37,5 +37,6 @@ import "./utils/demo.js";
|
||||
import "./financial_statements.js";
|
||||
import "./sales_trends_filters.js";
|
||||
import "./purchase_trends_filters.js";
|
||||
import "./utils/naming_series_dialog.js";
|
||||
|
||||
// import { sum } from 'frappe/public/utils/util.js'
|
||||
|
||||
312
erpnext/public/js/utils/naming_series_dialog.js
Normal file
312
erpnext/public/js/utils/naming_series_dialog.js
Normal file
@@ -0,0 +1,312 @@
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
get_row_data($rows) {
|
||||
this.transactions.forEach((t) => {
|
||||
frappe.model.with_doctype(t.doctype, () => {
|
||||
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);
|
||||
|
||||
$rows.append(this.make_row(t, series));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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>
|
||||
`);
|
||||
}
|
||||
|
||||
series_list_background(series_list) {
|
||||
if (!series_list.length) {
|
||||
return `<span class="text-muted">${__("Not configured")}</span>`;
|
||||
}
|
||||
return series_list
|
||||
.map(
|
||||
(s) => `<span class="badge badge-light"
|
||||
style="margin: 2px; font-family: monospace; font-weight: normal;">
|
||||
${frappe.utils.escape_html(s)}
|
||||
</span>`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
};
|
||||
@@ -2,7 +2,80 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Selling Settings", {
|
||||
refresh(frm) {
|
||||
const display = frm.doc.cust_master_name === "Naming Series";
|
||||
frm.set_df_property("naming_series_details", "hidden", !display);
|
||||
frm.set_df_property("configure", "hidden", !display);
|
||||
if (display) {
|
||||
find_naming_series("Customer", "naming_series_details", frm);
|
||||
}
|
||||
load_default_naming_series(frm);
|
||||
},
|
||||
cust_master_name(frm) {
|
||||
const display = frm.doc.cust_master_name === "Naming Series";
|
||||
frm.set_df_property("naming_series_details", "hidden", !display);
|
||||
frm.set_df_property("configure", "hidden", !display);
|
||||
if (display) {
|
||||
find_naming_series("Customer", "naming_series_details", frm);
|
||||
} else {
|
||||
frm.set_value("naming_series_details", "");
|
||||
}
|
||||
},
|
||||
|
||||
configure(frm) {
|
||||
show_naming_series_dialog("Customer", frm);
|
||||
},
|
||||
|
||||
after_save(frm) {
|
||||
frappe.boot.user.defaults.editable_price_list_rate = frm.doc.editable_price_list_rate;
|
||||
},
|
||||
});
|
||||
|
||||
function show_naming_series_dialog(doctype, frm) {
|
||||
if (!frm._naming_series_dialog) {
|
||||
frm._naming_series_dialog = new erpnext.NamingSeriesDialog({
|
||||
doctype: doctype,
|
||||
title: __("Naming Series for {0}", [__(doctype)]),
|
||||
on_update: ({ naming_series_options }) => {
|
||||
frm.set_value("naming_series_details", naming_series_options);
|
||||
},
|
||||
});
|
||||
}
|
||||
frm._naming_series_dialog.show();
|
||||
}
|
||||
function find_naming_series(doctype, field, frm) {
|
||||
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);
|
||||
|
||||
frm.doc[field] = series_list.length ? series_list.join("\n") : __("No naming series defined");
|
||||
|
||||
frm.refresh_field(field);
|
||||
});
|
||||
}
|
||||
|
||||
function load_default_naming_series(frm) {
|
||||
let transactions = [
|
||||
{ label: __("Customer"), doctype: "Customer" },
|
||||
{ label: __("Quotation"), doctype: "Quotation" },
|
||||
{ label: __("Sales Order"), doctype: "Sales Order" },
|
||||
{ label: __("Sales Invoice"), doctype: "Sales Invoice" },
|
||||
{ label: __("Delivery Note"), doctype: "Delivery Note" },
|
||||
{ label: __("Payment Entry"), doctype: "Payment Entry" },
|
||||
{ label: __("POS Invoice"), doctype: "POS Invoice" },
|
||||
];
|
||||
|
||||
if (frm.doc.cust_master_name !== "Naming Series") {
|
||||
transactions = transactions.filter((t) => t.doctype !== "Customer");
|
||||
}
|
||||
new erpnext.NamingSeriesTable({
|
||||
frm: frm,
|
||||
fieldname: "transaction_naming_html",
|
||||
transactions: transactions,
|
||||
}).render();
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
"customer_defaults_tab",
|
||||
"customer_defaults_section",
|
||||
"cust_master_name",
|
||||
"customer_group",
|
||||
"naming_series_details",
|
||||
"configure",
|
||||
"column_break_4",
|
||||
"customer_group",
|
||||
"territory",
|
||||
"item_price_tab",
|
||||
"item_price_settings_section",
|
||||
@@ -57,7 +59,9 @@
|
||||
"section_break_zwh6",
|
||||
"allow_delivery_of_overproduced_qty",
|
||||
"column_break_mla9",
|
||||
"deliver_secondary_items"
|
||||
"deliver_secondary_items",
|
||||
"default_naming_tab",
|
||||
"transaction_naming_html"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -279,7 +283,7 @@
|
||||
{
|
||||
"fieldname": "item_price_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Item Price"
|
||||
"label": "Pricing"
|
||||
},
|
||||
{
|
||||
"fieldname": "transaction_tab",
|
||||
@@ -377,6 +381,29 @@
|
||||
"fieldname": "blanket_orders_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Blanket Orders"
|
||||
},
|
||||
{
|
||||
"fieldname": "configure",
|
||||
"fieldtype": "Button",
|
||||
"hidden": 1,
|
||||
"label": "Configure Series"
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series_details",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 1,
|
||||
"is_virtual": 1,
|
||||
"label": "Naming Series options",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "default_naming_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Document Naming"
|
||||
},
|
||||
{
|
||||
"fieldname": "transaction_naming_html",
|
||||
"fieldtype": "HTML"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -385,7 +412,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-21 21:29:32.890098",
|
||||
"modified": "2026-04-29 11:05:48.836362",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Selling Settings",
|
||||
|
||||
Reference in New Issue
Block a user