oca-ocb-sale/odoo-bringout-oca-ocb-point_of_sale/point_of_sale/models/pos_preset.py
Ernad Husremovic 73afc09215 19.0 vanilla
2026-03-09 09:32:12 +01:00

117 lines
5.1 KiB
Python

from odoo import fields, models, api, _
from odoo.exceptions import ValidationError, UserError
from datetime import datetime, timedelta
from collections import defaultdict
class PosPreset(models.Model):
_name = 'pos.preset'
_inherit = ['pos.load.mixin']
_description = 'Easily load a set of configuration options'
name = fields.Char(string='Label', required=True, translate=True)
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist')
fiscal_position_id = fields.Many2one('account.fiscal.position', string='Fiscal Position')
identification = fields.Selection([('none', 'Not required'), ('address', 'Address'), ('name', 'Name')], default="none", string='Identification', required=True)
is_return = fields.Boolean(string='Return mode', default=False, help="All quantity in the cart will be in negative. Ideal for return managment.")
color = fields.Integer(string='Color', default=0)
image_512 = fields.Image(string='Image', max_width=512, max_height=512)
image_128 = fields.Image(string='Image 128', related="image_512", max_width=128, max_height=128, store=True)
has_image = fields.Boolean(compute='_compute_has_image')
count_linked_orders = fields.Integer(compute='_compute_count_linked_orders')
count_linked_config = fields.Integer(compute='_compute_count_linked_config')
# Timing options
use_timing = fields.Boolean(string='Manage orders by time', default=False)
resource_calendar_id = fields.Many2one('resource.calendar', 'Resource')
attendance_ids = fields.One2many(related="resource_calendar_id.attendance_ids", string="Attendances", readonly=False)
slots_per_interval = fields.Integer(string='Capacity', default=5)
interval_time = fields.Integer(string='Interval time (in min)', default=20)
@api.constrains('attendance_ids')
def _check_slots(self):
for preset in self:
for attendance in preset.attendance_ids:
if attendance.hour_from % 24 >= attendance.hour_to % 24:
raise ValidationError(_('The start time must be before the end time.'))
@api.model
def _load_pos_data_domain(self, data, config):
preset_ids = config.available_preset_ids.ids + [config.default_preset_id.id]
return [('id', 'in', preset_ids)]
@api.model
def _load_pos_data_fields(self, config):
return ['id', 'name', 'pricelist_id', 'fiscal_position_id', 'is_return', 'color', 'has_image', 'write_date', 'identification',
'use_timing', 'slots_per_interval', 'interval_time', 'attendance_ids']
def _compute_count_linked_orders(self):
for record in self:
record.count_linked_orders = self.env['pos.order'].search_count([('preset_id', 'in', record.ids)])
def _compute_count_linked_config(self):
for record in self:
record.count_linked_config = self.env['pos.config'].search_count([
'|', ('default_preset_id', 'in', record.ids),
('available_preset_ids', 'in', record.ids)
])
@api.depends('has_image')
def _compute_has_image(self):
for record in self:
record.has_image = bool(record.image_512)
# Slots are created directly here in the form of dates, to avoid polluting
# the database with a “slots” model. All we need is the slot time, and with the preset
# information we can deduce the maximum occupancy per slot.
def get_available_slots(self):
self.ensure_one()
usage = self._compute_slots_usage()
return {
'usage_utc': usage,
}
def _compute_slots_usage(self):
usage = defaultdict(int)
orders = self.env['pos.order'].search([
('preset_id', '=', self.id),
('session_id.state', '=', 'opened'),
('preset_time', '!=', False),
('state', 'in', ['draft', 'paid']),
('create_date', '>=', fields.Datetime.now() - timedelta(days=1))
])
for order in orders:
sql_datetime_str = order.preset_time.strftime("%Y-%m-%d %H:%M:%S")
if not usage[sql_datetime_str]:
usage[sql_datetime_str] = []
usage[sql_datetime_str].append(order.id)
return usage
def action_open_linked_orders(self):
self.ensure_one()
return {
'name': _('Linked Orders'),
'view_mode': 'list',
'res_model': 'pos.order',
'type': 'ir.actions.act_window',
'domain': [('preset_id', '=', self.id)],
}
def action_open_linked_config(self):
self.ensure_one()
return {
'name': _('Linked POS Configurations'),
'view_mode': 'list',
'res_model': 'pos.config',
'type': 'ir.actions.act_window',
'domain': ['|', ('default_preset_id', '=', self.id), ('available_preset_ids', 'in', self.id)]
}
@api.ondelete(at_uninstall=False)
def _unlink_except_used_preset(self):
for preset in self:
if preset.count_linked_config:
raise UserError(_('You cannot delete a preset that is linked to a POS configuration.'))