mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-09 23:39:28 +00:00
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
This commit is contained in:
@@ -12,8 +12,9 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "fieldname",
|
"fieldname": "fieldname",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"in_list_view": 1,
|
||||||
"label": "Fieldname"
|
"label": "Fieldname",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "field",
|
"fieldname": "field",
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-07-29 18:08:40.323579",
|
"modified": "2026-08-31 20:41:12.000000",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Search Fields",
|
"name": "POS Search Fields",
|
||||||
|
|||||||
@@ -1,40 +1,9 @@
|
|||||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
let search_fields_datatypes = [
|
function is_valid_invoice_field(df) {
|
||||||
"Data",
|
return frappe.model.no_value_type.indexOf(df.fieldtype) === -1 || df.fieldtype === "Button";
|
||||||
"Link",
|
}
|
||||||
"Dynamic Link",
|
|
||||||
"Long Text",
|
|
||||||
"Select",
|
|
||||||
"Small Text",
|
|
||||||
"Text",
|
|
||||||
"Text Editor",
|
|
||||||
];
|
|
||||||
let do_not_include_fields = [
|
|
||||||
"naming_series",
|
|
||||||
"item_code",
|
|
||||||
"item_name",
|
|
||||||
"stock_uom",
|
|
||||||
"asset_naming_series",
|
|
||||||
"default_material_request_type",
|
|
||||||
"valuation_method",
|
|
||||||
"warranty_period",
|
|
||||||
"weight_uom",
|
|
||||||
"batch_number_series",
|
|
||||||
"serial_no_series",
|
|
||||||
"purchase_uom",
|
|
||||||
"customs_tariff_number",
|
|
||||||
"sales_uom",
|
|
||||||
"deferred_revenue_account",
|
|
||||||
"deferred_expense_account",
|
|
||||||
"quality_inspection_template",
|
|
||||||
"route",
|
|
||||||
"slideshow",
|
|
||||||
"website_image_alt",
|
|
||||||
"thumbnail",
|
|
||||||
"web_long_description",
|
|
||||||
];
|
|
||||||
|
|
||||||
frappe.ui.form.on("POS Settings", {
|
frappe.ui.form.on("POS Settings", {
|
||||||
onload: function (frm) {
|
onload: function (frm) {
|
||||||
@@ -42,74 +11,97 @@ frappe.ui.form.on("POS Settings", {
|
|||||||
frm.trigger("add_search_options");
|
frm.trigger("add_search_options");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
invoice_type: function (frm) {
|
||||||
|
frm.trigger("get_invoice_fields");
|
||||||
|
},
|
||||||
|
|
||||||
get_invoice_fields: function (frm) {
|
get_invoice_fields: function (frm) {
|
||||||
frappe.model.with_doctype("POS Invoice", () => {
|
const invoice_type = frm.doc.invoice_type;
|
||||||
var fields = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function (d) {
|
if (!invoice_type) return;
|
||||||
if (
|
|
||||||
frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
|
frappe.model.with_doctype(invoice_type, () => {
|
||||||
["Button"].includes(d.fieldtype)
|
// the invoice type can change again while the meta loads
|
||||||
) {
|
if (frm.doc.invoice_type !== invoice_type) return;
|
||||||
return { label: d.label + " (" + d.fieldtype + ")", value: d.fieldname };
|
|
||||||
} else {
|
const fields = frappe.get_doc("DocType", invoice_type).fields.filter(is_valid_invoice_field);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
frm.fields_dict.invoice_fields.grid.update_docfield_property(
|
frm.fields_dict.invoice_fields.grid.update_docfield_property(
|
||||||
"fieldname",
|
"fieldname",
|
||||||
"options",
|
"options",
|
||||||
[""].concat(fields)
|
[""].concat(
|
||||||
|
fields.map((df) => {
|
||||||
|
return { label: `${df.label} (${df.fieldtype})`, value: df.fieldname };
|
||||||
|
})
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
frm.trigger("validate_invoice_fields");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
validate_invoice_fields: function (frm) {
|
||||||
|
const valid_fieldnames = frappe
|
||||||
|
.get_doc("DocType", frm.doc.invoice_type)
|
||||||
|
.fields.filter(is_valid_invoice_field)
|
||||||
|
.map((df) => df.fieldname);
|
||||||
|
|
||||||
|
const invalid_fields = (frm.doc.invoice_fields || [])
|
||||||
|
.filter((row) => row.fieldname && !valid_fieldnames.includes(row.fieldname))
|
||||||
|
.map((row) => `#${row.idx} ${row.fieldname}`);
|
||||||
|
|
||||||
|
if (!invalid_fields.length) return;
|
||||||
|
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __("Invalid POS Fields"),
|
||||||
|
indicator: "orange",
|
||||||
|
message: __("The following rows are not valid fields of {0} and have to be removed: {1}", [
|
||||||
|
frm.doc.invoice_type.bold(),
|
||||||
|
invalid_fields.join(", "),
|
||||||
|
]),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
add_search_options: function (frm) {
|
add_search_options: function (frm) {
|
||||||
frappe.model.with_doctype("Item", () => {
|
frappe.call({
|
||||||
var fields = $.map(frappe.get_doc("DocType", "Item").fields, function (d) {
|
method: "erpnext.accounts.doctype.pos_settings.pos_settings.get_pos_search_field_options",
|
||||||
if (
|
callback: ({ message }) => {
|
||||||
search_fields_datatypes.includes(d.fieldtype) &&
|
const fields = message || [];
|
||||||
!do_not_include_fields.includes(d.fieldname)
|
|
||||||
) {
|
|
||||||
return [d.label];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fields.unshift("");
|
frm.searchable_item_fields = Object.fromEntries(
|
||||||
frm.fields_dict.pos_search_fields.grid.update_docfield_property("field", "options", fields);
|
fields.map((df) => [df.option, df.fieldname])
|
||||||
|
);
|
||||||
|
|
||||||
|
frm.fields_dict.pos_search_fields.grid.update_docfield_property(
|
||||||
|
"field",
|
||||||
|
"options",
|
||||||
|
[""].concat(fields.map((df) => df.option))
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on("POS Search Fields", {
|
frappe.ui.form.on("POS Search Fields", {
|
||||||
field: function (frm, doctype, name) {
|
field: function (frm, doctype, name) {
|
||||||
var doc = frappe.get_doc(doctype, name);
|
const doc = frappe.get_doc(doctype, name);
|
||||||
var df = $.map(frappe.get_doc("DocType", "Item").fields, function (d) {
|
|
||||||
if (doc.field == d.label && search_fields_datatypes.includes(d.fieldtype)) {
|
|
||||||
return d;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
doc.fieldname = df.fieldname;
|
doc.fieldname = frm.searchable_item_fields?.[doc.field] || "";
|
||||||
frm.refresh_field("fields");
|
frm.refresh_field("pos_search_fields");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on("POS Field", {
|
frappe.ui.form.on("POS Field", {
|
||||||
fieldname: function (frm, doctype, name) {
|
fieldname: function (frm, doctype, name) {
|
||||||
var doc = frappe.get_doc(doctype, name);
|
const doc = frappe.get_doc(doctype, name);
|
||||||
var df = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function (d) {
|
const invoice_meta = frappe.get_doc("DocType", frm.doc.invoice_type);
|
||||||
return doc.fieldname == d.fieldname ? d : null;
|
const df = invoice_meta?.fields.find((d) => d.fieldname === doc.fieldname);
|
||||||
})[0];
|
if (!df) return;
|
||||||
|
|
||||||
doc.label = df.label;
|
doc.label = df.label;
|
||||||
doc.reqd = df.reqd;
|
doc.reqd = df.reqd;
|
||||||
doc.options = df.options;
|
doc.options = df.options;
|
||||||
doc.fieldtype = df.fieldtype;
|
doc.fieldtype = df.fieldtype;
|
||||||
doc.default_value = df.default;
|
doc.default_value = df.default;
|
||||||
frm.refresh_field("fields");
|
frm.refresh_field("invoice_fields");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,8 +5,46 @@ from collections import Counter
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.model import no_value_fields
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
SEARCH_FIELD_TYPES = (
|
||||||
|
"Data",
|
||||||
|
"Link",
|
||||||
|
"Dynamic Link",
|
||||||
|
"Long Text",
|
||||||
|
"Select",
|
||||||
|
"Small Text",
|
||||||
|
"Text",
|
||||||
|
"Text Editor",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Item fields that are of a searchable fieldtype, but are not meaningful to search a POS item by
|
||||||
|
DO_NOT_INCLUDE_FIELDS = (
|
||||||
|
"naming_series",
|
||||||
|
"item_code",
|
||||||
|
"item_name",
|
||||||
|
"stock_uom",
|
||||||
|
"asset_naming_series",
|
||||||
|
"default_material_request_type",
|
||||||
|
"valuation_method",
|
||||||
|
"warranty_period",
|
||||||
|
"weight_uom",
|
||||||
|
"batch_number_series",
|
||||||
|
"serial_no_series",
|
||||||
|
"purchase_uom",
|
||||||
|
"customs_tariff_number",
|
||||||
|
"sales_uom",
|
||||||
|
"deferred_revenue_account",
|
||||||
|
"deferred_expense_account",
|
||||||
|
"quality_inspection_template",
|
||||||
|
"route",
|
||||||
|
"slideshow",
|
||||||
|
"website_image_alt",
|
||||||
|
"thumbnail",
|
||||||
|
"web_long_description",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class POSSettings(Document):
|
class POSSettings(Document):
|
||||||
# begin: auto-generated types
|
# begin: auto-generated types
|
||||||
@@ -32,17 +70,10 @@ class POSSettings(Document):
|
|||||||
if old_doc.invoice_type != self.invoice_type:
|
if old_doc.invoice_type != self.invoice_type:
|
||||||
self.validate_invoice_type()
|
self.validate_invoice_type()
|
||||||
|
|
||||||
|
self.validate_duplicate_invoice_fields()
|
||||||
self.validate_invoice_fields()
|
self.validate_invoice_fields()
|
||||||
|
self.validate_duplicate_pos_search_fields()
|
||||||
def validate_invoice_fields(self):
|
self.validate_pos_search_fields()
|
||||||
invoice_fields = [field.fieldname for field in self.invoice_fields]
|
|
||||||
duplicate_invoice_fields = {key for key, value in Counter(invoice_fields).items() if value > 1}
|
|
||||||
|
|
||||||
if len(duplicate_invoice_fields):
|
|
||||||
for field in duplicate_invoice_fields:
|
|
||||||
frappe.throw(
|
|
||||||
title=_("Duplicate POS Fields"), msg=_("'{0}' has been already added.").format(field)
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_invoice_type(self):
|
def validate_invoice_type(self):
|
||||||
pos_opening_entries_count = frappe.db.count(
|
pos_opening_entries_count = frappe.db.count(
|
||||||
@@ -55,3 +86,94 @@ class POSSettings(Document):
|
|||||||
),
|
),
|
||||||
title=_("Invoice Document Type Selection Error"),
|
title=_("Invoice Document Type Selection Error"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_duplicate_invoice_fields(self):
|
||||||
|
invoice_fields = [field.fieldname for field in self.invoice_fields]
|
||||||
|
duplicate_invoice_fields = {key for key, value in Counter(invoice_fields).items() if value > 1}
|
||||||
|
|
||||||
|
if len(duplicate_invoice_fields):
|
||||||
|
for field in duplicate_invoice_fields:
|
||||||
|
frappe.throw(
|
||||||
|
title=_("Duplicate POS Fields"), msg=_("'{0}' has been already added.").format(field)
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_invoice_fields(self):
|
||||||
|
if not self.invoice_type:
|
||||||
|
return
|
||||||
|
|
||||||
|
meta = frappe.get_meta(self.invoice_type)
|
||||||
|
|
||||||
|
for field in self.invoice_fields:
|
||||||
|
df = meta.get_field(field.fieldname)
|
||||||
|
|
||||||
|
if not df or not is_valid_invoice_field(df):
|
||||||
|
frappe.throw(
|
||||||
|
title=_("Invalid POS Field"),
|
||||||
|
msg=_("Row #{0}: '{1}' is not a valid field of {2}.").format(
|
||||||
|
field.idx, frappe.bold(field.fieldname or ""), frappe.bold(_(self.invoice_type))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# read only in the form, so keep them in sync with the invoice
|
||||||
|
field.label = df.label
|
||||||
|
field.fieldtype = df.fieldtype
|
||||||
|
field.options = df.options
|
||||||
|
|
||||||
|
def validate_duplicate_pos_search_fields(self):
|
||||||
|
fieldnames = [field.fieldname for field in self.pos_search_fields]
|
||||||
|
duplicate_fieldnames = {key for key, value in Counter(fieldnames).items() if value > 1}
|
||||||
|
|
||||||
|
for fieldname in duplicate_fieldnames:
|
||||||
|
frappe.throw(
|
||||||
|
title=_("Duplicate POS Search Fields"),
|
||||||
|
msg=_("'{0}' has been already added.").format(fieldname),
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_pos_search_fields(self):
|
||||||
|
searchable_fields = {df.fieldname: df for df in get_searchable_item_fields()}
|
||||||
|
|
||||||
|
for field in self.pos_search_fields:
|
||||||
|
df = searchable_fields.get(field.fieldname)
|
||||||
|
|
||||||
|
if not df:
|
||||||
|
frappe.throw(
|
||||||
|
title=_("Invalid POS Search Field"),
|
||||||
|
msg=_("Row #{0}: '{1}' cannot be used to search items.").format(
|
||||||
|
field.idx, frappe.bold(field.fieldname or "")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if field.field != get_search_field_option(df):
|
||||||
|
frappe.throw(
|
||||||
|
title=_("Invalid POS Search Field"),
|
||||||
|
msg=_("Row #{0}: '{1}' does not match {2}.").format(
|
||||||
|
field.idx, frappe.bold(field.field or ""), frappe.bold(df.fieldname)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_invoice_field(df):
|
||||||
|
return df.fieldtype not in no_value_fields or df.fieldtype == "Button"
|
||||||
|
|
||||||
|
|
||||||
|
def get_searchable_item_fields():
|
||||||
|
return [
|
||||||
|
df
|
||||||
|
for df in frappe.get_meta("Item").fields
|
||||||
|
if df.fieldtype in SEARCH_FIELD_TYPES and df.fieldname not in DO_NOT_INCLUDE_FIELDS
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_search_field_option(df):
|
||||||
|
# the fieldname keeps the option unique, two Item fields can share a label
|
||||||
|
return f"{df.label} ({df.fieldname})"
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_pos_search_field_options():
|
||||||
|
frappe.has_permission("POS Settings", throw=True)
|
||||||
|
|
||||||
|
return [
|
||||||
|
{"option": get_search_field_option(df), "fieldname": df.fieldname}
|
||||||
|
for df in get_searchable_item_fields()
|
||||||
|
]
|
||||||
|
|||||||
@@ -2,8 +2,135 @@
|
|||||||
# See license.txt
|
# See license.txt
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
from erpnext.patches.v16_0.append_fieldname_to_pos_search_fields import execute as append_fieldname
|
||||||
from erpnext.tests.utils import ERPNextTestSuite
|
from erpnext.tests.utils import ERPNextTestSuite
|
||||||
|
|
||||||
|
|
||||||
class TestPOSSettings(ERPNextTestSuite):
|
class TestPOSSettings(ERPNextTestSuite):
|
||||||
pass
|
def setUp(self):
|
||||||
|
self.settings = frappe.get_single("POS Settings")
|
||||||
|
self.settings.invoice_fields = []
|
||||||
|
self.settings.pos_search_fields = []
|
||||||
|
|
||||||
|
def assertInvalid(self, message):
|
||||||
|
with self.assertRaises(frappe.ValidationError) as context:
|
||||||
|
self.settings.save()
|
||||||
|
|
||||||
|
self.assertIn(message, str(context.exception))
|
||||||
|
|
||||||
|
def test_invoice_field_is_validated_against_invoice_type(self):
|
||||||
|
# consolidated_invoice exists on POS Invoice only
|
||||||
|
self.settings.invoice_type = "POS Invoice"
|
||||||
|
self.settings.append("invoice_fields", {"fieldname": "consolidated_invoice"})
|
||||||
|
self.settings.save()
|
||||||
|
|
||||||
|
self.settings.invoice_type = "Sales Invoice"
|
||||||
|
self.assertInvalid("is not a valid field of")
|
||||||
|
|
||||||
|
def test_field_common_to_both_invoice_types_is_allowed(self):
|
||||||
|
for invoice_type in ("POS Invoice", "Sales Invoice"):
|
||||||
|
self.settings.invoice_type = invoice_type
|
||||||
|
self.settings.invoice_fields = []
|
||||||
|
self.settings.append("invoice_fields", {"fieldname": "po_no"})
|
||||||
|
self.settings.save()
|
||||||
|
|
||||||
|
def test_unknown_invoice_field_is_not_allowed(self):
|
||||||
|
self.settings.append("invoice_fields", {"fieldname": "not_a_field"})
|
||||||
|
self.assertInvalid("is not a valid field of")
|
||||||
|
|
||||||
|
def test_layout_invoice_field_is_not_allowed(self):
|
||||||
|
self.settings.append("invoice_fields", {"fieldname": "accounting_dimensions_section"})
|
||||||
|
self.assertInvalid("is not a valid field of")
|
||||||
|
|
||||||
|
def test_invoice_field_properties_are_set_from_the_invoice(self):
|
||||||
|
self.settings.append(
|
||||||
|
"invoice_fields", {"fieldname": "customer", "label": "Tampered", "fieldtype": "Data"}
|
||||||
|
)
|
||||||
|
self.settings.save()
|
||||||
|
|
||||||
|
field = self.settings.invoice_fields[0]
|
||||||
|
self.assertEqual(field.label, "Customer")
|
||||||
|
self.assertEqual(field.fieldtype, "Link")
|
||||||
|
self.assertEqual(field.options, "Customer")
|
||||||
|
|
||||||
|
def test_searchable_item_field_is_allowed(self):
|
||||||
|
self.settings.append(
|
||||||
|
"pos_search_fields", {"field": "Description (description)", "fieldname": "description"}
|
||||||
|
)
|
||||||
|
self.settings.save()
|
||||||
|
|
||||||
|
self.assertEqual(self.settings.pos_search_fields[0].fieldname, "description")
|
||||||
|
|
||||||
|
def test_excluded_search_field_is_not_allowed(self):
|
||||||
|
self.settings.append(
|
||||||
|
"pos_search_fields", {"field": "Item Name (item_name)", "fieldname": "item_name"}
|
||||||
|
)
|
||||||
|
self.assertInvalid("cannot be used to search items")
|
||||||
|
|
||||||
|
def test_search_field_of_unsearchable_type_is_not_allowed(self):
|
||||||
|
# maintain stock is a Check field
|
||||||
|
self.settings.append(
|
||||||
|
"pos_search_fields", {"field": "Maintain Stock (is_stock_item)", "fieldname": "is_stock_item"}
|
||||||
|
)
|
||||||
|
self.assertInvalid("cannot be used to search items")
|
||||||
|
|
||||||
|
def test_unknown_search_field_is_not_allowed(self):
|
||||||
|
self.settings.append(
|
||||||
|
"pos_search_fields", {"field": "Nope (not_an_item_field)", "fieldname": "not_an_item_field"}
|
||||||
|
)
|
||||||
|
self.assertInvalid("cannot be used to search items")
|
||||||
|
|
||||||
|
def test_search_field_without_a_fieldname_is_not_allowed(self):
|
||||||
|
# the form fills the fieldname in, it cannot be picked on its own
|
||||||
|
self.settings.append("pos_search_fields", {"field": "Description (description)"})
|
||||||
|
self.assertInvalid("cannot be used to search items")
|
||||||
|
|
||||||
|
def test_search_field_option_must_match_its_fieldname(self):
|
||||||
|
self.settings.append("pos_search_fields", {"field": "Brand (brand)", "fieldname": "description"})
|
||||||
|
self.assertInvalid("does not match")
|
||||||
|
|
||||||
|
def test_bare_label_is_not_accepted_as_a_search_field(self):
|
||||||
|
# the stored option carries the fieldname, the patch backfills older rows
|
||||||
|
self.settings.append("pos_search_fields", {"field": "Description", "fieldname": "description"})
|
||||||
|
self.assertInvalid("does not match")
|
||||||
|
|
||||||
|
def test_duplicate_search_fields_are_not_allowed(self):
|
||||||
|
for _ in range(2):
|
||||||
|
self.settings.append(
|
||||||
|
"pos_search_fields", {"field": "Description (description)", "fieldname": "description"}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertInvalid("has been already added")
|
||||||
|
|
||||||
|
def test_patch_appends_the_fieldname_to_a_legacy_search_field(self):
|
||||||
|
self.settings.append(
|
||||||
|
"pos_search_fields", {"field": "Description (description)", "fieldname": "description"}
|
||||||
|
)
|
||||||
|
self.settings.save()
|
||||||
|
|
||||||
|
row = self.settings.pos_search_fields[0].name
|
||||||
|
frappe.db.set_value("POS Search Fields", row, "field", "Description", update_modified=False)
|
||||||
|
|
||||||
|
append_fieldname()
|
||||||
|
|
||||||
|
self.assertEqual(frappe.db.get_value("POS Search Fields", row, "field"), "Description (description)")
|
||||||
|
|
||||||
|
def test_patch_leaves_an_already_migrated_search_field_alone(self):
|
||||||
|
self.settings.append(
|
||||||
|
"pos_search_fields", {"field": "Description (description)", "fieldname": "description"}
|
||||||
|
)
|
||||||
|
self.settings.save()
|
||||||
|
|
||||||
|
append_fieldname()
|
||||||
|
|
||||||
|
row = self.settings.pos_search_fields[0].name
|
||||||
|
self.assertEqual(frappe.db.get_value("POS Search Fields", row, "field"), "Description (description)")
|
||||||
|
|
||||||
|
def test_invoice_fields_are_skipped_when_no_invoice_type_is_selected(self):
|
||||||
|
self.settings.invoice_type = None
|
||||||
|
self.settings.append("invoice_fields", {"fieldname": "customer"})
|
||||||
|
self.settings.save()
|
||||||
|
|
||||||
|
self.assertEqual(self.settings.invoice_fields[0].fieldname, "customer")
|
||||||
|
|||||||
@@ -502,3 +502,4 @@ erpnext.patches.v16_0.recalculate_purchase_receipt_billing_status
|
|||||||
erpnext.patches.v16_0.repair_work_order_material_transfer
|
erpnext.patches.v16_0.repair_work_order_material_transfer
|
||||||
erpnext.patches.v16_0.remove_frappe_crm_custom_fields
|
erpnext.patches.v16_0.remove_frappe_crm_custom_fields
|
||||||
erpnext.patches.v16_0.rename_secondary_item_type_field
|
erpnext.patches.v16_0.rename_secondary_item_type_field
|
||||||
|
erpnext.patches.v16_0.append_fieldname_to_pos_search_fields
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
rows = frappe.get_all(
|
||||||
|
"POS Search Fields", filters={"parent": "POS Settings"}, fields=["name", "field", "fieldname"]
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
# the row used to hold the label alone, it now holds "Label (fieldname)"
|
||||||
|
if not (row.field and row.fieldname) or row.field.endswith(f"({row.fieldname})"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
frappe.db.set_value(
|
||||||
|
"POS Search Fields",
|
||||||
|
row.name,
|
||||||
|
"field",
|
||||||
|
f"{row.field} ({row.fieldname})",
|
||||||
|
update_modified=False,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user