[IMP] pos_self_order, pos_*: overal improvements & fixes

*:pos_self_order_adyen,pos_adyen,pos_online_payment_self_order

Some overral improvements and fixes

closes odoo/odoo#140041

Related: odoo/enterprise#49707
Signed-off-by: Adrien Guilliams (adgu) <adgu@odoo.com>
This commit is contained in:
David Monnom (moda)
2023-10-27 14:22:08 +00:00
parent d9c043e9bf
commit e9ce5608dd
16 changed files with 135 additions and 150 deletions
@@ -82,7 +82,7 @@ class PosPaymentMethod(models.Model):
def proxy_adyen_request(self, data, operation=False):
''' Necessary because Adyen's endpoints don't have CORS enabled '''
if data['SaleToPOIRequest']['MessageHeader']['MessageCategory'] == 'Payment': # Clear only if it is a payment request
if data.get('SaleToPOIRequest') and data['SaleToPOIRequest']['MessageHeader']['MessageCategory'] == 'Payment': # Clear only if it is a payment request
self.sudo().adyen_latest_response = '' # avoid handling old responses multiple times
if not operation:
@@ -22,3 +22,9 @@ class PosConfig(models.Model):
payment_methods = self.self_order_online_payment_method_id.read(payment_search_params['search_params']['fields'])
res['pos_payment_methods'] += payment_methods
return res
def _get_allowed_payment_methods(self):
if self.self_ordering_mode == 'kiosk':
return self.payment_method_ids.filtered(lambda p: p.use_payment_terminal in self._ALLOWED_PAYMENT_METHODS or p.is_online_payment)
else:
return self.payment_method_ids.filtered(lambda p: p.is_online_payment)
@@ -1,84 +0,0 @@
/** @odoo-module */
import { patch } from "@web/core/utils/patch";
import { CartPage } from "@pos_self_order/app/pages/cart_page/cart_page";
import { _t } from "@web/core/l10n/translation";
patch(CartPage.prototype, {
async pay() {
const isOnlinePayment = this.selfOrder.pos_payment_methods.find((p) => p.is_online_payment);
if (!isOnlinePayment) {
return super.pay(...arguments);
}
if (this.sendInProgress) {
return;
}
const order = this.selfOrder.currentOrder;
const mode = this.selfOrder.config.self_ordering_pay_after;
const service = this.selfOrder.config.self_ordering_service_mode;
const takeAway = this.selfOrder.currentOrder.take_away;
if (!this.selfOrder.table && service === "table" && !takeAway) {
this.state.selectTable = true;
return;
}
if (mode === "meal" && order.isSavedOnServer) {
this.checkAndOpenPaymentPage(order);
} else if (mode === "meal" && !order.isSavedOnServer) {
this.sendInProgress = true;
await this.selfOrder.sendDraftOrderToServer();
this.router.navigate("confirmation", {
orderAccessToken: order.access_token,
screenMode: "order",
});
} else if (mode === "each") {
this.checkAndOpenPaymentPage(order);
} else {
return super.pay(...arguments);
}
},
async checkAndOpenPaymentPage(order) {
const isOnlinePayment = this.selfOrder.pos_payment_methods.find((p) => p.is_online_payment);
if (order) {
if (order.state === "draft") {
if (!isOnlinePayment) {
// if no payment method is available -> pay at cashier
this.router.navigate("confirmation", {
orderAccessToken: order.access_token,
screenMode: "pay",
});
return;
}
if (
!order.isSavedOnServer &&
this.selfOrder.config.self_ordering_mode !== "kiosk"
) {
await this.selfOrder.sendDraftOrderToServer();
}
if (this.selfOrder.config.self_ordering_mode === "mobile") {
const onlinePaymentUrl = this.selfOrder.getOnlinePaymentUrl(order, true);
window.open(onlinePaymentUrl, "_self");
} else {
this.router.navigate("payment");
}
} else {
this.selfOrder.notification.add(
_t("The current order cannot be paid (maybe it is already paid)."),
{ type: "danger" }
);
}
} else {
this.selfOrder.notification.add(
_t("The order could not be saved, therefore its payment is unavailable."),
{ type: "danger" }
);
}
},
});
@@ -1,28 +1,32 @@
/** @odoo-module */
import { patch } from "@web/core/utils/patch";
import { PaymentPage } from "@pos_self_order/app/pages/payment_page/payment_page";
import { _t } from "@web/core/l10n/translation";
patch(PaymentPage.prototype, {
async startPayment() {
let order = this.selfOrder.currentOrder;
const selfMode = this.selfOrder.config.self_ordering_mode;
const pm = this.selectedPaymentMethod;
const device = this.selfOrder.config.self_ordering_mode;
if (selfMode !== "kiosk" || !pm || !pm.is_online_payment) {
if (!pm || !pm.is_online_payment) {
return super.startPayment(...arguments);
} else {
order = await this.selfOrder.sendDraftOrderToServer();
}
const url = this.selfOrder.getOnlinePaymentUrl(order, false);
this.generateQrcodeImg(url);
if (device === "kiosk") {
const url = this.selfOrder.getOnlinePaymentUrl(order, false);
this.generateQrcodeImg(url);
} else {
this.checkAndOpenPaymentPage(order);
}
},
get selectedPaymentIsOnline() {
const paymentMethods = this.selectedPaymentMethod;
return (
paymentMethods &&
paymentMethods.is_online_payment &&
this.selfOrder.config.self_ordering_mode === "kiosk"
paymentMethods.is_online_payment
);
},
generateQrcodeImg(url) {
@@ -30,4 +34,15 @@ patch(PaymentPage.prototype, {
const qr_code_svg = new XMLSerializer().serializeToString(codeWriter.write(url, 150, 150));
this.state.qrImage = "data:image/svg+xml;base64," + window.btoa(qr_code_svg);
},
async checkAndOpenPaymentPage(order) {
if (order.state === "draft") {
const onlinePaymentUrl = this.selfOrder.getOnlinePaymentUrl(order, true);
window.open(onlinePaymentUrl, "_self");
} else {
this.selfOrder.notification.add(
_t("The current order cannot be paid (maybe it is already paid)."),
{ type: "danger" }
);
}
},
});
@@ -2,7 +2,7 @@
<templates id="template" xml:space="preserve">
<t t-name="pos_online_payment_self_order.PaymentPage" t-inherit="pos_self_order.PaymentPage" t-inherit-mode="extension">
<xpath expr="//div[hasclass('payment-state-container')]" position="after">
<div t-if="this.selectedPaymentIsOnline and !state.selection" class="d-flex justify-content-center align-items-center flex-column h-100 px-3 text-center">
<div t-if="this.selectedPaymentIsOnline and !state.selection and selfOrder.config.self_ordering_mode === 'kiosk'" class="d-flex justify-content-center align-items-center flex-column h-100 px-3 text-center">
<h1 class="mb-4">Scan the QR code to pay</h1>
<div class="d-inline-flex flex-column border rounded p-4 bg-view mb-3">
<img t-att-src="state.qrImage" />
@@ -23,7 +23,6 @@ patch(SelfOrder.prototype, {
) {
const baseUrl = session.base_url;
const order = this.currentOrder;
const tableIdentifier = this.table?.identifier;
let exitRouteUrl = baseUrl;
if (exitRoute) {
@@ -34,10 +33,6 @@ patch(SelfOrder.prototype, {
}
exitRouteUrl += `?access_token=${this.access_token}`;
if (tableIdentifier) {
exitRouteUrl += `&table_identifier=${tableIdentifier}`;
}
}
const exit = encodeURIComponent(exitRouteUrl);
+1 -1
View File
@@ -79,7 +79,7 @@ class PosSelfOrderController(http.Controller):
order.write({
'lines': lines,
'state': 'paid' if amount_total == 0 and pos_config.self_ordering_pay_after == 'each' else 'draft',
'state': 'paid' if amount_total == 0 else 'draft',
'amount_tax': amount_total - amount_untaxed,
'amount_total': amount_total,
})
+8 -1
View File
@@ -18,6 +18,8 @@ from odoo.tools import file_open, split_every
class PosConfig(models.Model):
_inherit = "pos.config"
_ALLOWED_PAYMENT_METHODS = ['adyen', 'stripe']
def _self_order_kiosk_default_languages(self):
return self.env["res.lang"].get_installed()
@@ -145,6 +147,11 @@ class PosConfig(models.Model):
return pos_config_ids
def _get_allowed_payment_methods(self):
if self.self_ordering_mode == 'kiosk':
return self.payment_method_ids.filtered(lambda p: p.use_payment_terminal in self._ALLOWED_PAYMENT_METHODS)
return []
def write(self, vals):
for record in self:
if vals.get('self_ordering_mode') == 'kiosk' or (vals.get('pos_self_ordering_mode') == 'mobile' and vals.get('pos_self_ordering_service_mode') == 'counter'):
@@ -333,7 +340,7 @@ class PosConfig(models.Model):
def _get_self_ordering_data(self):
self.ensure_one()
payment_search_params = self.current_session_id._loader_params_pos_payment_method()
payment_methods = self.payment_method_ids.filtered(lambda p: p.use_payment_terminal in ['adyen', 'stripe'] or p.is_online_payment).read(payment_search_params['search_params']['fields'])
payment_methods = self._get_allowed_payment_methods().read(payment_search_params['search_params']['fields'])
default_language = self.self_ordering_default_language_id.read(["code", "name", "iso_code", "flag_image_url"])
return {
+13
View File
@@ -69,9 +69,22 @@ class PosOrder(models.Model):
@api.model
def create_from_ui(self, orders, draft=False):
old_order_ids = []
for order in orders:
if order['data'].get('server_id'):
server_id = order['data'].get('server_id')
old_order = self.env['pos.order'].browse(server_id).read(['id', 'take_away'])
old_order_ids.append(old_order[0])
orders = super().create_from_ui(orders, draft)
order_ids = self.env['pos.order'].browse([order['id'] for order in orders])
if old_order_ids:
for order in old_order_ids:
if order['take_away']:
order_ids.filtered(lambda o: o.id == order['id']).write({'take_away': True})
if self.env.context.get('from_self') is not True:
self._send_notification(order_ids)
@@ -51,9 +51,6 @@ export class CartPage extends Component {
const orderingMode = this.selfOrder.config.self_ordering_service_mode;
const type = this.selfOrder.config.self_ordering_mode;
const takeAway = this.selfOrder.currentOrder.take_away;
const mode = this.selfOrder.config.self_ordering_pay_after;
const order = this.selfOrder.currentOrder;
const isPaymentMethod = this.selfOrder.pos_payment_methods.length > 0;
if (this.sendInProgress || !this.selfOrder.verifyCart()) {
return;
@@ -64,33 +61,9 @@ export class CartPage extends Component {
return;
}
// in case of no payment methods available -> pay at cashier
if (!isPaymentMethod) {
let screenMode = "pay";
if (!order.isSavedOnServer) {
this.sendInProgress = true;
await this.selfOrder.sendDraftOrderToServer();
this.sendInProgress = false;
screenMode = mode === "meal" ? "order" : "pay";
}
this.router.navigate("confirmation", {
orderAccessToken: order.access_token,
screenMode: screenMode,
});
return;
}
if (orderingMode === "table" && !takeAway) {
if (type === "kiosk") {
this.router.navigate("stand_number");
} else {
this.router.navigate("payment");
}
} else {
this.router.navigate("payment");
}
this.sendInProgress = true;
await this.selfOrder.confirmOrder();
this.sendInProgress = false;
}
selectTable(table) {
@@ -91,6 +91,13 @@ export class LandingPage extends Component {
}
start() {
if (
this.draftOrder.length > 0 &&
this.selfOrder.config.self_ordering_pay_after === "each"
) {
return;
}
if (
this.selfOrder.config.self_ordering_takeaway &&
this.selfOrder.currentOrder.take_away === null
@@ -23,16 +23,9 @@ export class PaymentPage extends Component {
});
onWillStart(async () => {
const type = this.selfOrder.config.self_ordering_mode;
const paymentMethods = this.selfOrder.pos_payment_methods;
if (paymentMethods.length === 0 && type === "kiosk") {
await this.selfOrder.sendDraftOrderToServer();
this.router.navigate("confirmation", {
orderAccessToken: this.selfOrder.currentOrder.access_token,
screenMode: "pay",
});
} else if (paymentMethods.length === 1 && type === "kiosk") {
if (paymentMethods.length === 1) {
this.selectMethod(this.selfOrder.pos_payment_methods[0].id);
}
});
@@ -52,6 +45,8 @@ export class PaymentPage extends Component {
return this.selfOrder.pos_payment_methods.find((p) => p.id === this.state.paymentMethodId);
}
// this function will be override by pos_online_payment_self_order module
// in mobile is the only available payment method
async startPayment() {
this.selfOrder.paymentError = false;
try {
@@ -35,7 +35,7 @@ export class StandNumberPage extends Component {
confirm() {
if (this.state.standNumber.length > 0) {
this.selfOrder.currentOrder.table_stand_number = this.state.standNumber;
this.router.navigate("payment");
this.selfOrder.confirmOrder();
}
}
}
@@ -42,7 +42,6 @@ export class SelfOrder extends Reactive {
this.productsGroupedByCategory = {};
this.lastEditedProductId = null;
this.attributeValueById = {};
this.eatingLocation = "in"; // (in, out) in by default because out can be disabled in the config
this.currentProduct = 0;
this.attributeById = {};
this.priceLoading = false;
@@ -62,13 +61,67 @@ export class SelfOrder extends Reactive {
}
}
async confirmOrder() {
const payAfter = this.config.self_ordering_pay_after; // each, meal
const device = this.config.self_ordering_mode; // kiosk, mobile
const service = this.config.self_ordering_service_mode; // table, counter
const paymentMethods = this.pos_payment_methods; // Stripe, Adyen, Online
const order = this.currentOrder;
// Stand number page will recall this function after the stand number is set
if (service === "table" && !order.take_away && device === "kiosk" && !order.table_stand_number) {
this.router.navigate("stand_number");
return;
}
// if the amount is 0, we don't need to go to the payment page
// this directive works for both mode each and meal
if (order.amount_total === 0 && order.lines.length > 0) {
await this.sendDraftOrderToServer();
this.router.navigate("confirmation", {
orderAccessToken: order.access_token,
screenMode: "order",
});
return;
}
// When no payment methods redirect to confirmation page
// the client will be able to pay at counter
if (paymentMethods.length === 0) {
let screenMode = "pay";
if (!order.isSavedOnServer) {
await this.sendDraftOrderToServer();
screenMode = payAfter === "meal" ? "order" : "pay";
}
this.router.navigate("confirmation", {
orderAccessToken: order.access_token,
screenMode: screenMode,
});
} else {
// In meal mode, first time the customer validate his order, we send it to the server
// and we redirect him to the confirmation page, the next time he validate his order
// if the order is already saved on the server, we redirect him to the payment page
// In each mode, we redirect the customer to the payment page directly
if (payAfter === "meal" && !order.isSavedOnServer) {
await this.sendDraftOrderToServer();
this.router.navigate("confirmation", {
orderAccessToken: order.access_token,
screenMode: "order",
});
} else {
this.router.navigate("payment");
}
}
}
get currentOrder() {
if (this.editedOrder) {
if (this.editedOrder && this.editedOrder.state === "draft") {
return this.editedOrder;
}
const existingOrder = this.orders.find((o) => o.state === "draft");
if (!existingOrder) {
const newOrder = new Order({
pos_config_id: this.pos_config_id,
@@ -14,6 +14,10 @@ patch(PosStore.prototype, {
patch(Order.prototype, {
defaultTableNeeded(options) {
return super.defaultTableNeeded(...arguments) && !this.name.includes("Kiosk") && !this.name.includes("Self-Order");
return (
super.defaultTableNeeded(...arguments) &&
!this.name.includes("Kiosk") &&
!this.name.includes("Self-Order")
);
},
});
@@ -14,14 +14,15 @@ class PosSelfAdyenController(PosAdyenController):
order_reference = data['SaleToPOIResponse']['PaymentResponse']['SaleData']['SaleTransactionID']['TransactionID']
payment_result = data['SaleToPOIResponse']['PaymentResponse']['Response']['Result']
order_sudo = request.env['pos.order'].sudo().search([('pos_reference', '=', order_reference)], limit=1)
order = order_sudo.sudo(False).with_user(order_sudo.session_id.config_id.self_ordering_default_user_id).with_company(order_sudo.session_id.config_id.company_id)
terminal_identifier = data['SaleToPOIResponse']['MessageHeader']['POIID']
payment_method = request.env['pos.payment.method'].sudo().search([('adyen_terminal_identifier', '=', terminal_identifier)], limit=1)
if payment_result == 'Success' and order_sudo.config_id.self_ordering_mode == 'kiosk':
if payment_result == 'Success' and order.config_id.self_ordering_mode == 'kiosk':
payment_amount = data['SaleToPOIResponse']['PaymentResponse']['PaymentResult']['AmountsResp']['AuthorizedAmount']
card_type = data['SaleToPOIResponse']['PaymentResponse']['PaymentResult']['PaymentInstrumentData']['CardData']['PaymentBrand']
transaction_id = data['SaleToPOIResponse']['PaymentResponse']['SaleData']['SaleTransactionID']['TransactionID']
order_sudo.add_payment({
order.add_payment({
'amount': payment_amount,
'payment_date': fields.Datetime.now(),
'payment_method_id': payment_method.id,
@@ -30,13 +31,13 @@ class PosSelfAdyenController(PosAdyenController):
'transaction_id': transaction_id,
'payment_status': payment_result,
'ticket': '',
'pos_order_id': order_sudo.id
'pos_order_id': order.id
})
order_sudo.action_pos_order_paid()
order_sudo._send_order()
order.action_pos_order_paid()
order._send_order()
if order_sudo.config_id.self_ordering_mode == 'kiosk':
request.env['bus.bus']._sendone(f'pos_config-{order_sudo.config_id.access_token}', 'PAYMENT_STATUS', {
if order.config_id.self_ordering_mode == 'kiosk':
order.env['bus.bus']._sendone(f'pos_config-{order.config_id.access_token}', 'PAYMENT_STATUS', {
'payment_result': payment_result,
'order': order_sudo._export_for_self_order(),
'order': order._export_for_self_order(),
})