diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/point_of_sale/static/src/js/Screens/OrderManagementScreen | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/point_of_sale/static/src/js/Screens/OrderManagementScreen')
11 files changed, 844 insertions, 0 deletions
diff --git a/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/ControlButtons/InvoiceButton.js b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/ControlButtons/InvoiceButton.js new file mode 100644 index 00000000..53b858ba --- /dev/null +++ b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/ControlButtons/InvoiceButton.js @@ -0,0 +1,155 @@ +odoo.define('point_of_sale.InvoiceButton', function (require) { + 'use strict'; + + const { useListener } = require('web.custom_hooks'); + const { useContext } = owl.hooks; + const { isRpcError } = require('point_of_sale.utils'); + const PosComponent = require('point_of_sale.PosComponent'); + const OrderManagementScreen = require('point_of_sale.OrderManagementScreen'); + const OrderFetcher = require('point_of_sale.OrderFetcher'); + const Registries = require('point_of_sale.Registries'); + const contexts = require('point_of_sale.PosContext'); + + class InvoiceButton extends PosComponent { + constructor() { + super(...arguments); + useListener('click', this._onClick); + this.orderManagementContext = useContext(contexts.orderManagement); + } + get selectedOrder() { + return this.orderManagementContext.selectedOrder; + } + set selectedOrder(value) { + this.orderManagementContext.selectedOrder = value; + } + get isAlreadyInvoiced() { + if (!this.selectedOrder) return false; + return Boolean(this.selectedOrder.account_move); + } + get commandName() { + if (!this.selectedOrder) { + return 'Invoice'; + } else { + return this.isAlreadyInvoiced + ? 'Reprint Invoice' + : this.selectedOrder.isFromClosedSession + ? 'Cannot Invoice' + : 'Invoice'; + } + } + get isHighlighted() { + return this.selectedOrder && !this.isAlreadyInvoiced && !this.selectedOrder.isFromClosedSession; + } + async _downloadInvoice(orderId) { + try { + await this.env.pos.do_action('point_of_sale.pos_invoice_report', { + additional_context: { + active_ids: [orderId], + }, + }); + } catch (error) { + if (error instanceof Error) { + throw error; + } else { + // NOTE: error here is most probably undefined + this.showPopup('ErrorPopup', { + title: this.env._t('Network Error'), + body: this.env._t('Unable to download invoice.'), + }); + } + } + } + async _invoiceOrder() { + const order = this.selectedOrder; + if (!order) return; + + const orderId = order.backendId; + + // Part 0.1. If already invoiced, print the invoice. + if (this.isAlreadyInvoiced) { + await this._downloadInvoice(orderId); + return; + } + + // Part 0.2. Check if order belongs to an active session. + // If not, do not allow invoicing. + if (order.isFromClosedSession) { + this.showPopup('ErrorPopup', { + title: this.env._t('Session is closed'), + body: this.env._t('Cannot invoice order from closed session.'), + }); + return; + } + + // Part 1: Handle missing client. + // Write to pos.order the selected client. + if (!order.get_client()) { + const { confirmed: confirmedPopup } = await this.showPopup('ConfirmPopup', { + title: 'Need customer to invoice', + body: 'Do you want to open the customer list to select customer?', + }); + if (!confirmedPopup) return; + + const { confirmed: confirmedTempScreen, payload: newClient } = await this.showTempScreen( + 'ClientListScreen' + ); + if (!confirmedTempScreen) return; + + await this.rpc({ + model: 'pos.order', + method: 'write', + args: [[orderId], { partner_id: newClient.id }], + kwargs: { context: this.env.session.user_context }, + }); + } + + // Part 2: Invoice the order. + await this.rpc( + { + model: 'pos.order', + method: 'action_pos_order_invoice', + args: [orderId], + kwargs: { context: this.env.session.user_context }, + }, + { + timeout: 30000, + shadow: true, + } + ); + + // Part 3: Download invoice. + await this._downloadInvoice(orderId); + + // Invalidate the cache then fetch the updated order. + OrderFetcher.invalidateCache([orderId]); + await OrderFetcher.fetch(); + this.selectedOrder = OrderFetcher.get(this.selectedOrder.backendId); + } + async _onClick() { + try { + await this._invoiceOrder(); + } catch (error) { + if (isRpcError(error) && error.message.code < 0) { + this.showPopup('ErrorPopup', { + title: this.env._t('Network Error'), + body: this.env._t('Unable to invoice order.'), + }); + } else { + throw error; + } + } + } + } + InvoiceButton.template = 'InvoiceButton'; + + OrderManagementScreen.addControlButton({ + component: InvoiceButton, + condition: function () { + return this.env.pos.config.module_account; + }, + }); + + Registries.Component.add(InvoiceButton); + + return InvoiceButton; +}); diff --git a/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/ControlButtons/ReprintReceiptButton.js b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/ControlButtons/ReprintReceiptButton.js new file mode 100644 index 00000000..5a227827 --- /dev/null +++ b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/ControlButtons/ReprintReceiptButton.js @@ -0,0 +1,36 @@ +odoo.define('point_of_sale.ReprintReceiptButton', function (require) { + 'use strict'; + + const { useListener } = require('web.custom_hooks'); + const { useContext } = owl.hooks; + const PosComponent = require('point_of_sale.PosComponent'); + const OrderManagementScreen = require('point_of_sale.OrderManagementScreen'); + const Registries = require('point_of_sale.Registries'); + const contexts = require('point_of_sale.PosContext'); + + class ReprintReceiptButton extends PosComponent { + constructor() { + super(...arguments); + useListener('click', this._onClick); + this.orderManagementContext = useContext(contexts.orderManagement); + } + async _onClick() { + const order = this.orderManagementContext.selectedOrder; + if (!order) return; + + this.showScreen('ReprintReceiptScreen', { order: order }); + } + } + ReprintReceiptButton.template = 'ReprintReceiptButton'; + + OrderManagementScreen.addControlButton({ + component: ReprintReceiptButton, + condition: function () { + return true; + }, + }); + + Registries.Component.add(ReprintReceiptButton); + + return ReprintReceiptButton; +}); diff --git a/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/MobileOrderManagementScreen.js b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/MobileOrderManagementScreen.js new file mode 100644 index 00000000..b5766ccf --- /dev/null +++ b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/MobileOrderManagementScreen.js @@ -0,0 +1,25 @@ +odoo.define('point_of_sale.MobileOrderManagementScreen', function (require) { + const OrderManagementScreen = require('point_of_sale.OrderManagementScreen'); + const Registries = require('point_of_sale.Registries'); + const { useListener } = require('web.custom_hooks'); + const { useState } = owl.hooks; + + const MobileOrderManagementScreen = (OrderManagementScreen) => { + class MobileOrderManagementScreen extends OrderManagementScreen { + constructor() { + super(...arguments); + useListener('click-order', this._onShowDetails) + this.mobileState = useState({ showDetails: false }); + } + _onShowDetails() { + this.mobileState.showDetails = true; + } + } + MobileOrderManagementScreen.template = 'MobileOrderManagementScreen'; + return MobileOrderManagementScreen; + }; + + Registries.Component.addByExtending(MobileOrderManagementScreen, OrderManagementScreen); + + return MobileOrderManagementScreen; +}); diff --git a/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderDetails.js b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderDetails.js new file mode 100644 index 00000000..cc0c671c --- /dev/null +++ b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderDetails.js @@ -0,0 +1,29 @@ +odoo.define('point_of_sale.OrderDetails', function (require) { + 'use strict'; + + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + + /** + * @props {models.Order} order + */ + class OrderDetails extends PosComponent { + get order() { + return this.props.order; + } + get orderlines() { + return this.order ? this.order.orderlines.models : []; + } + get total() { + return this.env.pos.format_currency(this.order ? this.order.get_total_with_tax() : 0); + } + get tax() { + return this.env.pos.format_currency(this.order ? this.order.get_total_tax() : 0) + } + } + OrderDetails.template = 'OrderDetails'; + + Registries.Component.add(OrderDetails); + + return OrderDetails; +}); diff --git a/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderFetcher.js b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderFetcher.js new file mode 100644 index 00000000..57a02635 --- /dev/null +++ b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderFetcher.js @@ -0,0 +1,214 @@ +odoo.define('point_of_sale.OrderFetcher', function (require) { + 'use strict'; + + const { EventBus } = owl.core; + const { Gui } = require('point_of_sale.Gui'); + const { isRpcError } = require('point_of_sale.utils'); + const models = require('point_of_sale.models'); + + class OrderFetcher extends EventBus { + constructor() { + super(); + this.currentPage = 1; + this.ordersToShow = []; + this.cache = {}; + this.totalCount = 0; + } + get activeOrders() { + const allActiveOrders = this.comp.env.pos.get('orders').models; + return this.searchDomain + ? allActiveOrders.filter(this._predicateBasedOnSearchDomain.bind(this)) + : allActiveOrders; + } + _predicateBasedOnSearchDomain(order) { + function check(order, field, searchWord) { + searchWord = searchWord.toLowerCase(); + switch (field) { + case 'pos_reference': + return order.name.toLowerCase().includes(searchWord); + case 'partner_id.display_name': + const client = order.get_client(); + return client ? client.name.toLowerCase().includes(searchWord) : false; + case 'date_order': + return moment(order.creation_date).format('YYYY-MM-DD hh:mm A').includes(searchWord); + default: + return false; + } + } + for (let [field, _, searchWord] of (this.searchDomain || []).filter((item) => item !== '|')) { + // remove surrounding "%" from `searchWord` + searchWord = searchWord.substring(1, searchWord.length - 1); + if (check(order, field, searchWord)) { + return true; + } + } + return false; + } + get nActiveOrders() { + return this.activeOrders.length; + } + get lastPageFullOfActiveOrders() { + return Math.trunc(this.nActiveOrders / this.nPerPage); + } + get remainingActiveOrders() { + return this.nActiveOrders % this.nPerPage; + } + /** + * for nPerPage = 10 + * +--------+----------+ + * | nItems | lastPage | + * +--------+----------+ + * | 2 | 1 | + * | 10 | 1 | + * | 11 | 2 | + * | 30 | 3 | + * | 35 | 4 | + * +--------+----------+ + */ + get lastPage() { + const nItems = this.nActiveOrders + this.totalCount; + return Math.trunc(nItems / (this.nPerPage + 1)) + 1; + } + /** + * Calling this methods populates the `ordersToShow` then trigger `update` event. + * @related get + * + * NOTE: This is tightly-coupled with pagination. So if the current page contains all + * active orders, it will not fetch anything from the server but only sets `ordersToShow` + * to the active orders that fits the current page. + */ + async fetch() { + try { + let limit, offset; + let start, end; + if (this.currentPage <= this.lastPageFullOfActiveOrders) { + // Show only active orders. + start = (this.currentPage - 1) * this.nPerPage; + end = this.currentPage * this.nPerPage; + this.ordersToShow = this.activeOrders.slice(start, end); + } else if (this.currentPage === this.lastPageFullOfActiveOrders + 1) { + // Show partially the remaining active orders and + // some orders from the backend. + offset = 0; + limit = this.nPerPage - this.remainingActiveOrders; + start = (this.currentPage - 1) * this.nPerPage; + end = this.nActiveOrders; + this.ordersToShow = [ + ...this.activeOrders.slice(start, end), + ...(await this._fetch(limit, offset)), + ]; + } else { + // Show orders from the backend. + offset = + this.nPerPage - + this.remainingActiveOrders + + (this.currentPage - (this.lastPageFullOfActiveOrders + 1) - 1) * + this.nPerPage; + limit = this.nPerPage; + this.ordersToShow = await this._fetch(limit, offset); + } + this.trigger('update'); + } catch (error) { + if (isRpcError(error) && error.message.code < 0) { + Gui.showPopup('ErrorPopup', { + title: this.comp.env._t('Network Error'), + body: this.comp.env._t('Unable to fetch orders if offline.'), + }); + Gui.setSyncStatus('error'); + } else { + throw error; + } + } + } + /** + * This returns the orders from the backend that needs to be shown. + * If the order is already in cache, the full information about that + * order is not fetched anymore, instead, we use info from cache. + * + * @param {number} limit + * @param {number} offset + */ + async _fetch(limit, offset) { + const { ids, totalCount } = await this._getOrderIdsForCurrentPage(limit, offset); + const idsNotInCache = ids.filter((id) => !(id in this.cache)); + if (idsNotInCache.length > 0) { + const fetchedOrders = await this._fetchOrders(idsNotInCache); + // Cache these fetched orders so that next time, no need to fetch + // them again, unless invalidated. See `invalidateCache`. + fetchedOrders.forEach((order) => { + this.cache[order.id] = new models.Order( + {}, + { pos: this.comp.env.pos, json: order } + ); + }); + } + this.totalCount = totalCount; + return ids.map((id) => this.cache[id]); + } + async _getOrderIdsForCurrentPage(limit, offset) { + return await this.rpc({ + model: 'pos.order', + method: 'search_paid_order_ids', + kwargs: { config_id: this.configId, domain: this.searchDomain ? this.searchDomain : [], limit, offset }, + context: this.comp.env.session.user_context, + }); + } + async _fetchOrders(ids) { + return await this.rpc({ + model: 'pos.order', + method: 'export_for_ui', + args: [ids], + context: this.comp.env.session.user_context, + }); + } + nextPage() { + if (this.currentPage < this.lastPage) { + this.currentPage += 1; + this.fetch(); + } + } + prevPage() { + if (this.currentPage > 1) { + this.currentPage -= 1; + this.fetch(); + } + } + /** + * @param {integer|undefined} id id of the cached order + * @returns {Array<models.Order>} + */ + get(id) { + if (id) return this.cache[id]; + return this.ordersToShow; + } + setSearchDomain(searchDomain) { + this.searchDomain = searchDomain; + } + setComponent(comp) { + this.comp = comp; + return this; + } + setConfigId(configId) { + this.configId = configId; + } + setNPerPage(val) { + this.nPerPage = val; + } + setPage(page) { + this.currentPage = page; + } + invalidateCache(ids) { + for (let id of ids) { + delete this.cache[id]; + } + } + async rpc() { + Gui.setSyncStatus('connecting'); + const result = await this.comp.rpc(...arguments); + Gui.setSyncStatus('connected'); + return result; + } + } + + return new OrderFetcher(); +}); diff --git a/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderList.js b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderList.js new file mode 100644 index 00000000..2b4d3cd9 --- /dev/null +++ b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderList.js @@ -0,0 +1,31 @@ +odoo.define('point_of_sale.OrderList', function (require) { + 'use strict'; + + const { useState } = owl.hooks; + const { useListener } = require('web.custom_hooks'); + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + + /** + * @props {models.Order} [initHighlightedOrder] initially highligted order + * @props {Array<models.Order>} orders + */ + class OrderList extends PosComponent { + constructor() { + super(...arguments); + useListener('click-order', this._onClickOrder); + this.state = useState({ highlightedOrder: this.props.initHighlightedOrder || null }); + } + get highlightedOrder() { + return this.state.highlightedOrder; + } + _onClickOrder({ detail: order }) { + this.state.highlightedOrder = order; + } + } + OrderList.template = 'OrderList'; + + Registries.Component.add(OrderList); + + return OrderList; +}); diff --git a/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderManagementControlPanel.js b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderManagementControlPanel.js new file mode 100644 index 00000000..951a0956 --- /dev/null +++ b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderManagementControlPanel.js @@ -0,0 +1,124 @@ +odoo.define('point_of_sale.OrderManagementControlPanel', function (require) { + 'use strict'; + + const { useContext } = owl.hooks; + const { useAutofocus, useListener } = require('web.custom_hooks'); + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + const OrderFetcher = require('point_of_sale.OrderFetcher'); + const contexts = require('point_of_sale.PosContext'); + + // NOTE: These are constants so that they are only instantiated once + // and they can be used efficiently by the OrderManagementControlPanel. + const VALID_SEARCH_TAGS = new Set(['date', 'customer', 'client', 'name', 'order']); + const FIELD_MAP = { + date: 'date_order', + customer: 'partner_id.display_name', + client: 'partner_id.display_name', + name: 'pos_reference', + order: 'pos_reference', + }; + const SEARCH_FIELDS = ['pos_reference', 'partner_id.display_name', 'date_order']; + + function getDomainForSingleCondition(fields, toSearch) { + const orSymbols = Array(fields.length - 1).fill('|'); + return orSymbols.concat(fields.map((field) => [field, 'ilike', `%${toSearch}%`])); + } + + /** + * @emits close-screen + * @emits prev-page + * @emits next-page + * @emits search + */ + class OrderManagementControlPanel extends PosComponent { + constructor() { + super(...arguments); + // We are using context because we want the `searchString` to be alive + // even if this component is destroyed (unmounted). + this.orderManagementContext = useContext(contexts.orderManagement); + useListener('clear-search', this._onClearSearch); + useAutofocus({ selector: 'input' }); + } + onInputKeydown(event) { + if (event.key === 'Enter') { + this.trigger('search', this._computeDomain()); + } + } + get showPageControls() { + return OrderFetcher.lastPage > 1; + } + get pageNumber() { + const currentPage = OrderFetcher.currentPage; + const lastPage = OrderFetcher.lastPage; + return isNaN(lastPage) ? '' : `(${currentPage}/${lastPage})`; + } + get validSearchTags() { + return VALID_SEARCH_TAGS; + } + get fieldMap() { + return FIELD_MAP; + } + get searchFields() { + return SEARCH_FIELDS; + } + /** + * E.g. 1 + * ``` + * searchString = 'Customer 1' + * result = [ + * '|', + * '|', + * ['pos_reference', 'ilike', '%Customer 1%'], + * ['partner_id.display_name', 'ilike', '%Customer 1%'], + * ['date_order', 'ilike', '%Customer 1%'] + * ] + * ``` + * + * E.g. 2 + * ``` + * searchString = 'date: 2020-05' + * result = [ + * ['date_order', 'ilike', '%2020-05%'] + * ] + * ``` + * + * E.g. 3 + * ``` + * searchString = 'customer: Steward, date: 2020-05-01' + * result = [ + * ['partner_id.display_name', 'ilike', '%Steward%'], + * ['date_order', 'ilike', '%2020-05-01%'] + * ] + * ``` + */ + _computeDomain() { + const input = this.orderManagementContext.searchString.trim(); + if (!input) return; + + const searchConditions = this.orderManagementContext.searchString.split(/[,&]\s*/); + if (searchConditions.length === 1) { + let cond = searchConditions[0].split(/:\s*/); + if (cond.length === 1) { + return getDomainForSingleCondition(this.searchFields, cond[0]); + } + } + const domain = []; + for (let cond of searchConditions) { + let [tag, value] = cond.split(/:\s*/); + if (!this.validSearchTags.has(tag)) continue; + domain.push([this.fieldMap[tag], 'ilike', `%${value}%`]); + } + return domain; + } + _onClearSearch() { + this.orderManagementContext.searchString = ''; + this.onInputKeydown({ key: 'Enter' }); + } + } + OrderManagementControlPanel.template = 'OrderManagementControlPanel'; + + Registries.Component.add(OrderManagementControlPanel); + + return OrderManagementControlPanel; +}); diff --git a/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderManagementScreen.js b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderManagementScreen.js new file mode 100644 index 00000000..dcde9739 --- /dev/null +++ b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderManagementScreen.js @@ -0,0 +1,101 @@ +odoo.define('point_of_sale.OrderManagementScreen', function (require) { + 'use strict'; + + const { useContext } = owl.hooks; + const { useListener } = require('web.custom_hooks'); + const ControlButtonsMixin = require('point_of_sale.ControlButtonsMixin'); + const NumberBuffer = require('point_of_sale.NumberBuffer'); + const Registries = require('point_of_sale.Registries'); + const OrderFetcher = require('point_of_sale.OrderFetcher'); + const IndependentToOrderScreen = require('point_of_sale.IndependentToOrderScreen'); + const contexts = require('point_of_sale.PosContext'); + + class OrderManagementScreen extends ControlButtonsMixin(IndependentToOrderScreen) { + constructor() { + super(...arguments); + useListener('close-screen', this.close); + useListener('set-numpad-mode', this._setNumpadMode); + useListener('click-order', this._onClickOrder); + useListener('next-page', this._onNextPage); + useListener('prev-page', this._onPrevPage); + useListener('search', this._onSearch); + NumberBuffer.use({ + nonKeyboardInputEvent: 'numpad-click-input', + useWithBarcode: true, + }); + this.numpadMode = 'quantity'; + OrderFetcher.setComponent(this); + OrderFetcher.setConfigId(this.env.pos.config_id); + this.orderManagementContext = useContext(contexts.orderManagement); + } + mounted() { + OrderFetcher.on('update', this, this.render); + this.env.pos.get('orders').on('add remove', this.render, this); + + // calculate how many can fit in the screen. + // It is based on the height of the header element. + // So the result is only accurate if each row is just single line. + const flexContainer = this.el.querySelector('.flex-container'); + const cpEl = this.el.querySelector('.control-panel'); + const headerEl = this.el.querySelector('.order-row.header'); + const val = Math.trunc( + (flexContainer.offsetHeight - cpEl.offsetHeight - headerEl.offsetHeight) / + headerEl.offsetHeight + ); + OrderFetcher.setNPerPage(val); + + // Fetch the order after mounting so that order management screen + // is shown while fetching. + setTimeout(() => OrderFetcher.fetch(), 0); + } + willUnmount() { + OrderFetcher.off('update', this); + this.env.pos.get('orders').off('add remove', null, this); + } + get selectedClient() { + const order = this.orderManagementContext.selectedOrder; + return order ? order.get_client() : null; + } + get orders() { + return OrderFetcher.get(); + } + async _setNumpadMode(event) { + const { mode } = event.detail; + this.numpadMode = mode; + NumberBuffer.reset(); + } + _onNextPage() { + OrderFetcher.nextPage(); + } + _onPrevPage() { + OrderFetcher.prevPage(); + } + _onSearch({ detail: domain }) { + OrderFetcher.setSearchDomain(domain); + OrderFetcher.setPage(1); + OrderFetcher.fetch(); + } + _onClickOrder({ detail: clickedOrder }) { + if (!clickedOrder || clickedOrder.locked) { + this.orderManagementContext.selectedOrder = clickedOrder; + } else { + this._setOrder(clickedOrder); + } + } + /** + * @param {models.Order} order + */ + _setOrder(order) { + this.env.pos.set_order(order); + if (order === this.env.pos.get_order()) { + this.close(); + } + } + } + OrderManagementScreen.template = 'OrderManagementScreen'; + OrderManagementScreen.hideOrderSelector = true; + + Registries.Component.add(OrderManagementScreen); + + return OrderManagementScreen; +}); diff --git a/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderRow.js b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderRow.js new file mode 100644 index 00000000..959ea5a1 --- /dev/null +++ b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderRow.js @@ -0,0 +1,42 @@ +odoo.define('point_of_sale.OrderRow', function (require) { + 'use strict'; + + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + + /** + * @props {models.Order} order + * @props columns + * @emits click-order + */ + class OrderRow extends PosComponent { + get order() { + return this.props.order; + } + get highlighted() { + const highlightedOrder = this.props.highlightedOrder; + return !highlightedOrder ? false : highlightedOrder.backendId === this.props.order.backendId; + } + + // Column getters // + + get name() { + return this.order.get_name(); + } + get date() { + return moment(this.order.validation_date).format('YYYY-MM-DD hh:mm A'); + } + get customer() { + const customer = this.order.get('client'); + return customer ? customer.name : null; + } + get total() { + return this.env.pos.format_currency(this.order.get_total_with_tax()); + } + } + OrderRow.template = 'OrderRow'; + + Registries.Component.add(OrderRow); + + return OrderRow; +}); diff --git a/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderlineDetails.js b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderlineDetails.js new file mode 100644 index 00000000..35f6ec5d --- /dev/null +++ b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/OrderlineDetails.js @@ -0,0 +1,55 @@ +odoo.define('point_of_sale.OrderlineDetails', function (require) { + 'use strict'; + + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + const { format } = require('web.field_utils'); + const { round_precision: round_pr } = require('web.utils'); + + /** + * @props {pos.order.line} line + */ + class OrderlineDetails extends PosComponent { + get line() { + const line = this.props.line; + const formatQty = (line) => { + const quantity = line.get_quantity(); + const unit = line.get_unit(); + const decimals = this.env.pos.dp['Product Unit of Measure']; + const rounding = Math.max(unit.rounding, Math.pow(10, -decimals)); + const roundedQuantity = round_pr(quantity, rounding); + return format.float(roundedQuantity, { digits: [69, decimals] }); + }; + return { + productName: line.get_full_product_name(), + totalPrice: line.get_price_with_tax(), + quantity: formatQty(line), + unit: line.get_unit().name, + unitPrice: line.get_unit_price(), + }; + } + get productName() { + return this.line.productName; + } + get totalPrice() { + return this.env.pos.format_currency(this.line.totalPrice); + } + get quantity() { + return this.line.quantity; + } + get unitPrice() { + return this.line.unitPrice; + } + get unit() { + return this.line.unit; + } + get pricePerUnit() { + return ` ${this.unit} at ${this.unitPrice} / ${this.unit}`; + } + } + OrderlineDetails.template = 'OrderlineDetails'; + + Registries.Component.add(OrderlineDetails); + + return OrderlineDetails; +}); diff --git a/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/ReprintReceiptScreen.js b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/ReprintReceiptScreen.js new file mode 100644 index 00000000..7fcc514d --- /dev/null +++ b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/ReprintReceiptScreen.js @@ -0,0 +1,32 @@ +odoo.define('point_of_sale.ReprintReceiptScreen', function (require) { + 'use strict'; + + const AbstractReceiptScreen = require('point_of_sale.AbstractReceiptScreen'); + const Registries = require('point_of_sale.Registries'); + + const ReprintReceiptScreen = (AbstractReceiptScreen) => { + class ReprintReceiptScreen extends AbstractReceiptScreen { + mounted() { + this.printReceipt(); + } + confirm() { + this.showScreen('OrderManagementScreen'); + } + async printReceipt() { + if(this.env.pos.proxy.printer && this.env.pos.config.iface_print_skip_screen) { + let result = await this._printReceipt(); + if(result) + this.showScreen('OrderManagementScreen'); + } + } + async tryReprint() { + await this._printReceipt(); + } + } + ReprintReceiptScreen.template = 'ReprintReceiptScreen'; + return ReprintReceiptScreen; + }; + Registries.Component.addByExtending(ReprintReceiptScreen, AbstractReceiptScreen); + + return ReprintReceiptScreen; +}); |
