[REF] point_of_sale, pos_*: remove usage of useListener
*:point_of_sale,pos_ayden,pos_discount,pos_hr,pos_loyalty,pos_mercury, pos_restaurant,pos_sale,pos_six Previously, the point of sale and related modules made heavy usage of `useListener`. This is because the pos was converted early to owl, and in owl 1, using events to communicate between components was encouraged. In owl 2, the decision was made to stop encouraging this way of communicating between components, because it encourages communication accross many layers of components, and is also hard to debug. In addition, in owl 2, because components can have 0 or more than node, including non HTMLElement nodes like text or comment nodes, owl removed the implementation of useListener, as useListener relies on the presence and unicity of a root HTMLElement per component on which we can attach event listeners. Another problem is that events rely on the fact that the components are in the DOM so that the event can propagate, which can lead to issues in some cases and in particular, in the PoS, if you load the point of sale and switch tabs, some things will be broken because we are triggering events, but there have not been any animation frame fired in the tab because it's not focussed, and so owl has not mounted the component yet. When migrating odoo to owl 2, we introduced a shim for this.el and useListener in LegacyComponent, with the hope to remove it as soon as possible. This commit removes all usage of useListener from the pos modules for those reasons. In some cases, useListener was incorrectly used intstead of t-on, and the event was triggered and handled by the same component, in other cases, a simple callback could be passed to a child component. In a few cases, a method had to be implemented on the store as we wanted this method to be available everywhere. closes odoo/odoo#112219 Related: odoo/enterprise#36888 Signed-off-by: Samuel Degueldre <sad@odoo.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
@@ -12,8 +11,9 @@ import { SaleDetailsButton } from "@point_of_sale/js/ChromeWidgets/SaleDetailsBu
|
||||
import { SyncNotification } from "@point_of_sale/js/ChromeWidgets/SyncNotification";
|
||||
import { CashMovePopup } from "./cash_move_popup/cash_move_popup";
|
||||
import { TicketScreen } from "@point_of_sale/js/Screens/TicketScreen/TicketScreen";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class Navbar extends LegacyComponent {
|
||||
export class Navbar extends Component {
|
||||
static template = "point_of_sale.Navbar";
|
||||
static components = {
|
||||
// FIXME POSREF remove some of these components
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { useListener } from "@web/core/utils/hooks";
|
||||
import { parse } from "web.field_utils";
|
||||
import { barcodeService } from "@barcodes/barcode_service";
|
||||
import { _t } from "web.core";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { EventBus, onMounted, onWillUnmount, useComponent, useExternalListener } from "@odoo/owl";
|
||||
|
||||
const { EventBus, onMounted, onWillUnmount, useComponent, useExternalListener } = owl;
|
||||
const INPUT_KEYS = new Set(
|
||||
["Delete", "Backspace", "+1", "+2", "+5", "+10", "+20", "+50"].concat(
|
||||
"0123456789+-.,".split("")
|
||||
@@ -19,7 +18,6 @@ const getDefaultConfig = () => ({
|
||||
triggerAtEnter: false,
|
||||
triggerAtEsc: false,
|
||||
triggerAtInput: false,
|
||||
nonKeyboardInputEvent: false,
|
||||
useWithBarcode: false,
|
||||
});
|
||||
|
||||
@@ -124,7 +122,6 @@ class NumberBuffer extends EventBus {
|
||||
* @param {String|null} config.triggerAtEnter Event triggered when 'Enter' key is pressed.
|
||||
* @param {String|null} config.triggerAtEsc Event triggered when 'Esc' key is pressed.
|
||||
* @param {String|null} config.triggerAtInput Event triggered for every accepted input.
|
||||
* @param {String|null} config.nonKeyboardInputEvent Also listen to a non-keyboard input event
|
||||
* that carries a payload of { key }. The key is checked if it is a valid input. If valid,
|
||||
* the number buffer is modified just as it is modified when a keyboard key is pressed.
|
||||
* @param {Boolean} config.useWithBarcode Whether this buffer is used with barcode.
|
||||
@@ -148,10 +145,6 @@ class NumberBuffer extends EventBus {
|
||||
this.bufferHolderStack.pop();
|
||||
this._setUp();
|
||||
});
|
||||
// Add listener that accepts non keyboard inputs
|
||||
if (typeof config.nonKeyboardInputEvent === "string") {
|
||||
useListener(config.nonKeyboardInputEvent, this._onNonKeyboardInput.bind(this));
|
||||
}
|
||||
}
|
||||
get _currentBufferHolder() {
|
||||
return this.bufferHolderStack[this.bufferHolderStack.length - 1];
|
||||
@@ -172,7 +165,14 @@ class NumberBuffer extends EventBus {
|
||||
_onKeyboardInput(event) {
|
||||
return this._bufferEvents(this._onInput((event) => event.key))(event);
|
||||
}
|
||||
_onNonKeyboardInput(event) {
|
||||
sendKey(key) {
|
||||
const event = new CustomEvent("", {
|
||||
detail: {
|
||||
key: key,
|
||||
},
|
||||
});
|
||||
Object.defineProperty(event, "target", { value: {} });
|
||||
|
||||
return this._bufferEvents(this._onInput((event) => event.detail.key))(event);
|
||||
}
|
||||
_bufferEvents(handler) {
|
||||
@@ -211,13 +211,13 @@ class NumberBuffer extends EventBus {
|
||||
}
|
||||
_handleInput(key) {
|
||||
if (key === "Enter" && this.config.triggerAtEnter) {
|
||||
this.component.trigger(this.config.triggerAtEnter, this.state);
|
||||
this.config.triggerAtEnter(this.state);
|
||||
} else if (key === "Esc" && this.config.triggerAtEsc) {
|
||||
this.component.trigger(this.config.triggerAtEsc, this.state);
|
||||
this.config.triggerAtEsc(this.state);
|
||||
} else if (INPUT_KEYS.has(key)) {
|
||||
this._updateBuffer(key);
|
||||
if (this.config.triggerAtInput) {
|
||||
this.component.trigger(this.config.triggerAtInput, {
|
||||
this.config.triggerAtInput({
|
||||
buffer: this.state.buffer,
|
||||
key,
|
||||
});
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { reactive } from "@odoo/owl";
|
||||
import { Component, reactive } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
export class PopupContainer extends LegacyComponent {
|
||||
export class PopupContainer extends Component {
|
||||
static template = "point_of_sale.PopupContainer";
|
||||
setup() {
|
||||
// FIXME POSREF: remove this after LegacyComponent is removed.
|
||||
// FIXME POSREF: remove this when Chrome uses the new env
|
||||
this.__owl__.childEnv = usePos().legacyEnv;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,13 @@ import { PosGlobalState } from "@point_of_sale/js/models";
|
||||
import { pos_env as legacyEnv } from "@point_of_sale/js/pos_env";
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
|
||||
import { reactive, markRaw } from "@odoo/owl";
|
||||
import { Reactive } from "@point_of_sale/utils";
|
||||
import { identifyError } from "@point_of_sale/app/error_handlers/error_handlers";
|
||||
import { ConnectionLostError } from "@web/core/network/rpc_service";
|
||||
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
|
||||
export class PosStore extends Reactive {
|
||||
/** @type {'LOADING' | 'READY' | 'CLOSING'} */
|
||||
@@ -17,10 +22,12 @@ export class PosStore extends Reactive {
|
||||
legacyEnv = legacyEnv;
|
||||
globalState = new PosGlobalState({ env: markRaw(legacyEnv) });
|
||||
|
||||
static serviceDependencies = ["popup"];
|
||||
constructor({ popup }) {
|
||||
static serviceDependencies = ["popup", "orm", "number_buffer"];
|
||||
constructor({ popup, orm, number_buffer }) {
|
||||
super();
|
||||
this.orm = orm;
|
||||
this.popup = popup;
|
||||
this.numberBuffer = number_buffer;
|
||||
this.setup();
|
||||
}
|
||||
// use setup instead of constructor because setup can be patched.
|
||||
@@ -40,6 +47,207 @@ export class PosStore extends Reactive {
|
||||
this.showScreen(screenName);
|
||||
}
|
||||
|
||||
connect_to_proxy() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.globalState.env.barcode_reader.disconnect_from_proxy();
|
||||
this.globalState.loadingSkipButtonIsShown = true;
|
||||
this.globalState.env.proxy
|
||||
.autoconnect({
|
||||
force_ip: this.globalState.config.proxy_ip || undefined,
|
||||
progress: function (prog) {},
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
if (this.globalState.config.iface_scan_via_proxy) {
|
||||
this.globalState.env.barcode_reader.connect_to_proxy();
|
||||
}
|
||||
resolve();
|
||||
},
|
||||
(statusText, url) => {
|
||||
// this should reject so that it can be captured when we wait for pos.ready
|
||||
// in the chrome component.
|
||||
// then, if it got really rejected, we can show the error.
|
||||
if (statusText == "error" && window.location.protocol == "https:") {
|
||||
reject({
|
||||
title: _t("HTTPS connection to IoT Box failed"),
|
||||
body: _.str.sprintf(
|
||||
_t(
|
||||
"Make sure you are using IoT Box v18.12 or higher. Navigate to %s to accept the certificate of your IoT Box."
|
||||
),
|
||||
url
|
||||
),
|
||||
popup: "alert",
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async closePos() {
|
||||
// If pos is not properly loaded, we just go back to /web without
|
||||
// doing anything in the order data.
|
||||
if (!this.globalState || this.globalState.db.get_orders().length === 0) {
|
||||
window.location = "/web#action=point_of_sale.action_client_pos_menu";
|
||||
}
|
||||
|
||||
if (this.globalState.db.get_orders().length) {
|
||||
// If there are orders in the db left unsynced, we try to sync.
|
||||
// If sync successful, close without asking.
|
||||
// Otherwise, ask again saying that some orders are not yet synced.
|
||||
try {
|
||||
await this.globalState.push_orders();
|
||||
window.location = "/web#action=point_of_sale.action_client_pos_menu";
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
const reason = this.globalState.failed
|
||||
? _t(
|
||||
"Some orders could not be submitted to " +
|
||||
"the server due to configuration errors. " +
|
||||
"You can exit the Point of Sale, but do " +
|
||||
"not close the session before the issue " +
|
||||
"has been resolved."
|
||||
)
|
||||
: _t(
|
||||
"Some orders could not be submitted to " +
|
||||
"the server due to internet connection issues. " +
|
||||
"You can exit the Point of Sale, but do " +
|
||||
"not close the session before the issue " +
|
||||
"has been resolved."
|
||||
);
|
||||
const { confirmed } = await this.popup.add(ConfirmPopup, {
|
||||
title: _t("Offline Orders"),
|
||||
body: reason,
|
||||
});
|
||||
if (confirmed) {
|
||||
// FIXME POSREF setting the location prevents the next render, the loading screen never shows
|
||||
this.globalState.uiState = "CLOSING";
|
||||
this.globalState.loadingSkipButtonIsShown = false;
|
||||
window.location = "/web#action=point_of_sale.action_client_pos_menu";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
async closeSession(defaultCashCounted, notes, otherPaymentMethods, payments) {
|
||||
let response;
|
||||
|
||||
if (this.globalState.config.cashControl) {
|
||||
response = await this.orm.call(
|
||||
"pos.session",
|
||||
"post_closing_cash_details"[this.globalState.pos_session.id],
|
||||
{
|
||||
counted_cash: defaultCashCounted,
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.successful) {
|
||||
return this.handleClosingError(response);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await this.orm.call("pos.session", "update_closing_control_state_session", [
|
||||
this.globalState.pos_session.id,
|
||||
notes,
|
||||
]);
|
||||
} catch (error) {
|
||||
// We have to handle the error manually otherwise the validation check stops the script.
|
||||
// In case of "rescue session", we want to display the next popup with "handleClosingError".
|
||||
// FIXME
|
||||
if (
|
||||
!error.message &&
|
||||
!error.message.data &&
|
||||
error.message.data.message !== "This session is already closed."
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const bankPaymentMethodDiffPairs = otherPaymentMethods
|
||||
.filter((pm) => pm.type == "bank")
|
||||
.map((pm) => [pm.id, payments[pm.id].difference]);
|
||||
response = await this.orm.call("pos.session", "close_session_from_ui", [
|
||||
this.globalState.pos_session.id,
|
||||
bankPaymentMethodDiffPairs,
|
||||
]);
|
||||
if (!response.successful) {
|
||||
return this.handleClosingError(response);
|
||||
}
|
||||
window.location = "/web#action=point_of_sale.action_client_pos_menu";
|
||||
} catch (error) {
|
||||
if (identifyError(error) instanceof ConnectionLostError) {
|
||||
// Cannot redirect to backend when offline, let error handlers show the offline popup
|
||||
// FIXME POSREF: doing this means closing again when online will redo the beginning of the method
|
||||
// although it's impossible to close again because this.closeSessionClicked isn't reset to false
|
||||
// The application state is corrupted.
|
||||
throw error;
|
||||
} else {
|
||||
// FIXME POSREF: why are we catching errors here but not anywhere else in this method?
|
||||
await this.popup.add(ErrorPopup, {
|
||||
title: _t("Closing session error"),
|
||||
body: _t(
|
||||
"An error has occurred when trying to close the session.\n" +
|
||||
"You will be redirected to the back-end to manually close the session."
|
||||
),
|
||||
});
|
||||
window.location = "/web#action=point_of_sale.action_client_pos_menu";
|
||||
}
|
||||
}
|
||||
}
|
||||
async selectPartner() {
|
||||
// FIXME, find order to refund when we are in the ticketscreen.
|
||||
const currentOrder = this.globalState.get_order();
|
||||
if (!currentOrder) {
|
||||
return;
|
||||
}
|
||||
const currentPartner = currentOrder.get_partner();
|
||||
if (currentPartner && currentOrder.getHasRefundLines()) {
|
||||
this.popup.add(ErrorPopup, {
|
||||
title: this.env._t("Can't change customer"),
|
||||
body: _.str.sprintf(
|
||||
this.env._t(
|
||||
"This order already has refund lines for %s. We can't change the customer associated to it. Create a new order for the new customer."
|
||||
),
|
||||
currentPartner.name
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { confirmed, payload: newPartner } = await this.showTempScreen("PartnerListScreen", {
|
||||
partner: currentPartner,
|
||||
});
|
||||
if (confirmed) {
|
||||
currentOrder.set_partner(newPartner);
|
||||
currentOrder.updatePricelist(newPartner);
|
||||
}
|
||||
}
|
||||
// FIXME: POSREF, method exist only to be overrided
|
||||
async addProductFromUi(product, options) {
|
||||
this.globalState.get_order().add_product(product, options);
|
||||
}
|
||||
async addProductToCurrentOrder(product) {
|
||||
if (Number.isInteger(product)) {
|
||||
product = this.globalState.db.get_product_by_id(product);
|
||||
}
|
||||
const currentOrder = this.globalState.get_order();
|
||||
|
||||
if (!currentOrder) {
|
||||
this.globalState.add_new_order();
|
||||
}
|
||||
|
||||
const options = await product.getAddProductOptions();
|
||||
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the product after having the extra information.
|
||||
this.addProductFromUi(product, options);
|
||||
this.numberBuffer.reset();
|
||||
}
|
||||
// FIXME POSREF get rid of temp screens entirely?
|
||||
showTempScreen(name, props = {}) {
|
||||
return new Promise((resolve) => {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { loadCSS } from "@web/core/assets";
|
||||
import { useListener, useBus, useService } from "@web/core/utils/hooks";
|
||||
import { useBus, useService } from "@web/core/utils/hooks";
|
||||
import BarcodeParser from "barcodes.BarcodeParser";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { batched } from "@point_of_sale/js/utils";
|
||||
import { debounce } from "@web/core/utils/timing";
|
||||
import { Transition } from "@web/core/transition";
|
||||
@@ -18,7 +17,6 @@ import { pos_env as env } from "@point_of_sale/js/pos_env";
|
||||
|
||||
import { ErrorTracebackPopup } from "./Popups/ErrorTracebackPopup";
|
||||
import { CashOpeningPopup } from "./Popups/CashOpeningPopup";
|
||||
import { ConfirmPopup } from "./Popups/ConfirmPopup";
|
||||
|
||||
import {
|
||||
onMounted,
|
||||
@@ -27,13 +25,14 @@ import {
|
||||
useSubEnv,
|
||||
reactive,
|
||||
onWillUnmount,
|
||||
Component,
|
||||
} from "@odoo/owl";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
|
||||
/**
|
||||
* Chrome is the root component of the PoS App.
|
||||
*/
|
||||
export class Chrome extends LegacyComponent {
|
||||
export class Chrome extends Component {
|
||||
static template = "Chrome"; // FIXME POSREF namespace templates
|
||||
static components = { Transition, MainComponentsContainer, WithEnv, ErrorHandler, Navbar };
|
||||
setup() {
|
||||
@@ -90,9 +89,6 @@ export class Chrome extends LegacyComponent {
|
||||
|
||||
super.setup();
|
||||
useExternalListener(window, "beforeunload", this._onBeforeUnload);
|
||||
useListener("close-pos", this._closePos);
|
||||
useListener("loading-skip-callback", () => this.env.proxy.stop_searching());
|
||||
useListener("connect-to-proxy", this.connect_to_proxy);
|
||||
useBus(this.env.posbus, "start-cash-control", this.openCashControl);
|
||||
useService("number_buffer").activate();
|
||||
|
||||
@@ -157,7 +153,7 @@ export class Chrome extends LegacyComponent {
|
||||
await this.env.pos.load_server_data();
|
||||
await this.setupBarcodeParser();
|
||||
if (this.env.pos.config.use_proxy) {
|
||||
await this.connect_to_proxy();
|
||||
await this.pos.connect_to_proxy();
|
||||
}
|
||||
// Load the saved `env.pos.toRefundLines` from localStorage when
|
||||
// the PosGlobalState is ready.
|
||||
@@ -252,45 +248,6 @@ export class Chrome extends LegacyComponent {
|
||||
return barcode_parser.is_loaded();
|
||||
}
|
||||
|
||||
connect_to_proxy() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.env.barcode_reader.disconnect_from_proxy();
|
||||
this.pos.loadingSkipButtonIsShown = true;
|
||||
this.env.proxy
|
||||
.autoconnect({
|
||||
force_ip: this.env.pos.config.proxy_ip || undefined,
|
||||
progress: function (prog) {},
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
if (this.env.pos.config.iface_scan_via_proxy) {
|
||||
this.env.barcode_reader.connect_to_proxy();
|
||||
}
|
||||
resolve();
|
||||
},
|
||||
(statusText, url) => {
|
||||
// this should reject so that it can be captured when we wait for pos.ready
|
||||
// in the chrome component.
|
||||
// then, if it got really rejected, we can show the error.
|
||||
if (statusText == "error" && window.location.protocol == "https:") {
|
||||
reject({
|
||||
title: this.env._t("HTTPS connection to IoT Box failed"),
|
||||
body: _.str.sprintf(
|
||||
this.env._t(
|
||||
"Make sure you are using IoT Box v18.12 or higher. Navigate to %s to accept the certificate of your IoT Box."
|
||||
),
|
||||
url
|
||||
),
|
||||
popup: "alert",
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
openCashControl() {
|
||||
if (this.shouldShowCashControl()) {
|
||||
this.popup.add(CashOpeningPopup, { keepBehind: true });
|
||||
@@ -306,48 +263,7 @@ export class Chrome extends LegacyComponent {
|
||||
// EVENT HANDLERS //
|
||||
|
||||
async _closePos() {
|
||||
// If pos is not properly loaded, we just go back to /web without
|
||||
// doing anything in the order data.
|
||||
if (!this.env.pos || this.env.pos.db.get_orders().length === 0) {
|
||||
window.location = "/web#action=point_of_sale.action_client_pos_menu";
|
||||
}
|
||||
|
||||
if (this.env.pos.db.get_orders().length) {
|
||||
// If there are orders in the db left unsynced, we try to sync.
|
||||
// If sync successful, close without asking.
|
||||
// Otherwise, ask again saying that some orders are not yet synced.
|
||||
try {
|
||||
await this.env.pos.push_orders();
|
||||
window.location = "/web#action=point_of_sale.action_client_pos_menu";
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
const reason = this.env.pos.failed
|
||||
? this.env._t(
|
||||
"Some orders could not be submitted to " +
|
||||
"the server due to configuration errors. " +
|
||||
"You can exit the Point of Sale, but do " +
|
||||
"not close the session before the issue " +
|
||||
"has been resolved."
|
||||
)
|
||||
: this.env._t(
|
||||
"Some orders could not be submitted to " +
|
||||
"the server due to internet connection issues. " +
|
||||
"You can exit the Point of Sale, but do " +
|
||||
"not close the session before the issue " +
|
||||
"has been resolved."
|
||||
);
|
||||
const { confirmed } = await this.popup.add(ConfirmPopup, {
|
||||
title: this.env._t("Offline Orders"),
|
||||
body: reason,
|
||||
});
|
||||
if (confirmed) {
|
||||
// FIXME POSREF setting the location prevents the next render, the loading screen never shows
|
||||
this.pos.uiState = "CLOSING";
|
||||
this.pos.loadingSkipButtonIsShown = false;
|
||||
window.location = "/web#action=point_of_sale.action_client_pos_menu";
|
||||
}
|
||||
}
|
||||
}
|
||||
this.pos.closePos();
|
||||
}
|
||||
/**
|
||||
* Save `env.pos.toRefundLines` in localStorage on beforeunload - closing the
|
||||
@@ -356,6 +272,10 @@ export class Chrome extends LegacyComponent {
|
||||
_onBeforeUnload() {
|
||||
this.env.pos.db.save("TO_REFUND_LINES", this.env.pos.toRefundLines);
|
||||
}
|
||||
_onSetSyncStatus({ detail: { status, pending } }) {
|
||||
this.env.pos.synch.status = status;
|
||||
this.env.pos.synch.pending = pending;
|
||||
}
|
||||
|
||||
// MISC METHODS //
|
||||
_preloadImages() {
|
||||
@@ -416,7 +336,7 @@ export class Chrome extends LegacyComponent {
|
||||
msg.session == this.env.pos.pos_session.id
|
||||
) {
|
||||
console.info("POS / Session opened in another window. EXITING POS");
|
||||
this._closePos();
|
||||
this.closePos();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
// Previously UsernameWidget
|
||||
export class CashierName extends LegacyComponent {
|
||||
export class CashierName extends Component {
|
||||
static template = "CashierName";
|
||||
|
||||
get username() {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component, useState } from "@odoo/owl";
|
||||
|
||||
const { useState } = owl;
|
||||
|
||||
export class CustomerFacingDisplayButton extends LegacyComponent {
|
||||
export class CustomerFacingDisplayButton extends Component {
|
||||
static template = "CustomerFacingDisplayButton";
|
||||
|
||||
setup() {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { ClosePosPopup } from "@point_of_sale/js/Popups/ClosePosPopup";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
// Previously HeaderButtonWidget
|
||||
// This is the close session button
|
||||
export class HeaderButton extends LegacyComponent {
|
||||
export class HeaderButton extends Component {
|
||||
static template = "HeaderButton";
|
||||
|
||||
setup() {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
|
||||
const { onMounted, onWillUnmount, useState } = owl;
|
||||
import { Component, onMounted, onWillUnmount, useState } from "@odoo/owl";
|
||||
|
||||
// Previously ProxyStatusWidget
|
||||
export class ProxyStatus extends LegacyComponent {
|
||||
export class ProxyStatus extends Component {
|
||||
static template = "ProxyStatus";
|
||||
|
||||
setup() {
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { renderToString } from "@web/core/utils/render";
|
||||
import { ErrorPopup } from "../Popups/ErrorPopup";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class SaleDetailsButton extends LegacyComponent {
|
||||
export class SaleDetailsButton extends Component {
|
||||
static template = "SaleDetailsButton";
|
||||
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
this.popup = useService("popup");
|
||||
this.rpc = useService("rpc");
|
||||
}
|
||||
|
||||
async onClick() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class SyncNotification extends LegacyComponent {
|
||||
export class SyncNotification extends Component {
|
||||
static template = "SyncNotification";
|
||||
|
||||
onClick() {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { ConfirmPopup } from "../Popups/ConfirmPopup";
|
||||
import { ErrorPopup } from "../Popups/ErrorPopup";
|
||||
|
||||
const { useRef } = owl;
|
||||
import { Component, useRef } from "@odoo/owl";
|
||||
|
||||
/**
|
||||
* This relies on the assumption that there is a reference to
|
||||
@@ -13,7 +11,7 @@ const { useRef } = owl;
|
||||
* `order-receipt` in the template of the Component that extends
|
||||
* this abstract component.
|
||||
*/
|
||||
export class AbstractReceiptScreen extends LegacyComponent {
|
||||
export class AbstractReceiptScreen extends Component {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.orderReceipt = useRef("order-receipt");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class CurrencyAmount extends LegacyComponent {
|
||||
export class CurrencyAmount extends Component {
|
||||
static template = "CurrencyAmount";
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
|
||||
export class IndependentToOrderScreen extends LegacyComponent {
|
||||
export class IndependentToOrderScreen extends Component {
|
||||
static storeOnOrder = false;
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class MobileOrderWidget extends LegacyComponent {
|
||||
export class MobileOrderWidget extends Component {
|
||||
static template = "MobileOrderWidget";
|
||||
|
||||
get order() {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { useAutofocus, useListener } from "@web/core/utils/hooks";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
|
||||
const { useExternalListener, useState } = owl;
|
||||
import { Component, useExternalListener, useState } from "@odoo/owl";
|
||||
import { useAutofocus } from "@web/core/utils/hooks";
|
||||
|
||||
/**
|
||||
* This is a simple configurable search bar component. It has search fields
|
||||
@@ -23,20 +21,17 @@ const { useExternalListener, useState } = owl;
|
||||
* placeholder: string,
|
||||
* }}
|
||||
* @emits search @payload { fieldName: string, searchTerm: '' }
|
||||
* @emits filter-selected @payload { filter: string }
|
||||
*
|
||||
* NOTE: The payload of the emitted event is accessible via the `detail`
|
||||
* field of the event.
|
||||
*/
|
||||
export class SearchBar extends LegacyComponent {
|
||||
export class SearchBar extends Component {
|
||||
static template = "point_of_sale.SearchBar";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
useAutofocus();
|
||||
useExternalListener(window, "click", this._hideOptions);
|
||||
useListener("click-search-field", this._onClickSearchField);
|
||||
useListener("select-filter", this._onSelectFilter);
|
||||
this.filterOptionsList = [...this.props.config.filter.options.keys()];
|
||||
this.searchFieldsList = [...this.props.config.searchFields.keys()];
|
||||
const defaultSearchFieldId = this.searchFieldsList.indexOf(
|
||||
@@ -50,9 +45,9 @@ export class SearchBar extends LegacyComponent {
|
||||
selectedFilter: this.props.config.defaultFilter || this.filterOptionsList[0],
|
||||
});
|
||||
}
|
||||
_onSelectFilter({ detail: key }) {
|
||||
_onSelectFilter(key) {
|
||||
this.state.selectedFilter = key;
|
||||
this.trigger("filter-selected", { filter: this.state.selectedFilter });
|
||||
this.props.onFilterSelected(this.state.selectedFilter);
|
||||
}
|
||||
/**
|
||||
* When pressing vertical arrow keys, do not move the input cursor.
|
||||
@@ -83,9 +78,9 @@ export class SearchBar extends LegacyComponent {
|
||||
/**
|
||||
* Called when a search field is clicked.
|
||||
*/
|
||||
_onClickSearchField({ detail: fieldName }) {
|
||||
_onClickSearchField(fieldName) {
|
||||
this.state.showSearchFields = false;
|
||||
this.trigger("search", { fieldName, searchTerm: this.state.searchInput });
|
||||
this.props.onSearch({ fieldName, searchTerm: this.state.searchInput });
|
||||
}
|
||||
/**
|
||||
* Given an arrow key, return the next selectedSearchFieldId.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { useExternalListener } from "@odoo/owl";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component, useExternalListener } from "@odoo/owl";
|
||||
|
||||
/**
|
||||
* Implement this abstract class by extending it like so:
|
||||
@@ -28,7 +27,7 @@ import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
* // otherwise, payload = null
|
||||
* ```
|
||||
*/
|
||||
export class AbstractAwaitablePopup extends LegacyComponent {
|
||||
export class AbstractAwaitablePopup extends Component {
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
useExternalListener(window, "keyup", this._onWindowKeyup);
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { MoneyDetailsPopup } from "./MoneyDetailsPopup";
|
||||
|
||||
const { useState } = owl;
|
||||
import { useState } from "@odoo/owl";
|
||||
|
||||
export class CashOpeningPopup extends AbstractAwaitablePopup {
|
||||
static template = "CashOpeningPopup";
|
||||
@@ -19,6 +18,7 @@ export class CashOpeningPopup extends AbstractAwaitablePopup {
|
||||
openingCash: this.env.pos.pos_session.cash_register_balance_start || 0,
|
||||
});
|
||||
this.popup = useService("popup");
|
||||
this.rpc = useService("rpc");
|
||||
}
|
||||
//@override
|
||||
async confirm() {
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
|
||||
import { identifyError } from "@point_of_sale/app/error_handlers/error_handlers";
|
||||
import { ConnectionLostError } from "@web/core/network/rpc_service";
|
||||
import { SaleDetailsButton } from "../ChromeWidgets/SaleDetailsButton";
|
||||
import { ConfirmPopup } from "./ConfirmPopup";
|
||||
import { ErrorPopup } from "./ErrorPopup";
|
||||
import { MoneyDetailsPopup } from "./MoneyDetailsPopup";
|
||||
import { AlertPopup } from "./AlertPopup";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { useState } = owl;
|
||||
import { useState } from "@odoo/owl";
|
||||
|
||||
export class ClosePosPopup extends AbstractAwaitablePopup {
|
||||
static components = { SaleDetailsButton };
|
||||
@@ -19,6 +16,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.popup = useService("popup");
|
||||
this.pos = useService("pos");
|
||||
this.manualInputCashCount = false;
|
||||
this.cashControl = this.env.pos.config.cash_control;
|
||||
this.closeSessionClicked = false;
|
||||
@@ -123,82 +121,25 @@ export class ClosePosPopup extends AbstractAwaitablePopup {
|
||||
return true;
|
||||
}
|
||||
closePos() {
|
||||
this.trigger("close-pos");
|
||||
this.pos.closePos();
|
||||
}
|
||||
async closeSession() {
|
||||
if (!this.closeSessionClicked) {
|
||||
this.closeSessionClicked = true;
|
||||
let response;
|
||||
if (this.cashControl) {
|
||||
response = await this.rpc({
|
||||
model: "pos.session",
|
||||
method: "post_closing_cash_details",
|
||||
args: [this.env.pos.pos_session.id],
|
||||
kwargs: {
|
||||
counted_cash: this.state.payments[this.defaultCashDetails.id].counted,
|
||||
},
|
||||
});
|
||||
if (!response.successful) {
|
||||
return this.handleClosingError(response);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await this.rpc({
|
||||
model: "pos.session",
|
||||
method: "update_closing_control_state_session",
|
||||
args: [this.env.pos.pos_session.id, this.state.notes],
|
||||
});
|
||||
} catch (error) {
|
||||
// We have to handle the error manually otherwise the validation check stops the script.
|
||||
// In case of "rescue session", we want to display the next popup with "handleClosingError".
|
||||
// FIXME
|
||||
if (
|
||||
!error.message &&
|
||||
!error.message.data &&
|
||||
error.message.data.message !== "This session is already closed."
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const bankPaymentMethodDiffPairs = this.otherPaymentMethods
|
||||
.filter((pm) => pm.type == "bank")
|
||||
.map((pm) => [pm.id, this.state.payments[pm.id].difference]);
|
||||
response = await this.rpc({
|
||||
model: "pos.session",
|
||||
method: "close_session_from_ui",
|
||||
args: [this.env.pos.pos_session.id, bankPaymentMethodDiffPairs],
|
||||
context: this.env.session.user_context,
|
||||
});
|
||||
if (!response.successful) {
|
||||
return this.handleClosingError(response);
|
||||
}
|
||||
window.location = "/web#action=point_of_sale.action_client_pos_menu";
|
||||
} catch (error) {
|
||||
if (identifyError(error) instanceof ConnectionLostError) {
|
||||
// Cannot redirect to backend when offline, let error handlers show the offline popup
|
||||
// FIXME POSREF: doing this means closing again when online will redo the beginning of the method
|
||||
// although it's impossible to close again because this.closeSessionClicked isn't reset to false
|
||||
// The application state is corrupted.
|
||||
throw error;
|
||||
} else {
|
||||
// FIXME POSREF: why are we catching errors here but not anywhere else in this method?
|
||||
await this.popup.add(ErrorPopup, {
|
||||
title: this.env._t("Closing session error"),
|
||||
body: this.env._t(
|
||||
"An error has occurred when trying to close the session.\n" +
|
||||
"You will be redirected to the back-end to manually close the session."
|
||||
),
|
||||
});
|
||||
window.location = "/web#action=point_of_sale.action_client_pos_menu";
|
||||
}
|
||||
}
|
||||
const defaultCashCounted = this.env.pos.config.cashControl
|
||||
? this.state.payments[this.defaultCashDetails.id].counted
|
||||
: 0;
|
||||
this.pos.closeSession(
|
||||
defaultCashCounted,
|
||||
this.state.notes,
|
||||
this.otherPaymentMethods,
|
||||
this.state.payments
|
||||
);
|
||||
}
|
||||
|
||||
this.closeSessionClicked = false;
|
||||
}
|
||||
|
||||
async handleClosingError(response) {
|
||||
let popupType = "";
|
||||
let title = "";
|
||||
|
||||
@@ -2,28 +2,26 @@
|
||||
|
||||
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
|
||||
const { onMounted, useRef, useState } = owl;
|
||||
import { onMounted, useRef, useState } from "@odoo/owl";
|
||||
|
||||
export class DatePickerPopup extends AbstractAwaitablePopup {
|
||||
static template = 'DatePickerPopup';
|
||||
static template = "DatePickerPopup";
|
||||
static defaultProps = {
|
||||
confirmText: _lt('Confirm'),
|
||||
cancelText: _lt('Discard'),
|
||||
title: _lt('DatePicker'),
|
||||
confirmText: _lt("Confirm"),
|
||||
cancelText: _lt("Discard"),
|
||||
title: _lt("DatePicker"),
|
||||
};
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.state = useState({shippingDate: this._today()});
|
||||
this.inputRef = useRef('input');
|
||||
this.state = useState({ shippingDate: this._today() });
|
||||
this.inputRef = useRef("input");
|
||||
onMounted(() => this.inputRef.el.focus());
|
||||
}
|
||||
getPayload() {
|
||||
return this.state.shippingDate < this._today() ? this._today(): this.state.shippingDate;
|
||||
return this.state.shippingDate < this._today() ? this._today() : this.state.shippingDate;
|
||||
}
|
||||
_today() {
|
||||
return new Date().toISOString().split('T')[0]
|
||||
return new Date().toISOString().split("T")[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
/**
|
||||
* props {
|
||||
@@ -9,7 +9,7 @@ import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
* item: object,
|
||||
* }
|
||||
*/
|
||||
export class EditListInput extends LegacyComponent {
|
||||
export class EditListInput extends Component {
|
||||
static template = "EditListInput";
|
||||
|
||||
onKeyup(event) {
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
|
||||
import { useAutoFocusToLast } from "@point_of_sale/js/custom_hooks";
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
|
||||
import { useState } from "@odoo/owl";
|
||||
import { EditListInput } from "./EditListInput";
|
||||
|
||||
const { useState } = owl;
|
||||
|
||||
/**
|
||||
* Given a array of { id, text }, we show the user this popup to be able to modify this given array.
|
||||
* (used to replace PackLotLinePopupWidget)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
// formerly ErrorTracebackPopupWidget
|
||||
export class ErrorTracebackPopup extends ErrorPopup {
|
||||
@@ -14,9 +15,12 @@ export class ErrorTracebackPopup extends ErrorPopup {
|
||||
body: "",
|
||||
exitButtonIsShown: false,
|
||||
exitButtonText: _lt("Exit Pos"),
|
||||
exitButtonTrigger: "close-pos",
|
||||
};
|
||||
|
||||
setup() {
|
||||
this.pos = useService("pos");
|
||||
}
|
||||
|
||||
get tracebackUrl() {
|
||||
const blob = new Blob([this.props.body]);
|
||||
const URL = window.URL || window.webkitURL;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
|
||||
|
||||
import { useState } from "@odoo/owl";
|
||||
import { CurrencyAmount } from "../Misc/CurrencyAmount";
|
||||
|
||||
const { useState } = owl;
|
||||
|
||||
export class MoneyDetailsPopup extends AbstractAwaitablePopup {
|
||||
static components = { CurrencyAmount };
|
||||
static template = "MoneyDetailsPopup";
|
||||
|
||||
@@ -3,9 +3,8 @@ import core from "web.core";
|
||||
var _t = core._t;
|
||||
|
||||
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
|
||||
import { useListener, useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { useState } = owl;
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { useState } from "@odoo/owl";
|
||||
|
||||
export class NumberPopup extends AbstractAwaitablePopup {
|
||||
static template = "NumberPopup";
|
||||
@@ -31,8 +30,6 @@ export class NumberPopup extends AbstractAwaitablePopup {
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener("accept-input", this.confirm);
|
||||
useListener("close-this-popup", this.cancel);
|
||||
let startingBuffer = "";
|
||||
if (typeof this.props.startingValue === "number" && this.props.startingValue > 0) {
|
||||
startingBuffer = this.props.startingValue
|
||||
@@ -42,9 +39,8 @@ export class NumberPopup extends AbstractAwaitablePopup {
|
||||
this.state = useState({ buffer: startingBuffer, toStartOver: this.props.isInputSelected });
|
||||
this.numberBuffer = useService("number_buffer");
|
||||
this.numberBuffer.use({
|
||||
nonKeyboardInputEvent: "numpad-click-input",
|
||||
triggerAtEnter: "accept-input",
|
||||
triggerAtEscape: "close-this-popup",
|
||||
triggerAtEnter: () => this.confirm(),
|
||||
triggerAtEscape: () => this.cancel(),
|
||||
state: this.state,
|
||||
});
|
||||
}
|
||||
@@ -67,7 +63,7 @@ export class NumberPopup extends AbstractAwaitablePopup {
|
||||
}
|
||||
}
|
||||
sendInput(key) {
|
||||
this.trigger("numpad-click-input", { key });
|
||||
this.numberBuffer.sendKey(key);
|
||||
}
|
||||
getPayload() {
|
||||
return this.numberBuffer.get();
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
/** @odoo-module */
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
|
||||
import { Component, useRef, useState, useSubEnv } from "@odoo/owl";
|
||||
|
||||
const { useState, useSubEnv } = owl;
|
||||
|
||||
export class BaseProductAttribute extends LegacyComponent {
|
||||
export class BaseProductAttribute extends Component {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.env.attribute_components.push(this);
|
||||
@@ -38,6 +36,7 @@ export class RadioProductAttribute extends BaseProductAttribute {
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.root = useRef("root");
|
||||
owl.onMounted(this.onMounted);
|
||||
}
|
||||
onMounted() {
|
||||
@@ -45,7 +44,7 @@ export class RadioProductAttribute extends BaseProductAttribute {
|
||||
// a matching `value` attribute. In our case, we use `t-att-value` so `value` is
|
||||
// not found yet and no radio is selected by default.
|
||||
// We then manually select the first input of each radio attribute.
|
||||
$(this.el).find('input[type="radio"]:first').prop("checked", true);
|
||||
this.root.el.querySelector("input[type=radio]").checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
|
||||
const { useState } = owl;
|
||||
import { useState } from "@odoo/owl";
|
||||
|
||||
export class SelectionPopup extends AbstractAwaitablePopup {
|
||||
static template = "SelectionPopup";
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
|
||||
const { onMounted, useRef, useState } = owl;
|
||||
import { onMounted, useRef, useState } from "@odoo/owl";
|
||||
|
||||
// IMPROVEMENT: This code is very similar to TextInputPopup.
|
||||
// Combining them would reduce the code.
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
|
||||
const { onMounted, useRef, useState } = owl;
|
||||
import { onMounted, useRef, useState } from "@odoo/owl";
|
||||
|
||||
// formerly TextInputPopupWidget
|
||||
export class TextInputPopup extends AbstractAwaitablePopup {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/** @odoo-module */
|
||||
const { reactive } = owl;
|
||||
|
||||
import { reactive } from "@odoo/owl";
|
||||
// Create global context objects
|
||||
// e.g. component.env.device = new Context({ isMobile: false });
|
||||
export const orderManagement = reactive({ searchString: "", selectedOrder: null });
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
import { _t } from "web.core";
|
||||
import { getDataURLFromFile } from "web.utils";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Component, onMounted, onWillUnmount } from "@odoo/owl";
|
||||
|
||||
const { onMounted, onWillUnmount } = owl;
|
||||
|
||||
export class PartnerDetailsEdit extends LegacyComponent {
|
||||
export class PartnerDetailsEdit extends Component {
|
||||
static template = "PartnerDetailsEdit";
|
||||
|
||||
setup() {
|
||||
@@ -72,7 +70,7 @@ export class PartnerDetailsEdit extends LegacyComponent {
|
||||
});
|
||||
}
|
||||
processedChanges.id = this.props.partner.id || false;
|
||||
this.trigger("save-changes", { processedChanges });
|
||||
this.props.saveChanges(processedChanges);
|
||||
}
|
||||
async uploadImage(event) {
|
||||
const file = event.target.files[0];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class PartnerLine extends LegacyComponent {
|
||||
export class PartnerLine extends Component {
|
||||
static template = "PartnerLine";
|
||||
|
||||
get highlight() {
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { debounce } from "@web/core/utils/timing";
|
||||
import { useListener, useService } from "@web/core/utils/hooks";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
import { PartnerLine } from "./PartnerLine";
|
||||
import { PartnerDetailsEdit } from "./PartnerDetailsEdit";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
|
||||
const { onWillUnmount, useRef } = owl;
|
||||
import { Component, onWillUnmount, useRef } from "@odoo/owl";
|
||||
|
||||
/**
|
||||
* Render this screen using `showTempScreen` to select partner.
|
||||
@@ -27,16 +25,15 @@ const { onWillUnmount, useRef } = owl;
|
||||
*
|
||||
* @props partner - originally selected partner
|
||||
*/
|
||||
export class PartnerListScreen extends LegacyComponent {
|
||||
export class PartnerListScreen extends Component {
|
||||
static components = { PartnerDetailsEdit, PartnerLine };
|
||||
static template = "PartnerListScreen";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.pos = usePos();
|
||||
this.rpc = useService("rpc");
|
||||
this.notification = useService("pos_notification");
|
||||
useListener("click-save", () => this.env.bus.trigger("save-partner"));
|
||||
useListener("save-changes", this.saveChanges);
|
||||
this.searchWordInputRef = useRef("search-word-input-partner");
|
||||
|
||||
// We are not using useState here because the object
|
||||
@@ -159,11 +156,11 @@ export class PartnerListScreen extends LegacyComponent {
|
||||
};
|
||||
this.activateEditMode();
|
||||
}
|
||||
async saveChanges(event) {
|
||||
async saveChanges(processedChanges) {
|
||||
const partnerId = await this.rpc({
|
||||
model: "res.partner",
|
||||
method: "create_from_ui",
|
||||
args: [event.detail.processedChanges],
|
||||
args: [processedChanges],
|
||||
});
|
||||
await this.env.pos.load_new_partners();
|
||||
this.state.selectedPartner = this.env.pos.db.get_partner_by_id(partnerId);
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
export class PSNumpadInputButton extends LegacyComponent {
|
||||
export class PSNumpadInputButton extends Component {
|
||||
static template = "PSNumpadInputButton";
|
||||
|
||||
setup() {
|
||||
this.numberBuffer = useService("number_buffer");
|
||||
}
|
||||
|
||||
get _class() {
|
||||
return this.props.changeClassTo || "input-button number-char";
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { parse } from "web.field_utils";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { useErrorHandlers } from "@point_of_sale/js/custom_hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useListener, useService } from "@web/core/utils/hooks";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import utils from "web.utils";
|
||||
|
||||
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
|
||||
@@ -16,8 +15,9 @@ import { PaymentScreenNumpad } from "./PaymentScreenNumpad";
|
||||
import { PaymentScreenPaymentLines } from "./PaymentScreenPaymentLines";
|
||||
import { PaymentScreenStatus } from "./PaymentScreenStatus";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class PaymentScreen extends LegacyComponent {
|
||||
export class PaymentScreen extends Component {
|
||||
static template = "PaymentScreen";
|
||||
static components = {
|
||||
PaymentScreenNumpad,
|
||||
@@ -29,16 +29,8 @@ export class PaymentScreen extends LegacyComponent {
|
||||
super.setup();
|
||||
this.pos = usePos();
|
||||
this.popup = useService("popup");
|
||||
this.rpc = useService("rpc");
|
||||
this.notification = useService("pos_notification");
|
||||
useListener("delete-payment-line", this.deletePaymentLine);
|
||||
useListener("select-payment-line", this.selectPaymentLine);
|
||||
useListener("new-payment-line", this.addNewPaymentLine);
|
||||
useListener("update-selected-paymentline", this._updateSelectedPaymentline);
|
||||
useListener("send-payment-request", this._sendPaymentRequest);
|
||||
useListener("send-payment-cancel", this._sendPaymentCancel);
|
||||
useListener("send-payment-reverse", this._sendPaymentReverse);
|
||||
useListener("send-force-done", this._sendForceDone);
|
||||
useListener("validate-order", () => this.validateOrder(false));
|
||||
this.payment_methods_from_config = this.env.pos.payment_methods.filter((method) =>
|
||||
this.env.pos.config.payment_method_ids.includes(method.id)
|
||||
);
|
||||
@@ -59,12 +51,9 @@ export class PaymentScreen extends LegacyComponent {
|
||||
}
|
||||
get _getNumberBufferConfig() {
|
||||
const config = {
|
||||
// The numberBuffer listens to this event to update its state.
|
||||
// Basically means 'update the buffer when this event is triggered'
|
||||
nonKeyboardInputEvent: "input-from-numpad",
|
||||
// When the buffer is updated, trigger this event.
|
||||
// Note that the component listens to it.
|
||||
triggerAtInput: "update-selected-paymentline",
|
||||
triggerAtInput: () => this.updateSelectedPaymentline(),
|
||||
};
|
||||
// Check if pos has a cash payment method
|
||||
const hasCashPaymentMethod = this.payment_methods_from_config.some(
|
||||
@@ -102,7 +91,7 @@ export class PaymentScreen extends LegacyComponent {
|
||||
this.currentOrder.updatePricelist(newPartner);
|
||||
}
|
||||
}
|
||||
addNewPaymentLine({ detail: paymentMethod }) {
|
||||
addNewPaymentLine(paymentMethod) {
|
||||
// original function: click_paymentmethods
|
||||
const result = this.currentOrder.add_paymentline(paymentMethod);
|
||||
if (result) {
|
||||
@@ -116,7 +105,7 @@ export class PaymentScreen extends LegacyComponent {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_updateSelectedPaymentline() {
|
||||
updateSelectedPaymentline() {
|
||||
if (this.paymentLines.every((line) => line.paid)) {
|
||||
this.currentOrder.add_paymentline(this.payment_methods_from_config[0]);
|
||||
}
|
||||
@@ -132,7 +121,7 @@ export class PaymentScreen extends LegacyComponent {
|
||||
return;
|
||||
}
|
||||
if (this.numberBuffer.get() === null) {
|
||||
this.deletePaymentLine({ detail: { cid: this.selectedPaymentLine.cid } });
|
||||
this.deletePaymentLine(this.selectedPaymentLine.cid);
|
||||
} else {
|
||||
this.selectedPaymentLine.set_amount(this.numberBuffer.getFloat());
|
||||
}
|
||||
@@ -173,11 +162,9 @@ export class PaymentScreen extends LegacyComponent {
|
||||
this.currentOrder.setShippingDate(false);
|
||||
}
|
||||
}
|
||||
deletePaymentLine(event) {
|
||||
deletePaymentLine(cid) {
|
||||
var self = this;
|
||||
const { cid } = event.detail;
|
||||
const line = this.paymentLines.find((line) => line.cid === cid);
|
||||
|
||||
// If a paymentline with a payment terminal linked to
|
||||
// it is removed, the terminal should get a cancel
|
||||
// request.
|
||||
@@ -196,14 +183,14 @@ export class PaymentScreen extends LegacyComponent {
|
||||
this.render(true);
|
||||
}
|
||||
}
|
||||
selectPaymentLine(event) {
|
||||
const { cid } = event.detail;
|
||||
selectPaymentLine(cid) {
|
||||
const line = this.paymentLines.find((line) => line.cid === cid);
|
||||
this.currentOrder.select_paymentline(line);
|
||||
this.numberBuffer.reset();
|
||||
this.render(true);
|
||||
}
|
||||
async validateOrder(isForceValidate) {
|
||||
this.numberBuffer.capture();
|
||||
if (this.env.pos.config.cash_rounding) {
|
||||
if (!this.env.pos.get_order().check_paymentlines_rounding()) {
|
||||
this.popup.add(ErrorPopup, {
|
||||
@@ -467,8 +454,9 @@ export class PaymentScreen extends LegacyComponent {
|
||||
async _postPushOrderResolve(order, order_server_ids) {
|
||||
return true;
|
||||
}
|
||||
async _sendPaymentRequest({ detail: line }) {
|
||||
async sendPaymentRequest(line) {
|
||||
// Other payment lines can not be reversed anymore
|
||||
this.numberBuffer.capture();
|
||||
this.paymentLines.forEach(function (line) {
|
||||
line.can_be_reversed = false;
|
||||
});
|
||||
@@ -490,13 +478,13 @@ export class PaymentScreen extends LegacyComponent {
|
||||
) &&
|
||||
this.env.pos.config.auto_validate_terminal_payment
|
||||
) {
|
||||
this.trigger("validate-order");
|
||||
this.validateOrder(false);
|
||||
}
|
||||
} else {
|
||||
line.set_payment_status("retry");
|
||||
}
|
||||
}
|
||||
async _sendPaymentCancel({ detail: line }) {
|
||||
async sendPaymentCancel(line) {
|
||||
const payment_terminal = line.payment_method.payment_terminal;
|
||||
line.set_payment_status("waitingCancel");
|
||||
const isCancelSuccessful = await payment_terminal.send_payment_cancel(
|
||||
@@ -509,7 +497,7 @@ export class PaymentScreen extends LegacyComponent {
|
||||
line.set_payment_status("waitingCard");
|
||||
}
|
||||
}
|
||||
async _sendPaymentReverse({ detail: line }) {
|
||||
async sendPaymentReverse(line) {
|
||||
const payment_terminal = line.payment_method.payment_terminal;
|
||||
line.set_payment_status("reversing");
|
||||
|
||||
@@ -522,7 +510,7 @@ export class PaymentScreen extends LegacyComponent {
|
||||
line.set_payment_status("done");
|
||||
}
|
||||
}
|
||||
async _sendForceDone({ detail: line }) {
|
||||
async sendForceDone(line) {
|
||||
line.set_payment_status("done");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
|
||||
import { PSNumpadInputButton } from "./PSNumpadInputButton";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class PaymentScreenNumpad extends LegacyComponent {
|
||||
export class PaymentScreenNumpad extends Component {
|
||||
static components = { PSNumpadInputButton };
|
||||
static template = "PaymentScreenNumpad";
|
||||
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class PaymentScreenPaymentLines extends LegacyComponent {
|
||||
export class PaymentScreenPaymentLines extends Component {
|
||||
static template = "PaymentScreenPaymentLines";
|
||||
|
||||
formatLineAmount(paymentline) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class PaymentScreenStatus extends LegacyComponent {
|
||||
export class PaymentScreenStatus extends Component {
|
||||
static template = "PaymentScreenStatus";
|
||||
|
||||
get changeText() {
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
/**
|
||||
* @props partner
|
||||
* @emits click-partner
|
||||
* @emits click-pay
|
||||
*/
|
||||
export class ActionpadWidget extends LegacyComponent {
|
||||
export class ActionpadWidget extends Component {
|
||||
static template = "ActionpadWidget";
|
||||
static defaultProps = {
|
||||
isActionButtonHighlighted: false,
|
||||
};
|
||||
|
||||
setup() {
|
||||
this.pos = usePos();
|
||||
}
|
||||
|
||||
get isLongName() {
|
||||
return this.props.partner && this.props.partner.name.length > 10;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class CategoryButton extends LegacyComponent {
|
||||
export class CategoryButton extends Component {
|
||||
static template = "CategoryButton";
|
||||
|
||||
get imageUrl() {
|
||||
|
||||
+3
-4
@@ -1,17 +1,16 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
|
||||
import { useListener, useService } from "@web/core/utils/hooks";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { TextAreaPopup } from "@point_of_sale/js/Popups/TextAreaPopup";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class OrderlineCustomerNoteButton extends LegacyComponent {
|
||||
export class OrderlineCustomerNoteButton extends Component {
|
||||
static template = "OrderlineCustomerNoteButton";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.popup = useService("popup");
|
||||
useListener("click", this.onClick);
|
||||
}
|
||||
async onClick() {
|
||||
const selectedOrderline = this.env.pos.get_order().get_selected_orderline();
|
||||
|
||||
+4
-5
@@ -1,19 +1,18 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
|
||||
import { useListener, useService } from "@web/core/utils/hooks";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { ProductInfoPopup } from "@point_of_sale/js/Popups/ProductInfoPopup";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class ProductInfoButton extends LegacyComponent {
|
||||
export class ProductInfoButton extends Component {
|
||||
static template = "ProductInfoButton";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener("click", this.onClick);
|
||||
this.popup = useService("popup");
|
||||
}
|
||||
async onClick() {
|
||||
async click() {
|
||||
const orderline = this.env.pos.get_order().get_selected_orderline();
|
||||
if (orderline) {
|
||||
const product = orderline.get_product();
|
||||
|
||||
+3
-5
@@ -1,19 +1,17 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
|
||||
import { useListener } from "@web/core/utils/hooks";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class RefundButton extends LegacyComponent {
|
||||
export class RefundButton extends Component {
|
||||
static template = "point_of_sale.RefundButton";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.pos = usePos();
|
||||
useListener("click", this._onClick);
|
||||
}
|
||||
_onClick() {
|
||||
click() {
|
||||
const partner = this.env.pos.get_order().get_partner();
|
||||
const searchDetails = partner ? { fieldName: "PARTNER", searchTerm: partner.name } : {};
|
||||
this.pos.showScreen("TicketScreen", {
|
||||
|
||||
+4
-5
@@ -1,17 +1,16 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
|
||||
import { useListener, useService } from "@web/core/utils/hooks";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class SetFiscalPositionButton extends LegacyComponent {
|
||||
export class SetFiscalPositionButton extends Component {
|
||||
static template = "SetFiscalPositionButton";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.popup = useService("popup");
|
||||
useListener("click", this.onClick);
|
||||
}
|
||||
get currentOrder() {
|
||||
return this.env.pos.get_order();
|
||||
@@ -21,7 +20,7 @@ export class SetFiscalPositionButton extends LegacyComponent {
|
||||
? this.currentOrder.fiscal_position.display_name
|
||||
: this.env._t("Tax");
|
||||
}
|
||||
async onClick() {
|
||||
async click() {
|
||||
const currentFiscalPosition = this.currentOrder.fiscal_position;
|
||||
const fiscalPosList = [
|
||||
{
|
||||
|
||||
+4
-5
@@ -1,17 +1,16 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
|
||||
import { useListener, useService } from "@web/core/utils/hooks";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class SetPricelistButton extends LegacyComponent {
|
||||
export class SetPricelistButton extends Component {
|
||||
static template = "SetPricelistButton";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.popup = useService("popup");
|
||||
useListener("click", this.onClick);
|
||||
}
|
||||
get currentOrder() {
|
||||
return this.env.pos.get_order();
|
||||
@@ -20,7 +19,7 @@ export class SetPricelistButton extends LegacyComponent {
|
||||
const order = this.currentOrder;
|
||||
return order && order.pricelist ? order.pricelist.display_name : this.env._t("Pricelist");
|
||||
}
|
||||
async onClick() {
|
||||
async click() {
|
||||
// Create the list to be passed to the SelectionPopup.
|
||||
// Pricelist object is passed as item in the list because it
|
||||
// is the object that will be returned when the popup is confirmed.
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
/**
|
||||
* @prop {'quantity' | 'price' | 'discount'} activeMode
|
||||
* @prop {Array<'quantity' | 'price' | 'discount'>} disabledModes
|
||||
* @prop {boolean} disableSign
|
||||
* @event set-numpad-mode - triggered when mode button is clicked
|
||||
* @event numpad-click-input - triggered when numpad button is clicked
|
||||
*/
|
||||
export class NumpadWidget extends LegacyComponent {
|
||||
export class NumpadWidget extends Component {
|
||||
static template = "NumpadWidget";
|
||||
static defaultProps = {
|
||||
disabledModes: [],
|
||||
disableSign: false,
|
||||
};
|
||||
|
||||
setup() {
|
||||
this.numberBuffer = useService("number_buffer");
|
||||
this.numberBuffer.use({
|
||||
triggerAtInput: (event) => this.props.updateSelectedOrderline(event),
|
||||
useWithBarcode: true,
|
||||
});
|
||||
}
|
||||
get hasPriceControlRights() {
|
||||
return (
|
||||
this.env.pos.cashierHasPriceControlRights() &&
|
||||
@@ -34,10 +39,12 @@ export class NumpadWidget extends LegacyComponent {
|
||||
if (!this.hasManualDiscount && mode === "discount") {
|
||||
return;
|
||||
}
|
||||
this.trigger("set-numpad-mode", { mode });
|
||||
this.numberBuffer.capture();
|
||||
this.numberBuffer.reset();
|
||||
this.env.pos.numpadMode = mode;
|
||||
}
|
||||
sendInput(key) {
|
||||
this.trigger("numpad-click-input", { key });
|
||||
this.numberBuffer.sendKey(key);
|
||||
}
|
||||
get decimalSeparator() {
|
||||
return this.env._t.database.parameters.decimal_point;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
import { float_is_zero } from "web.utils";
|
||||
|
||||
export class OrderSummary extends LegacyComponent {
|
||||
export class OrderSummary extends Component {
|
||||
static template = "OrderSummary";
|
||||
|
||||
getTotal() {
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { useListener, useService } from "@web/core/utils/hooks";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { EditListPopup } from "@point_of_sale/js/Popups/EditListPopup";
|
||||
|
||||
import { Orderline } from "./Orderline";
|
||||
import { OrderSummary } from "./OrderSummary";
|
||||
import { Component, useEffect, useRef } from "@odoo/owl";
|
||||
|
||||
const { useEffect, useRef } = owl;
|
||||
|
||||
export class OrderWidget extends LegacyComponent {
|
||||
export class OrderWidget extends Component {
|
||||
static components = { Orderline, OrderSummary };
|
||||
static template = "OrderWidget";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.popup = useService("popup");
|
||||
useListener("select-line", this._selectLine);
|
||||
useListener("edit-pack-lot-lines", this._editPackLotLines);
|
||||
this.numberBuffer = useService("number_buffer");
|
||||
this.scrollableRef = useRef("scrollable");
|
||||
useEffect(
|
||||
() => {
|
||||
@@ -37,8 +34,9 @@ export class OrderWidget extends LegacyComponent {
|
||||
get orderlinesArray() {
|
||||
return this.order ? this.order.get_orderlines() : [];
|
||||
}
|
||||
_selectLine(event) {
|
||||
this.order.select_orderline(event.detail.orderline);
|
||||
selectLine(orderline) {
|
||||
this.numberBuffer.reset();
|
||||
this.order.select_orderline(orderline);
|
||||
}
|
||||
// IMPROVEMENT: Might be better to lift this to ProductScreen
|
||||
// because there is similar operation when clicking a product.
|
||||
@@ -47,8 +45,7 @@ export class OrderWidget extends LegacyComponent {
|
||||
// to an orderline that has product tracked by lot. Lot tracking (based
|
||||
// on the current implementation) requires that 1 item per orderline is
|
||||
// allowed.
|
||||
async _editPackLotLines(event) {
|
||||
const orderline = event.detail.orderline;
|
||||
async editPackLotLines(orderline) {
|
||||
const isAllowOnlyOneLot = orderline.product.isAllowOnlyOneLot();
|
||||
const packLotLinesToEdit = orderline.getPackLotLinesToEdit(isAllowOnlyOneLot);
|
||||
const { confirmed, payload } = await this.popup.add(EditListPopup, {
|
||||
@@ -68,6 +65,6 @@ export class OrderWidget extends LegacyComponent {
|
||||
|
||||
orderline.setPackLotLines({ modifiedPackLotLines, newPackLotLines });
|
||||
}
|
||||
this.order.select_orderline(event.detail.orderline);
|
||||
this.order.select_orderline(orderline);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class Orderline extends LegacyComponent {
|
||||
export class Orderline extends Component {
|
||||
static template = "Orderline";
|
||||
|
||||
selectLine() {
|
||||
this.trigger("select-line", { orderline: this.props.line });
|
||||
this.props.selectLine(this.props.line);
|
||||
}
|
||||
lotIconClicked() {
|
||||
this.trigger("edit-pack-lot-lines", { orderline: this.props.line });
|
||||
this.props.editPackLotLines(this.props.line);
|
||||
}
|
||||
get addedClasses() {
|
||||
return {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
|
||||
export class ProductItem extends LegacyComponent {
|
||||
export class ProductItem extends Component {
|
||||
static template = "ProductItem";
|
||||
|
||||
setup() {
|
||||
this.pos = usePos();
|
||||
}
|
||||
/**
|
||||
* For accessibility, pressing <space> should be like clicking the product.
|
||||
* <enter> is not considered because it conflicts with the barcode.
|
||||
@@ -13,7 +17,7 @@ export class ProductItem extends LegacyComponent {
|
||||
*/
|
||||
spaceClickProduct(event) {
|
||||
if (event.which === 32) {
|
||||
this.trigger("click-product", this.props.product);
|
||||
this.pos.addProductToCurrentOrder(this.props.product);
|
||||
}
|
||||
}
|
||||
get imageUrl() {
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { ControlButtonsMixin } from "@point_of_sale/js/ControlButtonsMixin";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useListener, useService } from "@web/core/utils/hooks";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { useBarcodeReader } from "@point_of_sale/js/custom_hooks";
|
||||
import { parse } from "web.field_utils";
|
||||
|
||||
import { ProductConfiguratorPopup } from "@point_of_sale/js/Popups/ProductConfiguratorPopup";
|
||||
import { EditListPopup } from "@point_of_sale/js/Popups/EditListPopup";
|
||||
import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup";
|
||||
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
|
||||
import { ErrorBarcodePopup } from "@point_of_sale/js/Popups/ErrorBarcodePopup";
|
||||
@@ -21,10 +18,9 @@ import { NumpadWidget } from "./NumpadWidget";
|
||||
import { OrderWidget } from "./OrderWidget";
|
||||
import { ProductsWidget } from "./ProductsWidget";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
import { Component, onMounted, useState } from "@odoo/owl";
|
||||
|
||||
const { onMounted, useState } = owl;
|
||||
|
||||
export class ProductScreen extends ControlButtonsMixin(LegacyComponent) {
|
||||
export class ProductScreen extends ControlButtonsMixin(Component) {
|
||||
static template = "ProductScreen";
|
||||
static components = {
|
||||
ActionpadWidget,
|
||||
@@ -38,12 +34,13 @@ export class ProductScreen extends ControlButtonsMixin(LegacyComponent) {
|
||||
super.setup();
|
||||
this.pos = usePos();
|
||||
this.popup = useService("popup");
|
||||
useListener("update-selected-orderline", this._updateSelectedOrderline);
|
||||
useListener("select-line", this._selectLine);
|
||||
useListener("set-numpad-mode", this._setNumpadMode);
|
||||
useListener("click-product", this._clickProduct);
|
||||
useListener("click-partner", this.onClickPartner);
|
||||
useListener("click-pay", this._onClickPay);
|
||||
this.rpc = useService("rpc");
|
||||
this.numberBuffer = useService("number_buffer");
|
||||
onMounted(this.onMounted);
|
||||
// Call `reset` when the `onMounted` callback in `numberBuffer.use` is done.
|
||||
// We don't do this in the `mounted` lifecycle method because it is called before
|
||||
// the callbacks in `onMounted` hook.
|
||||
onMounted(() => this.numberBuffer.reset());
|
||||
useBarcodeReader({
|
||||
product: this._barcodeProductAction,
|
||||
weight: this._barcodeProductAction,
|
||||
@@ -52,17 +49,6 @@ export class ProductScreen extends ControlButtonsMixin(LegacyComponent) {
|
||||
discount: this._barcodeDiscountAction,
|
||||
error: this._barcodeErrorAction,
|
||||
});
|
||||
this.numberBuffer = useService("number_buffer");
|
||||
this.numberBuffer.use({
|
||||
nonKeyboardInputEvent: "numpad-click-input",
|
||||
triggerAtInput: "update-selected-orderline",
|
||||
useWithBarcode: true,
|
||||
});
|
||||
onMounted(this.onMounted);
|
||||
// Call `reset` when the `onMounted` callback in `numberBuffer.use` is done.
|
||||
// We don't do this in the `mounted` lifecycle method because it is called before
|
||||
// the callbacks in `onMounted` hook.
|
||||
onMounted(() => this.numberBuffer.reset());
|
||||
this.state = useState({
|
||||
mobile_pane: this.props.mobile_pane || "right",
|
||||
});
|
||||
@@ -75,130 +61,13 @@ export class ProductScreen extends ControlButtonsMixin(LegacyComponent) {
|
||||
* connected scale.
|
||||
* @see _onScaleNotAvailable
|
||||
*/
|
||||
get isScaleAvailable() {
|
||||
return true;
|
||||
}
|
||||
get partner() {
|
||||
return this.currentOrder ? this.currentOrder.get_partner() : null;
|
||||
}
|
||||
get currentOrder() {
|
||||
return this.env.pos.get_order();
|
||||
}
|
||||
async _getAddProductOptions(product, base_code) {
|
||||
let price_extra = 0.0;
|
||||
let draftPackLotLines, weight, description, packLotLinesToEdit;
|
||||
|
||||
if (_.some(product.attribute_line_ids, (id) => id in this.env.pos.attributes_by_ptal_id)) {
|
||||
const attributes = _.map(
|
||||
product.attribute_line_ids,
|
||||
(id) => this.env.pos.attributes_by_ptal_id[id]
|
||||
).filter((attr) => attr !== undefined);
|
||||
const { confirmed, payload } = await this.popup.add(ProductConfiguratorPopup, {
|
||||
product: product,
|
||||
attributes: attributes,
|
||||
});
|
||||
|
||||
if (confirmed) {
|
||||
description = payload.selected_attributes.join(", ");
|
||||
price_extra += payload.price_extra;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Gather lot information if required.
|
||||
if (
|
||||
["serial", "lot"].includes(product.tracking) &&
|
||||
(this.env.pos.picking_type.use_create_lots ||
|
||||
this.env.pos.picking_type.use_existing_lots)
|
||||
) {
|
||||
const isAllowOnlyOneLot = product.isAllowOnlyOneLot();
|
||||
if (isAllowOnlyOneLot) {
|
||||
packLotLinesToEdit = [];
|
||||
} else {
|
||||
const orderline = this.currentOrder
|
||||
.get_orderlines()
|
||||
.filter((line) => !line.get_discount())
|
||||
.find((line) => line.product.id === product.id);
|
||||
if (orderline) {
|
||||
packLotLinesToEdit = orderline.getPackLotLinesToEdit();
|
||||
} else {
|
||||
packLotLinesToEdit = [];
|
||||
}
|
||||
}
|
||||
const { confirmed, payload } = await this.popup.add(EditListPopup, {
|
||||
title: this.env._t("Lot/Serial Number(s) Required"),
|
||||
name: product.display_name,
|
||||
isSingleItem: isAllowOnlyOneLot,
|
||||
array: packLotLinesToEdit,
|
||||
});
|
||||
if (confirmed) {
|
||||
// Segregate the old and new packlot lines
|
||||
const modifiedPackLotLines = Object.fromEntries(
|
||||
payload.newArray.filter((item) => item.id).map((item) => [item.id, item.text])
|
||||
);
|
||||
const newPackLotLines = payload.newArray
|
||||
.filter((item) => !item.id)
|
||||
.map((item) => ({ lot_name: item.text }));
|
||||
|
||||
draftPackLotLines = { modifiedPackLotLines, newPackLotLines };
|
||||
} else {
|
||||
// We don't proceed on adding product.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Take the weight if necessary.
|
||||
if (product.to_weight && this.env.pos.config.iface_electronic_scale) {
|
||||
// Show the ScaleScreen to weigh the product.
|
||||
if (this.isScaleAvailable) {
|
||||
const { confirmed, payload } = await this.pos.showTempScreen("ScaleScreen", {
|
||||
product,
|
||||
});
|
||||
if (confirmed) {
|
||||
weight = payload.weight;
|
||||
} else {
|
||||
// do not add the product;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
await this._onScaleNotAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
if (base_code && this.env.pos.db.product_packaging_by_barcode[base_code.code]) {
|
||||
weight = this.env.pos.db.product_packaging_by_barcode[base_code.code].qty;
|
||||
}
|
||||
|
||||
return { draftPackLotLines, quantity: weight, description, price_extra };
|
||||
}
|
||||
async _addProduct(product, options) {
|
||||
this.currentOrder.add_product(product, options);
|
||||
}
|
||||
async _clickProduct(event) {
|
||||
if (!this.currentOrder) {
|
||||
this.env.pos.add_new_order();
|
||||
}
|
||||
const product = event.detail;
|
||||
const options = await this._getAddProductOptions(product);
|
||||
// Do not add product if options is undefined.
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
// Add the product after having the extra information.
|
||||
await this._addProduct(product, options);
|
||||
this.numberBuffer.reset();
|
||||
}
|
||||
_setNumpadMode(event) {
|
||||
const { mode } = event.detail;
|
||||
this.numberBuffer.capture();
|
||||
this.numberBuffer.reset();
|
||||
this.env.pos.numpadMode = mode;
|
||||
}
|
||||
_selectLine() {
|
||||
this.numberBuffer.reset();
|
||||
}
|
||||
async _updateSelectedOrderline(event) {
|
||||
async updateSelectedOrderline({ buffer, key }) {
|
||||
if (this.env.pos.numpadMode === "quantity" && this.env.pos.disallowLineQuantityChange()) {
|
||||
const order = this.env.pos.get_order();
|
||||
if (!order.orderlines.length) {
|
||||
@@ -219,16 +88,15 @@ export class ProductScreen extends ControlButtonsMixin(LegacyComponent) {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const parsedInput = (event.detail.buffer && parse.float(event.detail.buffer)) || 0;
|
||||
const parsedInput = (buffer && parse.float(buffer)) || 0;
|
||||
if (lastId != selectedLine.cid) {
|
||||
this._showDecreaseQuantityPopup();
|
||||
} else if (currentQuantity < parsedInput) {
|
||||
this._setValue(event.detail.buffer);
|
||||
this._setValue(buffer);
|
||||
} else if (parsedInput < currentQuantity) {
|
||||
this._showDecreaseQuantityPopup();
|
||||
}
|
||||
} else {
|
||||
const { buffer } = event.detail;
|
||||
const val = buffer === null ? "remove" : buffer;
|
||||
this._setValue(val);
|
||||
if (val == "remove") {
|
||||
@@ -272,9 +140,9 @@ export class ProductScreen extends ControlButtonsMixin(LegacyComponent) {
|
||||
return this._barcodeErrorAction(code);
|
||||
}
|
||||
}
|
||||
const options = await this._getAddProductOptions(product, code);
|
||||
const options = await product.getAddProductOptions(code);
|
||||
// Do not proceed on adding the product when no options is returned.
|
||||
// This is consistent with _clickProduct.
|
||||
// This is consistent with clickProduct.
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
@@ -337,12 +205,8 @@ export class ProductScreen extends ControlButtonsMixin(LegacyComponent) {
|
||||
controlButtons: this.controlButtons,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* override this method to perform procedure if the scale is not available.
|
||||
* @see isScaleAvailable
|
||||
*/
|
||||
async _onScaleNotAvailable() {}
|
||||
async _showDecreaseQuantityPopup() {
|
||||
this.numberBuffer.reset();
|
||||
const { confirmed, payload: inputNumber } = await this.popup.add(NumberPopup, {
|
||||
startingValue: 0,
|
||||
title: this.env._t("Set the new quantity"),
|
||||
@@ -370,33 +234,7 @@ export class ProductScreen extends ControlButtonsMixin(LegacyComponent) {
|
||||
}
|
||||
}
|
||||
}
|
||||
async onClickPartner() {
|
||||
// IMPROVEMENT: This code snippet is very similar to selectPartner of PaymentScreen.
|
||||
const currentPartner = this.currentOrder.get_partner();
|
||||
if (currentPartner && this.currentOrder.getHasRefundLines()) {
|
||||
this.popup.add(ErrorPopup, {
|
||||
title: this.env._t("Can't change customer"),
|
||||
body: _.str.sprintf(
|
||||
this.env._t(
|
||||
"This order already has refund lines for %s. We can't change the customer associated to it. Create a new order for the new customer."
|
||||
),
|
||||
currentPartner.name
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { confirmed, payload: newPartner } = await this.pos.showTempScreen(
|
||||
"PartnerListScreen",
|
||||
{
|
||||
partner: currentPartner,
|
||||
}
|
||||
);
|
||||
if (confirmed) {
|
||||
this.currentOrder.set_partner(newPartner);
|
||||
this.currentOrder.updatePricelist(newPartner);
|
||||
}
|
||||
}
|
||||
async _onClickPay() {
|
||||
async onClickPay() {
|
||||
if (
|
||||
this.env.pos
|
||||
.get_order()
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { useListener } from "@web/core/utils/hooks";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
|
||||
import { ProductItem } from "./ProductItem";
|
||||
import { ProductsWidgetControlPanel } from "./ProductsWidgetControlPanel";
|
||||
import { Component, useState } from "@odoo/owl";
|
||||
|
||||
const { useState } = owl;
|
||||
|
||||
export class ProductsWidget extends LegacyComponent {
|
||||
export class ProductsWidget extends Component {
|
||||
static components = { ProductItem, ProductsWidgetControlPanel };
|
||||
static template = "ProductsWidget";
|
||||
|
||||
@@ -19,10 +16,6 @@ export class ProductsWidget extends LegacyComponent {
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener("switch-category", this._switchCategory);
|
||||
useListener("update-search", this._updateSearch);
|
||||
useListener("clear-search", this._clearSearch);
|
||||
useListener("update-product-list", this._updateProductList);
|
||||
this.state = useState({ searchWord: "" });
|
||||
this.pos = usePos();
|
||||
}
|
||||
@@ -66,17 +59,17 @@ export class ProductsWidget extends LegacyComponent {
|
||||
get shouldShowButton() {
|
||||
return this.productsToDisplay.length === 0 && this.searchWord;
|
||||
}
|
||||
_switchCategory(event) {
|
||||
this.env.pos.setSelectedCategoryId(event.detail);
|
||||
switchCategory(categoryId) {
|
||||
this.env.pos.setSelectedCategoryId(categoryId);
|
||||
}
|
||||
_updateSearch(event) {
|
||||
this.state.searchWord = event.detail;
|
||||
updateSearch(searchWord) {
|
||||
this.state.searchWord = searchWord;
|
||||
}
|
||||
_clearSearch() {
|
||||
clearSearch() {
|
||||
this.state.searchWord = "";
|
||||
}
|
||||
_updateProductList(event) {
|
||||
updateProductList(event) {
|
||||
this.render(true);
|
||||
this.trigger("switch-category", 0);
|
||||
this.switchCategory(0);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -1,15 +1,14 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { debounce } from "@web/core/utils/timing";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
|
||||
import { CategoryButton } from "./CategoryButton";
|
||||
|
||||
import { onMounted, onWillUnmount, useState } from "@odoo/owl";
|
||||
import { Component, onMounted, onWillUnmount, useState } from "@odoo/owl";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
export class ProductsWidgetControlPanel extends LegacyComponent {
|
||||
export class ProductsWidgetControlPanel extends Component {
|
||||
static components = { CategoryButton };
|
||||
static template = "ProductsWidgetControlPanel";
|
||||
|
||||
@@ -17,6 +16,7 @@ export class ProductsWidgetControlPanel extends LegacyComponent {
|
||||
super.setup();
|
||||
this.pos = usePos();
|
||||
this.notification = useService("pos_notification");
|
||||
this.rpc = useService("rpc");
|
||||
this.updateSearch = debounce(this.updateSearch, 100);
|
||||
this.state = useState({ searchInput: "", mobileSearchBarIsShown: false });
|
||||
|
||||
@@ -33,7 +33,7 @@ export class ProductsWidgetControlPanel extends LegacyComponent {
|
||||
}
|
||||
_clearSearch() {
|
||||
this.state.searchInput = "";
|
||||
this.trigger("clear-search");
|
||||
this.props.clearSearch();
|
||||
}
|
||||
get displayCategImages() {
|
||||
return (
|
||||
@@ -42,7 +42,7 @@ export class ProductsWidgetControlPanel extends LegacyComponent {
|
||||
);
|
||||
}
|
||||
updateSearch(event) {
|
||||
this.trigger("update-search", this.state.searchInput);
|
||||
this.props.updateSearch(this.state.searchInput);
|
||||
if (event.key === "Enter") {
|
||||
this._onPressEnterKey();
|
||||
}
|
||||
@@ -63,8 +63,8 @@ export class ProductsWidgetControlPanel extends LegacyComponent {
|
||||
}
|
||||
searchProductFromInfo(productName) {
|
||||
this.state.searchInput = productName;
|
||||
this.trigger("switch-category", 0);
|
||||
this.trigger("update-search", productName);
|
||||
this.props.switchCategory(0);
|
||||
this.props.updateSearch(productName);
|
||||
}
|
||||
async loadProductFromDB() {
|
||||
if (!this.state.searchInput) {
|
||||
@@ -90,7 +90,7 @@ export class ProductsWidgetControlPanel extends LegacyComponent {
|
||||
if (ProductIds.length) {
|
||||
await this.env.pos._addProducts(ProductIds, false);
|
||||
}
|
||||
this.trigger("update-product-list");
|
||||
this.props.updateProductList();
|
||||
return ProductIds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
|
||||
import { WrappedProductNameLines } from "./WrappedProductNameLines";
|
||||
import { Component, onWillUpdateProps } from "@odoo/owl";
|
||||
|
||||
const { onWillUpdateProps } = owl;
|
||||
|
||||
export class OrderReceipt extends LegacyComponent {
|
||||
export class OrderReceipt extends Component {
|
||||
static components = { WrappedProductNameLines };
|
||||
static template = "OrderReceipt";
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { registry } from "@web/core/registry";
|
||||
import { OrderReceipt } from "./OrderReceipt";
|
||||
import { onMounted, useRef, status } from "@odoo/owl";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
export class ReceiptScreen extends AbstractReceiptScreen {
|
||||
static template = "ReceiptScreen";
|
||||
@@ -18,6 +19,7 @@ export class ReceiptScreen extends AbstractReceiptScreen {
|
||||
super.setup();
|
||||
this.pos = usePos();
|
||||
useErrorHandlers();
|
||||
this.rpc = useService("rpc");
|
||||
this.orderReceipt = useRef("order-receipt");
|
||||
this.currentOrder = this.env.pos.get_order();
|
||||
const partner = this.currentOrder.get_partner();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class WrappedProductNameLines extends LegacyComponent {
|
||||
export class WrappedProductNameLines extends Component {
|
||||
static template = "WrappedProductNameLines";
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { round_precision as round_pr } from "web.utils";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
import { Component, onMounted, onWillUnmount, useExternalListener, useState } from "@odoo/owl";
|
||||
|
||||
const { onMounted, onWillUnmount, useExternalListener, useState } = owl;
|
||||
|
||||
export class ScaleScreen extends LegacyComponent {
|
||||
export class ScaleScreen extends Component {
|
||||
static template = "ScaleScreen";
|
||||
|
||||
/**
|
||||
|
||||
+9
-8
@@ -1,19 +1,20 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { useListener, useService } from "@web/core/utils/hooks";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
|
||||
import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
import { Component, useRef } from "@odoo/owl";
|
||||
|
||||
export class InvoiceButton extends LegacyComponent {
|
||||
export class InvoiceButton extends Component {
|
||||
static template = "InvoiceButton";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.pos = usePos();
|
||||
this.invoiceButton = useRef("invoice-button");
|
||||
this.popup = useService("popup");
|
||||
useListener("click", this._onClick);
|
||||
this.rpc = useService("rpc");
|
||||
}
|
||||
get isAlreadyInvoiced() {
|
||||
if (!this.props.order) {
|
||||
@@ -110,14 +111,14 @@ export class InvoiceButton extends LegacyComponent {
|
||||
|
||||
// Part 3: Download invoice.
|
||||
await this._downloadInvoice(orderId);
|
||||
this.trigger("order-invoiced", orderId);
|
||||
this.props.onInvoiceOrder(orderId);
|
||||
}
|
||||
async _onClick() {
|
||||
async click() {
|
||||
try {
|
||||
this.el.style.pointerEvents = "none";
|
||||
this.invoiceButton.el.style.pointerEvents = "none";
|
||||
await this._invoiceOrder();
|
||||
} finally {
|
||||
this.el.style.pointerEvents = "auto";
|
||||
this.invoiceButton.el.style.pointerEvents = "auto";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-5
@@ -1,18 +1,16 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { useListener } from "@web/core/utils/hooks";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class ReprintReceiptButton extends LegacyComponent {
|
||||
export class ReprintReceiptButton extends Component {
|
||||
static template = "ReprintReceiptButton";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.pos = usePos();
|
||||
useListener("click", this._onClick);
|
||||
}
|
||||
async _onClick() {
|
||||
async click() {
|
||||
if (!this.props.order) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { OrderlineDetails } from "./OrderlineDetails";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
/**
|
||||
* @props {models.Order} order
|
||||
*/
|
||||
export class OrderDetails extends LegacyComponent {
|
||||
export class OrderDetails extends Component {
|
||||
static components = { OrderlineDetails };
|
||||
static template = "OrderDetails";
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { Component } from "@odoo/owl";
|
||||
import { format } from "web.field_utils";
|
||||
import { round_precision as round_pr } from "web.utils";
|
||||
|
||||
/**
|
||||
* @props {pos.order.line} line
|
||||
*/
|
||||
export class OrderlineDetails extends LegacyComponent {
|
||||
export class OrderlineDetails extends Component {
|
||||
static template = "OrderlineDetails";
|
||||
|
||||
get line() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Order } from "@point_of_sale/js/models";
|
||||
import { IndependentToOrderScreen } from "@point_of_sale/js/Misc/IndependentToOrderScreen";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useListener, useService } from "@web/core/utils/hooks";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { parse } from "web.field_utils";
|
||||
|
||||
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
|
||||
@@ -42,22 +42,10 @@ export class TicketScreen extends IndependentToOrderScreen {
|
||||
super.setup();
|
||||
this.pos = usePos();
|
||||
this.popup = useService("popup");
|
||||
useListener("filter-selected", this._onFilterSelected);
|
||||
useListener("search", this._onSearch);
|
||||
useListener("click-order", this._onClickOrder);
|
||||
useListener("create-new-order", this._onCreateNewOrder);
|
||||
useListener("delete-order", this._onDeleteOrder);
|
||||
useListener("next-page", this._onNextPage);
|
||||
useListener("prev-page", this._onPrevPage);
|
||||
useListener("order-invoiced", this._onInvoiceOrder);
|
||||
useListener("click-order-line", this._onClickOrderline);
|
||||
useListener("click-refund-order-uid", this._onClickRefundOrderUid);
|
||||
useListener("update-selected-orderline", this._onUpdateSelectedOrderline);
|
||||
useListener("do-refund", this._onDoRefund);
|
||||
this.rpc = useService("rpc");
|
||||
this.numberBuffer = useService("number_buffer");
|
||||
this.numberBuffer.use({
|
||||
nonKeyboardInputEvent: "numpad-click-input",
|
||||
triggerAtInput: "update-selected-orderline",
|
||||
triggerAtInput: (event) => this._onUpdateSelectedOrderline(event),
|
||||
});
|
||||
this._state = this.env.pos.TICKET_SCREEN_STATE;
|
||||
this.state = useState({
|
||||
@@ -73,33 +61,39 @@ export class TicketScreen extends IndependentToOrderScreen {
|
||||
};
|
||||
Object.assign(this._state.ui, defaultUIState, this.props.ui || {});
|
||||
|
||||
onMounted(() => {
|
||||
this._onFilterSelected({ detail: { filter: this._state.ui.filter } });
|
||||
});
|
||||
onWillUnmount(() => {
|
||||
/**
|
||||
* Automatically create new order when there is no currently active order.
|
||||
* Important in fiscal modules to keep the sequence of the orders.
|
||||
*/
|
||||
if (this.env.pos.orders.length == 0 && this.allowNewOrders) {
|
||||
this.env.pos.add_new_order();
|
||||
}
|
||||
onMounted(this.onMounted);
|
||||
onWillUnmount(this.onWillUnmount);
|
||||
}
|
||||
//#region LIFECYCLE METHODS
|
||||
onMounted() {
|
||||
this.env.posbus.on("ticket-button-clicked", this, this.close);
|
||||
setTimeout(() => {
|
||||
// Show updated list of synced orders when going back to the screen.
|
||||
this.onFilterSelected(this._state.ui.filter);
|
||||
});
|
||||
}
|
||||
async _onFilterSelected(event) {
|
||||
this._state.ui.filter = event.detail.filter;
|
||||
onWillUnmount() {
|
||||
this.env.posbus.off("ticket-button-clicked", this);
|
||||
}
|
||||
//#endregion
|
||||
//#region EVENT HANDLERS
|
||||
onCloseScreen() {
|
||||
this.close();
|
||||
}
|
||||
async onFilterSelected(selectedFilter) {
|
||||
this._state.ui.filter = selectedFilter;
|
||||
if (this._state.ui.filter == "SYNCED") {
|
||||
await this._fetchSyncedOrders();
|
||||
}
|
||||
}
|
||||
async _onSearch(event) {
|
||||
Object.assign(this._state.ui.searchDetails, event.detail);
|
||||
async onSearch(search) {
|
||||
Object.assign(this._state.ui.searchDetails, search);
|
||||
if (this._state.ui.filter == "SYNCED") {
|
||||
this._state.syncedOrders.currentPage = 1;
|
||||
await this._fetchSyncedOrders();
|
||||
}
|
||||
}
|
||||
_onClickOrder({ detail: clickedOrder }) {
|
||||
onClickOrder(clickedOrder) {
|
||||
if (!clickedOrder || clickedOrder.locked) {
|
||||
if (this._state.ui.selectedSyncedOrderId == clickedOrder.backendId) {
|
||||
this._state.ui.selectedSyncedOrderId = null;
|
||||
@@ -118,7 +112,7 @@ export class TicketScreen extends IndependentToOrderScreen {
|
||||
this._setOrder(clickedOrder);
|
||||
}
|
||||
}
|
||||
_onCreateNewOrder() {
|
||||
onCreateNewOrder() {
|
||||
this.env.pos.add_new_order();
|
||||
this.pos.showScreen("ProductScreen");
|
||||
}
|
||||
@@ -129,7 +123,7 @@ export class TicketScreen extends IndependentToOrderScreen {
|
||||
orderList[currentOrderIndex + 1] || orderList[currentOrderIndex - 1]
|
||||
);
|
||||
}
|
||||
async _onDeleteOrder({ detail: order }) {
|
||||
async onDeleteOrder(order) {
|
||||
const screen = order.get_screen_data();
|
||||
if (
|
||||
["ProductScreen", "PaymentScreen"].includes(screen.name) &&
|
||||
@@ -156,36 +150,35 @@ export class TicketScreen extends IndependentToOrderScreen {
|
||||
this.env.pos.removeOrder(order);
|
||||
}
|
||||
}
|
||||
async _onNextPage() {
|
||||
async onNextPage() {
|
||||
if (this._state.syncedOrders.currentPage < this._getLastPage()) {
|
||||
this._state.syncedOrders.currentPage += 1;
|
||||
await this._fetchSyncedOrders();
|
||||
}
|
||||
}
|
||||
async _onPrevPage() {
|
||||
async onPrevPage() {
|
||||
if (this._state.syncedOrders.currentPage > 1) {
|
||||
this._state.syncedOrders.currentPage -= 1;
|
||||
await this._fetchSyncedOrders();
|
||||
}
|
||||
}
|
||||
async _onInvoiceOrder({ detail: orderId }) {
|
||||
async onInvoiceOrder(orderId) {
|
||||
this.env.pos._invalidateSyncedOrdersCache([orderId]);
|
||||
await this._fetchSyncedOrders();
|
||||
}
|
||||
_onClickOrderline({ detail: orderline }) {
|
||||
onClickOrderline(orderline) {
|
||||
const order = this.getSelectedSyncedOrder();
|
||||
this._state.ui.selectedOrderlineIds[order.backendId] = orderline.id;
|
||||
this.numberBuffer.reset();
|
||||
}
|
||||
_onClickRefundOrderUid({ detail: orderUid }) {
|
||||
onClickRefundOrderUid(orderUid) {
|
||||
// Open the refund order.
|
||||
const refundOrder = this.env.pos.orders.find((order) => order.uid == orderUid);
|
||||
if (refundOrder) {
|
||||
this._setOrder(refundOrder);
|
||||
}
|
||||
}
|
||||
_onUpdateSelectedOrderline({ detail }) {
|
||||
const buffer = detail.buffer;
|
||||
_onUpdateSelectedOrderline({ key, buffer }) {
|
||||
const order = this.getSelectedSyncedOrder();
|
||||
if (!order) {
|
||||
return this.numberBuffer.reset();
|
||||
@@ -229,7 +222,7 @@ export class TicketScreen extends IndependentToOrderScreen {
|
||||
}
|
||||
}
|
||||
}
|
||||
async _onDoRefund() {
|
||||
async onDoRefund() {
|
||||
const order = this.getSelectedSyncedOrder();
|
||||
|
||||
if (this._doesOrderHaveSoleItem(order)) {
|
||||
@@ -278,8 +271,10 @@ export class TicketScreen extends IndependentToOrderScreen {
|
||||
this.env.pos.set_order(destinationOrder);
|
||||
}
|
||||
|
||||
this.close();
|
||||
this.onCloseScreen();
|
||||
}
|
||||
//#endregion
|
||||
//#region PUBLIC METHODS
|
||||
getSelectedSyncedOrder() {
|
||||
if (this._state.ui.filter == "SYNCED") {
|
||||
return this._state.syncedOrders.cache[this._state.ui.selectedSyncedOrderId];
|
||||
|
||||
@@ -3,9 +3,7 @@ import { OfflineErrorPopup } from "./Popups/OfflineErrorPopup";
|
||||
import { ConfirmPopup } from "./Popups/ConfirmPopup";
|
||||
import { ErrorTracebackPopup } from "./Popups/ErrorTracebackPopup";
|
||||
import { ErrorPopup } from "./Popups/ErrorPopup";
|
||||
import { useEnv } from "@odoo/owl";
|
||||
|
||||
const { onMounted, onPatched, onWillUnmount, useComponent } = owl;
|
||||
import { useEnv, onMounted, onPatched, onWillUnmount, useComponent } from "@odoo/owl";
|
||||
|
||||
/**
|
||||
* Introduce error handlers in the component.
|
||||
|
||||
@@ -9,14 +9,15 @@ import time from "web.time";
|
||||
import utils from "web.utils";
|
||||
import { batched, uuidv4 } from "@point_of_sale/js/utils";
|
||||
import { ErrorPopup } from "./Popups/ErrorPopup";
|
||||
import { ProductConfiguratorPopup } from "@point_of_sale/js/Popups/ProductConfiguratorPopup";
|
||||
import { EditListPopup } from "@point_of_sale/js/Popups/EditListPopup";
|
||||
import { markRaw, reactive } from "@odoo/owl";
|
||||
|
||||
var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
var round_di = utils.round_decimals;
|
||||
var round_pr = utils.round_precision;
|
||||
const Markup = utils.Markup
|
||||
|
||||
const { markRaw, reactive } = owl;
|
||||
const Markup = utils.Markup;
|
||||
|
||||
// Container of the product images fetched during rendering
|
||||
// of customer display. There is no need to observe it, thus,
|
||||
@@ -1201,7 +1202,8 @@ export class PosGlobalState extends PosModel {
|
||||
} else if (tax.amount_type === "division") {
|
||||
incl_division_amount += tax.amount * tax.sum_repartition_factor;
|
||||
} else if (tax.amount_type === "fixed") {
|
||||
incl_fixed_amount += Math.abs(quantity) * tax.amount * tax.sum_repartition_factor;
|
||||
incl_fixed_amount +=
|
||||
Math.abs(quantity) * tax.amount * tax.sum_repartition_factor;
|
||||
} else {
|
||||
var tax_amount = self._compute_all(tax, base, quantity);
|
||||
incl_fixed_amount += tax_amount;
|
||||
@@ -1252,7 +1254,10 @@ export class PosGlobalState extends PosModel {
|
||||
}
|
||||
|
||||
tax_amount = round_pr(tax_amount, currency_rounding);
|
||||
var factorized_tax_amount = round_pr(tax_amount * tax.sum_repartition_factor, currency_rounding)
|
||||
var factorized_tax_amount = round_pr(
|
||||
tax_amount * tax.sum_repartition_factor,
|
||||
currency_rounding
|
||||
);
|
||||
|
||||
if (tax.price_include && total_included_checkpoints[i] === undefined) {
|
||||
cumulated_tax_included_amount += factorized_tax_amount;
|
||||
@@ -1476,6 +1481,103 @@ export class Product extends PosModel {
|
||||
}
|
||||
return this.pos.units_by_id[unit_id];
|
||||
}
|
||||
async _onScaleNotAvailable() {}
|
||||
get isScaleAvailable() {
|
||||
return true;
|
||||
}
|
||||
async getAddProductOptions(base_code) {
|
||||
let price_extra = 0.0;
|
||||
let draftPackLotLines, weight, description, packLotLinesToEdit;
|
||||
|
||||
if (_.some(this.attribute_line_ids, (id) => id in this.pos.attributes_by_ptal_id)) {
|
||||
const attributes = _.map(
|
||||
this.attribute_line_ids,
|
||||
(id) => this.pos.attributes_by_ptal_id[id]
|
||||
).filter((attr) => attr !== undefined);
|
||||
const { confirmed, payload } = await this.pos.env.services.popup.add(
|
||||
ProductConfiguratorPopup,
|
||||
{
|
||||
product: this,
|
||||
attributes: attributes,
|
||||
}
|
||||
);
|
||||
|
||||
if (confirmed) {
|
||||
description = payload.selected_attributes.join(", ");
|
||||
price_extra += payload.price_extra;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Gather lot information if required.
|
||||
if (
|
||||
["serial", "lot"].includes(this.tracking) &&
|
||||
(this.pos.picking_type.use_create_lots || this.pos.picking_type.use_existing_lots)
|
||||
) {
|
||||
const isAllowOnlyOneLot = this.isAllowOnlyOneLot();
|
||||
if (isAllowOnlyOneLot) {
|
||||
packLotLinesToEdit = [];
|
||||
} else {
|
||||
const orderline = this.currentOrder
|
||||
.get_orderlines()
|
||||
.filter((line) => !line.get_discount())
|
||||
.find((line) => line.product.id === this.id);
|
||||
if (orderline) {
|
||||
packLotLinesToEdit = orderline.getPackLotLinesToEdit();
|
||||
} else {
|
||||
packLotLinesToEdit = [];
|
||||
}
|
||||
}
|
||||
const { confirmed, payload } = await this.pos.env.services.popup.add(EditListPopup, {
|
||||
title: this.pos.env._t("Lot/Serial Number(s) Required"),
|
||||
name: this.display_name,
|
||||
isSingleItem: isAllowOnlyOneLot,
|
||||
array: packLotLinesToEdit,
|
||||
});
|
||||
if (confirmed) {
|
||||
// Segregate the old and new packlot lines
|
||||
const modifiedPackLotLines = Object.fromEntries(
|
||||
payload.newArray.filter((item) => item.id).map((item) => [item.id, item.text])
|
||||
);
|
||||
const newPackLotLines = payload.newArray
|
||||
.filter((item) => !item.id)
|
||||
.map((item) => ({ lot_name: item.text }));
|
||||
|
||||
draftPackLotLines = { modifiedPackLotLines, newPackLotLines };
|
||||
} else {
|
||||
// We don't proceed on adding product.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Take the weight if necessary.
|
||||
if (this.to_weight && this.pos.config.iface_electronic_scale) {
|
||||
// Show the ScaleScreen to weigh the product.
|
||||
if (this.isScaleAvailable) {
|
||||
const product = this;
|
||||
const { confirmed, payload } = await this.pos.env.services.pos.showTempScreen(
|
||||
"ScaleScreen",
|
||||
{
|
||||
product,
|
||||
}
|
||||
);
|
||||
if (confirmed) {
|
||||
weight = payload.weight;
|
||||
} else {
|
||||
// do not add the product;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
await this._onScaleNotAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
if (base_code && this.pos.db.product_packaging_by_barcode[base_code.code]) {
|
||||
weight = this.pos.db.product_packaging_by_barcode[base_code.code].qty;
|
||||
}
|
||||
|
||||
return { draftPackLotLines, quantity: weight, description, price_extra };
|
||||
}
|
||||
// Port of _get_product_price on product.pricelist.
|
||||
//
|
||||
// Anything related to UOM can be ignored, the POS will always use
|
||||
@@ -2995,7 +3097,7 @@ export class Order extends PosModel {
|
||||
line.set_unit_price(line.compute_fixed_price(line.price));
|
||||
}
|
||||
|
||||
add_product(product, options) {
|
||||
async add_product(product, options) {
|
||||
if (this._printed) {
|
||||
// when adding product with a barcode while being in receipt screen
|
||||
this.pos.removeOrder(this);
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
|
||||
<t t-if="pos.loadingSkipButtonIsShown">
|
||||
<h1 class="message">Connecting to devices</h1>
|
||||
<div class="button skip" t-on-click="() => this.trigger('loading-skip-callback')">
|
||||
<div class="button skip" t-on-click="() => this.env.proxy.stop_searching()">
|
||||
Skip
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="MobileOrderWidget" owl="1">
|
||||
<div class="switchpane">
|
||||
<button class="btn-switchpane" t-on-click="() => this.trigger('click-pay')">
|
||||
<button class="btn-switchpane" t-on-click="() => this.props.clickPay()">
|
||||
<h1>Pay</h1>
|
||||
<span><t t-esc="total" /></span>
|
||||
</button>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<ul>
|
||||
<t t-foreach="searchFieldsList" t-as="fieldName" t-key="fieldName">
|
||||
<li t-att-class="{ highlight: fieldName == searchFieldsList[state.selectedSearchFieldId] }"
|
||||
t-on-click="() => this.trigger('click-search-field', fieldName)">
|
||||
t-on-click="() => this._onClickSearchField(fieldName)">
|
||||
<span class="field">
|
||||
<t t-esc="props.config.searchFields.get(fieldName)"></t>
|
||||
</span>
|
||||
@@ -35,7 +35,7 @@
|
||||
<ul t-if="state.showFilterOptions" class="options">
|
||||
<t t-foreach="filterOptionsList" t-as="key" t-key="key">
|
||||
<t t-set="_option" t-value="props.config.filter.options.get(key)" />
|
||||
<li t-on-click="() => this.trigger('select-filter', key)" t-att-class="{ indented: _option.indented }">
|
||||
<li t-on-click="() => this._onSelectFilter(key)" t-att-class="{ indented: _option.indented }">
|
||||
<t t-esc="_option.text"></t>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div t-if="!props.exitButtonIsShown" class="button cancel" t-on-click="confirm">
|
||||
<t t-esc="props.confirmText" />
|
||||
</div>
|
||||
<div t-if="props.exitButtonIsShown" class="button cancel" t-on-click="() => this.trigger(props.exitButtonTrigger)">
|
||||
<div t-if="props.exitButtonIsShown" class="button cancel" t-on-click="() => this.pos.closePos()">
|
||||
<t t-esc="props.exitButtonText" />
|
||||
</div>
|
||||
<a t-att-download="tracebackFilename" t-att-href="tracebackUrl">
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</t>
|
||||
|
||||
<t t-name="RadioProductAttribute" owl="1">
|
||||
<div class="configurator_radio">
|
||||
<div class="configurator_radio" t-ref="root">
|
||||
<div class="table">
|
||||
<t t-foreach="values" t-as="value" t-key="value.id">
|
||||
<div class="attribute-name-cell">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="partnerlist-screen screen">
|
||||
<div class="screen-content">
|
||||
<div class="top-content">
|
||||
<div class="button highlight" t-if="state.detailIsShown" t-on-click="() => this.trigger('click-save')">
|
||||
<div class="button highlight" t-if="state.detailIsShown" t-on-click="() => this.env.bus.trigger('save-partner')">
|
||||
<i t-if="env.isMobile" class="fa fa-floppy-o"/>
|
||||
<t t-if="!env.isMobile">
|
||||
<span> Save</span>
|
||||
@@ -39,7 +39,7 @@
|
||||
<section class="subwindow collapsed">
|
||||
<div class="subwindow-container collapsed">
|
||||
<div t-if="state.detailIsShown" class="partner-details-contents subwindow-container-fix">
|
||||
<PartnerDetailsEdit t-props="state.editModeProps" />
|
||||
<PartnerDetailsEdit t-props="state.editModeProps" saveChanges.bind="saveChanges" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="PSNumpadInputButton" owl="1">
|
||||
<button t-attf-class="{{ _class }}"
|
||||
t-on-click="() => this.trigger('input-from-numpad', { key: props.value })">
|
||||
t-on-click="() => this.numberBuffer.sendKey(props.value)">
|
||||
<t t-slot="default">
|
||||
<t t-if="props.text">
|
||||
<t t-esc="props.text" />
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<t t-foreach="payment_methods_from_config" t-as="paymentMethod"
|
||||
t-key="paymentMethod.id">
|
||||
<div class="button paymentmethod"
|
||||
t-on-click="() => this.trigger('new-payment-line', paymentMethod)">
|
||||
t-on-click="() => this.addNewPaymentLine(paymentMethod)">
|
||||
<div class="payment-method-display">
|
||||
<div><img class="payment-method-icon" t-att-src="paymentMethodImage(paymentMethod.id)"/></div>
|
||||
<span class="payment-name"><t t-esc="paymentMethod.name" /></span>
|
||||
@@ -35,10 +35,17 @@
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<PaymentScreenPaymentLines paymentLines="paymentLines" />
|
||||
<PaymentScreenPaymentLines
|
||||
paymentLines="paymentLines"
|
||||
deleteLine.bind="deletePaymentLine"
|
||||
sendForceDone.bind="sendForceDone"
|
||||
sendPaymentReverse.bind="sendPaymentReverse"
|
||||
sendPaymentCancel.bind="sendPaymentCancel"
|
||||
sendPaymentRequest.bind="sendPaymentRequest"
|
||||
selectLine.bind="selectPaymentLine"/>
|
||||
</div>
|
||||
<div t-if="!env.isMobile" class="button next validation" t-att-class="{ highlight: currentOrder.is_paid() and currentOrder._isValidEmptyOrder() }"
|
||||
t-on-click="() => this.trigger('validate-order')">
|
||||
t-on-click="() => this.validateOrder()">
|
||||
<div class="pay-circle">
|
||||
<i class="fa fa-chevron-right" role="img" aria-label="Pay" title="Pay" />
|
||||
</div>
|
||||
@@ -105,7 +112,7 @@
|
||||
<div class="switchpane">
|
||||
<button class="btn-switchpane"
|
||||
t-att-class="{ secondary: !(currentOrder.is_paid() and currentOrder._isValidEmptyOrder()) }"
|
||||
t-on-click="() => this.trigger('validate-order')">
|
||||
t-on-click="() => this.validateOrder()">
|
||||
<h1>Validate</h1>
|
||||
</button>
|
||||
<button class="btn-switchpane secondary" t-on-click="() => pos.showScreen('ProductScreen', {mobile_pane: 'left'})">
|
||||
|
||||
+13
-13
@@ -7,11 +7,11 @@
|
||||
<t t-foreach="props.paymentLines" t-as="line" t-key="line.cid">
|
||||
<t t-if="line.selected">
|
||||
<div class="paymentline selected"
|
||||
t-att-class="selectedLineClass(line)"
|
||||
t-on-click="() => this.trigger('select-payment-line', { cid: line.cid })">
|
||||
<div class="payment-name">
|
||||
t-att-class="selectedLineClass(line)"
|
||||
t-on-click="() => this.props.selectLine(line.cid)">
|
||||
<div class="payment-name">
|
||||
<t t-esc="line.payment_method.name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="payment-amount">
|
||||
<t t-if="line and line.payment_status and ['done', 'waitingCard', 'waiting', 'reversing', 'reversed'].includes(line.payment_status)">
|
||||
<t t-esc="env.pos.format_currency_no_symbol(line.get_amount())" />
|
||||
@@ -22,7 +22,7 @@
|
||||
</div>
|
||||
<t t-if="!line.payment_status or !['done', 'reversed', 'waitingCard', 'waitingCapture'].includes(line.payment_status)">
|
||||
<div class="delete-button"
|
||||
t-on-click="() => this.trigger('delete-payment-line', { cid: line.cid })"
|
||||
t-on-click="() => this.props.deleteLine(line.cid)"
|
||||
aria-label="Delete" title="Delete">
|
||||
<i class="fa fa-times-circle" />
|
||||
</div>
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="electronic_status">
|
||||
Payment request pending
|
||||
</div>
|
||||
<div class="button send_payment_request highlight" title="Send Payment Request" t-on-click="() => this.trigger('send-payment-request', line)">
|
||||
<div class="button send_payment_request highlight" title="Send Payment Request" t-on-click="() => this.props.sendPaymentRequest(line)">
|
||||
Send
|
||||
</div>
|
||||
</t>
|
||||
@@ -42,7 +42,7 @@
|
||||
<div class="electronic_status">
|
||||
Transaction cancelled
|
||||
</div>
|
||||
<div class="button send_payment_request highlight" title="Send Payment Request" t-on-click="() => this.trigger('send-payment-request', line)">
|
||||
<div class="button send_payment_request highlight" title="Send Payment Request" t-on-click="() => this.props.sendPaymentRequest(line)">
|
||||
Retry
|
||||
</div>
|
||||
</t>
|
||||
@@ -50,7 +50,7 @@
|
||||
<div class="electronic_status">
|
||||
Connection error
|
||||
</div>
|
||||
<div class="button send_force_done" title="Force Done" t-on-click="() => this.trigger('send-force-done', line)">
|
||||
<div class="button send_force_done" title="Force Done" t-on-click="() => this.props.sendForceDone(line)">
|
||||
Force done
|
||||
</div>
|
||||
</t>
|
||||
@@ -58,7 +58,7 @@
|
||||
<div class="electronic_status">
|
||||
Waiting for card
|
||||
</div>
|
||||
<div class="button send_payment_cancel" title="Cancel Payment Request" t-on-click="() => this.trigger('send-payment-cancel', line)">
|
||||
<div class="button send_payment_cancel" title="Cancel Payment Request" t-on-click="() => this.props.sendPaymentCancel(line)">
|
||||
Cancel
|
||||
</div>
|
||||
</t>
|
||||
@@ -83,7 +83,7 @@
|
||||
Payment Successful
|
||||
</div>
|
||||
<t t-if="line.can_be_reversed">
|
||||
<div class="button send_payment_reversal" title="Reverse Payment" t-on-click="() => this.trigger('send-payment-reverse', line)">
|
||||
<div class="button send_payment_reversal" title="Reverse Payment" t-on-click="() => this.props.sendPaymentReverse(line)">
|
||||
Reverse
|
||||
</div>
|
||||
</t>
|
||||
@@ -102,8 +102,8 @@
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="paymentline"
|
||||
t-att-class="unselectedLineClass(line)"
|
||||
t-on-click="() => this.trigger('select-payment-line', { cid: line.cid })">
|
||||
t-att-class="unselectedLineClass(line)"
|
||||
t-on-click="() => this.props.selectLine(line.cid)">
|
||||
<div class="payment-name">
|
||||
<t t-esc="line.payment_method.name" />
|
||||
</div>
|
||||
@@ -117,7 +117,7 @@
|
||||
</div>
|
||||
<t t-if="!line.payment_status or !['done', 'reversed'].includes(line.payment_status)">
|
||||
<div class="delete-button"
|
||||
t-on-click="() => this.trigger('delete-payment-line', { cid: line.cid })"
|
||||
t-on-click="() => this.props.deleteLine(line.cid)"
|
||||
aria-label="Delete" title="Delete">
|
||||
<i class="fa fa-times-circle" />
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<t t-name="ActionpadWidget" owl="1">
|
||||
<div class="actionpad">
|
||||
<button class="button set-partner" t-att-class="{'decentered': isLongName}"
|
||||
t-on-click="() => this.trigger('click-partner')">
|
||||
t-on-click="() => this.pos.selectPartner()">
|
||||
<t t-if="!env.isMobile"><i class="fa fa-user" role="img" aria-label="Customer" title="Customer" /></t>
|
||||
<t t-if="props.partner">
|
||||
<t t-esc="props.partner.name" />
|
||||
@@ -13,7 +13,7 @@
|
||||
Customer
|
||||
</t>
|
||||
</button>
|
||||
<button class="button pay validation" t-on-click="() => this.trigger(props.actionToTrigger or 'click-pay')">
|
||||
<button class="button pay validation" t-on-click="props.actionToTrigger ? this.props.actionToTrigger : () => this.props.clickPay()">
|
||||
<div class="pay-circle" t-att-class="{ 'highlight': props.isActionButtonHighlighted }">
|
||||
<i class="fa fa-chevron-right" role="img" aria-label="Pay" title="Pay" />
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="CategoryButton" owl="1">
|
||||
<span class="category-button" t-on-click="() => this.trigger('switch-category', props.category.id)">
|
||||
<span class="category-button" t-on-click="() => this.props.switchCategory(props.category.id)">
|
||||
<div class="category-img">
|
||||
<img t-att-src="imageUrl" alt="Category" />
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="OrderlineCustomerNoteButton" owl="1">
|
||||
<div class="control-button">
|
||||
<div class="control-button" t-on-click="() => this.onClick()">
|
||||
<i class="fa fa-sticky-note" />
|
||||
<span> </span>
|
||||
<span>Customer Note</span>
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="ProductInfoButton" owl="1">
|
||||
<div class="control-button">
|
||||
<div class="control-button" t-on-click="() => this.click()">
|
||||
<i class="fa fa-info-circle" role="img" aria-label="Info" title="Info"/>
|
||||
Info
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.RefundButton" owl="1">
|
||||
<div class="control-button">
|
||||
<div class="control-button" t-on-click="() => this.click()">
|
||||
<i class="fa fa-undo" role="img" aria-label="Refund" title="Refund" />
|
||||
Refund
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="SetFiscalPositionButton" owl="1">
|
||||
<div class="control-button o_fiscal_position_button">
|
||||
<div class="control-button o_fiscal_position_button" t-on-click="() => this.click()">
|
||||
<i class="fa fa-book" role="img" aria-label="Set fiscal position"
|
||||
title="Set fiscal position" />
|
||||
<t t-esc='currentFiscalPositionName' />
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="SetPricelistButton" owl="1">
|
||||
<div class="control-button o_pricelist_button">
|
||||
<div class="control-button o_pricelist_button" t-on-click="() => this.click()">
|
||||
<i class="fa fa-th-list" role="img" aria-label="Price list" title="Price list" />
|
||||
<t t-esc="currentPricelistName" />
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
<t t-else="">
|
||||
<ul class="orderlines">
|
||||
<t t-foreach="orderlinesArray" t-as="orderline" t-key="orderline.id">
|
||||
<Orderline line="orderline" />
|
||||
<Orderline
|
||||
editPackLotLines.bind="editPackLotLines"
|
||||
selectLine.bind="selectLine"
|
||||
line="orderline" />
|
||||
</t>
|
||||
</ul>
|
||||
<OrderSummary order="order" />
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="ProductItem" owl="1">
|
||||
<article class="product" tabindex="0" t-on-keypress="spaceClickProduct"
|
||||
t-on-click="() => this.trigger('click-product', props.product)"
|
||||
t-on-click="() => this.pos.addProductToCurrentOrder(props.product)"
|
||||
t-att-data-product-id="props.product.id"
|
||||
t-attf-aria-labelledby="article_product_{{props.product.id}}">
|
||||
<div class="product-img">
|
||||
|
||||
@@ -19,14 +19,24 @@
|
||||
</div>
|
||||
<div class="subpads">
|
||||
<t t-set="_actionName">Payment</t>
|
||||
<ActionpadWidget partner="partner" actionName="_actionName" onSwitchPane.bind="switchPane" />
|
||||
<NumpadWidget activeMode="env.pos.numpadMode" />
|
||||
<ActionpadWidget
|
||||
clickPay.bind="onClickPay"
|
||||
partner="partner"
|
||||
actionName="_actionName"
|
||||
onSwitchPane.bind="switchPane" />
|
||||
<NumpadWidget
|
||||
updateSelectedOrderline.bind="updateSelectedOrderline"
|
||||
activeMode="env.pos.numpadMode" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rightpane" t-if="!env.isMobile || state.mobile_pane === 'right'">
|
||||
<ProductsWidget/>
|
||||
<MobileOrderWidget t-if="env.isMobile" pane="state.mobile_pane" onSwitchPane.bind="switchPane"/>
|
||||
<ProductsWidget />
|
||||
<MobileOrderWidget
|
||||
clickPay.bind="onClickPay"
|
||||
t-if="env.isMobile"
|
||||
pane="state.mobile_pane"
|
||||
onSwitchPane.bind="switchPane"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,11 +3,19 @@
|
||||
|
||||
<t t-name="ProductsWidget" owl="1">
|
||||
<div class="products-widget">
|
||||
<ProductsWidgetControlPanel breadcrumbs="breadcrumbs" subcategories="subcategories" hasNoCategories="hasNoCategories" />
|
||||
<ProductsWidgetControlPanel
|
||||
updateProductList.bind="updateProductList"
|
||||
clearSearch.bind="clearSearch"
|
||||
updateSearch.bind="updateSearch"
|
||||
switchCategory.bind="switchCategory"
|
||||
breadcrumbs="breadcrumbs"
|
||||
subcategories="subcategories"
|
||||
hasNoCategories="hasNoCategories" />
|
||||
<div class="product-list-container">
|
||||
<div t-if="productsToDisplay.length != 0" class="product-list">
|
||||
<t t-foreach="productsToDisplay" t-as="product" t-key="product.id">
|
||||
<ProductItem product="product" />
|
||||
<ProductItem
|
||||
product="product" />
|
||||
</t>
|
||||
</div>
|
||||
<div t-else="" class="product-list-empty no-results-message">
|
||||
|
||||
+6
-4
@@ -21,7 +21,7 @@
|
||||
<!-- Breadcrumbs -->
|
||||
<div class="breadcrumbs">
|
||||
<span class="breadcrumb">
|
||||
<span class="breadcrumb-button breadcrumb-home" t-on-click="() => this.trigger('switch-category', 0)">
|
||||
<span class="breadcrumb-button breadcrumb-home" t-on-click="() => this.props.switchCategory(0)">
|
||||
<i class="fa fa-home" role="img" aria-label="Home" title="Home"></i>
|
||||
</span>
|
||||
</span>
|
||||
@@ -29,7 +29,7 @@
|
||||
<span class="breadcrumb">
|
||||
<i class="breadcrumb-arrow fa fa-caret-right" aria-hidden="true"/>
|
||||
<span class="breadcrumb-button"
|
||||
t-on-click="() => this.trigger('switch-category', category.id)">
|
||||
t-on-click="() => this.props.switchCategory(category.id)">
|
||||
<t t-esc="category.name"></t>
|
||||
</span>
|
||||
</span>
|
||||
@@ -39,7 +39,7 @@
|
||||
<div t-if="props.subcategories.length > 0 and !displayCategImages" class="category-list simple">
|
||||
<t t-foreach="props.subcategories" t-as="category" t-key="category.id">
|
||||
<span class="category-simple-button"
|
||||
t-on-click="() => this.trigger('switch-category', category.id)">
|
||||
t-on-click="() => this.props.switchCategory(category.id)">
|
||||
<t t-esc="category.name" />
|
||||
</span>
|
||||
</t>
|
||||
@@ -52,7 +52,9 @@
|
||||
<div class="category-list-scroller">
|
||||
<div class="category-list">
|
||||
<t t-foreach="props.subcategories" t-as="category" t-key="category.id">
|
||||
<CategoryButton category="category" />
|
||||
<CategoryButton
|
||||
switchCategory.bind="props.switchCategory"
|
||||
category="category" />
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="InvoiceButton" owl="1">
|
||||
<div class="control-button">
|
||||
<div class="control-button" t-on-click="() => this.click()" t-ref="invoice-button">
|
||||
<i class="fa fa-file-pdf-o"></i>
|
||||
<span> </span>
|
||||
<span><t t-esc="commandName"></t></span>
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="ReprintReceiptButton" owl="1">
|
||||
<div class="control-button">
|
||||
<div class="control-button" t-on-click="() => this.click()">
|
||||
<i class="fa fa-print"></i>
|
||||
<span> </span>
|
||||
<span>Print Receipt</span>
|
||||
|
||||
@@ -25,7 +25,11 @@
|
||||
</div>
|
||||
<ul class="orderlines">
|
||||
<t t-foreach="orderlines" t-as="orderline" t-key="orderline.id">
|
||||
<OrderlineDetails line="orderline" isSelected="orderline.id == props.selectedOrderlineId" />
|
||||
<OrderlineDetails
|
||||
onClickOrderline.bind="this.props.onClickOrderline"
|
||||
onClickRefundOrderUid.bind="props.onClickRefundOrderUid"
|
||||
line="orderline"
|
||||
isSelected="orderline.id == props.selectedOrderlineId" />
|
||||
</t>
|
||||
</ul>
|
||||
<!-- Not using OrderSummary because we don't need it's overrides in this view. -->
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="OrderlineDetails" owl="1">
|
||||
<li class="orderline" t-on-click="() => this.trigger('click-order-line', props.line)" t-att-class="{ selected: props.isSelected }">
|
||||
<li class="orderline" t-on-click="() => this.props.onClickOrderline(props.line)" t-att-class="{ selected: props.isSelected }">
|
||||
<span class="product-name">
|
||||
<t t-esc="productName" />
|
||||
</span>
|
||||
@@ -35,7 +35,7 @@
|
||||
<t t-set="_destinationOrderUid" t-value="getToRefundDetail().destinationOrderUid"/>
|
||||
<t t-if="_destinationOrderUid">
|
||||
<t t-esc="getRefundingMessage()" />
|
||||
<span t-esc="_destinationOrderUid" class="order-link" t-on-click.stop="() => this.trigger('click-refund-order-uid', _destinationOrderUid)" />
|
||||
<span t-esc="_destinationOrderUid" class="order-link" t-on-click.stop="() => this.props.onClickRefundOrderUid(_destinationOrderUid)" />
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="getToRefundMessage()" />
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="controls">
|
||||
<t t-if="!env.isMobile || !state.showSearchBar">
|
||||
<div class="buttons">
|
||||
<button class="discard" t-on-click="() => this.pos.closeScreen()">
|
||||
<button class="discard" t-on-click="() => this.onCloseScreen()">
|
||||
<span class="search-icon">
|
||||
<i class="fa fa-angle-double-left"/>
|
||||
</span>
|
||||
@@ -16,7 +16,7 @@
|
||||
Back
|
||||
</t>
|
||||
</button>
|
||||
<button t-if="allowNewOrders" class="highlight" t-on-click="() => this.trigger('create-new-order')">New Order</button>
|
||||
<button t-if="allowNewOrders" class="highlight" t-on-click="() => this.onCreateNewOrder()">New Order</button>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="env.isMobile">
|
||||
@@ -36,13 +36,18 @@
|
||||
</t>
|
||||
</t>
|
||||
<t t-set="_placeholder">Search Orders...</t>
|
||||
<SearchBar t-if="state.showSearchBar" config="getSearchBarConfig()" placeholder="_placeholder" />
|
||||
<SearchBar
|
||||
t-if="state.showSearchBar"
|
||||
config="getSearchBarConfig()"
|
||||
placeholder="_placeholder"
|
||||
onSearch.bind="onSearch"
|
||||
onFilterSelected.bind="onFilterSelected" />
|
||||
<div t-if="shouldShowPageControls()" class="item">
|
||||
<div class="page-controls">
|
||||
<div class="previous" t-on-click="() => this.trigger('prev-page')">
|
||||
<div class="previous" t-on-click="() => this.onPrevPage()">
|
||||
<i class="fa fa-fw fa-caret-left" role="img" aria-label="Previous Order List" title="Previous Order List"></i>
|
||||
</div>
|
||||
<div class="next" t-on-click="() => this.trigger('next-page')">
|
||||
<div class="next" t-on-click="() => this.onNextPage()">
|
||||
<i class="fa fa-fw fa-caret-right" role="img" aria-label="Next Order List" title="Next Order List"></i>
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,7 +70,7 @@
|
||||
<div class="col very-narrow" name="delete"></div>
|
||||
</div>
|
||||
<t t-foreach="_filteredOrderList" t-as="order" t-key="order.cid">
|
||||
<div class="order-row" t-att-class="{ highlight: isHighlighted(order) }" t-on-click="() => this.trigger('click-order', order)">
|
||||
<div class="order-row" t-att-class="{ highlight: isHighlighted(order) }" t-on-click="() => this.onClickOrder(order)">
|
||||
<div class="col wide">
|
||||
<div t-if="env.isMobile">Date</div>
|
||||
<div><t t-esc="getDate(order)"></t></div>
|
||||
@@ -94,7 +99,7 @@
|
||||
<div t-if="env.isMobile">Status</div>
|
||||
<div><t t-esc="getStatus(order)"></t></div>
|
||||
</div>
|
||||
<div t-if="!shouldHideDeleteButton(order)" class="col very-narrow delete-button" name="delete" t-on-click.stop="() => this.trigger('delete-order', order)">
|
||||
<div t-if="!shouldHideDeleteButton(order)" class="col very-narrow delete-button" name="delete" t-on-click.stop="() => this.onDeleteOrder(order)">
|
||||
<i class="fa fa-trash" aria-hidden="true"/><t t-if="env.isMobile"> Delete</t>
|
||||
</div>
|
||||
<div t-else="" class="col very-narrow"></div>
|
||||
@@ -112,17 +117,32 @@
|
||||
<div class="leftpane">
|
||||
<t t-set="_selectedSyncedOrder" t-value="getSelectedSyncedOrder()" />
|
||||
<t t-set="_selectedOrderlineId" t-value="getSelectedOrderlineId()" />
|
||||
<OrderDetails order="_selectedSyncedOrder" selectedOrderlineId="_selectedOrderlineId" highlightHeaderNote="_state.ui.highlightHeaderNote" />
|
||||
<OrderDetails
|
||||
onClickOrderline.bind="onClickOrderline"
|
||||
onClickRefundOrderUid.bind="onClickRefundOrderUid"
|
||||
order="_selectedSyncedOrder"
|
||||
selectedOrderlineId="_selectedOrderlineId"
|
||||
highlightHeaderNote="_state.ui.highlightHeaderNote" />
|
||||
<div class="pads">
|
||||
<div class="control-buttons">
|
||||
<InvoiceButton order="_selectedSyncedOrder" />
|
||||
<InvoiceButton
|
||||
onInvoiceOrder.bind="onInvoiceOrder"
|
||||
order="_selectedSyncedOrder" />
|
||||
<ReprintReceiptButton order="_selectedSyncedOrder" />
|
||||
</div>
|
||||
<div class="subpads">
|
||||
<!-- We set so that the term 'Refund' is translated. -->
|
||||
<t t-set="_actionName">Refund</t>
|
||||
<ActionpadWidget partner="getSelectedPartner()" actionName="_actionName" actionToTrigger="'do-refund'" isActionButtonHighlighted="getHasItemsToRefund()" />
|
||||
<NumpadWidget disabledModes="['price', 'discount']" activeMode="_selectedOrderlineId and 'quantity'" disableSign="true" />
|
||||
<ActionpadWidget
|
||||
partner="getSelectedPartner()"
|
||||
actionName="_actionName"
|
||||
actionToTrigger.bind="onDoRefund"
|
||||
isActionButtonHighlighted="getHasItemsToRefund()" />
|
||||
<NumpadWidget
|
||||
disabledModes="['price', 'discount']"
|
||||
setNumpadMode="() => {}"
|
||||
activeMode="_selectedOrderlineId and 'quantity'"
|
||||
disableSign="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
import testUtils from "web.test_utils";
|
||||
import { mount } from "@web/../tests/helpers/utils";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { numberBuffer } from "@point_of_sale/app/number_buffer_service";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
|
||||
|
||||
import { useState, xml } from "@odoo/owl";
|
||||
import { Component, useState, xml } from "@odoo/owl";
|
||||
|
||||
QUnit.module("unit tests for NumberBuffer", {
|
||||
async beforeEach() {
|
||||
@@ -23,13 +22,12 @@ QUnit.test("simple fast inputs with capture in between", async function (assert)
|
||||
const target = testUtils.prepareTarget();
|
||||
const env = await makeTestEnv();
|
||||
|
||||
class Root extends LegacyComponent {
|
||||
class Root extends Component {
|
||||
setup() {
|
||||
this.state = useState({ buffer: "" });
|
||||
this.numberBuffer = useService("number_buffer");
|
||||
this.numberBuffer.activate();
|
||||
this.numberBuffer.use({
|
||||
nonKeyboardInputEvent: "numpad-click-input",
|
||||
state: this.state,
|
||||
});
|
||||
}
|
||||
@@ -38,10 +36,10 @@ QUnit.test("simple fast inputs with capture in between", async function (assert)
|
||||
this.numberBuffer.reset();
|
||||
}
|
||||
onClickOne() {
|
||||
this.trigger("numpad-click-input", { key: "1" });
|
||||
this.numberBuffer.sendKey("1");
|
||||
}
|
||||
onClickTwo() {
|
||||
this.trigger("numpad-click-input", { key: "2" });
|
||||
this.numberBuffer.sendKey("2");
|
||||
}
|
||||
}
|
||||
Root.template = xml/* html */ `
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import makeTestEnvironment from "web.test_env";
|
||||
import { click, getFixture, mount, nextTick, triggerEvent } from "@web/../tests/helpers/utils";
|
||||
import { clearRegistryWithCleanup, makeTestEnv } from "@web/../tests/helpers/mock_env";
|
||||
@@ -9,8 +8,7 @@ import { registry } from "@web/core/registry";
|
||||
import { popupService } from "@point_of_sale/app/popup/popup_service";
|
||||
import { MainComponentsContainer } from "@web/core/main_components_container";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { xml } = owl;
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
|
||||
// Note that we are creating new popups here to decouple this test from the pos app.
|
||||
class CustomPopup1 extends AbstractAwaitablePopup {}
|
||||
@@ -38,7 +36,7 @@ CustomPopup2.template = xml/* html */ `
|
||||
</div>
|
||||
`;
|
||||
|
||||
class Root extends LegacyComponent {
|
||||
class Root extends Component {
|
||||
static components = { MainComponentsContainer };
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { PaymentScreen } from "@point_of_sale/js/Screens/PaymentScreen/PaymentScreen";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
const { onMounted } = owl;
|
||||
import { onMounted } from "@odoo/owl";
|
||||
|
||||
patch(PaymentScreen.prototype, "pos_adyen.PaymentScreen", {
|
||||
setup() {
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
|
||||
import { useListener, useService } from "@web/core/utils/hooks";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup";
|
||||
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class DiscountButton extends LegacyComponent {
|
||||
export class DiscountButton extends Component {
|
||||
static template = "DiscountButton";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.popup = useService("popup");
|
||||
useListener("click", this.onClick);
|
||||
}
|
||||
async onClick() {
|
||||
async click() {
|
||||
var self = this;
|
||||
const { confirmed, payload } = await this.popup.add(NumberPopup, {
|
||||
title: this.env._t("Discount Percentage"),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="DiscountButton" owl="1">
|
||||
<span class="control-button js_discount">
|
||||
<span class="control-button js_discount" t-on-click="() => this.click()">
|
||||
<i class="fa fa-tag"></i>
|
||||
<span> </span>
|
||||
<span>Discount</span>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { Component, useState } from "@odoo/owl";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
|
||||
const { useState } = owl;
|
||||
|
||||
export class HeaderLockButton extends LegacyComponent {
|
||||
export class HeaderLockButton extends Component {
|
||||
static template = "HeaderLockButton";
|
||||
|
||||
setup() {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { useCashierSelector } from "@pos_hr/js/SelectCashierMixin";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { usePos } from "@point_of_sale/app/pos_hook";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class LoginScreen extends LegacyComponent {
|
||||
export class LoginScreen extends Component {
|
||||
static template = "LoginScreen";
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
'assets': {
|
||||
'point_of_sale.assets': [
|
||||
'pos_loyalty/static/src/css/Loyalty.scss',
|
||||
'pos_loyalty/static/src/app/**/*',
|
||||
'pos_loyalty/static/src/js/**/*',
|
||||
'pos_loyalty/static/src/xml/**/*',
|
||||
],
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { PosStore } from "@point_of_sale/app/pos_store";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup";
|
||||
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
|
||||
import { TextInputPopup } from "@point_of_sale/js/Popups/TextInputPopup";
|
||||
|
||||
patch(PosStore.prototype, "pos_loyalty.PosStore", {
|
||||
async addProductFromUi(product, options) {
|
||||
const _super = this._super;
|
||||
const order = this.globalState.get_order();
|
||||
const linkedProgramIds = this.globalState.productId2ProgramIds[product.id] || [];
|
||||
const linkedPrograms = linkedProgramIds.map((id) => this.globalState.program_by_id[id]);
|
||||
let selectedProgram = null;
|
||||
if (linkedPrograms.length > 1) {
|
||||
const { confirmed, payload: program } = await this.popup.add(SelectionPopup, {
|
||||
title: _t("Select program"),
|
||||
list: linkedPrograms.map((program) => ({
|
||||
id: program.id,
|
||||
item: program,
|
||||
label: program.name,
|
||||
})),
|
||||
});
|
||||
if (confirmed) {
|
||||
selectedProgram = program;
|
||||
} else {
|
||||
// Do nothing here if the selection is cancelled.
|
||||
return;
|
||||
}
|
||||
} else if (linkedPrograms.length === 1) {
|
||||
selectedProgram = linkedPrograms[0];
|
||||
}
|
||||
if (selectedProgram && selectedProgram.program_type == "gift_card") {
|
||||
const shouldProceed = await this._setupGiftCardOptions(selectedProgram, options);
|
||||
if (!shouldProceed) {
|
||||
return;
|
||||
}
|
||||
} else if (selectedProgram && selectedProgram.program_type == "ewallet") {
|
||||
const shouldProceed = await this.setupEWalletOptions(selectedProgram, options);
|
||||
if (!shouldProceed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const potentialRewards = this.getPotentialFreeProductRewards();
|
||||
const rewardsToApply = [];
|
||||
for (const reward of potentialRewards) {
|
||||
for (const reward_product_id of reward.reward.reward_product_ids) {
|
||||
if (reward_product_id == product.id) {
|
||||
rewardsToApply.push(reward);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _super(product, options);
|
||||
await order._updatePrograms();
|
||||
if (rewardsToApply.length == 1) {
|
||||
const reward = rewardsToApply[0];
|
||||
order._applyReward(reward.reward, reward.coupon_id, { product: product.id });
|
||||
}
|
||||
|
||||
order._updateRewards();
|
||||
return options;
|
||||
},
|
||||
/**
|
||||
* Sets up the options for the gift card product.
|
||||
* @param {object} program
|
||||
* @param {object} options
|
||||
* @returns {Promise<boolean>} whether to proceed with adding the product or not
|
||||
*/
|
||||
async _setupGiftCardOptions(program, options) {
|
||||
options.quantity = 1;
|
||||
options.merge = false;
|
||||
options.eWalletGiftCardProgram = program;
|
||||
|
||||
// If gift card program setting is 'scan_use', ask for the code.
|
||||
if (this.globalState.config.gift_card_settings == "scan_use") {
|
||||
const { confirmed, payload: code } = await this.globalState.env.services.popup.add(
|
||||
TextInputPopup,
|
||||
{
|
||||
title: _t("Generate a Gift Card"),
|
||||
startingValue: "",
|
||||
placeholder: _t("Enter the gift card code"),
|
||||
}
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
const trimmedCode = code.trim();
|
||||
if (trimmedCode && trimmedCode.startsWith("044")) {
|
||||
// check if the code exist in the database
|
||||
// if so, use its balance, otherwise, use the unit price of the gift card product
|
||||
const fetchedGiftCard = await this.globalState.env.services.rpc({
|
||||
model: "loyalty.card",
|
||||
method: "search_read",
|
||||
args: [
|
||||
[
|
||||
["code", "=", trimmedCode],
|
||||
["program_id", "=", program.id],
|
||||
],
|
||||
["points", "source_pos_order_id"],
|
||||
],
|
||||
});
|
||||
// There should be maximum one gift card for a given code.
|
||||
const giftCard = fetchedGiftCard[0];
|
||||
if (giftCard && giftCard.source_pos_order_id) {
|
||||
this.popup.add(ErrorPopup, {
|
||||
title: _t("This gift card has already been sold"),
|
||||
body: _t("You cannot sell a gift card that has already been sold."),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
options.giftBarcode = trimmedCode;
|
||||
if (giftCard) {
|
||||
// Use the balance of the gift card as the price of the orderline.
|
||||
// NOTE: No need to convert the points to price because when opening a session,
|
||||
// the gift card programs are made sure to have 1 point = 1 currency unit.
|
||||
options.price = giftCard.points;
|
||||
options.giftCardId = giftCard.id;
|
||||
}
|
||||
} else {
|
||||
this.globalState.env.services.pos_notification.add(
|
||||
"Please enter a valid gift card code."
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
async setupEWalletOptions(program, options) {
|
||||
options.quantity = 1;
|
||||
options.merge = false;
|
||||
options.eWalletGiftCardProgram = program;
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
* Returns the reward such that when its reward product is added
|
||||
* in the order, it will be added as free. That is, when added,
|
||||
* it comes with the corresponding reward product line.
|
||||
*/
|
||||
getPotentialFreeProductRewards() {
|
||||
const order = this.globalState.get_order();
|
||||
const allCouponPrograms = Object.values(order.couponPointChanges)
|
||||
.map((pe) => {
|
||||
return {
|
||||
program_id: pe.program_id,
|
||||
coupon_id: pe.coupon_id,
|
||||
};
|
||||
})
|
||||
.concat(
|
||||
order.codeActivatedCoupons.map((coupon) => {
|
||||
return {
|
||||
program_id: coupon.program_id,
|
||||
coupon_id: coupon.id,
|
||||
};
|
||||
})
|
||||
);
|
||||
const result = [];
|
||||
for (const couponProgram of allCouponPrograms) {
|
||||
const program = this.globalState.program_by_id[couponProgram.program_id];
|
||||
const points = order._getRealCouponPoints(couponProgram.coupon_id);
|
||||
const hasLine = order.orderlines.filter((line) => !line.is_reward_line).length > 0;
|
||||
for (const reward of program.rewards.filter(
|
||||
(reward) => reward.reward_type == "product"
|
||||
)) {
|
||||
if (points < reward.required_points) {
|
||||
continue;
|
||||
}
|
||||
// Loyalty program (applies_on == 'both') should needs an orderline before it can apply a reward.
|
||||
const considerTheReward =
|
||||
program.applies_on !== "both" || (program.applies_on == "both" && hasLine);
|
||||
if (reward.reward_type === "product" && considerTheReward) {
|
||||
const product = this.globalState.db.get_product_by_id(
|
||||
reward.reward_product_ids[0]
|
||||
);
|
||||
const potentialQty = order._computePotentialFreeProductQty(
|
||||
reward,
|
||||
product,
|
||||
points
|
||||
);
|
||||
if (potentialQty <= 0) {
|
||||
continue;
|
||||
}
|
||||
result.push({
|
||||
coupon_id: couponProgram.coupon_id,
|
||||
reward: reward,
|
||||
potentialQty,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
});
|
||||
@@ -1,20 +1,19 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
|
||||
import { useListener, useService } from "@web/core/utils/hooks";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { TextInputPopup } from "@point_of_sale/js/Popups/TextInputPopup";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class PromoCodeButton extends LegacyComponent {
|
||||
export class PromoCodeButton extends Component {
|
||||
static template = "PromoCodeButton";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.popup = useService("popup");
|
||||
useListener("click", this.onClick);
|
||||
}
|
||||
|
||||
async onClick() {
|
||||
async click() {
|
||||
let { confirmed, payload: code } = await this.popup.add(TextInputPopup, {
|
||||
title: this.env._t("Enter Code"),
|
||||
startingValue: "",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user