[IMP] pos_self_order: various modifications

Previously, QR codes could only be obtained by printing an A4 page.
The problem The problem was that some customers would want to create a
specific design and get the raw image of the QR code directly.

A button has now been added to the settings to download a zip file
containing all the QR codes in PNG format.

---

A change has been made to the restaurant table form to enable editing of
the access ID. editing of the access ID. This is useful if a customer
has reset his QR codes and wishes to put them back.

---

Previously, the "close" button in the table selection popup didn't work.
This has been corrected with this PR.

---

Small design change to the display of products that don't have an image
in the selfOrder.

Part-of: odoo/odoo#139287
This commit is contained in:
“moda”
2023-10-23 23:21:15 +00:00
parent 47a3d4c49a
commit e178f3cf89
11 changed files with 116 additions and 13 deletions
+1
View File
@@ -16,6 +16,7 @@
"views/pos_config_view.xml",
"views/pos_session_view.xml",
"views/custom_link_views.xml",
"views/pos_restaurant_views.xml",
"views/product_views.xml",
"data/init_access.xml",
"data/custom_link_data.xml",
@@ -14,7 +14,6 @@ class RestaurantTable(models.Model):
"Security Token",
copy=False,
required=True,
readonly=True,
default=lambda self: self._get_identifier(),
)
@@ -1,4 +1,8 @@
# -*- coding: utf-8 -*-
# -*- codingimport base64
import qrcode
import base64
import zipfile
from io import BytesIO
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
@@ -74,6 +78,68 @@ class ResConfigSettings(models.TransientModel):
"domain": ['|', ['pos_config_ids', 'in', self.pos_config_id.id], ["pos_config_ids", "=", False]],
}
def _generate_single_qr_code(self, url):
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(url)
qr.make(fit=True)
return qr.make_image(fill_color="black", back_color="white")
def generate_qr_codes_zip(self):
if not self.pos_self_ordering_mode in ['mobile', 'consultation']:
raise ValidationError(_("QR codes can only be generated in mobile or consultation mode."))
qr_images = []
if self.pos_module_pos_restaurant:
table_ids = self.pos_config_id.floor_ids.table_ids
if not table_ids:
raise ValidationError(_("In Self-Order mode, you must have at least one table to generate QR codes"))
for table in table_ids:
qr_images.append({
'image': self._generate_single_qr_code(self.pos_config_id._get_self_order_url(table.id)),
'name': f"{table.floor_id.name} - {table.name}",
})
else:
qr_images.append({
'image': self._generate_single_qr_code(self.pos_config_id._get_self_order_url()),
'name': "generic",
})
# Create a zip with all images in qr_images
zip_buffer = BytesIO()
with zipfile.ZipFile(zip_buffer, "w", 0) as zip_file:
for index, qr_image in enumerate(qr_images):
with zip_file.open(f"{qr_image['name']} ({index + 1}).png", "w") as buf:
qr_image['image'].save(buf, format="PNG")
zip_buffer.seek(0)
# Delete previous attachments
self.env["ir.attachment"].search([
("name", "=", "self_order_qr_code.zip"),
]).unlink()
# Create an attachment with the zip
attachment_id = self.env["ir.attachment"].create({
"name": "self_order_qr_code.zip",
"type": "binary",
"raw": zip_buffer.read(),
"res_model": self._name,
"res_id": self.id,
})
return {
"type": "ir.actions.act_url",
"url": f"/web/content/{attachment_id.id}",
"target": "new",
}
def generate_qr_codes_page(self):
"""
Generate the data needed to print the QR codes page
@@ -33,17 +33,23 @@ export class ProductCard extends Component {
return;
}
const pic = this.selfRef.el.querySelector(".o_self_order_item_card_image");
let pic = this.selfRef.el.querySelector(".o_self_order_item_card_image");
if (!pic) {
return;
pic = this.selfRef.el.querySelector(".o_self_order_item_card_no_image");
}
const picRect = pic.getBoundingClientRect();
const clonedPic = pic.cloneNode(true);
const toOrderRect = toOrder.getBoundingClientRect();
clonedPic.classList.remove('w-100', 'h-100');
clonedPic.classList.add('position-fixed', 'border', 'border-white', 'border-4', 'z-index-1');
clonedPic.classList.remove("w-100", "h-100");
clonedPic.classList.add(
"position-fixed",
"border",
"border-white",
"border-4",
"z-index-1"
);
clonedPic.style.top = `${picRect.top}px`;
clonedPic.style.left = `${picRect.left}px`;
clonedPic.style.width = `${picRect.width}px`;
@@ -53,9 +59,10 @@ export class ProductCard extends Component {
document.body.appendChild(clonedPic);
requestAnimationFrame(() => {
let offsetTop = (toOrderRect.top - picRect.top) - picRect.height * 0.5;
let offsetLeft = (toOrderRect.left - picRect.left) - picRect.width * 0.25;
clonedPic.style.transform = 'translateY(' + offsetTop +'px) translateX(' + offsetLeft +'px) scale(0.5)';
const offsetTop = toOrderRect.top - picRect.top - picRect.height * 0.5;
const offsetLeft = toOrderRect.left - picRect.left - picRect.width * 0.25;
clonedPic.style.transform =
"translateY(" + offsetTop + "px) translateX(" + offsetLeft + "px) scale(0.5)";
clonedPic.style.opacity = "0"; // Fading out the card
});
@@ -26,3 +26,9 @@
left: 22px;
top: 4px;
}
.o_self_order_item_card_no_image {
span {
color: #DFDFDF;
}
}
@@ -12,12 +12,15 @@
<div
class="ratio ratio-1x1 w-25 w-sm-50 w-md-100"
t-att-class="{
'd-none d-md-block': !props.product.has_image
'd-md-block': !props.product.has_image
}">
<div class="placeholder-glow">
<div class="placeholder w-100 h-100 bg-300 rounded"/>
<div class="placeholder-glow o_self_order_item_card_no_image">
<div t-attf-class="{{ props.product.has_image ? 'placeholder' : 'd-flex align-items-center justify-content-center h-100' }} bg-200 w-100 h-100 rounded">
<span t-if="!props.product.has_image" t-esc="props.product.name" class="text-center text-white fs-2 fw-bold mb-1 mb-sm-2"/>
</div>
</div>
<img
t-if="props.product.has_image"
class="o_self_order_item_card_image w-100 rounded"
t-attf-src="/menu/get-image/{{ props.product.id }}/512?unique={{props.product.write_date}}"
alt="Product image"
@@ -97,10 +97,10 @@ export class CartPage extends Component {
if (table) {
this.selfOrder.table = table;
this.router.addTableIdentifier(table);
this.pay();
}
this.state.selectTable = false;
this.pay();
}
getChildLines(line) {
@@ -11,6 +11,10 @@
&.active {
color: $headings-color;
&.active-no-image{
outline: 1px solid $o-action;
}
img {
outline: 1px solid $o-action;
}
@@ -11,6 +11,9 @@
t-key="category.id"
t-ref="category_{{category.id}}"
t-attf-class="nav-link category-item flex-shrink-0 p-0"
t-att-class="{
'active-no-image border border-sm rounded p-1 d-flex align-items-center': !category.has_image
}"
t-attf-href="#scrollspy_{{category.id}}">
<div t-if="category.has_image" class="ratio ratio-1x1 mb-1">
<div class="placeholder-glow">
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="pos_self_order_table_form_view" model="ir.ui.view">
<field name="name">Restaurant Table</field>
<field name="model">restaurant.table</field>
<field name="inherit_id" ref="pos_restaurant.view_restaurant_table_form" />
<field name="arch" type="xml">
<xpath expr="//group[@col='2']" position="inside">
<field name="identifier" groups="base.group_no_one" />
</xpath>
</field>
</record>
</odoo>
@@ -18,6 +18,7 @@
<button class="btn-link p-0" icon="oi-arrow-right" name="preview_self_order_app" type="object" string="Preview Web interface"/>
<button class="btn-link p-0" icon="oi-arrow-right" name="custom_link_action" type="object" string="Home buttons"/>
<button class="btn-link p-0" icon="oi-arrow-right" name="generate_qr_codes_page" type="object" string="Print QR Codes" invisible="not pos_self_ordering_mode in ['consultation', 'mobile']"/>
<button class="btn-link p-0" icon="oi-arrow-right" name="generate_qr_codes_zip" type="object" string="Download QR Codes" invisible="not pos_self_ordering_mode in ['consultation', 'mobile']"/>
<button groups="base.group_no_one" class="btn-link p-0" icon="oi-arrow-right" name="update_access_tokens" type="object" string="Reset QR Codes" invisible="not pos_self_ordering_mode in ['consultation', 'mobile']"/>
</div>
</setting>