Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[16.0] pos_self_service_* #1157

Draft
wants to merge 11 commits into
base: 16.0
Choose a base branch
from

Conversation

carmenbianca
Copy link
Member

This is a heavily WIP module to use the Odoo POS as a self-service terminal for customers. Customers will be able to:

  • Weigh their own products, like fruit at a supermarket.
  • Tare their containers, like bringing a pot in which to put any amount of rice.
    • And subsequently, weigh that rice with the weight of the container removed.

It'll be a while before this is finished. Opening the PR early for early reviews from colleagues. Other OCA contributors needn't look at this PR for the moment.

@carmenbianca
Copy link
Member Author

Pinging @huguesdk for an early eye-over of the code to see if it passes the sniff test.

@legalsylvain legalsylvain added this to the 16.0 milestone Feb 21, 2024
Copy link
Member

@huguesdk huguesdk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for this! it seems like a strong base. some things i noticed while testing it:

when it is not able to connect to the iotbox, a red dialog box appears saying:

Unknown Error

Unable to show information about this error.

a new one appears on top of the previous one every few seconds. it would be nice to have a more informative message (or an icon somewhere showing the status).

moreover, it doesn’t seem to take the status of the scale into account: even if it is reported as disconnected, it will read the weight repeatedly.

with this module loaded, the normal (not self-service weight) point of sale interface is different (while it shouldn’t): to purple odoo top bar is not visible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea to mention “weight” in the module name. it seems that i forgot about that. 👍

class PosConfig(models.Model):
_inherit = "pos.config"

iface_self_service = fields.Boolean(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i’m wondering about iface in the name. i see other settings like that in odoo, but they all seem directly related to hardware devices. what do you think about is_self_service_weight or enable_self_service_weight?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 4d2c034

Comment on lines 12 to 13
string="Is Self-Service",
help="Use this POS as self-service point",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think mentioning “weight” would be useful.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 4d2c034

class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

pos_iface_self_service = fields.Boolean(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here: what about pos_is_self_service_weight?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 4d2c034

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where are all these css rules coming from? is that self-made or copied from somewhere else?

const Chrome = require("point_of_sale.Chrome");
const Registries = require("point_of_sale.Registries");

const SelfServiceChrome = (Chrome_) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason for the underscore in Chrome_? i know it is a local variable independent of the one defined above, but i see that most oca modules use the same name.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like Carmen's way better but I changed it anyway for consistency.

cf 4d2c034


const {onMounted, onWillUnmount, useState} = owl;

class WeightWidget extends PosComponent {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this inherit from point_of_sale.ScaleScreen to avoid re-implementing similar methods?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's a good idea :

  • the template displays the order button : should be hidden in both tare and product mode
  • the template displays the price, it should be hidden in tare mode
  • it needs to be loaded with a product
  • it is usually called as a popup which returns a payload

If there is a clean way to do it, please point in the right direction :-)

Comment on lines 9 to 10
const Registries = require("point_of_sale.Registries");
const {useListener} = require("@web/core/utils/hooks");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huguesdk i don't understand the difference between these two syntaxes, can you enlighten me ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the second syntax is called destructuring assignment and in this case it is equivalent to:

    const useListener = require("@web/core/utils/hooks").useListener;

it avoids repeating the variable name, but it is even more useful when assigning multiple variables together, like this:

    const {useListener, useService} = require("@web/core/utils/hooks");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks !

Comment on lines 28 to 33
_readScale() {
this.env.proxy_queue.schedule(this._setWeight.bind(this), {
duration: 500,
repeat: true,
});
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I understand correctly ? Once the weight widget is mounted, _read_scale schedules a call of _set_weight every 500ms ?

I do not understand the bind instruction.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unlike in python, in javascript a reference to a method is not bound to a this variable. bind() returns a new function that, when called, provides the this variable to the method. more information on mdn.

i’m not familiar with the schedule() function, but as i understand it, it will call this._setWeight() every 500 ms, forever.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks !

@robinkeunen
Copy link

@huguesdk should i consider translating the code as a Native JS Module ?

@robinkeunen
Copy link

squashed previous work

@robinkeunen robinkeunen force-pushed the 16.0-self-service-weight branch 2 times, most recently from 574f882 to 9c19367 Compare March 28, 2024 11:18
@robinkeunen robinkeunen force-pushed the 16.0-self-service-weight branch 2 times, most recently from fd7eaec to b070c43 Compare April 4, 2024 14:13
Copy link
Member

@huguesdk huguesdk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@robinkeunen thanks for all the improvements!

some remarks:

  • the red “unknown error” dialog box still appears.
  • the top bar is now visible again in the normal pos, but in self-service weighing mode as well, while it should be hidden, as the customer shouldn’t be able to easily exit the interface.
  • previously, the module was called pos_self_service_base, without a reference to weighing, while here it is specifically about weighing, so i suggest to rename all classes (and files) from *SelfService* to *SelfServiceWeighing*.
  • (not that important) it is possible to create a self-service weight station without checking the “electronic scale” checkbox is the iot box section. having something checking this would be a plus (only if it is quick and easy).

}

.foodbox {
background-image: url("../../img/empty_foodbox_on_scale.svg");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this file?

is all this css needed at this point or is this mostly coming from the old pos_self_service_base?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not look into it at all. Writing it down for later.

@@ -0,0 +1,35 @@
/** @odoo-module alias=pos_self_service_weight_base.Chrome **/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the alias needed? (same remark for all modules)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is needed if we want to allow other modules to inherit this one using the old Odoo define syntax. so not strictly needed.

i also prefer importing module names than file names but that's maybe me coming from python and lack consistency.

do you think I should change it ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this module is extending classes that still use the old define syntax, i think t makes sense to keep it.

}

showTicketButton() {
return (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a why comment would help to understand what this method is for.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

_inherit = "pos.config"

is_self_service_weight_point = fields.Boolean(
string="Is Self-Service",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
string="Is Self-Service",
string="Is a Self-Service Weighing Station",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

<div class="o_setting_right_pane">
<label for="is_self_service_weight_point" />
<div class="text-muted mb16" id="is_self_service_weight_point_text">
Use your PoS as self-service
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Use your PoS as self-service
Use this PoS as a self-service weighing station

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

-->
<templates id="template" xml:space="preserve">

<t t-name="pos_self_service_weight_base.SelfServiceControlButton" owl="1">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<t t-name="pos_self_service_weight_base.SelfServiceControlButton" owl="1">
<t t-name="pos_self_service_weight_base.SelfServiceWeighingControlButton" owl="1">

-->
<templates id="template" xml:space="preserve">

<t t-name="pos_self_service_weight_base.AbstractSelfServiceScreen" owl="1">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<t t-name="pos_self_service_weight_base.AbstractSelfServiceScreen" owl="1">
<t t-name="pos_self_service_weight_base.AbstractSelfServiceWeighingScreen" owl="1">

-->
<templates id="template" xml:space="preserve">

<t t-name="pos_self_service_weight_base.SelfServiceActionButton" owl="1">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<t t-name="pos_self_service_weight_base.SelfServiceActionButton" owl="1">
<t t-name="pos_self_service_weight_base.SelfServiceWeighingActionButton" owl="1">

SPDX-License-Identifier: AGPL-3.0-or-later
-->
<templates id="template" xml:space="preserve">
<t t-name="pos_self_service_weight_base.SelfServiceWelcomeScreen" owl="1">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<t t-name="pos_self_service_weight_base.SelfServiceWelcomeScreen" owl="1">
<t t-name="pos_self_service_weight_base.SelfServiceWeighingWelcomeScreen" owl="1">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done for all

<div class="screen">
<div class="screen-full-width">
<div class="self-service-action-buttons">
<SelfServiceActionButton t-if="env.isDebug()" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this button shown only in debug mode?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It exists to test the behavior of the base module when there are nos other modules installed. It can be removed in the future.

@robinkeunen
Copy link

@huguesdk I restored the top bar because

  • i wanted to exit cleanly the interface
  • i find it useful to have the information of the connections and the logged user
  • i find the look more consistent with the other point of sale
  • (i read the specification too fast)

To mitigate the risk of closing the point of sale by accident, I added the confirmation popup (suggestion from Rémy).

I tend to agree that the risk to have customer closing the weighing station is high so, keeping in mind the add benefits of the bar, do you agree to rather hide the "close" button and remove the popup altogether.

@victor-champonnois fyi

Signed-off-by: Carmen Bianca BAKKER <carmen@coopiteasy.be>

[IMP] pos_self_service_weight: hide orders button

[IMP] pos_self_service: add CloseSelfServicePopup

No cash is manipulated in self service mode do not therefore
ask the user to control the amounts.

[IMP] pos_self_service: translate to js native modules

[IMP] pos_self_service: rename files to .esm.js

[IMP] pos_self_service: rename files to .esm.js

[ADD] pos_self_service: add welcome screen

[REF] pos_self_service: rename SSScreen
Copy link
Member Author

@carmenbianca carmenbianca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite a big review. Some of the things in the review are my own bad/questionable decisions.

@@ -0,0 +1,3 @@
* `Coop IT Easy SC, Odoo Community Association (OCA) <https://github.com/OCA/pos>`_:

* Carmen Bianca BAKKER
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add Robin Keunen

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

<attribute name="t-if">showTicketButton()</attribute>
</xpath>
</t>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There used to be this code here:

        <xpath expr="//div[hasclass('pos-topheader')]" position="attributes">
            <attribute
                name="t-att-class"
            >{ oe_hidden: state.uiState !== 'READY' || env.pos.config.iface_self_service }</attribute>
        </xpath>

        <xpath expr="//div[hasclass('pos-content')]" position="attributes">
            <t t-if="env.pos.config.ifaceself_service">
                <attribute name="class">pos-content positionzero</attribute>
            </t>
        </xpath>

This removed the bar at the top. Now, a user can click on 'close', and be sent back to the Odoo interface. This is bad for two reasons:

  • Depending on how Odoo is configured, this allows the user access to things they shouldn't be able to see.
  • It may be difficult/time-consuming to recover from such an error.

'Close' is also an incredibly normal button to press, so this may be very frequent.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I talked with @robinkeunen after forgetting to read his comment from earlier today → hiding the close button would also be a satisfactory solution. This would also allow getting rid of some code elsewhere related to the close pop-up.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 10 to 42
const SelfServiceWeighingHeaderButton = (HeaderButton) =>
class extends HeaderButton {
async onClick() {
if (!this.env.pos.config.is_self_service_weighing_point) {
return super.onClick();
}
try {
const info = await this.env.pos.getClosePosInfo();
this.showPopup("CloseSelfServiceWeighingPopup", {
info: info,
keepBehind: true,
});
} catch (e) {
if (isConnectionError(e)) {
this.showPopup("OfflineErrorPopup", {
title: this.env._t("Network Error"),
body: this.env._t(
"Please check your internet connection and try again."
),
});
} else {
this.showPopup("ErrorPopup", {
title: this.env._t("Unknown Error"),
body: this.env._t(
"An unknown error prevents us from getting closing information."
),
});
}
}
}
};
Registries.Component.extend(HeaderButton, SelfServiceWeighingHeaderButton);
export default SelfServiceWeighingHeaderButton;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oof… This is 99% upstream code, except uses a custom implementation of the closing pop-up. That isn't great. Can we not edit the XML/template of ClosePosPopup to display something completely different if the POS is a weighing station?

Although, as mentioned in a previous comment, I'm not sure we should be able to close from this screen.

Comment on lines 11 to 18
class SelfServiceWeighingActionButton extends PosComponent {
get name() {
return "Base Action";
}

get faSymbol() {
return "fa-barcode";
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this class? This seems very similar to SelfServiceWeighingControlButton, but with some strange defaults?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I've worked this out. It's the button on the welcome screen. The defaults are there because there's a button in the debug mode?

I think a few things need to happen to improve this:

  1. Rename SelfServiceWeighingActionButton to SelfServiceWeighingWelcomeButton. This is much easier to disambiguate from SelfServiceWeighingControlButton.
  2. Remove all the default values from SelfServiceWeighingWelcomeButton.
  3. Create SelfServiceWeighingDebugButton with the removed defaults.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

<templates id="template" xml:space="preserve">

<t
t-name="pos_self_service_weighing_base.AbstractSelfServiceWeighingScreen"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name does not match file name.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +9 to +25
<t t-name="pos_self_service_weighing_tare.SelfServiceWeighingTareScreen" owl="1">
<div class="screen">
<div class="screen-full-width">
<div class="leftpane pane-border">
<WeightWidget />
<div class="self-service-control-buttons">
<SelfServiceWeighingHomeButton />
</div>
</div>
<div class="rightpane">
<div class="self-service-action-buttons">
<SelfServiceWeighingPrintTareButton />
</div>
</div>
</div>
</div>
</t>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This completely re-implements pos_self_service_weighing_base.AbstractSelfServiceWeighingScreen, which I'm not sure is intentional. Can you inherit the latter template without changing it for other inheritors?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked with @robinkeunen about this. We can do primary inheritance here as described in my previous comment, but there's a chance that the primary inheritance would be more lines of code in a much less transparent way. e.g. insert a button here, insert a button there, etc.

For developer ease, it may be nice to just see the simple hierarchy like in the quoted code. The hierarchy is sufficiently small that the repetition is barely a problem. And there would be repetition, because there will be other screens that re-implement pos_self_service_weighing_base.AbstractSelfServiceWeighingScreen. Furthermore, although we currently think that other implementations of AbstractSelfServiceWeighingScreen will look incredibly similar, this may not always be the case.

But… For the time being, it's still repetition. And because this hierarchy kind of depends on pos.css from the main module to display well, it's probably better to do primary inheritance?

Your thoughts @huguesdk ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huguesdk we discussed this with Carmen. It is now obvious to us wether primary inheritance is better (less code - maybe) or worse (lacks clarity) then redefining a template. What is your point of view ?

import PosComponent from "point_of_sale.PosComponent";
import Registries from "point_of_sale.Registries";

class SelfServiceWeighingTareScreen extends PosComponent {}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this not extend AbstractSelfServiceWeighingScreen?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 9 to 17
<t
t-name="pos_self_service_weighing_tare.SelfServiceWeighingTareScreenButton"
owl="1"
>
<button class="button">
<t t-esc="name" />
<i t-attf-class="fa {{ faSymbol }}" />
</button>
</t>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is not needed.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done (and other similar)

Comment on lines 27 to 28
SelfServiceWeighingPrintTareButton.template =
"pos_self_service_weighing_tare.SelfServiceWeighingPrintTareButton";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can set the template to pos_self_service_weighing_base.SelfServiceWeighingControlButton.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants