From 2649dc3205bec050433aebf144325504bdd987b8 Mon Sep 17 00:00:00 2001 From: "Loukas Wets (lowe)" Date: Wed, 3 May 2023 11:06:01 +0000 Subject: [PATCH] [IMP] point_of_sale: trigger cash drawer opening when cash transaction Adding methods to trigger the cash drawer opening as well as a log in the message to state by who and for which action the cash drawer was opened. There is also a log now for when in an action for which the cash drawer was opened is canceled. The action concerned are: Cash control at opening, cash in / out, Cash control at closing. task-3293113 closes odoo/odoo#121110 Signed-off-by: Monnom David (moda) --- addons/point_of_sale/models/pos_session.py | 9 ++++++++- .../static/src/app/hardware_proxy/hardware_proxy.js | 8 ++++++++ .../app/navbar/cash_move_popup/cash_move_popup.js | 9 +++++++++ addons/point_of_sale/static/src/app/navbar/navbar.js | 3 ++- .../static/src/js/Popups/CashOpeningPopup.js | 5 +++++ .../static/src/js/Popups/ClosePosPopup.js | 3 +++ .../static/src/js/Popups/MoneyDetailsPopup.js | 11 +++++++++++ .../src/js/Screens/PaymentScreen/PaymentScreen.js | 9 +++------ addons/point_of_sale/static/src/js/models.js | 8 ++++++++ addons/pos_hr/models/pos_session.py | 2 +- addons/pos_hr/static/src/js/models.js | 12 ++++++++++++ 11 files changed, 70 insertions(+), 9 deletions(-) diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py index 690e97749eb..39a3c84518b 100644 --- a/addons/point_of_sale/models/pos_session.py +++ b/addons/point_of_sale/models/pos_session.py @@ -1822,7 +1822,7 @@ class PosSession(models.Model): return { 'search_params': { 'domain': [('id', '=', self.env.user.id)], - 'fields': ['name', 'groups_id'], + 'fields': ['name', 'groups_id', 'partner_id'], }, } @@ -2127,6 +2127,13 @@ class PosSession(models.Model): return fps + def log_partner_message(self, partner_id, action, message_type): + if message_type == 'ACTION_CANCELLED': + body = 'Action cancelled ({ACTION})'.format(ACTION=action) + elif message_type == 'CASH_DRAWER_ACTION': + body = 'Cash drawer opened ({ACTION})'.format(ACTION=action) + self.message_post(body=body, author_id=partner_id) + def load_product_frontend(self): convert.convert_file(self.env, 'point_of_sale', 'data/point_of_sale_onboarding.xml', None, mode='init', kind='data') diff --git a/addons/point_of_sale/static/src/app/hardware_proxy/hardware_proxy.js b/addons/point_of_sale/static/src/app/hardware_proxy/hardware_proxy.js index 3818b50801e..46f7f9247f6 100644 --- a/addons/point_of_sale/static/src/app/hardware_proxy/hardware_proxy.js +++ b/addons/point_of_sale/static/src/app/hardware_proxy/hardware_proxy.js @@ -190,6 +190,14 @@ export class HardwareProxy extends EventBus { log() { return this.message("log", { arguments: [...arguments] }); } + async openCashbox(action = false) { + if (this.pos.config.iface_cashdrawer && this.connectionInfo.status === "connected") { + this.printer.openCashbox(); + if (action) { + this.pos.logEmployeeMessage(action, "CASH_DRAWER_ACTION"); + } + } + } } export const hardwareProxyService = { diff --git a/addons/point_of_sale/static/src/app/navbar/cash_move_popup/cash_move_popup.js b/addons/point_of_sale/static/src/app/navbar/cash_move_popup/cash_move_popup.js index bd25973b762..7393e7eaf53 100644 --- a/addons/point_of_sale/static/src/app/navbar/cash_move_popup/cash_move_popup.js +++ b/addons/point_of_sale/static/src/app/navbar/cash_move_popup/cash_move_popup.js @@ -108,4 +108,13 @@ export class CashMovePopup extends AbstractAwaitablePopup { this.state.errorMessage = ""; this.amountInput.el.focus(); } + async cancel() { + super.cancel(); + if ( + this.pos.globalState.config.iface_cashdrawer && + this.pos.globalState.hardwareProxy.connectionInfo.status === "connected" + ) { + this.pos.globalState.logEmployeeMessage(_t("Cash in / out"), "ACTION_CANCELLED"); + } + } } diff --git a/addons/point_of_sale/static/src/app/navbar/navbar.js b/addons/point_of_sale/static/src/app/navbar/navbar.js index 814309dbdde..1a9466728d7 100644 --- a/addons/point_of_sale/static/src/app/navbar/navbar.js +++ b/addons/point_of_sale/static/src/app/navbar/navbar.js @@ -13,6 +13,7 @@ import { TicketScreen } from "@point_of_sale/js/Screens/TicketScreen/TicketScree import { BackButton } from "@point_of_sale/app/navbar/BackButton"; import { Component, useState, useExternalListener } from "@odoo/owl"; import { ClosePosPopup } from "@point_of_sale/js/Popups/ClosePosPopup"; +import { _t } from "@web/core/l10n/translation"; export class Navbar extends Component { static template = "point_of_sale.Navbar"; @@ -50,9 +51,9 @@ export class Navbar extends Component { } onCashMoveButtonClick() { + this.hardwareProxy.openCashbox(_t("Cash in / out")); this.popup.add(CashMovePopup); } - async onTicketButtonClick() { if (this.isTicketScreenShown) { this.pos.closeScreen(); diff --git a/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js b/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js index 48779c0391e..758f69f66ea 100644 --- a/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js @@ -5,6 +5,7 @@ import { useAutofocus, useService } from "@web/core/utils/hooks"; import { usePos } from "@point_of_sale/app/pos_hook"; import { MoneyDetailsPopup } from "./MoneyDetailsPopup"; import { useState } from "@odoo/owl"; +import { _t } from "@web/core/l10n/translation"; export class CashOpeningPopup extends AbstractAwaitablePopup { static template = "CashOpeningPopup"; @@ -22,6 +23,7 @@ export class CashOpeningPopup extends AbstractAwaitablePopup { this.popup = useService("popup"); this.orm = useService("orm"); useAutofocus({ refName: "cash-input" }); + this.hardwareProxy = useService("hardware_proxy"); } //@override async confirm() { @@ -35,9 +37,12 @@ export class CashOpeningPopup extends AbstractAwaitablePopup { super.confirm(); } async openDetailsPopup() { + const action = _t("Cash control - opening"); + this.hardwareProxy.openCashbox(action); const { confirmed, payload } = await this.popup.add(MoneyDetailsPopup, { moneyDetails: this.moneyDetails, total: this.manualInputCashCount ? 0 : this.state.openingCash, + action: action, }); if (confirmed) { const { total, moneyDetails, moneyDetailsNotes } = payload; diff --git a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js index 6f6fbf1bde7..c36d8e6f6e1 100644 --- a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js @@ -70,11 +70,14 @@ export class ClosePosPopup extends AbstractAwaitablePopup { } } async openDetailsPopup() { + const action = _t("Cash control - closing"); + this.hardwareProxy.openCashbox(action); const { confirmed, payload } = await this.popup.add(MoneyDetailsPopup, { moneyDetails: this.moneyDetails, total: this.manualInputCashCount ? 0 : this.state.payments[this.defaultCashDetails.id].counted, + action: action, }); if (confirmed) { const { total, moneyDetailsNotes, moneyDetails } = payload; diff --git a/addons/point_of_sale/static/src/js/Popups/MoneyDetailsPopup.js b/addons/point_of_sale/static/src/js/Popups/MoneyDetailsPopup.js index b0037953a77..b78b5f3ea46 100644 --- a/addons/point_of_sale/static/src/js/Popups/MoneyDetailsPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/MoneyDetailsPopup.js @@ -17,6 +17,7 @@ export class MoneyDetailsPopup extends AbstractAwaitablePopup { ? { ...this.props.moneyDetails } : Object.fromEntries(this.pos.globalState.bills.map((bill) => [bill.value, 0])), total: this.props.total ? this.props.total : 0, + action: this.props.action ? this.props.action : null, }); } get firstHalfMoneyDetails() { @@ -52,6 +53,16 @@ export class MoneyDetailsPopup extends AbstractAwaitablePopup { total: this.state.total, moneyDetailsNotes, moneyDetails: { ...this.state.moneyDetails }, + action: this.state.action, }; } + async cancel() { + super.cancel(); + if ( + this.pos.globalState.config.iface_cashdrawer && + this.pos.globalState.hardwareProxy.connectionInfo.status === "connected" + ) { + this.pos.globalState.logEmployeeMessage(this.state.action, "ACTION_CANCELLED"); + } + } } diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js index 8d9d424cb66..789aaf7e729 100644 --- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js @@ -147,7 +147,7 @@ export class PaymentScreen extends Component { this.currentOrder.set_to_invoice(!this.currentOrder.is_to_invoice()); } openCashbox() { - this.hardwareProxy.printer.openCashbox(); + this.hardwareProxy.openCashbox(); } async addTip() { // click_tip @@ -227,11 +227,8 @@ export class PaymentScreen extends Component { } async _finalizeValidation() { const { globalState } = this.pos; - if ( - (this.currentOrder.is_paid_with_cash() || this.currentOrder.get_change()) && - globalState.config.iface_cashdrawer - ) { - this.hardwareProxy.printer.openCashbox(); + if (this.currentOrder.is_paid_with_cash() || this.currentOrder.get_change()) { + this.hardwareProxy.openCashbox(); } this.currentOrder.initialize_validation_date(); diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index e5edb3ca684..61e2d26c301 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1532,6 +1532,14 @@ export class PosGlobalState extends PosModel { switchPane() { this.mobile_pane = this.mobile_pane === "left" ? "right" : "left"; } + async logEmployeeMessage(action, message) { + await this.orm.call("pos.session", "log_partner_message", [ + this.pos_session.id, + this.user.partner_id.id, + action, + message, + ]); + } } PosGlobalState.prototype.electronic_payment_interfaces = {}; diff --git a/addons/pos_hr/models/pos_session.py b/addons/pos_hr/models/pos_session.py index 52dd747efb5..df367492f6b 100644 --- a/addons/pos_hr/models/pos_session.py +++ b/addons/pos_hr/models/pos_session.py @@ -25,7 +25,7 @@ class PosSession(models.Model): domain = ['&', ('company_id', '=', self.config_id.company_id.id), '|', ('user_id', '=', self.user_id.id), ('id', 'in', self.config_id.employee_ids.ids)] else: domain = [('company_id', '=', self.config_id.company_id.id)] - return {'search_params': {'domain': domain, 'fields': ['name', 'id', 'user_id'], 'load': False}} + return {'search_params': {'domain': domain, 'fields': ['name', 'id', 'user_id', 'work_contact_id'], 'load': False}} def _get_pos_ui_hr_employee(self, params): employees = self.env['hr.employee'].search_read(**params['search_params']) diff --git a/addons/pos_hr/static/src/js/models.js b/addons/pos_hr/static/src/js/models.js index 2d7b55e083b..27ac6f2663c 100644 --- a/addons/pos_hr/static/src/js/models.js +++ b/addons/pos_hr/static/src/js/models.js @@ -56,6 +56,18 @@ patch(PosGlobalState.prototype, "pos_hr.PosGlobalState", { } return this._super(...arguments); }, + async logEmployeeMessage(action, message) { + if (!this.config.module_pos_hr) { + this._super(...arguments); + return; + } + await this.orm.call("pos.session", "log_partner_message", [ + this.pos_session.id, + this.cashier.work_contact_id, + action, + message, + ]); + }, }); patch(Order.prototype, "pos_hr.Order", {