forked from Mapan/odoo17e
152 lines
8.2 KiB
Python
152 lines
8.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import api, fields, models, _
|
|
from odoo.tools.translate import html_translate
|
|
|
|
|
|
class AppointmentResource(models.Model):
|
|
_name = "appointment.resource"
|
|
_description = "Appointment Resource"
|
|
_inherit = ["avatar.mixin", "resource.mixin"]
|
|
_order = 'sequence,id'
|
|
|
|
name = fields.Char('Appointment Resource', related="resource_id.name", store=True, required=True, readonly=False)
|
|
active = fields.Boolean('Active', related="resource_id.active", default=True, store=True, readonly=False)
|
|
sequence = fields.Integer("Sequence", default=1, required=True,
|
|
help="""The sequence dictates if the resource is going to be picked in higher priority against another resource
|
|
(e.g. for 2 tables of 4, the lowest sequence will be picked first)""")
|
|
company_id = fields.Many2one(default=False)
|
|
resource_id = fields.Many2one(copy=False)
|
|
resource_calendar_id = fields.Many2one(
|
|
default=lambda self: (
|
|
self.env.ref('appointment.appointment_default_resource_calendar', raise_if_not_found=False) or
|
|
self.env.company.resource_calendar_id),
|
|
help="If kept empty, the working schedule of the company set on the resource will be used")
|
|
capacity = fields.Integer("Capacity", default=1, required=True,
|
|
help="""Maximum amount of people for this resource (e.g. Table for 6 persons, ...)""")
|
|
shareable = fields.Boolean("Shareable",
|
|
help="""This allows to share the resource with multiple attendee for a same time slot (e.g. a bar counter)""")
|
|
source_resource_ids = fields.Many2many(
|
|
'appointment.resource', 'appointment_resource_linked_appointment_resource',
|
|
'resource_id', 'linked_resource_id',
|
|
domain="[('id', '!=', id)]",
|
|
string='Source combination',
|
|
)
|
|
destination_resource_ids = fields.Many2many(
|
|
'appointment.resource', 'appointment_resource_linked_appointment_resource',
|
|
'linked_resource_id', 'resource_id',
|
|
domain="[('id', '!=', id)]",
|
|
string='Destination combination',
|
|
)
|
|
linked_resource_ids = fields.Many2many(
|
|
'appointment.resource',
|
|
compute='_compute_linked_resource_ids',
|
|
inverse='_inverse_linked_resource_ids',
|
|
domain="[('id', '!=', id)]",
|
|
store=False,
|
|
help="""List of resources that can be combined to handle a bigger demand.""")
|
|
description = fields.Html("Description", translate=html_translate, sanitize_attributes=False)
|
|
appointment_type_ids = fields.Many2many('appointment.type', string="Available in",
|
|
relation="appointment_type_appointment_resource_rel",
|
|
domain="[('schedule_based_on', '=', 'resources')]")
|
|
|
|
_sql_constraints = [
|
|
('check_capacity', 'check(capacity >= 1)', 'The resource should have at least one capacity.')
|
|
]
|
|
|
|
@api.depends('source_resource_ids', 'destination_resource_ids')
|
|
def _compute_linked_resource_ids(self):
|
|
""" Compute based on two sided many2many relationships. Resources used
|
|
as source or destination of a relationship are combinable both ways. """
|
|
for resource in self:
|
|
linked = resource.source_resource_ids | resource.destination_resource_ids
|
|
resource.linked_resource_ids = linked
|
|
|
|
def _inverse_linked_resource_ids(self):
|
|
""" Update combination. When having new combination, consider current
|
|
record is always the source to simplify. When having to remove links
|
|
remove from both source and destination relationships to be sure to
|
|
really break the link. """
|
|
for resource in self:
|
|
actual_resources = resource.linked_resource_ids
|
|
current_resources = resource.source_resource_ids | resource.destination_resource_ids
|
|
new_resources = actual_resources - current_resources
|
|
old_resources = current_resources - actual_resources
|
|
resource.source_resource_ids = resource.source_resource_ids + new_resources - old_resources
|
|
resource.destination_resource_ids = resource.destination_resource_ids - old_resources
|
|
|
|
@api.depends('capacity')
|
|
def _compute_display_name(self):
|
|
""" Display the capacity of the resource next to its name if resource_manage_capacity is enabled """
|
|
for resource in self:
|
|
resource_name_capacity = f"{resource.name} (🪑{resource.capacity})"
|
|
display_name = resource_name_capacity if resource.capacity > 1 else resource.name
|
|
resource.display_name = display_name
|
|
|
|
def copy(self, default=None):
|
|
default = dict(default or {})
|
|
if not default.get('name'):
|
|
default['name'] = _("%(original_name)s (copy)", original_name=self.name)
|
|
return super().copy(default)
|
|
|
|
def _get_filtered_possible_capacity_combinations(self, asked_capacity, capacity_info):
|
|
""" Get combinations of resources with total capacity based on the capacity needed and the resources we want.
|
|
:param int asked_capacity: asked capacity for the appointment
|
|
:param dict main_resources_remaining_capacity: main resources available with the according total remaining capacity
|
|
:param dict linked_resources_remaining_capacity: linked resources with the according remaining capacity
|
|
:return list of tuple: e.g. [
|
|
((1, 3), 8), # here the key: (1, 3) => combination of resource_ids; the value: 8 => remaining capacity for these resources
|
|
((1, 2, 3), 10),
|
|
]"""
|
|
capacities = {}
|
|
# get all capacities combination for the resources
|
|
for resource in self:
|
|
capacities.update(
|
|
resource._get_possible_capacity_combinations(capacity_info))
|
|
# filter capacities combination that can fit the asked capacity for a group of resources
|
|
possible_capacities = {
|
|
resource_ids: remaining_capacity
|
|
for resource_ids, remaining_capacity in capacities.items()
|
|
if remaining_capacity >= asked_capacity and all(resource_id in self.ids for resource_id in resource_ids)
|
|
}
|
|
# Sort possible_capacities by capacity and number of resources used in the combination
|
|
# possible_capacity[0] = resource_ids and possible_capacity[1] = capacity
|
|
return sorted(possible_capacities.items(), key=lambda possible_capacity: (possible_capacity[1], len(possible_capacity[0])))
|
|
|
|
def _get_possible_capacity_combinations(self, capacity_info):
|
|
""" Return the possible capacity combination for the resource with all possible linked resources.
|
|
:param dict main_resources_remaining_capacity: main resources available with the according total remaining capacity
|
|
:param dict linked_resources_remaining_capacity: linked resources with the according remaining capacity
|
|
:return: a dict where the key is a tuple of resource ids and the value is the total remaining capacity of these resources
|
|
e.g. {
|
|
(1): 4,
|
|
(1, 2): 6,
|
|
(1, 3): 8,
|
|
(1, 2, 3): 10,
|
|
}
|
|
"""
|
|
self.ensure_one()
|
|
resource_remaining_capacity = capacity_info.get(self, {}).get('remaining_capacity', self.capacity)
|
|
capacities = {
|
|
tuple(self.ids): resource_remaining_capacity,
|
|
}
|
|
for linked_resource in self.linked_resource_ids.sorted('sequence'):
|
|
capacities_to_add = {}
|
|
for resource_ids, capacity in capacities.items():
|
|
new_resource_ids = set(resource_ids)
|
|
new_resource_ids.add(linked_resource.id)
|
|
linked_resource_capacity = capacity_info.get(linked_resource, {}).get('remaining_capacity', linked_resource.capacity)
|
|
capacities_to_add.update({
|
|
tuple(new_resource_ids): capacity + linked_resource_capacity,
|
|
})
|
|
capacities.update(capacities_to_add)
|
|
return capacities
|
|
|
|
def _prepare_resource_values(self, vals, tz):
|
|
""" Override of the resource.mixin model method to force "material" as resource type for
|
|
the resources created for our appointment.resources """
|
|
resource_values = super()._prepare_resource_values(vals, tz)
|
|
resource_values['resource_type'] = 'material'
|
|
return resource_values
|