[IMP] point_of_sale,pos_self_order: handling product attributes
When selecting an attribute value in POS, what we store is the `description`, which is a string that represents the selected attribute values. Ex: selecting `Size: M` and `Material: Leather` will result in the description: `(L, Leather)`. This choice does not lead to a logical API for dealing with product attibutes. In this pr, we add a new field that stores the selected `ids` of `"product.template.attribute.value"` and remove the `selected_attributes` `Json` field from the `pos_self_order` override of the `pos.order.line` model. The `attributeHelper` function from the `tour_utils.js` file from `pos_self_order` is improved such that it can now handle both checking if a certain attribute is selected and actually selecting an attribute. closes odoo/odoo#126398 Task: 3378533 Related: odoo/upgrade#4992 Signed-off-by: Joseph Caburnay (jcb) <jcb@odoo.com>
This commit is contained in:
@@ -1173,6 +1173,7 @@ class PosOrderLine(models.Model):
|
||||
skip_change = fields.Boolean('Skip line when sending ticket to kitchen printers.')
|
||||
notice = fields.Char(string='Discount Notice')
|
||||
product_id = fields.Many2one('product.product', string='Product', domain=[('sale_ok', '=', True)], required=True, change_default=True)
|
||||
attribute_value_ids = fields.Many2many('product.template.attribute.value', string="Selected Attributes")
|
||||
price_unit = fields.Float(string='Unit Price', digits=0)
|
||||
qty = fields.Float('Quantity', digits='Product Unit of Measure', default=1)
|
||||
price_subtotal = fields.Float(string='Subtotal w/o Tax', digits=0,
|
||||
|
||||
@@ -138,7 +138,7 @@ export class Product extends PosModel {
|
||||
}
|
||||
async getAddProductOptions(code) {
|
||||
let price_extra = 0.0;
|
||||
let draftPackLotLines, weight, description, packLotLinesToEdit;
|
||||
let draftPackLotLines, weight, description, packLotLinesToEdit, attribute_value_ids;
|
||||
let quantity = 1;
|
||||
let comboLines = [];
|
||||
|
||||
@@ -161,6 +161,7 @@ export class Product extends PosModel {
|
||||
|
||||
if (confirmed) {
|
||||
description = payload.selected_attributes.join(", ");
|
||||
attribute_value_ids = payload.attribute_value_ids;
|
||||
price_extra += payload.price_extra;
|
||||
quantity = payload.quantity;
|
||||
} else {
|
||||
@@ -262,6 +263,7 @@ export class Product extends PosModel {
|
||||
description,
|
||||
price_extra,
|
||||
comboLines,
|
||||
attribute_value_ids,
|
||||
};
|
||||
}
|
||||
isPricelistItemUsable(item, date) {
|
||||
@@ -425,6 +427,7 @@ export class Orderline extends PosModel {
|
||||
this.set_discount(json.discount);
|
||||
this.set_quantity(json.qty, "do not recompute unit price");
|
||||
this.set_description(json.description);
|
||||
this.attribute_value_ids = json.attribute_value_ids || [];
|
||||
this.set_price_extra(json.price_extra);
|
||||
this.set_full_product_name(json.full_product_name);
|
||||
this.id = json.server_id || json.id || orderline_id++;
|
||||
@@ -784,6 +787,7 @@ export class Orderline extends PosModel {
|
||||
id: this.id,
|
||||
pack_lot_ids: pack_lot_ids,
|
||||
description: this.description,
|
||||
attribute_value_ids: this.attribute_value_ids,
|
||||
full_product_name: this.get_full_product_name(),
|
||||
price_extra: this.get_price_extra(),
|
||||
customer_note: this.get_customer_note(),
|
||||
@@ -2155,6 +2159,7 @@ export class Order extends PosModel {
|
||||
|
||||
if (options.description !== undefined) {
|
||||
orderline.description += options.description;
|
||||
orderline.attribute_value_ids = options.attribute_value_ids;
|
||||
}
|
||||
|
||||
if (options.extras !== undefined) {
|
||||
|
||||
+5
-1
@@ -25,6 +25,7 @@ export class BaseProductAttribute extends Component {
|
||||
|
||||
return {
|
||||
value,
|
||||
valueId: selected_value.id,
|
||||
extra: selected_value.price_extra,
|
||||
};
|
||||
}
|
||||
@@ -74,17 +75,20 @@ export class ProductConfiguratorPopup extends AbstractAwaitablePopup {
|
||||
|
||||
getPayload() {
|
||||
var selected_attributes = [];
|
||||
const attribute_value_ids = [];
|
||||
var price_extra = 0.0;
|
||||
const quantity = this.state.quantity;
|
||||
|
||||
this.env.attribute_components.forEach((attribute_component) => {
|
||||
const { value, extra } = attribute_component.getValue();
|
||||
const { value, valueId, extra } = attribute_component.getValue();
|
||||
selected_attributes.push(value);
|
||||
attribute_value_ids.push(valueId);
|
||||
price_extra += extra;
|
||||
});
|
||||
|
||||
return {
|
||||
selected_attributes,
|
||||
attribute_value_ids,
|
||||
price_extra,
|
||||
quantity,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
"name": "POS Self Order",
|
||||
'version': '1.0.1',
|
||||
"summary": """
|
||||
Addon for the POS App that allows customers to view the menu on their smartphone.
|
||||
""",
|
||||
|
||||
@@ -247,7 +247,7 @@ class PosSelfOrderController(http.Controller):
|
||||
'product_id': child.get('product_id'),
|
||||
'qty': child.get('qty'),
|
||||
'customer_note': child.get('customer_note'),
|
||||
'selected_attributes': child.get('selected_attributes'),
|
||||
'attribute_value_ids': child.get('selected_attributes') and [int(v) for v in child['selected_attributes'].values()] or [],
|
||||
'full_product_name': child.get('full_product_name'),
|
||||
'combo_parent_uuid': child.get('combo_parent_uuid'),
|
||||
'combo_id': child.get('combo_id'),
|
||||
@@ -273,7 +273,7 @@ class PosSelfOrderController(http.Controller):
|
||||
'product_id': line.get('product_id'),
|
||||
'qty': line.get('qty'),
|
||||
'customer_note': line.get('customer_note'),
|
||||
'selected_attributes': line.get('selected_attributes'),
|
||||
'attribute_value_ids': line.get('selected_attributes') and [int(v) for v in line['selected_attributes'].values()] or [],
|
||||
'full_product_name': line.get('full_product_name'),
|
||||
'combo_parent_uuid': line.get('combo_parent_uuid'),
|
||||
'combo_id': line.get('combo_id'),
|
||||
|
||||
@@ -9,8 +9,6 @@ from odoo import models, fields, api
|
||||
class PosOrderLine(models.Model):
|
||||
_inherit = "pos.order.line"
|
||||
|
||||
# For the moment we need this to keep attributes consistency between the server and client_side.
|
||||
selected_attributes = fields.Json(string="Selected Attributes")
|
||||
combo_parent_id = fields.Many2one('pos.order.line', string='Combo Parent')
|
||||
combo_line_ids = fields.One2many('pos.order.line', 'combo_parent_id', string='Combo Lines')
|
||||
combo_id = fields.Many2one('pos.combo', string='Combo line reference')
|
||||
@@ -43,6 +41,9 @@ class PosOrderLine(models.Model):
|
||||
**super()._export_for_ui(orderline),
|
||||
}
|
||||
|
||||
def _get_selected_attributes(self) -> Dict:
|
||||
self.ensure_one()
|
||||
return {value.attribute_line_id.id: value.id for value in self.attribute_value_ids}
|
||||
|
||||
class PosOrder(models.Model):
|
||||
_inherit = "pos.order"
|
||||
@@ -108,7 +109,7 @@ class PosOrder(models.Model):
|
||||
"price_subtotal": line.price_subtotal,
|
||||
"price_subtotal_incl": line.price_subtotal_incl,
|
||||
"product_id": line.product_id.id,
|
||||
"selected_attributes": line.selected_attributes,
|
||||
"selected_attributes": line._get_selected_attributes(),
|
||||
"uuid": line.uuid,
|
||||
"qty": line.qty,
|
||||
"customer_note": line.customer_note,
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
</div>
|
||||
<div t-if="line.customer_note" class="d-inline-block m-0 text-muted small break-line">
|
||||
<i class="fa fa-pencil-square-o" aria-hidden="true" />
|
||||
<span t-esc="line.customer_note" class="ms-1" />
|
||||
<span t-esc="line.customer_note" class="customer_note ms-1" />
|
||||
</div>
|
||||
</div>
|
||||
<span t-attf-class="card-text small"
|
||||
<span t-attf-class="card-text line_price small"
|
||||
t-esc="selfOrder.formatMonetary(getPrice(line))"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+22
-11
@@ -29,7 +29,6 @@ export class ProductMainView extends Component {
|
||||
this.state = useState({
|
||||
qty: 1,
|
||||
customer_note: "",
|
||||
selectedVariants: [],
|
||||
cartQty: 0,
|
||||
selectedCombos: Object.fromEntries(
|
||||
this.product.pos_combo_ids?.map?.((id) => [
|
||||
@@ -37,6 +36,9 @@ export class ProductMainView extends Component {
|
||||
this.selfOrder.comboByIds[id].combo_line_ids[0].id,
|
||||
]) || []
|
||||
),
|
||||
/** this will be an object that will have the attribute id as key and the value id as value
|
||||
* ex: selectedVariants: {4: 9, 5: 11} */
|
||||
selectedVariants: {},
|
||||
});
|
||||
|
||||
onWillUnmount(() => {
|
||||
@@ -45,6 +47,13 @@ export class ProductMainView extends Component {
|
||||
|
||||
this.initState();
|
||||
}
|
||||
getAttributeWithStringIds(attribute) {
|
||||
const values = attribute.values.map((value) => ({
|
||||
...value,
|
||||
id: value.id.toString(),
|
||||
}));
|
||||
return { ...attribute, values };
|
||||
}
|
||||
|
||||
get disableAttributes() {
|
||||
const order = this.selfOrder.currentOrder;
|
||||
@@ -66,24 +75,26 @@ export class ProductMainView extends Component {
|
||||
this.state.selectedVariants = editedLine.selected_attributes;
|
||||
}
|
||||
} else {
|
||||
this.state.selectedVariants = this.product.attributes.reduce((acc, curr) => {
|
||||
acc[curr.name] = curr.values[0].name;
|
||||
return acc;
|
||||
}, {});
|
||||
this.state.selectedVariants = Object.fromEntries(
|
||||
this.product.attributes.map((att) => [att.id, att.values[0].id.toString()])
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
get fullProductName() {
|
||||
const productAttributeString = Object.values(this.state.selectedVariants).join(", ");
|
||||
let name = `${this.product.name}`;
|
||||
|
||||
if (productAttributeString) {
|
||||
name += ` (${productAttributeString})`;
|
||||
if (!this.product.attributes.length) {
|
||||
return this.product.name;
|
||||
}
|
||||
|
||||
return name;
|
||||
const findAttribute = (id) => this.product.attributes.find((attr) => attr.id == id);
|
||||
const findValue = (attr, id) => attr.values.find((value) => value.id == id);
|
||||
const productAttributeString = Object.entries(this.state.selectedVariants)
|
||||
.map(([attrId, valueId]) => findValue(findAttribute(attrId), valueId).name)
|
||||
.join(", ");
|
||||
|
||||
return `${this.product.name} (${productAttributeString})`;
|
||||
}
|
||||
|
||||
changeQuantity(increase) {
|
||||
|
||||
+11
-8
@@ -29,17 +29,20 @@
|
||||
t-key="attribute.id">
|
||||
<div class="mb-3">
|
||||
<div class="attribute_name py-1 fs-5 fw-bold" t-esc="attribute.name"/>
|
||||
<!-- FIXME: here we are changing the attribute ids to strings because of a bug in t-model
|
||||
Once t-model gets fixed, remove the following line -->
|
||||
<t t-set="attribute" t-value="getAttributeWithStringIds(attribute)"/>
|
||||
<t t-if="attribute.display_type === 'radio' or attribute.display_type === 'pills'">
|
||||
<t t-foreach="attribute.values" t-as="value" t-key="value.id">
|
||||
<div class="d-flex align-items-center justify-content-between py-2">
|
||||
<input
|
||||
type="radio"
|
||||
class="form-check-input my-1 me-2"
|
||||
t-att-name="attribute.id"
|
||||
t-att-value="value.name"
|
||||
t-att-name="value.name"
|
||||
t-att-value="value.id"
|
||||
t-attf-id="{{ attribute.id }}_{{ value.id }}"
|
||||
t-model="state.selectedVariants[attribute.name]"
|
||||
t-att-disabled="disableAttributes"
|
||||
t-model="state.selectedVariants[attribute.id]"
|
||||
/>
|
||||
<span class="flex-grow-1">
|
||||
<t t-esc="value.name"/>
|
||||
@@ -51,8 +54,8 @@
|
||||
</t>
|
||||
</t>
|
||||
<select t-elif="attribute.display_type === 'select'" class="configurator_select form-select"
|
||||
t-model="state.selectedVariants[attribute.name]">
|
||||
<option t-foreach="attribute.values" t-as="value" t-key="value.id" t-att-value="value.name" t-att-disabled="disableAttributes">
|
||||
t-model="state.selectedVariants[attribute.id]">
|
||||
<option t-foreach="attribute.values" t-as="value" t-key="value.id" t-att-name="value.name" t-att-value="value.id" t-att-disabled="disableAttributes">
|
||||
<t t-esc="value.name"/>
|
||||
<t t-if="value.price_extra.list_price">
|
||||
+ <t t-esc="selfOrder.formatMonetary(value.price_extra.list_price)" />
|
||||
@@ -68,11 +71,11 @@
|
||||
<input
|
||||
type="radio"
|
||||
class="form-check-input my-1 me-2"
|
||||
t-att-name="attribute.id"
|
||||
t-att-value="value.name"
|
||||
t-att-name="attribute.name"
|
||||
t-att-value="value.id"
|
||||
t-attf-id="{{ attribute.id }}_{{ value.id }}"
|
||||
t-model="state.selectedVariants[attribute.name]"
|
||||
t-att-disabled="disableAttributes"
|
||||
t-model="state.selectedVariants[attribute.id]"
|
||||
/>
|
||||
</label>
|
||||
<span class="flex-grow-1">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { PosSelf } from "./tour_utils";
|
||||
import { PosSelf, descriptionHelper } from "./tour_utils";
|
||||
|
||||
registry.category("web_tour.tours").add("self_order_after_meal_cart_tour", {
|
||||
test: true,
|
||||
@@ -77,23 +77,34 @@ registry.category("web_tour.tours").add("self_order_after_meal_cart_tour", {
|
||||
PosSelf.action.clickBack(),
|
||||
|
||||
// Here we check that the product attributes are correctly selected.
|
||||
...PosSelf.action.addProduct("Desk Organizer", 1, "kidding", {
|
||||
radio: { name: "Size", value: "M" },
|
||||
select: { name: "Fabric", value: "Leather" },
|
||||
}),
|
||||
...PosSelf.action.addProduct("Desk Organizer", 2, "okkk", {
|
||||
radio: { name: "Size", value: "L" },
|
||||
select: { name: "Fabric", value: "Custom" },
|
||||
}),
|
||||
...PosSelf.action.addProduct("Desk Organizer", 1, "kidding", [
|
||||
{ type: "radio", name: "Size", value: "M" },
|
||||
{ type: "select", name: "Fabric", value: "Leather" },
|
||||
]),
|
||||
...PosSelf.action.addProduct("Desk Organizer", 2, "okkk", [
|
||||
{ type: "radio", name: "Size", value: "L" },
|
||||
{ type: "select", name: "Fabric", value: "Custom" },
|
||||
]),
|
||||
PosSelf.action.clickPrimaryBtn("Review"),
|
||||
PosSelf.check.isOrderline("Desk Organizer", "5.87", "kidding", "M, Leather"),
|
||||
PosSelf.check.isOrderline("Desk Organizer", "11.73", "okkk", "L, Custom"),
|
||||
|
||||
PosSelf.action.clickOrderline("Desk Organizer", "5.87", "kidding"),
|
||||
...PosSelf.check.attributes([
|
||||
{ type: "radio", name: "Size", value: "M" },
|
||||
{ type: "select", name: "Fabric", value: "Leather" },
|
||||
]),
|
||||
// Check if we can edit the product attributes, and if the changes are made to the orderline
|
||||
...PosSelf.action.editOrderline("Desk Organizer", "5.87", "kidding", 0, "dav", {
|
||||
radio: { name: "Size", value: "S" },
|
||||
select: { name: "Fabric", value: "Custom" },
|
||||
}),
|
||||
...PosSelf.action.selectAttributes([
|
||||
{ type: "radio", name: "Size", value: "S" },
|
||||
{ type: "select", name: "Fabric", value: "Custom" },
|
||||
]),
|
||||
descriptionHelper("dav"),
|
||||
{
|
||||
content: `Click on 'Add' button`,
|
||||
trigger: `.o_self_order_main_button`,
|
||||
},
|
||||
|
||||
PosSelf.check.isOrderline("Desk Organizer", "5.87", "dav", "S, Custom"),
|
||||
PosSelf.check.isOrderline("Desk Organizer", "11.73", "okkk", "L, Custom"),
|
||||
PosSelf.action.clickPrimaryBtn("Order"),
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
/** @odoo-module */
|
||||
|
||||
/**
|
||||
* @typedef {object} TourStep
|
||||
* @property {string} content
|
||||
* @property {string} trigger
|
||||
* @property {boolean?} isCheck
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} Attribute
|
||||
* @property {string} type
|
||||
* @property {string} name
|
||||
* @property {string} value
|
||||
*/
|
||||
|
||||
export const PosSelf = {
|
||||
check: {
|
||||
tablePopupIsShown: () => {
|
||||
@@ -37,11 +51,23 @@ export const PosSelf = {
|
||||
run: () => {},
|
||||
};
|
||||
},
|
||||
isOrderline: (name, price, description = "", attributes = "") => {
|
||||
isOrderline: (name, price, description = "", attributes = "", click = false) => {
|
||||
let trigger = `.o_self_order_item_card:has(.o_self_product_name:contains("${name}"))`;
|
||||
if (description) {
|
||||
trigger += `:has(span.customer_note:contains("${description}"))`;
|
||||
}
|
||||
if (price) {
|
||||
trigger += `:has(span.line_price:contains("${price}"))`;
|
||||
}
|
||||
if (attributes) {
|
||||
trigger += `:has(span:contains("${attributes}"))`;
|
||||
}
|
||||
return {
|
||||
content: `Verify is there an orderline with ${name} and ${price} and ${description}`,
|
||||
trigger: `.o_self_order_item_card:has(.o_self_product_name:contains("${name}")):has(span:contains("${description}")):has(span:contains("${attributes}")):has(span.card-text:contains("${price}"))`,
|
||||
run: () => {},
|
||||
content: `${
|
||||
(click && "Click") || "Check"
|
||||
} orderline with ${name} and ${price} and ${description}`,
|
||||
trigger: trigger,
|
||||
isCheck: !click,
|
||||
};
|
||||
},
|
||||
isNotOrderline: (name, price, description = "", attributes = "") => {
|
||||
@@ -85,6 +111,7 @@ export const PosSelf = {
|
||||
run: () => {},
|
||||
};
|
||||
},
|
||||
attributes: (attributes) => attributes.map((attribute) => attributeHelper(attribute, true)),
|
||||
},
|
||||
action: {
|
||||
cancelOrder: () => {
|
||||
@@ -118,7 +145,7 @@ export const PosSelf = {
|
||||
run: `text ${table}`,
|
||||
};
|
||||
},
|
||||
addProduct: (name, quantity = 1, description, attributes) => {
|
||||
addProduct: (name, quantity = 1, description, attributes = []) => {
|
||||
return [
|
||||
{
|
||||
content: `Click on product '${name}'`,
|
||||
@@ -126,7 +153,8 @@ export const PosSelf = {
|
||||
},
|
||||
...quantityHelper(quantity),
|
||||
descriptionHelper(description),
|
||||
...attributeHelper(attributes),
|
||||
...attributes.map((attribute) => attributeHelper(attribute)),
|
||||
|
||||
{
|
||||
content: `Click on 'Add' button`,
|
||||
trigger: `.o_self_order_main_button:contains('Add')`,
|
||||
@@ -135,10 +163,7 @@ export const PosSelf = {
|
||||
},
|
||||
editSentOrderline: (name, price, description, addQuantity = 0) => {
|
||||
return [
|
||||
{
|
||||
content: `Click on orderline ${name}, price ${price} and description ${description}`,
|
||||
trigger: `.o_self_order_item_card:has(.o_self_product_name:contains("${name}")):has(span:contains("${description}")):has(span.card-text:contains("${price}"))`,
|
||||
},
|
||||
clickOrderline(name, price, description),
|
||||
...quantityHelper(addQuantity),
|
||||
{
|
||||
content: `Click on 'Add' button`,
|
||||
@@ -146,49 +171,55 @@ export const PosSelf = {
|
||||
},
|
||||
];
|
||||
},
|
||||
editOrderline: (name, price, description, addQuantity = 0, newDescription, newAtr) => {
|
||||
clickOrderline: clickOrderline,
|
||||
|
||||
editOrderline: (name, price, description, addQuantity = 0, newDescription) => {
|
||||
return [
|
||||
{
|
||||
content: `Click on orderline ${name}, price ${price} and description ${description}`,
|
||||
trigger: `.o_self_order_item_card:has(.o_self_product_name:contains("${name}")):has(span:contains("${description}")):has(span.card-text:contains("${price}"))`,
|
||||
},
|
||||
clickOrderline(name, price, description),
|
||||
...quantityHelper(addQuantity),
|
||||
descriptionHelper(newDescription),
|
||||
...attributeHelper(newAtr),
|
||||
{
|
||||
content: `Click on 'Add' button`,
|
||||
trigger: `.o_self_order_main_button`,
|
||||
},
|
||||
];
|
||||
},
|
||||
selectAttributes: (attributes) => attributes.map((attribute) => attributeHelper(attribute)),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
// This function is used to generate the steps to add attributes to a product.
|
||||
// Missing the color function, but I didn't have example for the moment see in the future.
|
||||
const attributeHelper = (attributes = { radio: {}, select: {}, color: {} }) => {
|
||||
const attributesSteps = [];
|
||||
|
||||
if (attributes.radio.value) {
|
||||
attributesSteps.push({
|
||||
content: `Select radio ${attributes.radio.name} with value ${attributes.radio.value}`,
|
||||
trigger: `.o_self_order_main_options div:contains('${attributes.radio.name}') ~ div input[value='${attributes.radio.value}']`,
|
||||
});
|
||||
* @param {Attribute} attribute
|
||||
* @param {boolean} [isCheck=false]
|
||||
* @returns {TourStep}
|
||||
*/
|
||||
function attributeHelper(attribute, isCheck = false) {
|
||||
const content = `${isCheck ? "Check" : "Select"} ${attribute.name} with value ${
|
||||
attribute.value
|
||||
}`;
|
||||
const attributeDiv = `.o_self_order_main_options div:contains('${attribute.name}')`;
|
||||
if (attribute.type === "radio") {
|
||||
const radioInput = `${attributeDiv} ~ div input[name='${attribute.value}']`;
|
||||
return {
|
||||
content,
|
||||
trigger: radioInput + (isCheck ? ":checked" : ""),
|
||||
isCheck,
|
||||
};
|
||||
} else if (attribute.type === "select") {
|
||||
const selectInput = `${attributeDiv} ~ select:has(option[name='${attribute.value}'])`;
|
||||
const selectInputSelected = `${selectInput.slice(0, -1)}[selected])`;
|
||||
return {
|
||||
content,
|
||||
trigger: isCheck ? selectInputSelected : selectInput,
|
||||
run: isCheck ? () => {} : `text ${attribute.value}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (attributes.select.value) {
|
||||
attributesSteps.push({
|
||||
content: `Select ${attributes.select.name} with value ${attributes.select.value}`,
|
||||
trigger: `.o_self_order_main_options div:contains('${attributes.select.name}') ~ select:has(option[value='${attributes.select.value}'])`,
|
||||
run: `text ${attributes.select.value}`,
|
||||
});
|
||||
}
|
||||
|
||||
return attributesSteps;
|
||||
};
|
||||
}
|
||||
|
||||
// This function is used to generate the steps to add a description to a product.
|
||||
const descriptionHelper = (description = "") => {
|
||||
export const descriptionHelper = (description = "") => {
|
||||
return {
|
||||
content: `Add description ${description}`,
|
||||
trigger: `.o_self_order_main_options textarea`,
|
||||
@@ -215,6 +246,8 @@ const quantityHelper = (quantity, el) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return increaseQuantity;
|
||||
};
|
||||
function clickOrderline(name, price, description) {
|
||||
return PosSelf.check.isOrderline(name, price, description, false, true);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user