[FIX] point_of_sale: display attributes

In the current system, the order widget experienced undesired updates to all order lines when a new product with the same attribute was added. Our recent changes address this issue, ensuring that existing attribute values remain unaffected by the addition of new ones.

Moreover, we've refined the display of attribute information. If an attribute is designated as "never," it will now be incorporated into the order line note. Conversely, attributes with different settings will continue to be displayed in their usual format.

closes odoo/odoo#152213

Signed-off-by: Vlad Stroia (vlst) <vlst@odoo.com>
This commit is contained in:
rhe-odoo
2024-02-15 11:10:08 +00:00
parent b708a909ac
commit 672c853ab7
6 changed files with 36 additions and 22 deletions
@@ -36,7 +36,7 @@
<t t-esc="line.internalNote" />
</li>
<li t-if="line.attributes">
<t t-foreach="line.attributes" t-as="attribute" t-key="attribute.id">
<div t-foreach="line.attributes" t-as="attribute" t-key="attribute.id">
<t t-esc="attribute.name"/>:
<t t-foreach="attribute.valuesForOrderLine" t-as="value" t-key="value.id">
<t t-if="value_index !== 0" t-esc="' | '"/>
@@ -46,7 +46,7 @@
</t>
</t><br/>
</t>
</div>
</li>
</ul>
</li>
@@ -1,6 +1,6 @@
/** @odoo-module */
import { random5Chars, uuidv4, qrCodeSrc, constructFullProductName } from "@point_of_sale/utils";
import { random5Chars, uuidv4, qrCodeSrc } from "@point_of_sale/utils";
// FIXME POSREF - unify use of native parseFloat and web's parseFloat. We probably don't need the native version.
import { parseFloat as oParseFloat } from "@web/views/fields/parsers";
import {
@@ -574,11 +574,7 @@ export class Orderline extends PosModel {
this.price_extra = parseFloat(price_extra) || 0.0;
}
set_full_product_name() {
this.full_product_name = constructFullProductName(
this,
this.pos.db.attribute_value_by_id,
this.product.display_name
);
this.full_product_name = this.product.display_name;
}
get_price_extra() {
return this.price_extra;
@@ -738,7 +734,10 @@ export class Orderline extends PosModel {
orderline.compute_fixed_price(order_line_price),
this.pos.currency.decimal_places
);
// only orderlines of the same product can be merged
let hasSameAttributes = Object.keys(Object(orderline.attribute_value_ids)).length === Object.keys(Object(this.attribute_value_ids)).length;
if(hasSameAttributes && Object(orderline.attribute_value_ids)?.length && Object(this.attribute_value_ids)?.length) {
hasSameAttributes = orderline.attribute_value_ids.every((value, index) => value === this.attribute_value_ids[index]);
}
return (
!this.skipChange &&
orderline.getNote() === this.getNote() &&
@@ -759,7 +758,8 @@ export class Orderline extends PosModel {
orderline.get_customer_note() === this.get_customer_note() &&
!this.refunded_orderline_id &&
!this.isPartOfCombo() &&
!orderline.isPartOfCombo()
!orderline.isPartOfCombo() &&
hasSameAttributes
);
}
is_pos_groupable() {
@@ -1077,13 +1077,18 @@ export class Orderline extends PosModel {
return Boolean(this.comboParent || this.comboLines?.length);
}
findAttribute(values) {
const listOfAttributes = Object.values(this.pos.attributes_by_ptal_id).filter(
const listOfAttributes = [];
Object.values(this.pos.attributes_by_ptal_id).filter(
(attribute) => {
const attFound = attribute.values.filter((target) => {
return Object.values(values).includes(target.id);
});
if (attFound.length > 0) {
attribute.valuesForOrderLine = attFound;
const modifiedAttribute = {
...attribute,
valuesForOrderLine: attFound,
};
listOfAttributes.push(modifiedAttribute);
return true;
}
return false;
-2
View File
@@ -29,10 +29,8 @@ export function deduceUrl(url) {
}
return url;
}
export function constructFullProductName(line, attribute_value_by_id, display_name) {
let attributeString = "";
if (line.attribute_value_ids && line.attribute_value_ids.length > 0) {
for (const valId of line.attribute_value_ids) {
const value = attribute_value_by_id[valId];
@@ -4,6 +4,8 @@ import * as ProductScreen from "@point_of_sale/../tests/tours/helpers/ProductScr
import * as Chrome from "@point_of_sale/../tests/tours/helpers/ChromeTourMethods";
import * as ProductConfigurator from "@point_of_sale/../tests/tours/helpers/ProductConfiguratorTourMethods";
import { registry } from "@web/core/registry";
import * as Order from "@point_of_sale/../tests/tours/helpers/generic_components/OrderWidgetMethods";
import { inLeftSide } from "@point_of_sale/../tests/tours/helpers/utils";
registry.category("web_tour.tours").add("ProductConfiguratorTour", {
test: true,
@@ -43,7 +45,7 @@ registry.category("web_tour.tours").add("ProductConfiguratorTour", {
// Check that the product has been added to the order with correct attributes and price
ProductScreen.selectedOrderlineHas(
"Configurable Chair (Red, Metal, Other: Custom Fabric)",
"Configurable Chair",
"1.0",
"11.0"
),
@@ -56,12 +58,18 @@ registry.category("web_tour.tours").add("ProductConfiguratorTour", {
ProductConfigurator.pickRadio("Other"),
ProductConfigurator.fillCustomAttribute("Custom Fabric"),
ProductConfigurator.confirmAttributes(),
ProductScreen.selectedOrderlineHas(
"Configurable Chair (Red, Metal, Other: Custom Fabric)",
"2.0",
"22.0"
inLeftSide(Order.hasLine({
withClass: ".selected",
productName: "Configurable Chair",
quantity: "2",
price: "22.0",
atts: {
"Color": "Red ($ 1.00)",
"Chair Legs": "Metal",
"Fabrics": "Other"
}
})
),
// Orderlines with different attributes shouldn't be merged
ProductScreen.clickHomeCategory(),
ProductScreen.clickDisplayedProduct("Configurable Chair"),
@@ -70,7 +78,7 @@ registry.category("web_tour.tours").add("ProductConfiguratorTour", {
ProductConfigurator.pickRadio("Leather"),
ProductConfigurator.confirmAttributes(),
ProductScreen.selectedOrderlineHas(
"Configurable Chair (Blue, Metal, Leather)",
"Configurable Chair",
"1.0",
"10.0"
),
@@ -31,6 +31,7 @@ export function hasLine({
comboParent,
discount,
oldPrice,
atts
} = {}) {
let trigger = `.order-container .orderline${withClass}`;
if (withoutClass) {
@@ -60,6 +61,9 @@ export function hasLine({
if (oldPrice) {
trigger += `:has(.info-list .price-per-unit s:contains("${oldPrice}"))`;
}
if(atts) {
trigger += Object.entries(atts).map(([key, value]) => `:has(.info-list div:contains("${key}: ${value}"))`).join();
}
const args = JSON.stringify(arguments[0]);
return [
{
@@ -570,7 +570,6 @@ class TestUi(TestPointOfSaleHttpCommon):
self.assertEqual(n_paid, 2, 'There should be 2 paid order.')
def test_04_product_configurator(self):
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config, 'ProductConfiguratorTour', login="pos_user")