diff options
Diffstat (limited to 'addons/point_of_sale/static/src/js/ChromeWidgets')
9 files changed, 550 insertions, 0 deletions
diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/CashierName.js b/addons/point_of_sale/static/src/js/ChromeWidgets/CashierName.js new file mode 100644 index 00000000..02e61967 --- /dev/null +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/CashierName.js @@ -0,0 +1,23 @@ +odoo.define('point_of_sale.CashierName', function(require) { + 'use strict'; + + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + + // Previously UsernameWidget + class CashierName extends PosComponent { + get username() { + const cashier = this.env.pos.get_cashier(); + if (cashier) { + return cashier.name; + } else { + return ''; + } + } + } + CashierName.template = 'CashierName'; + + Registries.Component.add(CashierName); + + return CashierName; +}); diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/ClientScreenButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/ClientScreenButton.js new file mode 100644 index 00000000..38403b58 --- /dev/null +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/ClientScreenButton.js @@ -0,0 +1,87 @@ +odoo.define('point_of_sale.ClientScreenButton', function(require) { + 'use strict'; + + const { useState } = owl; + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + + // Formerly ClientScreenWidget + class ClientScreenButton extends PosComponent { + constructor() { + super(...arguments); + this.state = useState({ status: 'failure' }); + this._start(); + } + get message() { + return { + success: '', + warning: this.env._t('Connected, Not Owned'), + failure: this.env._t('Disconnected'), + not_found: this.env._t('Client Screen Unsupported. Please upgrade the IoT Box'), + }[this.state.status]; + } + async onClick() { + try { + const renderedHtml = await this.env.pos.render_html_for_customer_facing_display(); + const ownership = await this.env.pos.proxy.take_ownership_over_client_screen( + renderedHtml + ); + if (typeof ownership === 'string') { + ownership = JSON.parse(ownership); + } + if (ownership.status === 'success') { + this.state.status = 'success'; + } else { + this.state.status = 'warning'; + } + if (!this.env.pos.proxy.posbox_supports_display) { + this.env.pos.proxy.posbox_supports_display = true; + this._start(); + } + } catch (error) { + if (typeof error == 'undefined') { + this.state.status = 'failure'; + } else { + this.state.status = 'not_found'; + } + } + } + _start() { + const self = this; + async function loop() { + if (self.env.pos.proxy.posbox_supports_display) { + try { + const ownership = await self.env.pos.proxy.test_ownership_of_client_screen(); + if (typeof ownership === 'string') { + ownership = JSON.parse(ownership); + } + if (ownership.status === 'OWNER') { + self.state.status = 'success'; + } else { + self.state.status = 'warning'; + } + setTimeout(loop, 3000); + } catch (error) { + if (error.abort) { + // Stop the loop + return; + } + if (typeof error == 'undefined') { + self.state.status = 'failure'; + } else { + self.state.status = 'not_found'; + self.env.pos.proxy.posbox_supports_display = false; + } + setTimeout(loop, 3000); + } + } + } + loop(); + } + } + ClientScreenButton.template = 'ClientScreenButton'; + + Registries.Component.add(ClientScreenButton); + + return ClientScreenButton; +}); diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/DebugWidget.js b/addons/point_of_sale/static/src/js/ChromeWidgets/DebugWidget.js new file mode 100644 index 00000000..f5158428 --- /dev/null +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/DebugWidget.js @@ -0,0 +1,161 @@ +odoo.define('point_of_sale.DebugWidget', function (require) { + 'use strict'; + + const { useState } = owl; + const { useRef } = owl.hooks; + const { getFileAsText } = require('point_of_sale.utils'); + const { parse } = require('web.field_utils'); + const NumberBuffer = require('point_of_sale.NumberBuffer'); + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + + class DebugWidget extends PosComponent { + constructor() { + super(...arguments); + this.state = useState({ + barcodeInput: '', + weightInput: '', + isPaidOrdersReady: false, + isUnpaidOrdersReady: false, + buffer: NumberBuffer.get(), + }); + + // 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 = {}; + for (let eventName of ['open_cashbox', 'print_receipt', 'scale_read']) { + this.eventElementsRef[eventName] = useRef(eventName); + this.env.pos.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) + ); + } + } + mounted() { + NumberBuffer.on('buffer-update', this, this._onBufferUpdate); + } + willUnmount() { + NumberBuffer.off('buffer-update', this, this._onBufferUpdate); + } + toggleWidget() { + this.state.isShown = !this.state.isShown; + } + setWeight() { + var weightInKg = parse.float(this.state.weightInput); + if (!isNaN(weightInKg)) { + this.env.pos.proxy.debug_set_weight(weightInKg); + } + } + resetWeight() { + this.state.weightInput = ''; + this.env.pos.proxy.debug_reset_weight(); + } + barcodeScan() { + this.env.pos.barcode_reader.scan(this.state.barcodeInput); + } + barcodeScanEAN() { + const ean = this.env.pos.barcode_reader.barcode_parser.sanitize_ean( + this.state.barcodeInput || '0' + ); + this.state.barcodeInput = ean; + this.env.pos.barcode_reader.scan(ean); + } + async deleteOrders() { + const { confirmed } = await this.showPopup('ConfirmPopup', { + title: this.env._t('Delete Paid Orders ?'), + body: this.env._t( + 'This operation will permanently destroy all paid orders from the local storage. You will lose all the data. This operation cannot be undone.' + ), + }); + if (confirmed) { + this.env.pos.db.remove_all_orders(); + this.env.pos.set_synch('connected', 0); + } + } + async deleteUnpaidOrders() { + const { confirmed } = await this.showPopup('ConfirmPopup', { + title: this.env._t('Delete Unpaid Orders ?'), + body: this.env._t( + 'This operation will destroy all unpaid orders in the browser. You will lose all the unsaved data and exit the point of sale. This operation cannot be undone.' + ), + }); + if (confirmed) { + this.env.pos.db.remove_all_unpaid_orders(); + window.location = '/'; + } + } + _createBlob(contents) { + if (typeof contents !== 'string') { + contents = JSON.stringify(contents, null, 2); + } + return new Blob([contents]); + } + // IMPROVEMENT: Duplicated codes for downloading paid and unpaid orders. + // The implementation can be better. + preparePaidOrders() { + try { + this.paidOrdersBlob = this._createBlob(this.env.pos.export_paid_orders()); + this.state.isPaidOrdersReady = true; + } catch (error) { + console.warn(error); + } + } + get paidOrdersFilename() { + return `${this.env._t('paid orders')} ${moment().format('YYYY-MM-DD-HH-mm-ss')}.json`; + } + get paidOrdersURL() { + var URL = window.URL || window.webkitURL; + return URL.createObjectURL(this.paidOrdersBlob); + } + prepareUnpaidOrders() { + try { + this.unpaidOrdersBlob = this._createBlob(this.env.pos.export_unpaid_orders()); + this.state.isUnpaidOrdersReady = true; + } catch (error) { + console.warn(error); + } + } + get unpaidOrdersFilename() { + return `${this.env._t('unpaid orders')} ${moment().format('YYYY-MM-DD-HH-mm-ss')}.json`; + } + get unpaidOrdersURL() { + var URL = window.URL || window.webkitURL; + return URL.createObjectURL(this.unpaidOrdersBlob); + } + async importOrders(event) { + const file = event.target.files[0]; + if (file) { + const report = this.env.pos.import_orders(await getFileAsText(file)); + await this.showPopup('OrderImportPopup', { report }); + } + } + refreshDisplay() { + this.env.pos.proxy.message('display_refresh', {}); + } + _onBufferUpdate(buffer) { + this.state.buffer = buffer; + } + get bufferRepr() { + return `"${this.state.buffer}"`; + } + } + DebugWidget.template = 'DebugWidget'; + + Registries.Component.add(DebugWidget); + + return DebugWidget; +}); diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js new file mode 100644 index 00000000..84036ecb --- /dev/null +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js @@ -0,0 +1,36 @@ +odoo.define('point_of_sale.HeaderButton', function(require) { + 'use strict'; + + const { useState } = owl; + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + + // Previously HeaderButtonWidget + // This is the close session button + class HeaderButton extends PosComponent { + constructor() { + super(...arguments); + this.state = useState({ label: 'Close' }); + this.confirmed = null; + } + get translatedLabel() { + return this.env._t(this.state.label); + } + onClick() { + if (!this.confirmed) { + this.state.label = 'Confirm'; + this.confirmed = setTimeout(() => { + this.state.label = 'Close'; + this.confirmed = null; + }, 2000); + } else { + this.trigger('close-pos'); + } + } + } + HeaderButton.template = 'HeaderButton'; + + Registries.Component.add(HeaderButton); + + return HeaderButton; +}); diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/OrderManagementButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/OrderManagementButton.js new file mode 100644 index 00000000..0bee8880 --- /dev/null +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/OrderManagementButton.js @@ -0,0 +1,36 @@ +odoo.define('point_of_sale.OrderManagementButton', function (require) { + 'use strict'; + + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + const { isRpcError } = require('point_of_sale.utils'); + + class OrderManagementButton extends PosComponent { + async onClick() { + try { + // ping the server, if no error, show the screen + await this.rpc({ + model: 'pos.order', + method: 'browse', + args: [[]], + kwargs: { context: this.env.session.user_context }, + }); + this.showScreen('OrderManagementScreen'); + } catch (error) { + if (isRpcError(error) && error.message.code < 0) { + this.showPopup('ErrorPopup', { + title: this.env._t('Network Error'), + body: this.env._t('Cannot access order management screen if offline.'), + }); + } else { + throw error; + } + } + } + } + OrderManagementButton.template = 'OrderManagementButton'; + + Registries.Component.add(OrderManagementButton); + + return OrderManagementButton; +}); diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/ProxyStatus.js b/addons/point_of_sale/static/src/js/ChromeWidgets/ProxyStatus.js new file mode 100644 index 00000000..98c24c02 --- /dev/null +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/ProxyStatus.js @@ -0,0 +1,91 @@ +odoo.define('point_of_sale.ProxyStatus', function(require) { + 'use strict'; + + const { useState } = owl; + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + + // Previously ProxyStatusWidget + class ProxyStatus extends PosComponent { + constructor() { + super(...arguments); + const initialProxyStatus = this.env.pos.proxy.get('status'); + this.state = useState({ + status: initialProxyStatus.status, + msg: initialProxyStatus.msg, + }); + this.statuses = ['connected', 'connecting', 'disconnected', 'warning']; + this.index = 0; + } + mounted() { + this.env.pos.proxy.on('change:status', this, this._onChangeStatus); + } + willUnmount() { + this.env.pos.proxy.off('change:status', this, this._onChangeStatus); + } + async onClick() { + try { + await this.env.pos.connect_to_proxy(); + } catch (error) { + if (error instanceof Error) { + throw error; + } else { + this.showPopup('ErrorPopup', error); + } + } + } + _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 || ''; + } + } + } + ProxyStatus.template = 'ProxyStatus'; + + Registries.Component.add(ProxyStatus); + + return ProxyStatus; +}); diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js new file mode 100644 index 00000000..e646547e --- /dev/null +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js @@ -0,0 +1,38 @@ +odoo.define('point_of_sale.SaleDetailsButton', function(require) { + 'use strict'; + + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + + class SaleDetailsButton extends PosComponent { + async onClick() { + // IMPROVEMENT: Perhaps put this logic in a parent component + // so that for unit testing, we can check if this simple + // component correctly triggers an event. + const saleDetails = await this.rpc({ + model: 'report.point_of_sale.report_saledetails', + method: 'get_sale_details', + args: [false, false, false, [this.env.pos.pos_session.id]], + }); + const report = this.env.qweb.renderToString( + 'SaleDetailsReport', + Object.assign({}, saleDetails, { + date: new Date().toLocaleString(), + pos: this.env.pos, + }) + ); + const printResult = await this.env.pos.proxy.printer.print_receipt(report); + if (!printResult.successful) { + await this.showPopup('ErrorPopup', { + title: printResult.message.title, + body: printResult.message.body, + }); + } + } + } + SaleDetailsButton.template = 'SaleDetailsButton'; + + Registries.Component.add(SaleDetailsButton); + + return SaleDetailsButton; +}); diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/SyncNotification.js b/addons/point_of_sale/static/src/js/ChromeWidgets/SyncNotification.js new file mode 100644 index 00000000..5a4e158d --- /dev/null +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/SyncNotification.js @@ -0,0 +1,37 @@ +odoo.define('point_of_sale.SyncNotification', function(require) { + 'use strict'; + + const { useState } = owl; + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + + // Previously SynchNotificationWidget + class SyncNotification extends PosComponent { + constructor() { + super(...arguments); + const synch = this.env.pos.get('synch'); + this.state = useState({ status: synch.status, msg: synch.pending }); + } + mounted() { + this.env.pos.on( + 'change:synch', + (pos, synch) => { + this.state.status = synch.status; + this.state.msg = synch.pending; + }, + this + ); + } + willUnmount() { + this.env.pos.on('change:synch', null, this); + } + onClick() { + this.env.pos.push_orders(null, { show_error: true }); + } + } + SyncNotification.template = 'SyncNotification'; + + Registries.Component.add(SyncNotification); + + return SyncNotification; +}); diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/TicketButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/TicketButton.js new file mode 100644 index 00000000..d142bbde --- /dev/null +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/TicketButton.js @@ -0,0 +1,41 @@ +odoo.define('point_of_sale.TicketButton', function (require) { + 'use strict'; + + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + const { posbus } = require('point_of_sale.utils'); + + class TicketButton extends PosComponent { + onClick() { + if (this.props.isTicketScreenShown) { + posbus.trigger('ticket-button-clicked'); + } else { + this.showScreen('TicketScreen'); + } + } + willPatch() { + posbus.off('order-deleted', this); + } + patched() { + posbus.on('order-deleted', this, this.render); + } + mounted() { + posbus.on('order-deleted', this, this.render); + } + willUnmount() { + posbus.off('order-deleted', this); + } + get count() { + if (this.env.pos) { + return this.env.pos.get_order_list().length; + } else { + return 0; + } + } + } + TicketButton.template = 'TicketButton'; + + Registries.Component.add(TicketButton); + + return TicketButton; +}); |
