Skip to content

Commit

Permalink
WIP: Write implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Carmen Bianca BAKKER <[email protected]>
  • Loading branch information
carmenbianca committed Aug 29, 2024
1 parent 5440859 commit 75568a3
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 8 deletions.
1 change: 1 addition & 0 deletions resource_multi_week_calendar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

from . import models
from .hooks import post_load_hook
3 changes: 1 addition & 2 deletions resource_multi_week_calendar/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,5 @@
"data": [
"views/resource_calendar_views.xml",
],
"demo": [],
"qweb": [],
"post_load": "post_load_hook",
}
135 changes: 135 additions & 0 deletions resource_multi_week_calendar/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# SPDX-FileCopyrightText: 2024 Coop IT Easy SC
# SPDX-FileCopyrightText: Odoo SA
#
# SPDX-License-Identifier: AGPL-3.0-or-later AND LGPL-3.0-or-later

# flake8: noqa

import datetime
import itertools
from collections import defaultdict

from dateutil.rrule import DAILY, rrule
from pytz import timezone, utc

from odoo.osv import expression

from odoo.addons.resource.models.resource import (
Intervals,
ResourceCalendar,
float_to_time,
)


def post_load_hook():
if not hasattr(ResourceCalendar, "_attendance_intervals_batch_original"):
ResourceCalendar._attendance_intervals_batch_original = (
ResourceCalendar._attendance_intervals_batch
)

# fmt: off
def _new_attendance_intervals_batch(self, start_dt, end_dt, resources=None, domain=None, tz=None):
assert start_dt.tzinfo and end_dt.tzinfo
self.ensure_one()

Check warning on line 33 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L32-L33

Added lines #L32 - L33 were not covered by tests

if not resources:
resources = self.env['resource.resource']
resources_list = [resources]

Check warning on line 37 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L36-L37

Added lines #L36 - L37 were not covered by tests
else:
resources_list = list(resources) + [self.env['resource.resource']]

Check warning on line 39 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L39

Added line #L39 was not covered by tests
resource_ids = [r.id for r in resources_list]
domain = domain if domain is not None else []
domain = expression.AND([domain, [

Check warning on line 42 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L41-L42

Added lines #L41 - L42 were not covered by tests
('calendar_id', '=', self.id),
('resource_id', 'in', resource_ids),
('display_type', '=', False),
]])

attendances = self.env['resource.calendar.attendance'].search(domain)

Check warning on line 48 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L48

Added line #L48 was not covered by tests
# Since we only have one calendar to take in account
# Group resources per tz they will all have the same result
resources_per_tz = defaultdict(list)

Check warning on line 51 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L51

Added line #L51 was not covered by tests
for resource in resources_list:
resources_per_tz[tz or timezone((resource or self).tz)].append(resource)

Check warning on line 53 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L53

Added line #L53 was not covered by tests
# Resource specific attendances
attendance_per_resource = defaultdict(lambda: self.env['resource.calendar.attendance'])
# Calendar attendances per day of the week
# * 7 days per week * 2 for two week calendars
attendances_per_day = [self.env['resource.calendar.attendance']] * 7 * 2
weekdays = set()

Check warning on line 59 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L58-L59

Added lines #L58 - L59 were not covered by tests
for attendance in attendances:
if attendance.resource_id:
attendance_per_resource[attendance.resource_id] |= attendance
weekday = int(attendance.dayofweek)
weekdays.add(weekday)

Check warning on line 64 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L62-L64

Added lines #L62 - L64 were not covered by tests
if self.two_weeks_calendar:
weektype = int(attendance.week_type)
attendances_per_day[weekday + 7 * weektype] |= attendance

Check warning on line 67 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L66-L67

Added lines #L66 - L67 were not covered by tests
else:
attendances_per_day[weekday] |= attendance
attendances_per_day[weekday + 7] |= attendance

Check warning on line 70 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L69-L70

Added lines #L69 - L70 were not covered by tests

start = start_dt.astimezone(utc)
end = end_dt.astimezone(utc)

Check warning on line 73 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L72-L73

Added lines #L72 - L73 were not covered by tests
bounds_per_tz = {
tz: (start_dt.astimezone(tz), end_dt.astimezone(tz))
for tz in resources_per_tz.keys()
}
# Use the outer bounds from the requested timezones
for tz, bounds in bounds_per_tz.items():
start = min(start, bounds[0].replace(tzinfo=utc))
end = max(end, bounds[1].replace(tzinfo=utc))

Check warning on line 81 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L80-L81

Added lines #L80 - L81 were not covered by tests
# Generate once with utc as timezone
days = rrule(DAILY, start.date(), until=end.date(), byweekday=weekdays)
ResourceCalendarAttendance = self.env['resource.calendar.attendance']
base_result = []
per_resource_result = defaultdict(list)

Check warning on line 86 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L83-L86

Added lines #L83 - L86 were not covered by tests
for day in days:
# begin change
if not self._day_in_calendar(day):
continue

Check warning on line 90 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L90

Added line #L90 was not covered by tests
# end change
week_type = ResourceCalendarAttendance.get_week_type(day)
attendances = attendances_per_day[day.weekday() + 7 * week_type]

Check warning on line 93 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L92-L93

Added lines #L92 - L93 were not covered by tests
for attendance in attendances:
if (attendance.date_from and day.date() < attendance.date_from) or\
(attendance.date_to and attendance.date_to < day.date()):
continue
day_from = datetime.combine(day, float_to_time(attendance.hour_from))
day_to = datetime.combine(day, float_to_time(attendance.hour_to))

Check warning on line 99 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L97-L99

Added lines #L97 - L99 were not covered by tests
if attendance.resource_id:
per_resource_result[attendance.resource_id].append((day_from, day_to, attendance))

Check warning on line 101 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L101

Added line #L101 was not covered by tests
else:
base_result.append((day_from, day_to, attendance))

Check warning on line 103 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L103

Added line #L103 was not covered by tests

# Copy the result localized once per necessary timezone
# Strictly speaking comparing start_dt < time or start_dt.astimezone(tz) < time
# should always yield the same result. however while working with dates it is easier
# if all dates have the same format
result_per_tz = {
tz: [(max(bounds_per_tz[tz][0], tz.localize(val[0])),
min(bounds_per_tz[tz][1], tz.localize(val[1])),
val[2])
for val in base_result]
for tz in resources_per_tz.keys()
}
result_per_resource_id = dict()

Check warning on line 116 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L116

Added line #L116 was not covered by tests
for tz, resources in resources_per_tz.items():
res = result_per_tz[tz]
res_intervals = Intervals(res)

Check warning on line 119 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L118-L119

Added lines #L118 - L119 were not covered by tests
for resource in resources:
if resource in per_resource_result:
resource_specific_result = [(max(bounds_per_tz[tz][0], tz.localize(val[0])), min(bounds_per_tz[tz][1], tz.localize(val[1])), val[2])
for val in per_resource_result[resource]]
result_per_resource_id[resource.id] = Intervals(itertools.chain(res, resource_specific_result))

Check warning on line 124 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L124

Added line #L124 was not covered by tests
else:
result_per_resource_id[resource.id] = res_intervals
return result_per_resource_id

Check warning on line 127 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L126-L127

Added lines #L126 - L127 were not covered by tests

# fmt: on

def _day_in_calendar(self, day):
return True

Check warning on line 132 in resource_multi_week_calendar/hooks.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/hooks.py#L132

Added line #L132 was not covered by tests

ResourceCalendar._attendance_intervals_batch = _new_attendance_intervals_batch
ResourceCalendar._day_in_calendar = _day_in_calendar
60 changes: 54 additions & 6 deletions resource_multi_week_calendar/models/resource_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ def _get_first_day_of_epoch_week(self):
days=self.multi_week_epoch_date.weekday()
)

def _get_week_number(self, day=None):
self.ensure_one()
if day is None:
day = fields.Date.today()
family_size = len(self.family_calendar_ids)
weeks_since_epoch = math.floor(
(day - self._get_first_day_of_epoch_week()).days / 7
)
return (weeks_since_epoch % family_size) + 1

@api.depends(
"multi_week_epoch_date",
"week_number",
Expand All @@ -134,12 +144,7 @@ def _get_first_day_of_epoch_week(self):
)
def _compute_current_week(self):
for calendar in self:
family_size = len(calendar.family_calendar_ids)
weeks_since_epoch = math.floor(
(fields.Date.today() - calendar._get_first_day_of_epoch_week()).days / 7
)
current_week_number = (weeks_since_epoch % family_size) + 1
# TODO: does this work in the negative, too?
current_week_number = calendar._get_week_number()
calendar.current_week_number = current_week_number
calendar.current_calendar_id = calendar.family_calendar_ids.filtered(
lambda item: item.week_number == current_week_number
Expand Down Expand Up @@ -225,3 +230,46 @@ def _check_epoch_date_matches_parent(self):
)
% calendar.name
)

def _attendance_intervals_batch(
self, start_dt, end_dt, resources=None, domain=None, tz=None
):
self.ensure_one()

Check warning on line 237 in resource_multi_week_calendar/models/resource_calendar.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/models/resource_calendar.py#L237

Added line #L237 was not covered by tests
if not self.is_multi_week:
return super()._attendance_intervals_batch(

Check warning on line 239 in resource_multi_week_calendar/models/resource_calendar.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/models/resource_calendar.py#L239

Added line #L239 was not covered by tests
start_dt, end_dt, resources=resources, domain=domain, tz=tz
)
if self.parent_calendar_id:
return self.parent_calendar_id._attendance_intervals_batch(

Check warning on line 243 in resource_multi_week_calendar/models/resource_calendar.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/models/resource_calendar.py#L243

Added line #L243 was not covered by tests
start_dt, end_dt, resources=resources, domain=domain, tz=tz
)
calendars = self | self.child_calendar_ids
results = []

Check warning on line 247 in resource_multi_week_calendar/models/resource_calendar.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/models/resource_calendar.py#L246-L247

Added lines #L246 - L247 were not covered by tests
for calendar in calendars:
results.append(

Check warning on line 249 in resource_multi_week_calendar/models/resource_calendar.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/models/resource_calendar.py#L249

Added line #L249 was not covered by tests
super(
ResourceCalendar,
# This context isn't used here, but could be used by
# dependencies to prevent loops.
calendar.with_context(recursive_multi_week=True),
)._attendance_intervals_batch(
start_dt, end_dt, resources=resources, domain=domain, tz=tz
)
)

result = {}

Check warning on line 260 in resource_multi_week_calendar/models/resource_calendar.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/models/resource_calendar.py#L260

Added line #L260 was not covered by tests
for item in results:
for resource, intervals in item.items():
if resource not in result:
result[resource] = intervals

Check warning on line 264 in resource_multi_week_calendar/models/resource_calendar.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/models/resource_calendar.py#L264

Added line #L264 was not covered by tests
else:
result[resource] += intervals

Check warning on line 266 in resource_multi_week_calendar/models/resource_calendar.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/models/resource_calendar.py#L266

Added line #L266 was not covered by tests

return result

Check warning on line 268 in resource_multi_week_calendar/models/resource_calendar.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/models/resource_calendar.py#L268

Added line #L268 was not covered by tests

# See hooks.py for where this is used.
def _day_in_calendar(self, day):
self.ensure_one()

Check warning on line 272 in resource_multi_week_calendar/models/resource_calendar.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/models/resource_calendar.py#L272

Added line #L272 was not covered by tests
if not self.is_multi_week:
return super()._day_in_calendar(day)
return self._get_week_number(day) == self.week_number

Check warning on line 275 in resource_multi_week_calendar/models/resource_calendar.py

View check run for this annotation

Codecov / codecov/patch

resource_multi_week_calendar/models/resource_calendar.py#L274-L275

Added lines #L274 - L275 were not covered by tests

0 comments on commit 75568a3

Please sign in to comment.