Files

83 lines
4.3 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from dateutil.relativedelta import relativedelta
import pytz
from odoo import fields, models
from odoo.tools.intervals import Intervals
class ProductProduct(models.Model):
_inherit = 'product.product'
def _renting_product_availabilities(self, min_date, max_date):
if not (
self.type == 'service'
and self.rent_ok
and self.planning_enabled
and (resources := self.planning_role_id.filtered('sync_shift_rental').resource_ids)
):
return []
min_date = fields.Datetime.to_datetime(min_date)
max_date = fields.Datetime.to_datetime(max_date)
slots_sudo = self.env['planning.slot'].sudo().search([
('resource_id', 'in', resources.ids),
('start_datetime', '<=', max_date),
('end_datetime', '>=', min_date),
], order='start_datetime') # In sudo mode to access to planning slots' fields from eCommerce.
rented_quantities = defaultdict(int)
slot_periods_per_resource = {}
for resource, slots in slots_sudo.grouped('resource_id').items():
slot_periods_per_resource[resource.id] = Intervals([])
for slot in slots:
rented_quantities[slot.start_datetime] += 1
rented_quantities[slot.end_datetime] -= 1
slot_periods_per_resource[resource.id] |= Intervals([(pytz.UTC.localize(slot.start_datetime), pytz.UTC.localize(slot.end_datetime), slot)])
if human_resources := resources.filtered(lambda r: r.resource_type != 'material'):
min_date_in_utc = pytz.UTC.localize(min_date)
max_date_in_utc = pytz.UTC.localize(max_date)
unavailable_intervals_per_resource_id = defaultdict(list)
unavailable_intervals_per_calendar_id = defaultdict(Intervals)
date = min_date_in_utc + relativedelta(hour=0, minute=0, second=0, microsecond=0)
while date < max_date_in_utc + relativedelta(days=1):
for calendar in human_resources.calendar_id:
if not calendar._works_on_date(date):
unavailable_intervals_per_calendar_id[calendar.id] |= Intervals([(
date,
date + relativedelta(hour=23, minute=59, second=59, microsecond=999999),
self.env['resource.calendar.leaves']
)])
date += relativedelta(days=1)
for calendar, workers in human_resources.grouped('calendar_id').items():
leave_intervals_batch_per_resource_id = calendar._leave_intervals_batch(min_date_in_utc, max_date_in_utc, workers)
for resource_id, leave_intervals in leave_intervals_batch_per_resource_id.items():
if not resource_id:
continue
unavailable_intervals_per_resource_id[resource_id] = leave_intervals | unavailable_intervals_per_calendar_id[calendar.id]
for resource_id, intervals in unavailable_intervals_per_resource_id.items():
slot_intervals = slot_periods_per_resource.get(resource_id, Intervals())
for start_dt, end_dt, _dummy in intervals - slot_intervals:
if start_dt < max_date_in_utc and end_dt > min_date_in_utc:
naive_start_dt = max(start_dt, min_date_in_utc).astimezone(pytz.UTC).replace(tzinfo=None)
naive_end_dt = min(end_dt, max_date_in_utc).astimezone(pytz.UTC).replace(tzinfo=None)
rented_quantities[naive_start_dt] += 1
rented_quantities[naive_end_dt] -= 1
key_dates = sorted(set(rented_quantities.keys()) | {min_date, max_date})
availabilities = []
current_qty_available = len(resources)
for i in range(1, len(key_dates)):
start_dt = key_dates[i - 1]
if start_dt > max_date:
break
current_qty_available -= rented_quantities[start_dt]
if start_dt >= min_date:
availabilities.append({
'start': start_dt,
'end': key_dates[i],
'quantity_available': current_qty_available,
})
return availabilities