diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py
index 74a739515d0..bbab3b251a4 100644
--- a/addons/point_of_sale/models/pos_config.py
+++ b/addons/point_of_sale/models/pos_config.py
@@ -106,7 +106,7 @@ class PosConfig(models.Model):
session_ids = fields.One2many('pos.session', 'config_id', string='Sessions')
current_session_id = fields.Many2one('pos.session', compute='_compute_current_session', string="Current Session")
current_session_state = fields.Char(compute='_compute_current_session')
- number_of_opened_session = fields.Integer(string="Number of Opened Session", compute='_compute_current_session')
+ number_of_rescue_session = fields.Integer(string="Number of Rescue Session", compute='_compute_current_session')
last_session_closing_cash = fields.Float(compute='_compute_last_session')
last_session_closing_date = fields.Date(compute='_compute_last_session')
pos_session_username = fields.Char(compute='_compute_current_session_user')
@@ -213,13 +213,14 @@ class PosConfig(models.Model):
"""If there is an open session, store it to current_session_id / current_session_State.
"""
for pos_config in self:
- opened_sessions = pos_config.session_ids.filtered(lambda s: not s.state == 'closed')
- session = pos_config.session_ids.filtered(lambda s: not s.state == 'closed' and not s.rescue)
+ opened_sessions = pos_config.session_ids.filtered(lambda s: s.state != 'closed')
+ rescue_sessions = opened_sessions.filtered('rescue')
+ session = pos_config.session_ids.filtered(lambda s: s.state != 'closed' and not s.rescue)
# sessions ordered by id desc
- pos_config.number_of_opened_session = len(opened_sessions)
pos_config.has_active_session = opened_sessions and True or False
pos_config.current_session_id = session and session[0].id or False
pos_config.current_session_state = session and session[0].state or False
+ pos_config.number_of_rescue_session = len(rescue_sessions)
@api.depends('session_ids')
def _compute_last_session(self):
@@ -543,13 +544,13 @@ class PosConfig(models.Model):
'type': 'ir.actions.act_window',
}
- def open_opened_session_list(self):
+ def open_opened_rescue_session_form(self):
+ self.ensure_one()
return {
- 'name': _('Opened Sessions'),
'res_model': 'pos.session',
- 'view_mode': 'tree,kanban,form',
+ 'view_mode': 'form',
+ 'res_id': self.session_ids.filtered(lambda s: s.state != 'closed' and s.rescue).id,
'type': 'ir.actions.act_window',
- 'domain': [('state', '!=', 'closed'), ('config_id', '=', self.id)]
}
# All following methods are made to create data needed in POS, when a localisation
diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py
index 0bebe549f71..1cfd0636359 100644
--- a/addons/point_of_sale/models/pos_session.py
+++ b/addons/point_of_sale/models/pos_session.py
@@ -494,7 +494,16 @@ class PosSession(models.Model):
if any(order.state == 'draft' for order in self.order_ids):
return {'successful': False, 'message': _("You cannot close the POS when orders are still in draft"), 'redirect': False}
if self.state == 'closed':
- return {'successful': False, 'message': _("This session is already closed."), 'redirect': True}
+ return {
+ 'successful': False,
+ 'type': 'alert',
+ 'title': 'Session already closed',
+ 'message': _("The session has been already closed by another User. "
+ "All sales completed in the meantime have been saved in a "
+ "Rescue Session, which can be reviewed anytime and posted "
+ "to Accounting from Point of Sale's dashboard."),
+ 'redirect': True
+ }
if bank_payment_method_diffs:
no_loss_account = self.env['account.journal']
no_profit_account = self.env['account.journal']
diff --git a/addons/point_of_sale/static/src/js/Popups/AlertPopup.js b/addons/point_of_sale/static/src/js/Popups/AlertPopup.js
new file mode 100644
index 00000000000..a6acd64d4f7
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/Popups/AlertPopup.js
@@ -0,0 +1,17 @@
+/** @odoo-module */
+
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
+import { _lt } from "@web/core/l10n/translation";
+
+export class AlertPopup extends AbstractAwaitablePopup {}
+
+AlertPopup.template = "AlertPopup";
+AlertPopup.defaultProps = {
+ confirmText: _lt("Ok"),
+ title: "",
+ body: "",
+ cancelKey: false,
+};
+
+Registries.Component.add(AlertPopup);
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 befc433b188..dc58a0ff984 100644
--- a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js
@@ -132,11 +132,26 @@ class ClosePosPopup extends AbstractAwaitablePopup {
return this.handleClosingError(response);
}
}
- await this.rpc({
- model: "pos.session",
- method: "update_closing_control_state_session",
- args: [this.env.pos.pos_session.id, this.state.notes],
- });
+
+ try {
+ await this.rpc({
+ model: "pos.session",
+ method: "update_closing_control_state_session",
+ args: [this.env.pos.pos_session.id, this.state.notes],
+ });
+ } catch (error) {
+ // We have to handle the error manually otherwise the validation check stops the script.
+ // In case of "rescue session", we want to display the next popup with "handleClosingError".
+ // FIXME
+ if (
+ !error.message &&
+ !error.message.data &&
+ error.message.data.message !== "This session is already closed."
+ ) {
+ throw error;
+ }
+ }
+
try {
const bankPaymentMethodDiffPairs = this.otherPaymentMethods
.filter((pm) => pm.type == "bank")
@@ -170,11 +185,23 @@ class ClosePosPopup extends AbstractAwaitablePopup {
window.location = "/web#action=point_of_sale.action_client_pos_menu";
}
}
- this.closeSessionClicked = false;
}
+ this.closeSessionClicked = false;
}
+
async handleClosingError(response) {
- await this.showPopup("ErrorPopup", { title: "Error", body: response.message });
+ let popupType = "";
+ let title = "";
+
+ if (response.type == "alert") {
+ popupType = "AlertPopup";
+ title = response.title ? response.title : "";
+ } else {
+ popupType = "ErrorPopup";
+ title = "Error";
+ }
+
+ await this.showPopup(popupType, { title: title, body: response.message });
if (response.redirect) {
window.location = "/web#action=point_of_sale.action_client_pos_menu";
}
diff --git a/addons/point_of_sale/static/src/xml/Popups/AlertPopup.xml b/addons/point_of_sale/static/src/xml/Popups/AlertPopup.xml
new file mode 100644
index 00000000000..3c2dd7a07ec
--- /dev/null
+++ b/addons/point_of_sale/static/src/xml/Popups/AlertPopup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/addons/point_of_sale/views/point_of_sale_dashboard.xml b/addons/point_of_sale/views/point_of_sale_dashboard.xml
index 2bfddd213c0..f7246b5986f 100644
--- a/addons/point_of_sale/views/point_of_sale_dashboard.xml
+++ b/addons/point_of_sale/views/point_of_sale_dashboard.xml
@@ -60,7 +60,7 @@
-
+
@@ -149,9 +149,11 @@
-
+