Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions resource_calendar_flexible_exclude_weekend/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

================================================
Resource Calendar Flexible Hours Exclude Weekend
================================================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:61e58a28c612d99b70b031c84cd27a2ac0274bf9281c8257e64871b66c31d9c0
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr--holidays-lightgray.png?logo=github
:target: https://github.com/OCA/hr-holidays/tree/18.0/resource_calendar_flexible_exclude_weekend
:alt: OCA/hr-holidays
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/hr-holidays-18-0/hr-holidays-18-0-resource_calendar_flexible_exclude_weekend
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/hr-holidays&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module allows to compute the resource calendar as flexible, without
taking the week-end days on the computation.

**Table of contents**

.. contents::
:local:

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/hr-holidays/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/hr-holidays/issues/new?body=module:%20resource_calendar_flexible_exclude_weekend%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Camptocamp SA

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/hr-holidays <https://github.com/OCA/hr-holidays/tree/18.0/resource_calendar_flexible_exclude_weekend>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions resource_calendar_flexible_exclude_weekend/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
15 changes: 15 additions & 0 deletions resource_calendar_flexible_exclude_weekend/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2025 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Resource Calendar Flexible Hours Exclude Weekend",
"version": "19.0.1.0.0",
"category": "Tools",
"website": "https://github.com/OCA/hr-holidays",
"author": "Camptocamp SA, Odoo Community Association (OCA)",
"license": "AGPL-3",
"depends": ["resource", "hr_work_entry"],
"data": [
"views/resource_calendar_view.xml",
],
"installable": True,
}
27 changes: 27 additions & 0 deletions resource_calendar_flexible_exclude_weekend/i18n/it.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * resource_calendar_flexible_exclude_weekend
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-03-16 10:45+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.15.2\n"

#. module: resource_calendar_flexible_exclude_weekend
#: model:ir.model.fields,field_description:resource_calendar_flexible_exclude_weekend.field_resource_calendar__exclude_weekends
msgid "Exclude Weekends"
msgstr "Escludi fine settimana"

#. module: resource_calendar_flexible_exclude_weekend
#: model:ir.model,name:resource_calendar_flexible_exclude_weekend.model_resource_calendar
msgid "Resource Working Time"
msgstr "Orario lavoro risorsa"
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * resource_calendar_flexible_exclude_weekend
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"

#. module: resource_calendar_flexible_exclude_weekend
#: model:ir.model.fields,field_description:resource_calendar_flexible_exclude_weekend.field_resource_calendar__exclude_weekends
msgid "Exclude Weekends"
msgstr ""

#. module: resource_calendar_flexible_exclude_weekend
#: model:ir.model,name:resource_calendar_flexible_exclude_weekend.model_resource_calendar
msgid "Resource Working Time"
msgstr ""
28 changes: 28 additions & 0 deletions resource_calendar_flexible_exclude_weekend/i18n/sl.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * resource_calendar_flexible_exclude_weekend
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-06-16 19:46+0000\n"
"Last-Translator: Matjaz Mozetic <matjaz@luxim.si>\n"
"Language-Team: none\n"
"Language: sl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || "
"n%100==4 ? 2 : 3;\n"
"X-Generator: Weblate 5.15.2\n"

#. module: resource_calendar_flexible_exclude_weekend
#: model:ir.model.fields,field_description:resource_calendar_flexible_exclude_weekend.field_resource_calendar__exclude_weekends
msgid "Exclude Weekends"
msgstr "Izključi vikende"

#. module: resource_calendar_flexible_exclude_weekend
#: model:ir.model,name:resource_calendar_flexible_exclude_weekend.model_resource_calendar
msgid "Resource Working Time"
msgstr "Delovni čas zaposlenega"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import resource_calendar
161 changes: 161 additions & 0 deletions resource_calendar_flexible_exclude_weekend/models/resource_calendar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# Copyright 2025 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging
from collections import defaultdict
from datetime import timedelta

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

from odoo import fields, models
from odoo.fields import Domain
from odoo.tools.intervals import Intervals

_logger = logging.getLogger(__name__)


class ResourceCalendar(models.Model):
_inherit = "resource.calendar"

exclude_weekends = fields.Boolean()

# Override to return weekends as special days if exclude_weekends is set
def _attendance_intervals_batch( # noqa: C901
self, start_dt, end_dt, resources=None, domain=None, tz=None, lunch=False
):
"""
Override to adjust start_dt if it falls on a weekend.
- Convert start_dt to the relevant timezone
- If Saturday or Sunday → move to next Monday
- Then call super() with the adjusted start_dt
"""
# Ensure timezone awareness
assert start_dt.tzinfo and end_dt.tzinfo, "Datetimes must be timezone-aware"
self.ensure_one()

# shortcut: the method can be called with and end_dt before start_dt,
# in this case the expected value is a dictionary with empty intervals.
if end_dt < start_dt:
return super()._attendance_intervals_batch(
start_dt, end_dt, resources, domain, tz, lunch
)
if not resources:
resources = self.env["resource.resource"]
resources_list = [resources]
else:
resources_list = list(resources) + [self.env["resource.resource"]]

resources_with_flex_no_weekend = []
other_resources = []
for resource in resources_list:
if (
resource
and resource.calendar_id.flexible_hours
and resource.calendar_id.exclude_weekends
) or (self.flexible_hours and self.exclude_weekends):
resources_with_flex_no_weekend.append(resource)
else:
other_resources.append(resource)
if other_resources:
res_others = super()._attendance_intervals_batch(
start_dt,
end_dt,
sum(other_resources, self.env["resource.resource"]),
domain,
tz,
lunch,
)
else:
# containing resources (empty or not)
res_others = {}
if resources_with_flex_no_weekend:
skipping_res = defaultdict(dict)
resources_per_tz = defaultdict(list)
for resource in resources_with_flex_no_weekend:
resources_per_tz[tz or timezone((resource or self).tz)].append(resource)
for tz, tz_resources in resources_per_tz.items():
skipping_start_dt = start_dt.astimezone(tz)
# 0=Monday, 6=Sunday
weekday = skipping_start_dt.weekday()
if weekday in (5, 6): # Saturday or Sunday
# Move to next Monday, midnight in the considered TZ
days_to_monday = 7 - weekday
skipping_start_dt += timedelta(days=days_to_monday, hours=1)
# reconvert to get Daylight saving time applied if needed
skipping_start_dt = skipping_start_dt.astimezone(tz)
skipping_start_dt -= timedelta(
hours=skipping_start_dt.hour,
minutes=skipping_start_dt.minute,
seconds=skipping_start_dt.second,
)
# check if we moved past the end date, in which case we want to move
# it to ensure we enter the while loop below
if end_dt < skipping_start_dt:
end_dt = skipping_start_dt

# for resources which should skip weekend we have to iterate by week
# I thought that maybe we can be smarter and only do 1 or 2 calls:
# 1 call if the first day is a monday, and a second if we need to
# check the following week, but this does not work when there are
# holidays in the period
while skipping_start_dt <= end_dt:
# find the end of the current week or the end of the period
skipping_end_dt = (
skipping_start_dt
+ timedelta(days=7 - skipping_start_dt.weekday(), hours=1)
).astimezone(tz)
skipping_end_dt -= timedelta(
hours=skipping_end_dt.hour,
minutes=skipping_end_dt.minute,
seconds=skipping_end_dt.second,
)
skipping_end_dt = min(skipping_end_dt, end_dt)
res_skip = super()._attendance_intervals_batch(
skipping_start_dt,
skipping_end_dt,
sum(tz_resources, self.env["resource.resource"]),
domain,
tz,
lunch,
)
for resource_id, work_intervals in res_skip.items():
new_intervals = skipping_res[resource_id]
for start, end, attendance in work_intervals:
if start.weekday() not in (5, 6):
new_intervals[(start, end)] = (start, end, attendance)
if skipping_end_dt == end_dt:
break
else:
# go to next monday
skipping_start_dt = skipping_end_dt
# merge both result set
for resource_id, intervals in skipping_res.items():
res_others[resource_id] = Intervals(
intervals.values(), keep_distinct=True
)
return res_others

def _get_unusual_days(self, start_dt, end_dt, company_id=False):
if self.flexible_hours and self.exclude_weekends:
# when we exclude weekend, use the same implementation as
# for non flexible hours
utc = timezone("UTC")
if not start_dt.tzinfo:
start_dt = start_dt.replace(tzinfo=utc)
if not end_dt.tzinfo:
end_dt = end_dt.replace(tzinfo=utc)
domain = Domain(True)
if company_id:
domain = Domain("company_id", "in", (company_id.id, False))
works = {
d[0].date()
for d in self._work_intervals_batch(start_dt, end_dt, domain=domain)[
False
]
}
return {
fields.Date.to_string(day.date()): (day.date() not in works)
for day in rrule(DAILY, start_dt, until=end_dt)
}
else:
return super()._get_unusual_days(start_dt, end_dt, company_id)
3 changes: 3 additions & 0 deletions resource_calendar_flexible_exclude_weekend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This module allows to compute the resource calendar as flexible, without taking the week-end days on the computation.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading