[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) <moda@odoo.com>
This commit is contained in:
Loukas Wets (lowe)
2023-06-01 17:55:09 +02:00
parent ab8615eed5
commit 2649dc3205
11 changed files with 70 additions and 9 deletions
+8 -1
View File
@@ -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')
@@ -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 = {
@@ -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");
}
}
}
@@ -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();
@@ -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;
@@ -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;
@@ -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");
}
}
}
@@ -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();
@@ -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 = {};
+1 -1
View File
@@ -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'])
+12
View File
@@ -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", {