diff --git a/pos_multi_session/models/pos_multi_session_models.py b/pos_multi_session/models/pos_multi_session_models.py index ce024a3a13..d513e7012a 100644 --- a/pos_multi_session/models/pos_multi_session_models.py +++ b/pos_multi_session/models/pos_multi_session_models.py @@ -5,8 +5,11 @@ # License MIT (https://opensource.org/licenses/MIT). import logging +from odoo import api, models, fields, _ +from odoo.exceptions import UserError +import datetime +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT -from odoo import api, fields, models _logger = logging.getLogger(__name__) @@ -60,35 +63,40 @@ def _search_current_session_state(self, operator, value): else: return [("id", "in", [])] + @api.multi + def open_ui(self): + res = super(PosConfig, self).open_ui() + active_sessions = self.env['pos.session'].search( + [('state', '!=', 'closed'), ('config_id.multi_session_id', '=', self.multi_session_id.id)]) + if len(active_sessions) == 1 and active_sessions.id == self.current_session_id.id and self.multi_session_id.load_unpaid_orders: + orders = self.multi_session_id.get_unpaid_ms_orders() + if orders: + orders.write({ + 'state': 'draft', + 'run_ID': self.multi_session_id.run_ID + }) + return res + class PosMultiSession(models.Model): _name = "pos.multi_session" - name = fields.Char("Name") - multi_session_active = fields.Boolean( - string="Active", - help="Select the checkbox to enable synchronization for POSes", - default=True, - ) - pos_ids = fields.One2many( - "pos.config", "multi_session_id", string="POSes in Multi-session" - ) - order_ID = fields.Integer( - string="Order number", - default=0, - help="Current Order Number shared across all POS in Multi Session", - ) - sync_server = fields.Char("Sync Server", default="") - run_ID = fields.Integer( - string="Running count", - default=1, - help="Number of Multi-session starts. " - "It's incremented each time the last session in Multi-session is closed. " - "It's used to prevent synchronization of old orders", - ) - fiscal_position_ids = fields.Many2many( - "account.fiscal.position", string="Fiscal Positions", ondelete="restrict" - ) + name = fields.Char('Name') + multi_session_active = fields.Boolean(string="Active", help="Select the checkbox to enable synchronization for POSes", default=True) + pos_ids = fields.One2many('pos.config', 'multi_session_id', string='POSes in Multi-session') + order_ID = fields.Integer(string="Order number", default=0, help="Current Order Number shared across all POS in Multi Session") + sync_server = fields.Char('Sync Server', default='') + run_ID = fields.Integer(string="Running count", default=1, + help="Number of Multi-session starts. " + "It's incremented each time the last session in Multi-session is closed. " + "It's used to prevent synchronization of old orders") + fiscal_position_ids = fields.Many2many('account.fiscal.position', string='Fiscal Positions', ondelete="restrict") + load_unpaid_orders = fields.Boolean(string="Load Unpaid Orders", default=False, + help="Allows you to load unpaid orders to POS." + "Please close all POS sessions before loading unpaid orders.") + load_orders_of_last_n_days = fields.Boolean("Unpaid Orders of last 'n' days", default=False, + help="if the setting is disabled then all orders will be loaded to POS") + number_of_days = fields.Integer("Number of days", default=0, help='0 - load orders of current day') company_id = fields.Many2one( "res.company", string="Company", @@ -96,6 +104,33 @@ class PosMultiSession(models.Model): default=lambda self: self.env.user.company_id, ) + @api.constrains('load_unpaid_orders') + def _check_load_unpaid_orders(self): + if self.load_unpaid_orders: + active_sessions = self.env['pos.session'].search( + [('state', '!=', 'closed'), ('config_id.multi_session_id', '=', self.id)]) + if active_sessions: + raise UserError(_("Please close all POSes for this multi-session for load unpaid Orders.")) + + @api.constrains('number_of_days') + def _check_number_of_days(self): + if self.load_unpaid_orders and self.load_orders_of_last_n_days and self.number_of_days < 0: + raise UserError(_('The number of days should not be negative.')) + + @api.multi + def get_unpaid_ms_orders(self): + self.ensure_one() + pos_multi_session_sync = self.env['pos_multi_session_sync.multi_session'].search([('multi_session_ID', '=', self.id)]) + if self.load_orders_of_last_n_days: + limit_date = datetime.datetime.utcnow() - datetime.timedelta(days=self.number_of_days) + limit_date_str = datetime.datetime.strftime(limit_date, DEFAULT_SERVER_DATE_FORMAT + ' 00:00:00') + return self.env['pos_multi_session_sync.order'].search([('multi_session_ID', 'in', pos_multi_session_sync.ids), + ('state', '=', 'unpaid'), + ('write_date', '>=', limit_date_str)]) + + return self.env['pos_multi_session_sync.order'].search([("multi_session_ID", "=", pos_multi_session_sync.multi_session_ID), + ('state', '=', 'unpaid')]) + @api.multi def action_set_default_multi_session(self): """ @@ -137,5 +172,10 @@ def action_pos_session_close(self): if len(active_sessions) == 0: self.config_id.multi_session_id.sudo().write({"order_ID": 0}) run_ID = self.config_id.multi_session_id.run_ID + 1 - self.config_id.multi_session_id.sudo().write({"run_ID": run_ID}) + self.config_id.multi_session_id.sudo().write({'run_ID': run_ID}) + pos_multi_session_sync = self.env['pos_multi_session_sync.multi_session'].search( + [('multi_session_ID', '=', self.config_id.multi_session_id.id)]) + orders = self.env['pos_multi_session_sync.order'].search([('multi_session_ID', '=', pos_multi_session_sync.multi_session_ID), + ('state', '=', 'draft')]) + orders.write({'state': 'unpaid'}) return res diff --git a/pos_multi_session/multi_session_view.xml b/pos_multi_session/multi_session_view.xml index 34d442e251..28820ce30d 100644 --- a/pos_multi_session/multi_session_view.xml +++ b/pos_multi_session/multi_session_view.xml @@ -17,21 +17,14 @@ <field name="name" /> </h1> <group string="Settings"> - <field - name="pos_ids" - widget="many2many_tags" - domain="[('current_session_state', '!=', 'opened')]" - options="{'not_delete': True}" - /> - <field name="multi_session_active" /> - <field name="sync_server" placeholder="http://yourhost" /> - <field - name="fiscal_position_ids" - widget="many2many_tags" - options="{'not_delete': True}" - /> - <field name="company_id" groups="base.group_multi_company" /> - <field name="order_ID" readonly="1" /> + <field name="pos_ids" widget="many2many_tags" domain="[('current_session_state', '!=', 'opened')]" options="{'not_delete': True}"/> + <field name="multi_session_active"/> + <field name="sync_server" placeholder="http://yourhost"/> + <field name="fiscal_position_ids" widget="many2many_tags" options="{'not_delete': True}"/> + <field name="order_ID" readonly="1"/> + <field name="load_unpaid_orders"/> + <field name="load_orders_of_last_n_days" attrs="{'invisible':[('load_unpaid_orders', '=', False)]}"/> + <field name="number_of_days" attrs="{'invisible':['|', ('load_unpaid_orders', '=', False), ('load_orders_of_last_n_days', '=', False)]}"/> </group> <group> <p diff --git a/pos_multi_session/views/pos_multi_session_views.xml b/pos_multi_session/views/pos_multi_session_views.xml index c7bf83f334..2881d7b97e 100644 --- a/pos_multi_session/views/pos_multi_session_views.xml +++ b/pos_multi_session/views/pos_multi_session_views.xml @@ -33,6 +33,77 @@ /> </xpath> </template> + + <record id="view_pos_multi_session_sync_order_form" model="ir.ui.view"> + <field name="name">pos_multi_session_sync.order.form</field> + <field name="model">pos_multi_session_sync.order</field> + <field name="arch" type="xml"> + <form string="POS Multi-Session Sync Orders" create="false" edit="false" delete="false"> + <header> + <button name="action_pos_multi_session_restore_order" string="Restore" type="object" states="deleted"/> + <field name="state" widget="statusbar" statusbar_visible="draft,paid,deleted" /> + </header> + <sheet> + <group col="4" colspan="4"> + <field name="order_uid"/> + <field name="write_date"/> + <field name="revision_ID"/> + <field name="multi_session_ID"/> + <field name="pos_session_ID"/> + <field name="run_ID"/> + </group> + <group> + <field name="order"/> + </group> + </sheet> + </form> + </field> + </record> + + <record id="view_pos_multi_session_sync_order_tree" model="ir.ui.view"> + <field name="name">pos_multi_session_sync.order.tree</field> + <field name="model">pos_multi_session_sync.order</field> + <field name="arch" type="xml"> + <tree create="0"> + <field name="order_uid"/> + <field name="write_date"/> + <field name="state"/> + </tree> + </field> + </record> + + <record id="view_pos_multi_session_sync_order_filter" model="ir.ui.view"> + <field name="name">pos_multi_session_sync.order.filter</field> + <field name="model">pos_multi_session_sync.order</field> + <field name="arch" type="xml"> + <search string="Search POS Multi-Session Order"> + <field name="state"/> + <filter name="paid" string="Paid" domain="[('state','=','paid')]"/> + <filter name="new" string="New" domain="[('state','=','draft')]"/> + <filter name="deleted" string="Deleted" domain="[('state','=','deleted')]"/> + <filter name="unpaid" string="Unpaid" domain="[('state','=','unpaid')]"/> + </search> + </field> + </record> + + <record id="action_pos_multi_session_sync_order" model="ir.actions.act_window"> + <field name="name">Multi Session Orders</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">pos_multi_session_sync.order</field> + <field name="view_type">form</field> + <field name="view_id" ref="view_pos_multi_session_sync_order_tree"/> + <field name="view_mode">tree,form</field> + <field name="context">{'search_default_deleted': 1} + </field> + </record> + + <menuitem id="menu_multi_session_orders" + parent="point_of_sale.menu_point_of_sale" + action="action_pos_multi_session_sync_order" + sequence="99" + groups="base.group_no_one"/> + + <record model="ir.ui.view" id="view_pos_config_form"> <field name="name">pos.config.form.view.inherit</field> <field name="model">pos.config</field> diff --git a/pos_multi_session_sync/models/pos_multi_session_sync_models.py b/pos_multi_session_sync/models/pos_multi_session_sync_models.py index f587d4186c..ed5fa63713 100644 --- a/pos_multi_session_sync/models/pos_multi_session_sync_models.py +++ b/pos_multi_session_sync/models/pos_multi_session_sync_models.py @@ -63,18 +63,11 @@ def on_update_message(self, message): @api.multi def prepare_new_session(self, message): self.ensure_one() - run_ID = message["data"]["run_ID"] - self.write({"run_ID": run_ID}) - old_orders = self.env["pos_multi_session_sync.order"].search( - [ - ("multi_session_ID", "=", self.multi_session_ID), - ("state", "=", "draft"), - ("run_ID", "<", run_ID), - ] - ) - if old_orders: - old_orders.write({"state": "unpaid"}) - self.write({"order_ID": 0}) + run_ID = message['data']['run_ID'] + self.write({ + 'run_ID': run_ID, + 'order_ID': 0 + }) @api.multi def check_order_revision(self, message, order): @@ -168,17 +161,10 @@ def set_order(self, message): run_ID = order.run_ID or message["data"]["run_ID"] or False if revision == "nonce": - return (False, {"action": ""}) - elif not revision or (order and order.state == "deleted"): - _logger.debug("Revision error %s %s", order_uid, order.state) - return ( - False, - { - "action": "revision_error", - "order_uid": order_uid, - "state": order.state, - }, - ) + return (False, {'action': ''}) + elif not revision or (order and (order.state == 'deleted' or order.state == 'paid')): + _logger.debug('Revision error %s %s', order_uid, order.state) + return (False, {'action': 'revision_error', 'order_uid': order_uid, 'state': order.state}) if order: # order already exists message = self.set_changes(message, order) @@ -297,18 +283,16 @@ def remove_order(self, message): order, order_data = self.set_order(order_data) order_uid = order.order_uid - if order.state != "deleted": + if order.state not in ['deleted', 'paid']: revision = self.check_order_revision(message, order) if not revision: - return {"action": "revision_error", "order_uid": order_uid} + return {'action': 'revision_error', 'order_uid': order_uid} + + state = 'paid' if message['data']['finalized'] else 'deleted' if order: - order.state = "deleted" - _logger.debug( - "Remove Order: %s Finalized: %s Revision: %s", - order_uid, - message["data"]["finalized"], - message["data"]["revision_ID"], - ) + order.state = state + _logger.debug('Remove Order: %s Finalized: %s Revision: %s', + order_uid, message['data']['finalized'], message['data']['revision_ID']) self.broadcast_message(message) return {"order_ID": self.order_ID} @@ -365,26 +349,30 @@ def broadcast_message(self, message): class PosMultiSessionSyncOrder(models.Model): - _name = "pos_multi_session_sync.order" + _name = 'pos_multi_session_sync.order' + _order = 'write_date desc' + _rec_name = 'order_uid' order = fields.Text("Order JSON format") nonce = fields.Char("Random nonce") order_uid = fields.Char(index=True) - state = fields.Selection( - [("draft", "Draft"), ("deleted", "Deleted"), ("unpaid", "Unpaid and removed")], - default="draft", - index=True, - ) - revision_ID = fields.Integer( - default=1, string="Revision", help="Number of updates received from clients" - ) - multi_session_ID = fields.Integer(default=0, string="Multi session") - pos_session_ID = fields.Integer(index=True, default=0, string="POS session") - run_ID = fields.Integer( - index=True, - string="Running count", - default=1, - help="Number of Multi-session starts. " - "It's incremented each time the last session in Multi-session is closed. " - "It's used to prevent synchronization of old orders", - ) + state = fields.Selection([('draft', 'Draft'), ('deleted', 'Deleted'), ('unpaid', 'Unpaid and removed'), + ('paid', 'Paid')], default='draft', index=True) + revision_ID = fields.Integer(default=1, string="Revision", help="Number of updates received from clients") + multi_session_ID = fields.Integer(default=0, string='Multi session') + pos_session_ID = fields.Integer(index=True, default=0, string='POS session') + run_ID = fields.Integer(index=True, string="Running count", default=1, + help="Number of Multi-session starts. " + "It's incremented each time the last session in Multi-session is closed. " + "It's used to prevent synchronization of old orders") + + @api.multi + def action_pos_multi_session_restore_order(self): + for r in self: + # sync_ms = self.env['pos_multi_session_sync.multi_session'].browse(r.multi_session_ID) + sync_ms = self.env['pos_multi_session_sync.multi_session'].search([('multi_session_ID', '=', r.multi_session_ID)]) + r.write({ + 'state': 'draft', + 'run_ID': sync_ms.run_ID + }) + # TODO: send the order to POSes via bus after restore