[REF] point_of_sale, pos*: convert ProxyDevice to ES6 class and service

*: pos_epson_printer, pos_restaurant, pos_six

This commit continues the work of reducing the reliance of the pos
modules on legacy features from the JS framework, such as core.Class and
mixins, by converting the ProxyDevice class to ES6 (and renaming it
HardwareProxy, to avoid confusion with DeviceProxy from iot). It also
makes it available as a service in the new environment so that we can
hopefully get rid of the legacy environment in the near future.

closes odoo/odoo#114821

Related: odoo/enterprise#37991
Signed-off-by: Joseph Caburnay (jcb) <jcb@odoo.com>
This commit is contained in:
Samuel Degueldre
2023-03-30 15:09:14 +02:00
parent 87941198a5
commit 25cbd9c4e7
29 changed files with 456 additions and 772 deletions
@@ -8,9 +8,11 @@ import { ErrorBarcodePopup } from "@point_of_sale/js/Popups/ErrorBarcodePopup";
import BarcodeParser from "barcodes.BarcodeParser";
export class BarcodeReader {
constructor({ parser, popup }) {
static serviceDependencies = ["popup", "hardware_proxy"];
constructor(parser, { popup, hardware_proxy }) {
this.parser = parser;
this.popup = popup;
this.hardwareProxy = hardware_proxy;
this.setup();
}
@@ -75,33 +77,22 @@ export class BarcodeReader {
// the barcode scanner will listen on the hw_proxy/scanner interface for
// scan events until disconnectFromProxy is called
connectToProxy(hwProxy) {
connectToProxy() {
this.remoteScanning = true;
if (this.remoteActive >= 1) {
return;
}
this.remoteActive = 1;
this.waitForBarcode(hwProxy);
this.waitForBarcode();
}
async waitForBarcode(hwProxy) {
try {
const barcode = await hwProxy.connection.rpc(
"/hw_proxy/scanner",
{},
{ shadow: true, timeout: 7500 }
);
if (!this.remoteScanning) {
this.remoteActive = 0;
return;
}
this.scan(barcode);
} catch {
if (!this.remoteScanning) {
this.remoteActive = 0;
return;
}
async waitForBarcode() {
const barcode = await this.hardwareProxy.message("scanner").catch(() => {});
if (!this.remoteScanning) {
this.remoteActive = 0;
return;
}
this.scan(barcode);
this.waitForBarcode();
}
@@ -112,14 +103,15 @@ export class BarcodeReader {
}
export const barcodeReader = {
dependencies: ["barcode", "popup"],
async start(env, { barcode, popup }) {
dependencies: [...BarcodeReader.serviceDependencies, "popup", "barcode"],
async start(env, deps) {
const { popup, barcode } = deps;
let barcodeReader = null;
if (session.nomenclature_id) {
const parser = new BarcodeParser({ nomenclature_id: [session.nomenclature_id] });
await parser.is_loaded();
barcodeReader = new BarcodeReader({ parser, popup });
barcodeReader = new BarcodeReader(parser, deps);
}
barcode.bus.addEventListener("barcode_scanned", (ev) => {
@@ -18,6 +18,7 @@ export class DebugWidget extends Component {
this.debug = useService("debug");
this.popup = useService("popup");
this.barcodeReader = useService("barcode_reader");
this.hardwareProxy = useService("hardware_proxy");
const numberBuffer = useService("number_buffer");
useBus(numberBuffer, "buffer-update", this._onBufferUpdate);
this.state = useState({
@@ -49,29 +50,15 @@ export class DebugWidget extends Component {
},
});
// NOTE: Perhaps this can still be improved.
// What we do here is loop thru the `event` elements
// then we assign animation that happens when the event is triggered
// in the proxy. E.g. if open_cashbox is sent, the open_cashbox element
// changes color from '#6CD11D' to '#1E1E1E' for a duration of 2sec.
this.eventElementsRef = {};
this.animations = {};
// Make the background of the "hardware events" section flash when a corresponding message
// is sent to the proxy.
for (const eventName of ["open_cashbox", "print_receipt", "scale_read"]) {
this.eventElementsRef[eventName] = useRef(eventName);
this.env.proxy.add_notification(
eventName,
(() => {
if (this.animations[eventName]) {
this.animations[eventName].cancel();
}
const eventElement = this.eventElementsRef[eventName].el;
eventElement.style.backgroundColor = "#6CD11D";
this.animations[eventName] = eventElement.animate(
{ backgroundColor: ["#6CD11D", "#1E1E1E"] },
2000
);
}).bind(this)
);
const ref = useRef(eventName);
let animation;
useBus(this.hardwareProxy, `send_message:${eventName}`, () => {
animation?.cancel();
animation = ref.el?.animate({ backgroundColor: ["#6CD11D", "#1E1E1E"] }, 2000);
});
}
}
toggleWidget() {
@@ -80,12 +67,12 @@ export class DebugWidget extends Component {
setWeight() {
var weightInKg = parse.float(this.state.weightInput);
if (!isNaN(weightInKg)) {
this.env.proxy.debug_set_weight(weightInKg);
this.hardwareProxy.setDebugWeight(weightInKg);
}
}
resetWeight() {
this.state.weightInput = "";
this.env.proxy.debug_reset_weight();
this.hardwareProxy.resetDebugWeight();
}
async barcodeScan() {
if (!this.barcodeReader) {
@@ -172,7 +159,7 @@ export class DebugWidget extends Component {
}
}
refreshDisplay() {
this.env.proxy.message("display_refresh", {});
this.hardwareProxy.message("display_refresh", {});
}
_onBufferUpdate({ detail: value }) {
this.state.buffer = value;
@@ -0,0 +1,228 @@
/** @odoo-module */
import { Printer } from "@point_of_sale/js/printers";
import { EventBus, reactive } from "@odoo/owl";
import { browser } from "@web/core/browser/browser";
import { registry } from "@web/core/registry";
import { effect } from "@point_of_sale/utils";
/**
* This object interfaces with the local proxy to communicate to the various hardware devices
* connected to the Point of Sale. As the communication only goes from the POS to the proxy,
* methods are used both to signal an event, and to fetch information. Maybe could be improved
* by using the bus for two-way communication?
*/
export class HardwareProxy extends EventBus {
static serviceDependencies = ["rpc"];
constructor() {
super();
this.setup(...arguments);
}
setup({ rpc }) {
this.rpc = rpc;
this.debugWeight = 0;
this.useDebugWeight = false;
this.host = "";
this.keptalive = false;
this.customerDisplayAvailable = true;
this.connectionInfo = reactive({ status: "disconnected", drivers: {} });
effect(
(info) => {
if (info.status === "connected" && this.printer) {
this.printer.print_receipt();
}
},
[this.connectionInfo]
);
}
setConnectionInfo(info) {
Object.assign(this.connectionInfo, info);
if (!info.drivers && this.connectionInfo.status === "disconnected") {
this.connectionInfo.drivers = {};
}
}
disconnect() {
if (this.connectionInfo.status !== "disconnected") {
this.host = null;
this.setConnectionInfo({ status: "disconnected" });
}
}
async connect() {
if (this.pos.config.iface_print_via_proxy) {
this.connectToPrinter();
}
try {
if (await this.message("handshake")) {
this.setConnectionInfo({ status: "connected" });
localStorage.hw_proxy_url = this.host;
this.keepalive();
} else {
this.setConnectionInfo({ status: "disconnected" });
console.error("Connection refused by the Proxy");
}
} catch {
this.setConnectionInfo({ status: "disconnected" });
console.error("Could not connect to the Proxy");
}
}
connectToPrinter() {
this.printer = new Printer(this.host, this.pos);
}
/**
* Find a proxy and connects to it.
*
* @param {Object} [options]
* @param {string} [options.force_ip] only try to connect to the specified ip.
* @param {string} [options.port]
* @returns {Promise}
*/
async autoconnect(options) {
this.setConnectionInfo({ status: "connecting", drivers: {} });
let url = options.force_ip || localStorage.hw_proxy_url;
// Return a pending promise if there is no url to connect to
// FIXME POSREF do something useful instead if this condition can happen, remove if not
if (!url) {
return new Promise(() => {});
}
const { protocol } = window.location;
if (!url.includes("//")) {
url = `${protocol}//${url}`;
}
if (url.indexOf(":", 5) < 0) {
url += ":" + (options.port || (protocol === "https:" ? 443 : 8069));
}
if (await this.checkProxyAvailability(url)) {
this.host = url;
return this.connect(url);
}
}
// starts a loop that updates the connection status
keepalive() {
const status = () => {
const always = () => setTimeout(status, 5000);
const xhr = new browser.XMLHttpRequest();
xhr.timeout = 2500;
this.rpc(`${this.host}/hw_proxy/status_json`, {}, { silent: true, xhr })
.then(
(drivers) => this.setConnectionInfo({ status: "connected", drivers }),
() => {
if (this.connectionInfo.status !== "connecting") {
this.setConnectionInfo({ status: "disconnected" });
}
}
)
.then(always, always);
};
if (!this.keptalive) {
this.keptalive = true;
status();
}
}
/**
* @param {string} name
* @param {Object} [params]
* @returns {Promise}
*/
message(name, params) {
this.dispatchEvent(new CustomEvent(`send_message:${name}`));
if (this.connectionInfo.status === "disconnected") {
return Promise.reject();
}
return this.rpc(`${this.host}/hw_proxy/${name}`, params, { silent: true });
}
/**
* Makes sure that the proxy is available by attempting to call the hello
* route on the proxy.
*
* @param {string} url
* @returns {Promise<void>}
*/
async checkProxyAvailability(url) {
this.setConnectionInfo({ status: "connecting" });
const maxRetries = 3;
for (let i = 0; i <= maxRetries; i++) {
const timeoutController = new AbortController();
setTimeout(() => timeoutController.abort(), 1000);
const response = await browser
.fetch(`${url}/hw_proxy/hello`, {
signal: timeoutController.signal,
})
.catch(() => ({}));
if (response.ok) {
return true;
}
}
this.setConnectionInfo({ status: "disconnected" });
return false;
}
/**
* Returns the weight on the scale.
*
* @returns {Promise<number>}
*/
readScale() {
if (this.useDebugWeight) {
return this.debugWeight;
}
return this.message("scale_read")
.then(({ weight }) => weight)
.catch(() => 0);
}
// sets a custom weight, ignoring the proxy returned value.
setDebugWeight(weight) {
this.useDebugWeight = true;
this.debugWeight = weight;
}
// resets the custom weight and re-enable listening to the proxy for weight values
resetDebugWeight() {
this.useDebugWeight = false;
this.debugWeight = 0;
}
updateCustomerDisplay(html) {
if (this.customerDisplayAvailable && this.connectionInfo.status == "connected") {
return this.message("customer_facing_display", { html }, { timeout: 5000 });
}
}
/**
* @param {string} html
* @returns {Promise}
*/
takeControlOfCustomerDisplay(html) {
return this.message("take_control", { html });
}
/**
* @returns {Promise}
*/
testOwnershipOfCustomerDisplay() {
return this.host ? this.message("test_ownership") : Promise.reject({ abort: true });
}
// asks the proxy to log some information, as with the debug.log you can provide several arguments.
log() {
return this.message("log", { arguments: [...arguments] });
}
}
export const hardwareProxyService = {
dependencies: HardwareProxy.serviceDependencies,
start(env, deps) {
return new HardwareProxy(deps);
},
};
registry.category("services").add("hardware_proxy", hardwareProxyService);
@@ -18,6 +18,7 @@ export class CashMovePopup extends AbstractAwaitablePopup {
this.notification = useService("pos_notification");
this.popup = useService("popup");
this.orm = useService("orm");
this.hardwareProxy = useService("hardware_proxy");
this.state = useState({
/** @type {'in'|'out'} */
type: "out",
@@ -62,7 +63,7 @@ export class CashMovePopup extends AbstractAwaitablePopup {
reason,
extras,
]);
if (this.env.proxy.printer) {
if (this.hardwareProxy.printer) {
const renderedReceipt = renderToString("point_of_sale.CashMoveReceipt", {
_receipt: {
type,
@@ -74,7 +75,7 @@ export class CashMovePopup extends AbstractAwaitablePopup {
company: this.env.pos.company,
},
});
const printResult = await this.env.proxy.printer.print_receipt(renderedReceipt);
const printResult = await this.hardwareProxy.printer.print_receipt(renderedReceipt);
if (!printResult.successful) {
this.popup.add(ErrorPopup, {
title: printResult.message.title,
@@ -32,6 +32,7 @@ export class Navbar extends Component {
this.debug = useService("debug");
this.popup = useService("popup");
this.notification = useService("pos_notification");
this.hardwareProxy = useService("hardware_proxy");
}
get customerFacingDisplayButtonIsShown() {
return this.env.pos.config.iface_customer_facing_display;
@@ -20,7 +20,7 @@
<div class="pos-rightheader">
<div class="status-buttons">
<CashierName />
<SaleDetailsButton t-if="env.proxy.printer" isHeaderButton="true"/>
<SaleDetailsButton t-if="hardwareProxy.printer" isHeaderButton="true"/>
<ProxyStatus t-if="env.pos.config.use_proxy" />
<CustomerFacingDisplayButton t-if="customerFacingDisplayButtonIsShown" />
<SyncNotification />
@@ -1,7 +1,7 @@
/** @odoo-module */
import { PosGlobalState } from "@point_of_sale/js/models";
import { pos_env as legacyEnv } from "@point_of_sale/js/pos_env";
import legacyEnv from "web.env";
import { registry } from "@web/core/registry";
import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
@@ -19,19 +19,30 @@ export class PosStore extends Reactive {
mainScreen = { name: null, component: null };
tempScreen = null;
legacyEnv = legacyEnv;
globalState = new PosGlobalState({ env: markRaw(legacyEnv) });
static serviceDependencies = ["popup", "orm", "number_buffer", "barcode_reader"];
constructor({ popup, orm, number_buffer, barcode_reader }) {
static serviceDependencies = [
"popup",
"orm",
"number_buffer",
"barcode_reader",
"hardware_proxy",
];
constructor() {
super();
this.setup(...arguments);
}
// use setup instead of constructor because setup can be patched.
setup({ popup, orm, number_buffer, hardware_proxy, barcode_reader }) {
this.orm = orm;
this.popup = popup;
this.numberBuffer = number_buffer;
this.barcodeReader = barcode_reader;
this.setup();
this.hardwareProxy = hardware_proxy;
this.globalState = new PosGlobalState({
env: markRaw(legacyEnv),
hardwareProxy: hardware_proxy,
});
}
// use setup instead of constructor because setup can be patched.
setup() {}
showScreen(name, props) {
const component = registry.category("pos_screens").get(name);
@@ -51,38 +62,34 @@ export class PosStore extends Reactive {
return new Promise((resolve, reject) => {
this.barcodeReader?.disconnectFromProxy();
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.barcodeReader?.connectToProxy(this.globalState.env.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();
}
this.hardwareProxy.autoconnect({ force_ip: this.globalState.config.proxy_ip }).then(
() => {
if (this.globalState.config.iface_scan_via_proxy) {
this.barcodeReader?.connectToProxy();
}
);
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:") {
// FIXME POSREF this looks like it's dead code.
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();
}
}
);
});
}
+3 -2
View File
@@ -12,7 +12,7 @@ import { Navbar } from "@point_of_sale/app/navbar/navbar";
// ChromeAdapter imports
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { registry } from "@web/core/registry";
import { pos_env as env } from "@point_of_sale/js/pos_env";
import env from "web.env";
import { ErrorTracebackPopup } from "./Popups/ErrorTracebackPopup";
@@ -48,7 +48,8 @@ export class Chrome extends Component {
env.legacyActionManager = legacyActionManager;
// The proxy requires the instance of PosGlobalState to function properly.
env.proxy.set_pos(reactivePos);
this.hardwareProxy = useService("hardware_proxy");
this.hardwareProxy.pos = reactivePos;
// TODO: Should we continue on exposing posmodel as global variable?
// Expose only the reactive version of `pos` when in debug mode.
@@ -1,12 +1,14 @@
/** @odoo-module */
import { Component, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
export class CustomerFacingDisplayButton extends Component {
static template = "CustomerFacingDisplayButton";
setup() {
super.setup();
this.hardwareProxy = useService("hardware_proxy");
this.local =
this.env.pos.config.iface_customer_facing_display_local &&
!this.env.pos.config.iface_customer_facing_display_via_proxy;
@@ -42,7 +44,7 @@ export class CustomerFacingDisplayButton extends Component {
async onClickProxy() {
try {
const renderedHtml = await this.env.pos.render_html_for_customer_facing_display();
let ownership = await this.env.proxy.take_ownership_over_customer_screen(renderedHtml);
let ownership = await this.hardwareProxy.takeControlOfCustomerDisplay(renderedHtml);
if (typeof ownership === "string") {
ownership = JSON.parse(ownership);
}
@@ -51,8 +53,8 @@ export class CustomerFacingDisplayButton extends Component {
} else {
this.state.status = "warning";
}
if (!this.env.proxy.posbox_supports_display) {
this.env.proxy.posbox_supports_display = true;
if (!this.hardwareProxy.customerDisplayAvailable) {
this.hardwareProxy.customerDisplayAvailable = true;
this._start();
}
} catch (error) {
@@ -68,35 +70,33 @@ export class CustomerFacingDisplayButton extends Component {
return;
}
const self = this;
async function loop() {
if (self.env.proxy.posbox_supports_display) {
const loop = async () => {
if (this.hardwareProxy.customerDisplayAvailable) {
try {
let ownership = await self.env.proxy.test_ownership_of_customer_screen();
let ownership = await this.hardwareProxy.testOwnershipOfCustomerDisplay();
if (typeof ownership === "string") {
ownership = JSON.parse(ownership);
}
if (ownership.status === "OWNER") {
self.state.status = "success";
this.state.status = "success";
} else {
self.state.status = "warning";
this.state.status = "warning";
}
setTimeout(loop, 3000);
} catch (error) {
if (error.abort) {
// Stop the loop
return;
}
if (typeof error == "undefined") {
self.state.status = "failure";
this.state.status = "failure";
} else {
self.state.status = "not_found";
self.env.proxy.posbox_supports_display = false;
this.state.status = "not_found";
this.hardwareProxy.customerDisplayAvailable = false;
}
setTimeout(loop, 3000);
}
setTimeout(loop, 3000);
}
}
};
loop();
}
}
@@ -1,6 +1,9 @@
/** @odoo-module */
import { Component, onMounted, onWillUnmount, useState } from "@odoo/owl";
import { Component, useState } from "@odoo/owl";
import { usePos } from "@point_of_sale/app/pos_hook";
import { useService } from "@web/core/utils/hooks";
import { _t } from "@web/core/l10n/translation";
// Previously ProxyStatusWidget
export class ProxyStatus extends Component {
@@ -8,63 +11,45 @@ export class ProxyStatus extends Component {
setup() {
super.setup();
const initialProxyStatus = this.env.proxy.get("status");
this.state = useState({
status: initialProxyStatus.status,
msg: initialProxyStatus.msg,
});
this.statuses = ["connected", "connecting", "disconnected", "warning"];
this.index = 0;
onMounted(() => {
this.env.proxy.on("change:status", this, this._onChangeStatus);
});
onWillUnmount(() => {
this.env.proxy.off("change:status", this, this._onChangeStatus);
});
this.pos = usePos();
const hardwareProxy = useService("hardware_proxy");
this.connectionInfo = useState(hardwareProxy.connectionInfo);
}
_onChangeStatus(posProxy, statusChange) {
this._setStatus(statusChange.newValue);
}
_setStatus(newStatus) {
if (newStatus.status === "connected") {
var warning = false;
var msg = "";
if (this.env.pos.config.iface_scan_via_proxy) {
var scannerStatus = newStatus.drivers.scanner
? newStatus.drivers.scanner.status
: false;
if (scannerStatus != "connected" && scannerStatus != "connecting") {
warning = true;
msg += this.env._t("Scanner");
}
}
if (this.env.pos.config.iface_print_via_proxy || this.env.pos.config.iface_cashdrawer) {
var printerStatus = newStatus.drivers.printer
? newStatus.drivers.printer.status
: false;
if (printerStatus != "connected" && printerStatus != "connecting") {
warning = true;
msg = msg ? msg + " & " : msg;
msg += this.env._t("Printer");
}
}
if (this.env.pos.config.iface_electronic_scale) {
var scaleStatus = newStatus.drivers.scale ? newStatus.drivers.scale.status : false;
if (scaleStatus != "connected" && scaleStatus != "connecting") {
warning = true;
msg = msg ? msg + " & " : msg;
msg += this.env._t("Scale");
}
}
msg = msg ? msg + " " + this.env._t("Offline") : msg;
this.state.status = warning ? "warning" : "connected";
this.state.msg = msg;
} else {
this.state.status = newStatus.status;
this.state.msg = newStatus.msg || "";
get message() {
if (this.connectionInfo.status === "connected") {
const { drivers } = this.connectionInfo;
const {
iface_scan_via_proxy,
iface_print_via_proxy,
iface_cashdrawer,
iface_electronic_scale,
} = this.env.pos.config;
const devices = [
{
name: _t("Scanner"),
driver: drivers.scanner,
enabled: iface_scan_via_proxy,
},
{
name: _t("Printer"),
driver: drivers.printer,
enabled: iface_print_via_proxy || iface_cashdrawer,
},
{
name: _t("Scale"),
driver: drivers.scale,
enabled: iface_electronic_scale,
},
];
const disconnectedDevices = devices.filter(({ enabled, driver }) => {
return enabled && !["connected", "connecting"].includes(driver?.status);
});
if (disconnectedDevices.length) {
return `${disconnectedDevices.map((d) => d.name).join(" & ")} ${_t("Offline")}`;
}
return "";
}
return this.connectionInfo.message || "";
}
}
@@ -12,6 +12,7 @@ export class SaleDetailsButton extends Component {
super.setup(...arguments);
this.popup = useService("popup");
this.orm = useService("orm");
this.hardwareProxy = useService("hardware_proxy");
}
async onClick() {
@@ -30,11 +31,11 @@ export class SaleDetailsButton extends Component {
pos: this.env.pos,
})
);
const printResult = await this.env.proxy.printer.print_receipt(report);
if (!printResult.successful) {
const { successful, message } = await this.hardwareProxy.printer.print_receipt(report);
if (!successful) {
await this.popup.add(ErrorPopup, {
title: printResult.message.title,
body: printResult.message.body,
title: message.title,
body: message.body,
});
}
}
@@ -6,6 +6,6 @@ export class SyncNotification extends Component {
static template = "SyncNotification";
onClick() {
this.env.pos.push_orders(null, { show_error: true });
this.env.pos.push_orders({ show_error: true });
}
}
@@ -16,10 +16,11 @@ export class AbstractReceiptScreen extends Component {
super.setup();
this.orderReceipt = useRef("order-receipt");
this.popup = useService("popup");
this.hardwareProxy = useService("hardware_proxy");
}
async _printReceipt() {
if (this.env.proxy.printer) {
const printResult = await this.env.proxy.printer.print_receipt(
if (this.hardwareProxy.printer) {
const printResult = await this.hardwareProxy.printer.print_receipt(
this.orderReceipt.el.innerHTML
);
if (printResult.successful) {
@@ -21,6 +21,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup {
this.popup = useService("popup");
this.pos = useService("pos");
this.orm = useService("orm");
this.hardwareProxy = useService("hardware_proxy");
this.manualInputCashCount = false;
this.cashControl = this.env.pos.config.cash_control;
this.closeSessionClicked = false;
@@ -31,6 +31,7 @@ export class PaymentScreen extends Component {
this.orm = useService("orm");
this.popup = useService("popup");
this.notification = useService("pos_notification");
this.hardwareProxy = useService("hardware_proxy");
this.payment_methods_from_config = this.env.pos.payment_methods.filter((method) =>
this.env.pos.config.payment_method_ids.includes(method.id)
);
@@ -132,7 +133,7 @@ export class PaymentScreen extends Component {
this.render(true);
}
openCashbox() {
this.env.proxy.printer.open_cashbox();
this.hardwareProxy.printer.open_cashbox();
}
async addTip() {
// click_tip
@@ -217,7 +218,7 @@ export class PaymentScreen extends Component {
(this.currentOrder.is_paid_with_cash() || this.currentOrder.get_change()) &&
this.env.pos.config.iface_cashdrawer
) {
this.env.proxy.printer.open_cashbox();
this.hardwareProxy.printer.open_cashbox();
}
this.currentOrder.initialize_validation_date();
@@ -14,6 +14,7 @@ import { ConnectionLostError } from "@web/core/network/rpc_service";
import { usePos } from "@point_of_sale/app/pos_hook";
import { Component, onMounted, useState } from "@odoo/owl";
import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
import { ErrorBarcodePopup } from "@point_of_sale/js/Popups/ErrorBarcodePopup";
import { MobileOrderWidget } from "../../Misc/MobileOrderWidget";
import { NumpadWidget } from "./NumpadWidget";
@@ -142,7 +143,7 @@ export class ProductScreen extends ControlButtonsMixin(Component) {
// assume that the result is unique.
product = this.env.pos.db.get_product_by_id(foundProductIds[0]);
} else {
return this._barcodeErrorAction(code);
return this.popup.add(ErrorBarcodePopup, { code: code.base_code });
}
}
const options = await product.getAddProductOptions(code);
@@ -181,10 +182,9 @@ export class ProductScreen extends ControlButtonsMixin(Component) {
this.currentOrder.set_partner(partner);
this.currentOrder.updatePricelist(partner);
}
return true;
return;
}
this._barcodeErrorAction(code);
return false;
return this.popup.add(ErrorBarcodePopup, { code: code.base_code });
}
_barcodeDiscountAction(code) {
var last_orderline = this.currentOrder.get_last_orderline();
@@ -162,7 +162,7 @@ export class ReceiptScreen extends AbstractReceiptScreen {
? this.currentOrder.finalized
: true;
return (
this.env.proxy.printer &&
this.hardwareProxy.printer &&
this.env.pos.config.iface_print_skip_screen &&
invoiced_finalized
);
@@ -4,6 +4,7 @@ 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";
import { useService } from "@web/core/utils/hooks";
export class ScaleScreen extends Component {
static template = "ScaleScreen";
@@ -14,6 +15,7 @@ export class ScaleScreen extends Component {
*/
setup() {
super.setup();
this.hardwareProxy = useService("hardware_proxy");
useExternalListener(document, "keyup", this._onHotkeys);
this.state = useState({ weight: 0 });
onMounted(this.onMounted);
@@ -26,7 +28,7 @@ export class ScaleScreen extends Component {
}
onWillUnmount() {
// stop the scale reading
this.env.proxy_queue.clear();
this.shouldRead = false;
}
back() {
this.props.resolve({ confirmed: false, payload: null });
@@ -47,14 +49,15 @@ export class ScaleScreen extends Component {
}
}
_readScale() {
this.env.proxy_queue.schedule(this._setWeight.bind(this), {
duration: 500,
repeat: true,
});
this.shouldRead = true;
this._setWeight();
}
async _setWeight() {
const reading = await this.env.proxy.scale_read();
this.state.weight = reading.weight;
if (!this.shouldRead) {
return;
}
this.state.weight = await this.hardwareProxy.readScale();
setTimeout(() => this._setWeight(), 500);
}
get _activePricelist() {
const current_order = this.env.pos.get_order();
@@ -21,7 +21,7 @@ export class ReprintReceiptScreen extends AbstractReceiptScreen {
this.pos.showScreen("TicketScreen", { reuseSavedUIState: true });
}
async printReceipt() {
if (this.env.proxy.printer && this.env.pos.config.iface_print_skip_screen) {
if (this.hardwareProxy.printer && this.env.pos.config.iface_print_skip_screen) {
const result = await this._printReceipt();
if (result) {
this.pos.showScreen("TicketScreen", { reuseSavedUIState: true });
@@ -1,488 +0,0 @@
/** @odoo-module */
import core from "web.core";
import mixins from "web.mixins";
import Session from "web.Session";
import { Printer } from "@point_of_sale/js/printers";
// the JobQueue schedules a sequence of 'jobs'. each job is
// a function returning a promise. The queue waits for each job to finish
// before launching the next. Each job can also be scheduled with a delay.
// the is used to prevent parallel requests to the proxy.
export const JobQueue = function () {
var queue = [];
var running = false;
var scheduled_end_time = 0;
var end_of_queue = Promise.resolve();
var stoprepeat = false;
var run = function () {
var runNextJob = function () {
if (queue.length === 0) {
running = false;
scheduled_end_time = 0;
return Promise.resolve();
}
running = true;
var job = queue[0];
if (!job.opts.repeat || stoprepeat) {
queue.shift();
stoprepeat = false;
}
// the time scheduled for this job
scheduled_end_time = new Date().getTime() + (job.opts.duration || 0);
// we run the job and put in prom when it finishes
var prom = job.fun() || Promise.resolve();
var always = function () {
// we run the next job after the scheduled_end_time, even if it finishes before
return new Promise(function (resolve, reject) {
setTimeout(resolve, Math.max(0, scheduled_end_time - new Date().getTime()));
});
};
// we don't care if a job fails ...
return prom.then(always, always).then(runNextJob);
};
if (!running) {
end_of_queue = runNextJob();
}
};
/**
* Adds a job to the schedule.
*
* @param {function} fun must return a promise
* @param {object} [opts]
* @param {number} [opts.duration] the job is guaranteed to finish no quicker than this (milisec)
* @param {boolean} [opts.repeat] if true, the job will be endlessly repeated
* @param {boolean} [opts.important] if true, the scheduled job cannot be canceled by a queue.clear()
*/
this.schedule = function (fun, opts) {
queue.push({ fun: fun, opts: opts || {} });
if (!running) {
run();
}
};
// remove all jobs from the schedule (except the ones marked as important)
this.clear = function () {
queue = _.filter(queue, function (job) {
return job.opts.important === true;
});
};
// end the repetition of the current job
this.stoprepeat = function () {
stoprepeat = true;
};
/**
* Returns a promise that resolves when all scheduled jobs have been run.
* (jobs added after the call to this method are considered as well)
*
* @returns {Promise}
*/
this.finished = function () {
return end_of_queue;
};
};
// this object interfaces with the local proxy to communicate to the various hardware devices
// connected to the Point of Sale. As the communication only goes from the POS to the proxy,
// methods are used both to signal an event, and to fetch information.
export const ProxyDevice = core.Class.extend(mixins.PropertiesMixin, {
init: function (options) {
mixins.PropertiesMixin.init.call(this);
var self = this;
options = options || {};
this.env = options.env;
this.weighing = false;
this.debug_weight = 0;
this.use_debug_weight = false;
this.paying = false;
this.notifications = {};
this.bypass_proxy = false;
this.connection = null;
this.host = "";
this.keptalive = false;
this.set("status", {});
this.set_connection_status("disconnected");
this.on("change:status", this, function (eh, status) {
status = status.newValue;
if (status.status === "connected" && self.printer) {
self.printer.print_receipt();
}
});
this.posbox_supports_display = true;
window.hw_proxy = this;
},
set_pos: function (pos) {
this.setParent(pos);
this.pos = pos;
},
set_connection_status: function (status, drivers, msg = "") {
var oldstatus = this.get("status");
var newstatus = {};
newstatus.status = status;
newstatus.drivers = status === "disconnected" ? {} : oldstatus.drivers;
newstatus.drivers = drivers ? drivers : newstatus.drivers;
newstatus.msg = msg;
this.set("status", newstatus);
},
disconnect: function () {
if (this.get("status").status !== "disconnected") {
this.connection.destroy();
this.set_connection_status("disconnected");
}
},
/**
* Connects to the specified url.
*
* @param {string} url
* @returns {Promise}
*/
connect: function (url) {
var self = this;
this.connection = new Session(undefined, url, { use_cors: true });
this.host = url;
if (this.pos.config.iface_print_via_proxy) {
this.connect_to_printer();
}
this.set_connection_status("connecting", {});
return this.message("handshake").then(
function (response) {
if (response) {
self.set_connection_status("connected");
localStorage.hw_proxy_url = url;
self.keepalive();
} else {
self.set_connection_status("disconnected");
console.error("Connection refused by the Proxy");
}
},
function () {
self.set_connection_status("disconnected");
console.error("Could not connect to the Proxy");
}
);
},
connect_to_printer: function () {
this.printer = new Printer(this.host, this.pos);
},
/**
* Find a proxy and connects to it.
*
* @param {Object} [options]
* @param {string} [options.force_ip] only try to connect to the specified ip.
* @param {string} [options.port] @see find_proxy
* @param {function} [options.progress] @see find_proxy
* @returns {Promise}
*/
autoconnect: function (options) {
var self = this;
this.set_connection_status("connecting", {});
if (this.pos.config.iface_print_via_proxy) {
this.connect_to_printer();
}
var found_url = new Promise(function () {});
if (options.force_ip) {
// if the ip is forced by server config, bailout on fail
found_url = this.try_hard_to_connect(options.force_ip, options);
} else if (localStorage.hw_proxy_url) {
// try harder when we remember a good proxy url
found_url = this.try_hard_to_connect(localStorage.hw_proxy_url, options).catch(
function () {
if (window.location.protocol != "https:") {
return self.find_proxy(options);
}
}
);
} else {
// just find something quick
if (window.location.protocol != "https:") {
found_url = this.find_proxy(options);
}
}
var successProm = found_url.then(function (url) {
return self.connect(url);
});
successProm.catch(function () {
self.set_connection_status("disconnected");
});
return successProm;
},
// starts a loop that updates the connection status
keepalive: function () {
var self = this;
function status() {
var always = function () {
setTimeout(status, 5000);
};
self.connection
.rpc("/hw_proxy/status_json", {}, { shadow: true, timeout: 2500 })
.then(
function (driver_status) {
self.set_connection_status("connected", driver_status);
},
function () {
if (self.get("status").status !== "connecting") {
self.set_connection_status("disconnected");
}
}
)
.then(always, always);
}
if (!this.keptalive) {
this.keptalive = true;
status();
}
},
/**
* @param {string} name
* @param {Object} [params]
* @returns {Promise}
*/
message: function (name, params) {
var callbacks = this.notifications[name] || [];
for (var i = 0; i < callbacks.length; i++) {
callbacks[i](params);
}
if (this.get("status").status !== "disconnected") {
return this.connection.rpc("/hw_proxy/" + name, params || {}, { shadow: true });
} else {
return Promise.reject();
}
},
/**
* Tries several time to connect to a known proxy url.
*
* @param {*} url
* @param {Object} [options]
* @param {string} [options.port=8069] what port to listen to
* @returns {Promise<string|Array>}
*/
try_hard_to_connect: function (url, options) {
options = options || {};
var protocol = window.location.protocol;
var port = !options.port && protocol == "https:" ? ":443" : ":" + (options.port || "8069");
this.set_connection_status("connecting");
if (url.indexOf("//") < 0) {
url = protocol + "//" + url;
}
if (url.indexOf(":", 5) < 0) {
url = url + port;
}
// try real hard to connect to url, with a 1sec timeout and up to 'retries' retries
function try_real_hard_to_connect(url, retries) {
return Promise.resolve(
$.ajax({
url: url + "/hw_proxy/hello",
method: "GET",
timeout: 1000,
}).then(
function () {
return Promise.resolve(url);
},
function (resp) {
if (retries > 0) {
return try_real_hard_to_connect(url, retries - 1);
} else {
return Promise.reject([resp.statusText, url]);
}
}
)
);
}
return try_real_hard_to_connect(url, 3);
},
/**
* Returns as a promise a valid host url that can be used as proxy.
*
* @param {Object} [options]
* @param {string} [options.port] what port to listen to (default 8069)
* @param {function} [options.progress] callback for search progress ( fac in [0,1] )
* @returns {Promise<string>} will be resolved with the proxy valid url
*/
find_proxy: function (options) {
options = options || {};
var self = this;
var port = ":" + (options.port || "8069");
var urls = [];
var found = false;
var parallel = 8;
var threads = [];
var progress = 0;
urls.push("http://localhost" + port);
for (var i = 0; i < 256; i++) {
urls.push("http://192.168.0." + i + port);
urls.push("http://192.168.1." + i + port);
urls.push("http://10.0.0." + i + port);
}
var prog_inc = 1 / urls.length;
function update_progress() {
progress = found ? 1 : progress + prog_inc;
if (options.progress) {
options.progress(progress);
}
}
function thread() {
var url = urls.shift();
if (!url || found || !self.searching_for_proxy) {
return Promise.resolve();
}
return Promise.resolve(
$.ajax({
url: url + "/hw_proxy/hello",
method: "GET",
timeout: 400,
}).then(
function () {
found = true;
update_progress();
return Promise.resolve(url);
},
function () {
update_progress();
return thread();
}
)
);
}
this.searching_for_proxy = true;
var len = Math.min(parallel, urls.length);
for (i = 0; i < len; i++) {
threads.push(thread());
}
return new Promise(function (resolve, reject) {
Promise.all(threads).then(function (results) {
var urls = [];
for (var i = 0; i < results.length; i++) {
if (results[i]) {
urls.push(results[i]);
}
}
resolve(urls[0]);
});
});
},
stop_searching: function () {
this.searching_for_proxy = false;
this.set_connection_status("disconnected");
},
// this allows the client to be notified when a proxy call is made. The notification
// callback will be executed with the same arguments as the proxy call
add_notification: function (name, callback) {
if (!this.notifications[name]) {
this.notifications[name] = [];
}
this.notifications[name].push(callback);
},
/**
* Returns the weight on the scale.
*
* @returns {Promise<Object>}
*/
scale_read: function () {
var self = this;
if (self.use_debug_weight) {
return Promise.resolve({ weight: this.debug_weight, unit: "Kg", info: "ok" });
}
return new Promise(function (resolve, reject) {
self.message("scale_read", {}).then(
function (weight) {
resolve(weight);
},
function () {
//failed to read weight
resolve({ weight: 0.0, unit: "Kg", info: "ok" });
}
);
});
},
// sets a custom weight, ignoring the proxy returned value.
debug_set_weight: function (kg) {
this.use_debug_weight = true;
this.debug_weight = kg;
},
// resets the custom weight and re-enable listening to the proxy for weight values
debug_reset_weight: function () {
this.use_debug_weight = false;
this.debug_weight = 0;
},
update_customer_facing_display: function (html) {
if (this.posbox_supports_display && this.get("status").status == "connected") {
return this.message("customer_facing_display", { html: html }, { timeout: 5000 });
}
},
/**
* @param {string} html
* @returns {Promise}
*/
take_ownership_over_customer_screen: function (html) {
return this.message("take_control", { html: html });
},
/**
* @returns {Promise}
*/
test_ownership_of_customer_screen: function () {
if (this.connection) {
return this.message("test_ownership", {});
}
return Promise.reject({ abort: true });
},
// asks the proxy to log some information, as with the debug.log you can provide several arguments.
log: function () {
return this.message("log", { arguments: _.toArray(arguments) });
},
});
+45 -64
View File
@@ -13,7 +13,8 @@ import { ProductConfiguratorPopup } from "@point_of_sale/js/Popups/ProductConfig
import { EditListPopup } from "@point_of_sale/js/Popups/EditListPopup";
import { markRaw, reactive } from "@odoo/owl";
import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
import { escape } from "@web/core/utils/strings";
import { escape } from "@web/core/utils/strings";
import { Mutex } from "@web/core/utils/concurrency";
var QWeb = core.qweb;
var _t = core._t;
@@ -97,6 +98,7 @@ export class PosGlobalState extends PosModel {
this.db = new PosDB(); // a local database used to search trough products and categories & store pending orders
this.debug = config.isDebug(); //debug mode
this.unwatched = markRaw({});
this.pushOrderMutex = new Mutex();
// Business data; loaded from the server at launch
this.company_logo = null;
@@ -461,19 +463,15 @@ export class PosGlobalState extends PosModel {
const ordersToSync = this.db.get_unpaid_orders_to_sync(ordersUidsToSync);
const ordersResponse = await this._save_to_server(ordersToSync, { draft: true });
const orders = [...this.ordersToUpdateSet].map((order) => order);
ordersResponse.forEach((orderResponseData) =>
this._updateOrder(orderResponseData, orders)
);
ordersResponse.forEach((orderResponseData) => this._updateOrder(orderResponseData, orders));
this.ordersToUpdateSet.clear();
}
addOrderToUpdateSet() {
this.ordersToUpdateSet.add(this.selectedOrder)
this.ordersToUpdateSet.add(this.selectedOrder);
}
// created this hook for modularity
_updateOrder(ordersResponseData, orders) {
const order = orders.find(
(order) => order.name === ordersResponseData.pos_reference
);
const order = orders.find((order) => order.name === ordersResponseData.pos_reference);
if (order) {
order.server_id = ordersResponseData.id;
return order;
@@ -654,7 +652,7 @@ export class PosGlobalState extends PosModel {
});
let removeSelected = true;
newOrdersJsons.forEach((json) => {
let isSelectedOrder = this._createOrder(json);
const isSelectedOrder = this._createOrder(json);
if (removeSelected && isSelectedOrder) {
removeSelected = false;
}
@@ -664,7 +662,11 @@ export class PosGlobalState extends PosModel {
}
}
_shouldRemoveOrder(order) {
return (!this.selectedOrder || (this.selectedOrder.uid != order.uid)) && order.server_id && !order.finalized;
return (
(!this.selectedOrder || this.selectedOrder.uid != order.uid) &&
order.server_id &&
!order.finalized
);
}
_shouldRemoveSelectedOrder(removeSelected) {
return removeSelected && this.selectedOrder.server_id && !this.selectedOrder.finalized;
@@ -714,10 +716,10 @@ export class PosGlobalState extends PosModel {
});
}
async _addPricelists(ordersJson) {
let pricelistsToGet = [];
const pricelistsToGet = [];
ordersJson.forEach((order) => {
let found = false;
for (let pricelist of this.pricelists) {
for (const pricelist of this.pricelists) {
if (pricelist.id === order.pricelist_id) {
found = true;
break;
@@ -735,10 +737,11 @@ export class PosGlobalState extends PosModel {
return message;
}
async _getPricelistJson(pricelistsToGet) {
return await this.env.services.orm.call("pos.session", "get_pos_ui_product_pricelists_by_ids", [
[odoo.pos_session_id],
pricelistsToGet,
]);
return await this.env.services.orm.call(
"pos.session",
"get_pos_ui_product_pricelists_by_ids",
[[odoo.pos_session_id], pricelistsToGet]
);
}
_addPosPricelists(pricelistsJson) {
if (!this.config.use_pricelist) {
@@ -748,15 +751,18 @@ export class PosGlobalState extends PosModel {
let message = "";
const pricelistsNames = pricelistsJson.map((pricelist) => {
return pricelist.display_name;
})
message = _.str.sprintf(_t("%s fiscal position(s) added to the configuration."), pricelistsNames.join(", "));
});
message = _.str.sprintf(
_t("%s fiscal position(s) added to the configuration."),
pricelistsNames.join(", ")
);
return message;
}
async _addFiscalPositions(ordersJson) {
let fiscalPositionToGet = [];
const fiscalPositionToGet = [];
ordersJson.forEach((order) => {
let found = false;
for (let fp of this.fiscal_positions) {
for (const fp of this.fiscal_positions) {
if (fp.id === order.fiscal_position_id) {
found = true;
break;
@@ -774,22 +780,26 @@ export class PosGlobalState extends PosModel {
return message;
}
async _getFiscalPositionJson(fiscalPositionToGet) {
return await this.env.services.orm.call("pos.session", "get_pos_ui_account_fiscal_positions_by_ids", [
[odoo.pos_session_id],
fiscalPositionToGet,
]);
return await this.env.services.orm.call(
"pos.session",
"get_pos_ui_account_fiscal_positions_by_ids",
[[odoo.pos_session_id], fiscalPositionToGet]
);
}
_addPosFiscalPosition(fiscalPositionJson) {
this.fiscal_positions.push(...fiscalPositionJson);
let message = "";
const fiscalPositionNames = fiscalPositionJson.map((fp) => {
return fp.display_name;
})
message = _.str.sprintf(_t("%s fiscal position(s) added to the configuration."), fiscalPositionNames.join(", "));
});
message = _.str.sprintf(
_t("%s fiscal position(s) added to the configuration."),
fiscalPositionNames.join(", ")
);
return message;
}
sortOrders() {
this.orders.sort((a, b) => (a.name > b.name) ? 1 : -1)
this.orders.sort((a, b) => (a.name > b.name ? 1 : -1));
}
async getProductInfo(product, quantity) {
const order = this.get_order();
@@ -951,9 +961,9 @@ export class PosGlobalState extends PosModel {
orderlines.scrollTop(orderlines.prop("scrollHeight"));
} else if (
this.config.iface_customer_facing_display_via_proxy &&
this.env.proxy.posbox_supports_display
this.hardwareProxy.customerDisplayAvailable
) {
this.env.proxy.update_customer_facing_display(rendered_html);
this.hardwareProxy.updateCustomerDisplay(rendered_html);
}
});
}
@@ -1006,42 +1016,13 @@ export class PosGlobalState extends PosModel {
});
}
// saves the order locally and try to send it to the backend.
// it returns a promise that succeeds after having tried to send the order and all the other pending orders.
push_orders(order, opts) {
opts = opts || {};
var self = this;
if (order) {
this.db.add_order(order.export_as_JSON());
}
return new Promise((resolve, reject) => {
this.env.posMutex.exec(async () => {
try {
resolve(await self._flush_orders(self.db.get_orders(), opts));
} catch (error) {
reject(error);
}
});
});
push_orders(opts = {}) {
return this.pushOrderMutex.exec(() => this._flush_orders(this.db.get_orders(), opts));
}
push_single_order(order, opts) {
opts = opts || {};
const self = this;
const order_id = self.db.add_order(order.export_as_JSON());
return new Promise((resolve, reject) => {
this.env.posMutex.exec(async () => {
const order = self.db.get_order(order_id);
try {
resolve(await self._flush_orders([order], opts));
} catch (error) {
reject(error);
}
});
});
push_single_order(order) {
const order_id = this.db.add_order(order.export_as_JSON());
return this.pushOrderMutex.exec(() => this._flush_orders([this.db.get_order(order_id)]));
}
// Send validated orders to the backend.
@@ -2783,7 +2764,7 @@ export class Payment extends PosModel {
};
}
//exports as JSON for receipt printing
export_for_printing(){
export_for_printing() {
const ticket = escape(this.ticket).replace(/\n/g, "<br />"); // formatting
return {
cid: this.cid,
@@ -1,16 +0,0 @@
/* @odoo-module */
// This module is basically web.env but with added fields
// that are specific to point_of_sale and extensions.
import env from "web.env";
import concurrency from "web.concurrency";
import { JobQueue, ProxyDevice } from "@point_of_sale/js/devices";
// Create new env object base on web.env.
// Mutating this new object won't affect the original object.
export const pos_env = Object.create(env);
pos_env.proxy_queue = new JobQueue(); // used to prevent parallels communications to the proxy
pos_env.proxy = new ProxyDevice({ env: pos_env }); // used to communicate to the hardware devices via a local proxy
pos_env.posMutex = new concurrency.Mutex();
@@ -33,9 +33,6 @@
<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.env.proxy.stop_searching()">
Skip
</div>
</t>
</div>
</div>
@@ -2,26 +2,26 @@
<templates id="template" xml:space="preserve">
<t t-name="ProxyStatus" owl="1">
<div class="oe_status js_proxy" t-on-click="() => this.trigger('connect-to-proxy')">
<span t-if="state.msg and !env.isMobile" class="js_msg">
<t t-esc="state.msg" />
<div class="oe_status js_proxy" t-on-click="() => this.pos.connectToProxy()">
<span t-if="this.message and !env.isMobile" class="js_msg">
<t t-esc="this.message" />
</span>
<span t-if="state.status === 'connected'" class="js_connected oe_green">
<span t-if="connectionInfo.status === 'connected' and !this.message" class="js_connected oe_green">
<i class="fa fa-fw fa-sitemap" role="img" aria-label="Proxy Connected"
title="Proxy Connected"></i>
</span>
<span t-if="state.status === 'connecting'" class="js_connecting">
<span t-if="connectionInfo.status === 'connecting'" class="js_connecting">
<i class="fa fa-fw fa-spin fa-circle-o-notch" role="img" aria-label="Connecting to Proxy"
title="Connecting to Proxy"></i>
</span>
<span t-if="state.status === 'warning'" class="js_warning oe_orange">
<i class="fa fa-fw fa-sitemap" role="img" aria-label="Proxy Warning"
title="Proxy Warning"></i>
</span>
<span t-if="state.status === 'disconnected'" class="js_disconnected oe_red">
<span t-elif="connectionInfo.status === 'disconnected'" class="js_disconnected oe_red">
<i class="fa fa-fw fa-sitemap" role="img" aria-label="Proxy Disconnected"
title="Proxy Disconnected"></i>
</span>
<span t-elif="connectionInfo.status === 'warning' or this.message" class="js_warning oe_orange">
<i class="fa fa-fw fa-sitemap" role="img" aria-label="Proxy Warning"
title="Proxy Warning"></i>
</span>
</div>
</t>
@@ -89,7 +89,7 @@
<i class="fa fa-download" role="img"/>
</div>
<!-- Print Sale Details -->
<div t-if="env.proxy.printer" class="small button icon">
<div t-if="hardwareProxy.printer" class="small button icon">
<SaleDetailsButton/>
</div>
</footer>
@@ -9,7 +9,7 @@ patch(PosGlobalState.prototype, "pos_epson_printer.PosGlobalState", {
var self = this;
return this._super(...arguments).then(function () {
if (self.config.other_devices && self.config.epson_printer_ip) {
self.env.proxy.printer = new EpsonPrinter(self.config.epson_printer_ip, self);
self.hardwareProxy.printer = new EpsonPrinter(self.config.epson_printer_ip, self);
}
});
},
@@ -18,6 +18,7 @@ export class TipScreen extends Component {
this.posReceiptContainer = useRef("pos-receipt-container");
this.popup = useService("popup");
this.orm = useService("orm");
this.hardwareProxy = useService("hardware_proxy");
this.state = this.currentOrder.uiState.TipScreen;
this._totalAmount = this.currentOrder.get_total_with_tax();
@@ -124,7 +125,7 @@ export class TipScreen extends Component {
total: this.env.pos.format_currency(this.totalAmount),
});
if (this.env.proxy.printer) {
if (this.hardwareProxy.printer) {
await this._printIoT(receipt);
} else {
await this._printWeb(receipt);
@@ -133,7 +134,7 @@ export class TipScreen extends Component {
}
async _printIoT(receipt) {
const printResult = await this.env.proxy.printer.print_receipt(receipt);
const printResult = await this.hardwareProxy.printer.print_receipt(receipt);
if (!printResult.successful) {
await this.popup.add(ErrorPopup, {
title: printResult.message.title,
@@ -11,7 +11,7 @@
<span> </span>
<span>Back</span>
</span>
<span class="button" t-if="env.proxy.printer" t-on-click="printTipReceipt">
<span class="button" t-if="hardwareProxy.printer" t-on-click="printTipReceipt">
<i class="fa fa-print"></i>
<span> </span>
<span>Reprint receipts</span>
+2 -2
View File
@@ -155,9 +155,9 @@ export const PaymentSix = PaymentInterface.extend({
_.forEach(receipts, (receipt) => {
if (
receipt.recipient === timapi.constants.Recipient.merchant &&
this.pos.env.proxy.printer
this.pos.hardwareProxy.printer
) {
this.pos.env.proxy.printer.print_receipt(
this.pos.hardwareProxy.printer.print_receipt(
"<div class='pos-receipt'><div class='pos-payment-terminal-receipt'>" +
escape(receipt.value).replace(/\n/g, "<br />") +
"</div></div>"