[IMP] pos_self_order: cancel order button
Previously, users could not cancel/delete their order once it had begun. Only order lines not sent to the server could be deleted one by one. Now, a "cancel" button has been added to the self shopping cart screen. This button only appears when the order has not yet been sent to the server. If the order has already been sent, it can only be cancelled from a PoS. closes odoo/odoo#128044 Signed-off-by: Adrien Guilliams (adgu) <adgu@odoo.com>
This commit is contained in:
@@ -28,7 +28,7 @@ export class Line extends Reactive {
|
||||
this.customer_note = line.customer_note;
|
||||
this.price_subtotal_incl = line.price_subtotal_incl || 0;
|
||||
this.price_subtotal = line.price_subtotal || 0;
|
||||
this.selected_attributes = line.selected_attributes || [];
|
||||
this.selected_attributes = line.selected_attributes || {};
|
||||
}
|
||||
|
||||
isChange(lastChange) {
|
||||
|
||||
@@ -49,7 +49,7 @@ export class Order extends Reactive {
|
||||
this.lines = this.lines.map((line) => new Line(line));
|
||||
}
|
||||
|
||||
computelastChangesSent() {
|
||||
updateLastChanges() {
|
||||
for (const changeIdx in this.lastChangesSent) {
|
||||
const changeFound = this.lines.find((line) => line.uuid === changeIdx);
|
||||
if (!changeFound) {
|
||||
@@ -60,7 +60,7 @@ export class Order extends Reactive {
|
||||
this.lastChangesSent = this.lines.reduce((acc, line) => {
|
||||
acc[line.uuid] = {
|
||||
qty: line.qty,
|
||||
selected_attributes: line.selected_attributes,
|
||||
selected_attributes: { ...line.selected_attributes },
|
||||
customer_note: line.customer_note,
|
||||
};
|
||||
return acc;
|
||||
|
||||
@@ -20,6 +20,7 @@ export class OrderCart extends Component {
|
||||
this.router = useService("router");
|
||||
this.state = useState({
|
||||
selectTable: false,
|
||||
cancelConfirmation: false,
|
||||
});
|
||||
|
||||
onWillStart(() => {
|
||||
@@ -31,6 +32,10 @@ export class OrderCart extends Component {
|
||||
return this.selfOrder.self_order_mode === "each" ? "Pay" : "Order";
|
||||
}
|
||||
|
||||
get orderToPay() {
|
||||
return this.selfOrder.self_order_mode === "each";
|
||||
}
|
||||
|
||||
async selectTable(table) {
|
||||
if (table) {
|
||||
this.selfOrder.table = table;
|
||||
@@ -62,4 +67,13 @@ export class OrderCart extends Component {
|
||||
this.selfOrder.notification.add(_t("Not yet implemented!"), { type: "danger" });
|
||||
}
|
||||
}
|
||||
|
||||
cancelToggle() {
|
||||
this.state.cancelConfirmation = !this.state.cancelConfirmation;
|
||||
}
|
||||
|
||||
cancelOrder() {
|
||||
this.selfOrder.cancelOrder();
|
||||
this.cancelToggle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.o_self-cancel-toggle-btn {
|
||||
height: 55px;
|
||||
width: 55px;
|
||||
}
|
||||
@@ -3,21 +3,39 @@
|
||||
<t t-name="pos_self_order.OrderCart" owl="1">
|
||||
<div class="d-flex flex-column dvh-100">
|
||||
<t t-set="order" t-value="selfOrder.currentOrder"/>
|
||||
<NavBar customText="'Cart'" shadow="true" />
|
||||
<NavBar customText="'Cart'" />
|
||||
<main class="h-100 bg-100 overflow-auto">
|
||||
<Lines order="order" />
|
||||
<div class="px-3 pt-4 pb-3 bg-white">
|
||||
<PriceDetails tax="order.amount_tax" total="order.amount_total"/>
|
||||
</div>
|
||||
</main>
|
||||
<div class="d-flex flex-column align-items-center w-100 p-3 bg-white shadow-lg">
|
||||
<button t-on-click="processOrder" t-attf-class="{{ !orderToPay and !order.isChange() ? 'disabled' : '' }} o_self_order_main_button btn btn-primary w-100 px-2 py-3 mt-2 rounded">
|
||||
<span t-if="orderToPay">Pay</span>
|
||||
<span t-else="">Order</span>
|
||||
<t t-if="!selfOrder.priceLoading">
|
||||
<t t-esc="` | ${selfOrder.formatMonetary(order.amount_total)}`"/>
|
||||
</t>
|
||||
</button>
|
||||
<div class="d-flex align-items-center w-100 p-3 bg-white shadow-lg">
|
||||
<div t-attf-class="{{ state.cancelConfirmation ? 'flex-row-reverse' : '' }} d-flex mt-2 flex-fill">
|
||||
<button
|
||||
t-if="order.isChange() and order.lines.length > 0"
|
||||
t-on-click.prevent="cancelToggle"
|
||||
t-attf-class="{{ state.cancelConfirmation ? 'btn-primary' : 'btn-secondary' }} o_self-cancel-toggle-btn btn"
|
||||
t-attf-style="{{ state.cancelConfirmation ? 'margin-left: 10px;' : 'margin-right: 10px;' }}"
|
||||
>
|
||||
<i t-attf-class="{{ state.cancelConfirmation ? 'fa-fw fa-chevron-left' : 'fa-times' }} fa" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
t-if="!state.cancelConfirmation"
|
||||
t-on-click="processOrder"
|
||||
t-attf-class="{{ !orderToPay and !order.isChange() ? 'disabled' : '' }} o_self_order_main_button btn btn-primary px-2 py-3 flex-grow-1"
|
||||
>
|
||||
<span t-if="orderToPay">Pay</span>
|
||||
<span t-else="">Order</span>
|
||||
<t t-if="!selfOrder.priceLoading">
|
||||
<span> | <t t-esc="selfOrder.formatMonetary(order.amount_total)"/></span>
|
||||
</t>
|
||||
</button>
|
||||
<button t-else="" t-on-click="cancelOrder" class="btn btn-secondary flex-grow-1 o_self-cancel-btn">
|
||||
<t t-if="order.access_token">Cancel last selection</t>
|
||||
<t t-else="">Confirm order cancellation</t>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<PopupTable t-if="this.state.selectTable" selectTable.bind="selectTable" />
|
||||
</div>
|
||||
|
||||
@@ -39,6 +39,15 @@ export class ProductMainView extends Component {
|
||||
this.initState();
|
||||
}
|
||||
|
||||
get disableAttributes() {
|
||||
const order = this.selfOrder.currentOrder;
|
||||
return (
|
||||
this.selfOrder.editedLine &&
|
||||
this.selfOrder.editedLine.uuid &&
|
||||
order.lastChangesSent[this.selfOrder.editedLine.uuid]
|
||||
);
|
||||
}
|
||||
|
||||
initState() {
|
||||
const editedLine = this.selfOrder.editedLine;
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
t-att-value="value.name"
|
||||
t-attf-id="{{ attribute.id }}_{{ value.id }}"
|
||||
t-model="state.selectedVariants[attribute.name]"
|
||||
t-att-disabled="disableAttributes"
|
||||
/>
|
||||
<span class="flex-grow-1">
|
||||
<t t-esc="value.name"/>
|
||||
@@ -51,7 +52,7 @@
|
||||
</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">
|
||||
<option t-foreach="attribute.values" t-as="value" t-key="value.id" t-att-value="value.name" 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)" />
|
||||
@@ -71,6 +72,7 @@
|
||||
t-att-value="value.name"
|
||||
t-attf-id="{{ attribute.id }}_{{ value.id }}"
|
||||
t-model="state.selectedVariants[attribute.name]"
|
||||
t-att-disabled="disableAttributes"
|
||||
/>
|
||||
</label>
|
||||
<span class="flex-grow-1">
|
||||
@@ -84,9 +86,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<div t-if="selfOrder.ordering">
|
||||
<div t-if="selfOrder.ordering and (!disableAttributes || state.customer_note)">
|
||||
<label class="form-label fw-bold" for="note">Add a note:</label>
|
||||
<textarea class="form-control rounded bg-white" id="note" type="textarea" rows="1" placeholder="No onions please" t-model="state.customer_note"/>
|
||||
<textarea class="form-control rounded bg-white" id="note" type="textarea" rows="1" placeholder="No onions please" t-model="state.customer_note" t-att-disabled="disableAttributes" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -113,6 +113,29 @@ export class SelfOrder {
|
||||
return this.editedOrder;
|
||||
}
|
||||
|
||||
cancelOrder() {
|
||||
const changes = this.currentOrder.lastChangesSent;
|
||||
const lines = this.currentOrder.lines;
|
||||
const keptLines = [];
|
||||
|
||||
for (const line of lines) {
|
||||
const change = changes[line.uuid];
|
||||
|
||||
if (change) {
|
||||
line.qty = change.qty;
|
||||
line.customer_note = change.customer_note;
|
||||
line.selected_attributes = change.selected_attributes;
|
||||
keptLines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
this.currentOrder.lines = keptLines;
|
||||
|
||||
if (this.currentOrder.totalQuantity === 0) {
|
||||
this.router.navigate("default");
|
||||
}
|
||||
}
|
||||
|
||||
formatMonetary(price) {
|
||||
return formatMonetary(price, { currencyId: this.currency_id });
|
||||
}
|
||||
@@ -131,7 +154,7 @@ export class SelfOrder {
|
||||
|
||||
this.editedOrder.access_token = order.access_token;
|
||||
this.updateOrdersFromServer([order], [order.access_token]);
|
||||
this.editedOrder.computelastChangesSent();
|
||||
this.editedOrder.updateLastChanges();
|
||||
|
||||
if (this.self_order_mode === "each") {
|
||||
this.editedOrder = null;
|
||||
|
||||
@@ -11,6 +11,14 @@ registry.category("web_tour.tours").add("self_order_after_meal_cart_tour", {
|
||||
PosSelf.check.isNotPrimaryBtn("My Orders"),
|
||||
PosSelf.action.clickPrimaryBtn("View Menu"),
|
||||
|
||||
// Cancel an orders
|
||||
...PosSelf.action.addProduct("Office Chair Black", 1),
|
||||
...PosSelf.action.addProduct("Office Chair Black", 2, "Description"),
|
||||
...PosSelf.action.addProduct("Large Cabinet", 2),
|
||||
PosSelf.action.clickPrimaryBtn("Review"),
|
||||
...PosSelf.action.cancelOrder(),
|
||||
PosSelf.action.clickPrimaryBtn("View Menu"),
|
||||
|
||||
// Add some products
|
||||
...PosSelf.action.addProduct("Office Chair Black", 1),
|
||||
...PosSelf.action.addProduct("Office Chair Black", 2, "Description"),
|
||||
@@ -39,7 +47,7 @@ registry.category("web_tour.tours").add("self_order_after_meal_cart_tour", {
|
||||
// longer reduce the quantity of his orderlines. We check this behaviour.
|
||||
PosSelf.action.clickPrimaryBtn("View Menu"),
|
||||
PosSelf.action.clickPrimaryBtn("Review"),
|
||||
...PosSelf.action.editOrderline("Large Cabinet", "736.00", "", -1),
|
||||
...PosSelf.action.editSentOrderline("Large Cabinet", "736.00", "", -1),
|
||||
PosSelf.check.isNotification("You cannot reduce the quantity"),
|
||||
PosSelf.check.isOrderline("Large Cabinet", "736.00", ""),
|
||||
|
||||
@@ -59,9 +67,9 @@ registry.category("web_tour.tours").add("self_order_after_meal_cart_tour", {
|
||||
...PosSelf.action.addProduct("Office Chair Black", 1, "kidding"),
|
||||
PosSelf.action.clickPrimaryBtn("Review"),
|
||||
PosSelf.check.isOrderline("Office Chair Black", "277.16", "kidding"),
|
||||
...PosSelf.action.editOrderline("Office Chair Black", "277.16", "kidding", -1),
|
||||
...PosSelf.action.editSentOrderline("Office Chair Black", "277.16", "kidding", -1),
|
||||
PosSelf.check.isOrderline("Office Chair Black", "138.58", "kidding"),
|
||||
...PosSelf.action.editOrderline("Office Chair Black", "138.58", "kidding", -1),
|
||||
...PosSelf.action.editSentOrderline("Office Chair Black", "138.58", "kidding", -1),
|
||||
PosSelf.check.isNotification("You cannot reduce the quantity"),
|
||||
PosSelf.action.clickPrimaryBtn("Order"),
|
||||
|
||||
@@ -78,11 +86,8 @@ registry.category("web_tour.tours").add("self_order_after_meal_cart_tour", {
|
||||
PosSelf.action.clickPrimaryBtn("Review"),
|
||||
PosSelf.check.isOrderline("Desk Organizer", "5.87", "kidding", "M, Leather"),
|
||||
PosSelf.check.isOrderline("Desk Organizer", "11.74", "okkk", "L, Custom"),
|
||||
PosSelf.action.clickPrimaryBtn("Order"),
|
||||
|
||||
// Check if we can edit the product attributes, and if the changes are made to the orderline
|
||||
PosSelf.action.clickPrimaryBtn("View Menu"),
|
||||
PosSelf.action.clickPrimaryBtn("Review"),
|
||||
...PosSelf.action.editOrderline("Desk Organizer", "5.87", "kidding", 0, "dav", {
|
||||
radio: { name: "Size", value: "S" },
|
||||
select: { name: "Fabric", value: "Custom" },
|
||||
|
||||
@@ -87,6 +87,18 @@ export const PosSelf = {
|
||||
},
|
||||
},
|
||||
action: {
|
||||
cancelOrder: () => {
|
||||
return [
|
||||
{
|
||||
content: `Toggle 'Cancel' button`,
|
||||
trigger: `.o_self-cancel-toggle-btn`,
|
||||
},
|
||||
{
|
||||
content: `Click on 'Cancel' button`,
|
||||
trigger: `.o_self-cancel-btn`,
|
||||
},
|
||||
];
|
||||
},
|
||||
clickBack: () => {
|
||||
return {
|
||||
content: "Click the navbar back button",
|
||||
@@ -121,6 +133,19 @@ 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}"))`,
|
||||
},
|
||||
...quantityHelper(addQuantity),
|
||||
{
|
||||
content: `Click on 'Add' button`,
|
||||
trigger: `.o_self_order_main_button`,
|
||||
},
|
||||
];
|
||||
},
|
||||
editOrderline: (name, price, description, addQuantity = 0, newDescription, newAtr) => {
|
||||
return [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user