1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/pos_preparation_display/models/preparation_display.py
2024-12-10 09:04:09 +07:00

213 lines
9.3 KiB
Python

import secrets
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo.addons.pos_preparation_display.models.preparation_display_orderline import PosPreparationDisplayOrderline
class PosPreparationDisplay(models.Model):
_name = 'pos_preparation_display.display'
_description = "Preparation display"
name = fields.Char("Name", required=True)
company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company)
pos_config_ids = fields.Many2many(string="Point of Sale", comodel_name='pos.config')
category_ids = fields.Many2many('pos.category', string="Product categories", help="Product categories that will be displayed on this screen.")
order_count = fields.Integer("Order count", compute='_compute_order_count')
average_time = fields.Integer("Order average time", compute='_compute_order_count', help="Average time of all order that not in a done stage.")
stage_ids = fields.One2many('pos_preparation_display.stage', 'preparation_display_id', string="Stages", default=[
{'name': 'To prepare', 'color': '#6C757D', 'alert_timer': 10},
{'name': 'Ready', 'color': '#4D89D1', 'alert_timer': 5},
{'name': 'Completed', 'color': '#4ea82a', 'alert_timer': 0}
])
contains_bar_restaurant = fields.Boolean("Is a Bar/Restaurant", compute='_compute_contains_bar_restaurant', store=True)
access_token = fields.Char("Access Token",
copy=False,
required=True,
readonly=True,
default=lambda self: self._get_access_token())
@staticmethod
def _get_access_token():
return secrets.token_hex(16)
@api.model_create_multi
def create(self, vals_list):
displays = super().create(vals_list)
displays.reset()
return displays
# getter for pos_category_ids and pos_config_ids, in case of no one selected, return all of each.
def _get_pos_category_ids(self):
self.ensure_one()
if not self.category_ids:
return self.env['pos.category'].search([])
else:
return self.category_ids
def _should_include(self, orderline: PosPreparationDisplayOrderline) -> bool:
"""
Returns whether the orderline should be included in the preparation
display, based on the categories that are selected for the preparation
"""
return any(categ_id in self._get_pos_category_ids().ids for categ_id in orderline.product_id.pos_categ_ids.ids)
def get_pos_config_ids(self):
self.ensure_one()
if not self.pos_config_ids:
return self.env['pos.config'].search([])
else:
return self.pos_config_ids
def _get_open_orders_in_display(self):
self.ensure_one()
PosPreparationDisplayOrder = self.env['pos_preparation_display.order']
open_orders = self.env['pos_preparation_display.order.stage']._read_group(
domain=[
('preparation_display_id', '=', self.id),
('order_id.pos_order_id.session_id.state', 'not in', ['closed', 'closing_control']),
],
groupby=['order_id'],
having=[('done:bool_or', '=', False)],
)
orders = PosPreparationDisplayOrder
if open_orders:
orders = PosPreparationDisplayOrder.union(*(order[0] for order in open_orders))
return orders
def _get_stageless_orders_in_display(self):
self.ensure_one()
stageless_orders_ids = self.env['pos_preparation_display.order']._search([
'|', ('pos_order_id', '=', False),
('pos_config_id', 'in', self.get_pos_config_ids().ids),
])
stageless_orders_ids.add_where(
"""
NOT EXISTS
(
SELECT 1
FROM pos_preparation_display_order_stage
WHERE order_id = pos_preparation_display_order.id AND preparation_display_id = %s
)
""",
(self.id,)
)
return self.env['pos_preparation_display.order'].browse(stageless_orders_ids)
def get_preparation_display_data(self):
return {
'categories': self._get_pos_category_ids().read(['id', 'display_name', 'sequence']),
'stages': self.stage_ids.read(),
'orders': self.env["pos_preparation_display.order"].get_preparation_display_order(self.id),
'attributes': self.env['product.attribute'].search([]).read(['id', 'name']),
'attribute_values': self.env['product.template.attribute.value'].search([]).read(['id', 'name', 'attribute_id']),
}
def open_reset_wizard(self):
return {
'name': _("Reset Preparation Display"),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'pos_preparation_display.reset.wizard',
'target': 'new',
'context': {'preparation_display_id': self.id}
}
def open_ui(self):
return {
'type': 'ir.actions.act_url',
'url': '/pos_preparation_display/web?display_id=%d' % self.id,
'target': 'self',
}
# if needed the user can instantly reset a preparation display and archive all the orders.
def reset(self):
for preparation_display in self:
last_stage = preparation_display.stage_ids[-1]
orders = preparation_display._get_open_orders_in_display()
new_orders = preparation_display._get_stageless_orders_in_display()
order_stages = []
for order in new_orders:
order_stages.append({
'preparation_display_id': preparation_display.id,
'stage_id': last_stage.id,
'order_id': order.id,
'done': True
})
self.env['pos_preparation_display.order.stage'].create(order_stages)
for order in orders:
current_order_stage = None
for stage in order.order_stage_ids[::-1]:
if stage.preparation_display_id.id == preparation_display.id:
current_order_stage = stage
break
current_order_stage.done = True
preparation_display._send_load_orders_message()
def _send_load_orders_message(self, sound=False):
self.ensure_one()
self.env['bus.bus']._sendone(f'preparation_display-{self.access_token}', 'load_orders', {
'preparation_display_id': self.id,
'sound': sound
})
@api.depends('stage_ids', 'pos_config_ids', 'category_ids')
def _compute_order_count(self):
for preparation_display in self:
progress_order_count = 0
orders = preparation_display.env['pos_preparation_display.order'].search([
('pos_config_id', 'in', preparation_display.get_pos_config_ids().ids),
('create_date', '>=', fields.Date.today())
])
for order in orders:
order_stage = order.order_stage_ids.filtered(lambda s: s.preparation_display_id.id == preparation_display.id)
if order_stage:
order_stage_last = sorted(order_stage, key=lambda s: s.write_date, reverse=True)[0]
if order_stage_last.stage_id.id == preparation_display.stage_ids[-1].id:
continue
for orderline in order.preparation_display_order_line_ids:
if preparation_display._should_include(orderline) and orderline.product_quantity > 0:
progress_order_count += 1
break
preparation_display.order_count = progress_order_count
order_stages = self.env['pos_preparation_display.order.stage'].search([
('preparation_display_id', '=', preparation_display.id),
('create_date', '>=', fields.Date.today()),
('done', '=', True)
])
completed_order_times = [(order_stage.write_date - order_stage.order_id.create_date).total_seconds() for order_stage in order_stages]
preparation_display.average_time = round(sum(completed_order_times) / len(completed_order_times) / 60) if completed_order_times else 0
@api.constrains('stage_ids')
def _check_stage_ids(self):
for preparation_display in self:
if len(preparation_display.stage_ids) == 0:
raise ValidationError(_("A preparation display must have a minimum of one step."))
@api.depends('pos_config_ids')
def _compute_contains_bar_restaurant(self):
for preparation_display in self:
preparation_display.contains_bar_restaurant = any(pos_config_id.module_pos_restaurant for pos_config_id in preparation_display.get_pos_config_ids())
@api.model
def pos_has_valid_product(self):
return self.env['product.product'].sudo().search_count([('available_in_pos', '=', True), ('list_price', '>=', 0), ('id', 'not in', self.env['pos.config']._get_special_products().ids)], limit=1) > 0
def load_product_frontend(self):
allowed = not self.pos_has_valid_product()
if allowed:
self.env['pos.session']._load_onboarding_data()
categories = self._get_pos_category_ids().read(['id', 'display_name', 'sequence'])
return categories