コード例 #1
0
ファイル: hr_leave.py プロジェクト: xabispacebiker/odoo
    def _cancel_benefit_conflict(self):
        """
        Unlink any benefit linked to a leave in self.
        Re-create new benefits where the leaves do not cover the full range of the deleted benefits.
        Create a leave benefit for each leave in self.
        Return True if one or more benefits are unlinked.
        e.g.:
            |---------------- benefit ----------------|
                    |------ leave ------|
                            ||
                            vv
            |-benef-|---benefit leave---|----benefit---|
        """
        benefits = self.env['hr.benefit'].search([('leave_id', 'in', self.ids)
                                                  ])
        if benefits:
            self.copy_to_benefits()
            # create new benefits where the leave does not cover the full benefit
            benefits_intervals = Intervals(intervals=[(b.date_start,
                                                       b.date_stop, b)
                                                      for b in benefits])
            leave_intervals = Intervals(intervals=[(l.date_from, l.date_to, l)
                                                   for l in self])
            remaining_benefits = benefits_intervals - leave_intervals

            for interval in remaining_benefits:
                benefit = interval[2]
                leave = benefit.leave_id
                benefit_type = benefit.benefit_type_id
                employee = benefit.employee_id

                benefit_start = interval[0] + relativedelta(
                    seconds=1) if leave.date_to == interval[0] else interval[0]
                benefit_stop = interval[1] - relativedelta(
                    seconds=1
                ) if leave.date_from == interval[1] else interval[1]

                self.env['hr.benefit'].safe_duplicate_create({
                    'name':
                    "%s: %s" % (benefit_type.name, employee.name),
                    'date_start':
                    benefit_start,
                    'date_stop':
                    benefit_stop,
                    'benefit_type_id':
                    benefit_type.id,
                    'contract_id':
                    benefit.contract_id.id,
                    'employee_id':
                    employee.id,
                    'state':
                    'confirmed',
                })
            benefits.unlink()
            return True
        return False
コード例 #2
0
    def _public_holidays_leave_intervals(self, start_dt, end_dt, employee_id,
                                         tz):
        """Get the public holidays for the current employee and given dates in
        the format expected by resource methods.

        :param: start_dt: Initial datetime.
        :param: end_dt: End datetime.
        :param: employee_id: Employee ID. It can be false.
        :return: List of tuples with (start_date, end_date) as elements.
        """
        HrHolidaysPublic = self.env['hr.holidays.public']

        leaves = []
        if start_dt.year != end_dt.year:
            # This fixes the case of leave request asked over 2 years.
            #
            # adding 1 year to end_dt for rrule to retrieve correct years for
            # public holidays to work
            # rrule.rrule(rrule.YEARLY, dtstart=2019-12-22, until=2020-01-05)
            # gives [2019]
            # rrule.rrule(rrule.YEARLY, dtstart=2019-12-22, until=2021-01-05)
            # gives [2019, 2020]
            end_dt = end_dt.replace(year=end_dt.year + 1)

        for day in rrule.rrule(rrule.YEARLY, dtstart=start_dt, until=end_dt):
            lines = HrHolidaysPublic.get_holidays_list(
                day.year,
                employee_id=employee_id,
            )
            for line in lines:
                leaves.append(
                    (datetime.combine(line.date, time.min).replace(tzinfo=tz),
                     datetime.combine(line.date,
                                      time.max).replace(tzinfo=tz), line), )
        return Intervals(leaves)
コード例 #3
0
ファイル: resource.py プロジェクト: yustas147/odoo
    def _get_calendars_validity_within_period(self,
                                              start,
                                              end,
                                              default_company=None):
        """
            Returns a dict of dict with resource's id as first key and resource's calendar as secondary key
            The value is the validity interval of the calendar for the given resource.

            The validity interval of the employee resource calendar is the lifetime of the employee, from creation to departure.
        """
        assert start.tzinfo and end.tzinfo
        calendars_within_period_per_resource = super(
        )._get_calendars_validity_within_period(
            start, end, default_company=default_company)
        for resource in self:
            if not resource.employee_id:
                continue
            create_date = max(start,
                              utc.localize(resource.employee_id.create_date))
            if resource.employee_id.departure_date and resource.employee_id.departure_date <= end.date(
            ):
                departure_datetime = timezone(resource.tz).localize(
                    datetime.combine(resource.employee_id.departure_date,
                                     datetime.max.time()))
                departure_datetime = min(departure_datetime, end)
            else:
                departure_datetime = end
            interval = Intervals([(create_date, departure_datetime,
                                   self.env['resource.calendar.attendance'])])
            for calendar in calendars_within_period_per_resource[resource.id]:
                calendars_within_period_per_resource[
                    resource.id][calendar] &= interval
        return calendars_within_period_per_resource
コード例 #4
0
    def _get_first_available_slot(self, start_datetime, duration):
        """Get the first available interval for the workcenter in `self`.

        The available interval is disjoinct with all other workorders planned on this workcenter, but
        can overlap the time-off of the related calendar (inverse of the working hours).
        Return the first available interval (start datetime, end datetime) or,
        if there is none before 700 days, a tuple error (False, 'error message').

        :param start_datetime: begin the search at this datetime
        :param duration: minutes needed to make the workorder (float)
        :rtype: tuple
        """
        self.ensure_one()
        start_datetime, revert = make_aware(start_datetime)

        resource = self.resource_id
        get_available_intervals = partial(
            self.resource_calendar_id._work_intervals_batch,
            domain=[('time_type', 'in', ['other', 'leave'])],
            resources=resource,
            tz=timezone(self.resource_calendar_id.tz))
        get_workorder_intervals = partial(
            self.resource_calendar_id._leave_intervals_batch,
            domain=[('time_type', '=', 'other')],
            resources=resource,
            tz=timezone(self.resource_calendar_id.tz))

        remaining = duration
        start_interval = start_datetime
        delta = timedelta(days=14)

        for n in range(50):  # 50 * 14 = 700 days in advance (hardcoded)
            dt = start_datetime + delta * n
            available_intervals = get_available_intervals(dt, dt +
                                                          delta)[resource.id]
            workorder_intervals = get_workorder_intervals(dt, dt +
                                                          delta)[resource.id]
            for start, stop, dummy in available_intervals:
                # Shouldn't loop more than 2 times because the available_intervals contains the workorder_intervals
                # And remaining == duration can only occur at the first loop and at the interval intersection (cannot happen several time because available_intervals > workorder_intervals
                for _i in range(2):
                    interval_minutes = (stop - start).total_seconds() / 60
                    # If the remaining minutes has never decrease update start_interval
                    if remaining == duration:
                        start_interval = start
                    # If there is a overlap between the possible available interval and a others WO
                    if Intervals(
                        [(start_interval, start +
                          timedelta(minutes=min(remaining, interval_minutes)),
                          dummy)]) & workorder_intervals:
                        remaining = duration
                    elif float_compare(interval_minutes,
                                       remaining,
                                       precision_digits=3) >= 0:
                        return revert(start_interval), revert(
                            start + timedelta(minutes=remaining))
                    else:
                        # Decrease a part of the remaining duration
                        remaining -= interval_minutes
        return False, 'Not available slot 700 days after the planned start'
コード例 #5
0
    def _cancel_benefit_conflict(self):
        benefits = self.env['hr.benefit'].search([('leave_id', 'in', self.ids)
                                                  ])
        if benefits:
            self.copy_to_benefits()

            # create new benefits where the leave does not cover the full benefit
            benefits_intervals = Intervals(intervals=[(b.date_start,
                                                       b.date_stop, b)
                                                      for b in benefits])
            leave_intervals = Intervals(intervals=[(l.date_from, l.date_to, l)
                                                   for l in self])
            remaining_benefits = benefits_intervals - leave_intervals

            for interval in remaining_benefits:
                benefit = interval[2]
                leave = benefit.leave_id
                benefit_type = benefit.benefit_type_id
                employee = benefit.employee_id

                benefit_start = interval[0] + relativedelta(
                    seconds=1) if leave.date_to == interval[0] else interval[0]
                benefit_stop = interval[1] - relativedelta(
                    seconds=1
                ) if leave.date_from == interval[1] else interval[1]

                self.env['hr.benefit'].safe_duplicate_create({
                    'name':
                    "%s: %s" % (benefit_type.name, employee.name),
                    'date_start':
                    benefit_start,
                    'date_stop':
                    benefit_stop,
                    'benefit_type_id':
                    benefit_type.id,
                    'employee_id':
                    employee.id,
                    'state':
                    'confirmed',
                })
            benefits.unlink()
コード例 #6
0
ファイル: test_resource.py プロジェクト: yustas147/odoo
    def test_calendars_validity_within_period(self):
        tz = timezone(self.employee.tz)
        calendars = self.employee.resource_id._get_calendars_validity_within_period(
            tz.localize(datetime(2021, 10, 1, 0, 0, 0)),
            tz.localize(datetime(2021, 12, 1, 0, 0, 0)),
        )
        interval_35h = Intervals([
            (tz.localize(datetime(2021, 10, 1, 0, 0, 0)),
             tz.localize(
                 datetime.combine(date(2021, 10, 31), datetime.max.time())),
             self.env['resource.calendar.attendance'])
        ])
        interval_40h = Intervals([(tz.localize(datetime(2021, 11, 1, 0, 0, 0)),
                                   tz.localize(datetime(2021, 12, 1, 0, 0, 0)),
                                   self.env['resource.calendar.attendance'])])

        self.assertEqual(
            1, len(calendars),
            "The dict returned by calendars validity should only have 1 entry")
        self.assertEqual(2, len(calendars[self.employee.resource_id.id]),
                         "Jean should only have one calendar")
        richard_entries = calendars[self.employee.resource_id.id]
        for calendar in richard_entries:
            self.assertTrue(
                calendar in (self.calendar_35h | self.calendar_richard),
                "Each calendar should be listed")
            if calendar == self.calendar_35h:
                self.assertFalse(
                    richard_entries[calendar] - interval_35h,
                    "Interval 35h should cover all calendar 35h validity")
                self.assertFalse(
                    interval_35h - richard_entries[calendar],
                    "Calendar 35h validity should cover all interval 35h")
            elif calendar == self.calendar_richard:
                self.assertFalse(
                    richard_entries[calendar] - interval_40h,
                    "Interval 40h should cover all calendar 40h validity")
                self.assertFalse(
                    interval_40h - richard_entries[calendar],
                    "Calendar 40h validity should cover all interval 40h")
コード例 #7
0
 def test_calendars_validity_within_period_creation(self):
     calendars = self.employee_niv.resource_id._get_calendars_validity_within_period(
         utc.localize(datetime(2020, 12, 1, 8, 0, 0)),
         utc.localize(datetime(2021, 1, 31, 17, 0, 0)),
     )
     interval = Intervals([(utc.localize(datetime(2021, 1, 1, 10, 0, 0)),
                            utc.localize(datetime(2021, 1, 31, 17, 0, 0)),
                            self.env['resource.calendar.attendance'])])
     niv_entry = calendars[self.employee_niv.resource_id.id]
     self.assertFalse(niv_entry[self.calendar_40h] - interval,
                      "Interval should cover all calendar's validity")
     self.assertFalse(interval - niv_entry[self.calendar_40h],
                      "Calendar validity should cover all interval")
コード例 #8
0
 def test_calendars_validity_within_period_before_departure(self):
     calendars = self.employee_niv.resource_id._get_calendars_validity_within_period(
         utc.localize(datetime(2022, 5, 1, 8, 0, 0)),
         utc.localize(datetime(2022, 6, 30, 17, 0, 0)),
     )
     interval = Intervals([(utc.localize(datetime(2022, 5, 1, 8, 0, 0)),
                            timezone(self.employee_niv.tz).localize(
                                datetime(2022, 6, 1, 23, 59, 59, 999999)),
                            self.env['resource.calendar.attendance'])])
     niv_entry = calendars[self.employee_niv.resource_id.id]
     self.assertFalse(niv_entry[self.calendar_40h] - interval,
                      "Interval should cover all calendar's validity")
     self.assertFalse(interval - niv_entry[self.calendar_40h],
                      "Calendar validity should cover all interval")
コード例 #9
0
ファイル: hr_benefit.py プロジェクト: ypapouin/odoo
 def _mark_conflicting_benefits(self, start, stop):
     conflict = False
     domain = [
         ('date_start', '<', stop),
         ('date_stop', '>', start),
     ]
     benefs = self.search(domain)
     benefits_by_employee = itertools.groupby(benefs, lambda b: b.employee_id)
     for employee, benefs in benefits_by_employee:
         intervals = Intervals(intervals=((b.date_start, b.date_stop, b) for b in benefs))
         for interval in intervals:
             if len(interval[2]) > 1:
                 interval[2].write({'display_warning': True})
                 conflict = True
     return conflict
コード例 #10
0
ファイル: hr_benefit.py プロジェクト: zxh69500/odoo
    def _compute_schedule_conflicts(self):
        conflict = False
        date_start_benefits = min(self.mapped('date_start'))
        date_stop_benefits = max(self.mapped('date_stop'))
        domain = [
            ('date_start', '<', date_stop_benefits),
            ('date_stop', '>', date_start_benefits),
        ]

        benefs = self.search(domain)
        benefits_by_employee = itertools.groupby(benefs, lambda b: b.employee_id)
        for employee, benefs in benefits_by_employee:
            intervals = Intervals(intervals=((b.date_start, b.date_stop, b) for b in benefs))
            for interval in intervals:
                if len(interval[2]) > 1:
                    interval[2].write({'display_warning': True})
                    conflict = True
        return conflict
コード例 #11
0
 def _natural_period_intervals_batch(self, start_dt, end_dt, intervals, resources):
     for resource in resources:
         interval_resource = intervals[resource.id]
         tz = timezone(resource.tz)
         attendances = []
         if len(interval_resource._items) > 0:
             attendances = interval_resource._items
         for day in rrule.rrule(rrule.DAILY, dtstart=start_dt, until=end_dt):
             exist_interval = self._exist_interval_in_date(attendances, day.date())
             if not exist_interval:
                 attendances.append(
                     (
                         datetime.combine(day.date(), time.min).replace(tzinfo=tz),
                         datetime.combine(day.date(), time.max).replace(tzinfo=tz),
                         self.env["resource.calendar.attendance"],
                     )
                 )
         intervals[resource.id] = Intervals(attendances)
     return intervals
コード例 #12
0
    def _public_holidays_leave_intervals(self, start_dt, end_dt, employee_id,
                                         tz):
        """Get the public holidays for the current employee and given dates in
        the format expected by resource methods.

        :param: start_dt: Initial datetime.
        :param: end_dt: End datetime.
        :param: employee_id: Employee ID. It can be false.
        :return: List of tuples with (start_date, end_date) as elements.
        """
        HrHolidaysPublic = self.env["hr.holidays.public"]

        leaves = []
        if start_dt.year != end_dt.year:
            # This fixes the case of leave request asked over 2 years.
            #
            # adding 1 year to end_dt for rrule to retrieve correct years for
            # public holidays to work
            # rrule.rrule(rrule.YEARLY, dtstart=2019-12-22, until=2020-01-05)
            # gives [2019]
            # rrule.rrule(rrule.YEARLY, dtstart=2019-12-22, until=2021-01-05)
            # gives [2019, 2020]
            end_dt = end_dt.replace(year=end_dt.year + 1)

        for day in rrule.rrule(rrule.YEARLY, dtstart=start_dt, until=end_dt):
            lines = HrHolidaysPublic.get_holidays_list(day.year,
                                                       employee_id=employee_id)
            # In some cases, an error appears about mixing 2 models
            # (hr.holidays.public.line + resource.calendar.leaves)
            # in _leave_intervals or _leave_intervals_batch functions
            # It only happen when both holidays and leaves exist.
            # The solution is to pass an empty leave in the tuple instead
            # of the public holiday line record, as this element has no
            # further use except the union operation.
            resource_leave_model = self.env["resource.calendar.leaves"]
            for line in lines:
                leaves.append((
                    datetime.combine(line.date, time.min).replace(tzinfo=tz),
                    datetime.combine(line.date, time.max).replace(tzinfo=tz),
                    resource_leave_model,
                ))
        return Intervals(leaves)
コード例 #13
0
 def _attendance_intervals_batch_exclude_public_holidays(
     self, start_dt, end_dt, intervals, resources, tz
 ):
     list_by_dates = (
         self.env["hr.holidays.public"]
         .get_holidays_list(
             start_dt=start_dt.date(),
             end_dt=end_dt.date(),
             employee_id=self.env.context.get("employee_id", False),
         )
         .mapped("date")
     )
     for resource in resources:
         interval_resource = intervals[resource.id]
         attendances = []
         for attendance in interval_resource._items:
             if attendance[0].date() not in list_by_dates:
                 attendances.append(attendance)
         intervals[resource.id] = Intervals(attendances)
     return intervals
コード例 #14
0
    def test_calendars_validity_within_period_default(self):
        calendars = self.employee_niv.resource_id._get_calendars_validity_within_period(
            utc.localize(datetime(2021, 7, 1, 8, 0, 0)),
            utc.localize(datetime(2021, 7, 30, 17, 0, 0)),
        )
        interval = Intervals([(utc.localize(datetime(2021, 7, 1, 8, 0, 0)),
                               utc.localize(datetime(2021, 7, 30, 17, 0, 0)),
                               self.env['resource.calendar.attendance'])])

        self.assertEqual(
            1, len(calendars),
            "The dict returned by calendars validity should only have 1 entry")
        self.assertEqual(1, len(calendars[self.employee_niv.resource_id.id]),
                         "Niv should only have one calendar")
        niv_entry = calendars[self.employee_niv.resource_id.id]
        niv_calendar = next(iter(niv_entry))
        self.assertEqual(niv_calendar, self.calendar_40h,
                         "It should be Niv's Calendar")
        self.assertFalse(niv_entry[niv_calendar] - interval,
                         "Interval should cover all calendar's validity")
        self.assertFalse(interval - niv_entry[niv_calendar],
                         "Calendar validity should cover all interval")
コード例 #15
0
    def _weekend_intervals(self, start_dt, end_dt, resource=None):
        """ Return the weekend intervals in the given datetime range.
            The returned intervals are expressed in the resource's timezone.
        """
        tz = timezone((resource or self).tz)
        start_dt = start_dt.astimezone(tz)
        end_dt = end_dt.astimezone(tz)
        start = start_dt.date()
        until = end_dt.date()
        result = []

        weekdays = [
            int(attendance.dayofweek) for attendance in self.attendance_ids
        ]
        weekends = [d for d in range(7) if d not in weekdays]
        for day in rrule.rrule(rrule.DAILY,
                               start,
                               until=until,
                               byweekday=weekends):
            result.append(
                (datetime.combine(day, time.min).astimezone(tz),
                 datetime.combine(day, time.max).astimezone(tz), self), )

        return Intervals(result)
コード例 #16
0
    def _public_holidays_leave_intervals(self, start_dt, end_dt, employee_id,
                                         tz):
        """Get the public holidays for the current employee and given dates in
        the format expected by resource methods.

        :param: start_dt: Initial datetime.
        :param: end_dt: End datetime.
        :param: employee_id: Employee ID. It can be false.
        :return: List of tuples with (start_date, end_date) as elements.
        """
        HrHolidaysPublic = self.env['hr.holidays.public']

        leaves = []
        for day in rrule.rrule(rrule.YEARLY, dtstart=start_dt, until=end_dt):
            lines = HrHolidaysPublic.get_holidays_list(
                day.year,
                employee_id=employee_id,
            )
            for line in lines:
                leaves.append(
                    (datetime.combine(line.date, time.min).replace(tzinfo=tz),
                     datetime.combine(line.date,
                                      time.max).replace(tzinfo=tz), line), )
        return Intervals(leaves)
コード例 #17
0
ファイル: resource_resource.py プロジェクト: yustas147/odoo
 def _get_calendars_validity_within_period(self, start, end, default_company=None):
     assert start.tzinfo and end.tzinfo
     if not self:
         return super()._get_calendars_validity_within_period(start, end, default_company=default_company)
     calendars_within_period_per_resource = defaultdict(lambda: defaultdict(Intervals))  # keys are [resource id:integer][calendar:self.env['resource.calendar']]
     resource_without_contract = self.filtered(lambda r: not r.employee_id or r.employee_id.employee_type not in ['employee', 'student'])
     if resource_without_contract:
         calendars_within_period_per_resource.update(
             super(ResourceResource, resource_without_contract)._get_calendars_validity_within_period(start, end, default_company=default_company)
         )
     resource_with_contract = self - resource_without_contract
     if not resource_with_contract:
         return calendars_within_period_per_resource
     timezones = {resource.tz for resource in resource_with_contract}
     date_start = min(start.astimezone(timezone(tz)).date() for tz in timezones)
     date_end = max(end.astimezone(timezone(tz)).date() for tz in timezones)
     contracts = resource_with_contract.employee_id._get_contracts(
         date_start, date_end, states=['open', 'draft', 'close']
     ).filtered(lambda c: c.state in ['open', 'close'] or c.kanban_state == 'done')
     for contract in contracts:
         tz = timezone(contract.employee_id.tz)
         calendars_within_period_per_resource[contract.employee_id.resource_id.id][contract.resource_calendar_id] |= Intervals([(
             tz.localize(datetime.combine(contract.date_start, datetime.min.time())) if contract.date_start > start.astimezone(tz).date() else start,
             tz.localize(datetime.combine(contract.date_end, datetime.max.time())) if contract.date_end and contract.date_end < end.astimezone(tz).date() else end,
             self.env['resource.calendar.attendance']
         )])
     return calendars_within_period_per_resource
コード例 #18
0
ファイル: hr_contract.py プロジェクト: WilldooIT/odoo
    def _get_contract_work_entries_values(self, date_start, date_stop):
        contract_vals = []
        bypassing_work_entry_type_codes = self._get_bypassing_work_entry_type_codes()
        for contract in self:
            employee = contract.employee_id
            calendar = contract.resource_calendar_id
            resource = employee.resource_id
            tz = pytz.timezone(calendar.tz)
            start_dt = pytz.utc.localize(date_start) if not date_start.tzinfo else date_start
            end_dt = pytz.utc.localize(date_stop) if not date_stop.tzinfo else date_stop

            attendances = calendar._attendance_intervals_batch(
                start_dt, end_dt, resources=resource, tz=tz
            )[resource.id]

            # Other calendars: In case the employee has declared time off in another calendar
            # Example: Take a time off, then a credit time.
            # YTI TODO: This mimics the behavior of _leave_intervals_batch, while waiting to be cleaned
            # in master.
            resources_list = [self.env['resource.resource'], resource]
            resource_ids = [False, resource.id]
            leave_domain = [
                ('time_type', '=', 'leave'),
                # ('calendar_id', '=', self.id), --> Get all the time offs
                ('resource_id', 'in', resource_ids),
                ('date_from', '<=', datetime_to_string(end_dt)),
                ('date_to', '>=', datetime_to_string(start_dt)),
                ('company_id', '=', self.env.company.id),
            ]
            result = defaultdict(lambda: [])
            tz_dates = {}
            for leave in self.env['resource.calendar.leaves'].search(leave_domain):
                for resource in resources_list:
                    if leave.resource_id.id not in [False, resource.id]:
                        continue
                    tz = tz if tz else pytz.timezone((resource or contract).tz)
                    if (tz, start_dt) in tz_dates:
                        start = tz_dates[(tz, start_dt)]
                    else:
                        start = start_dt.astimezone(tz)
                        tz_dates[(tz, start_dt)] = start
                    if (tz, end_dt) in tz_dates:
                        end = tz_dates[(tz, end_dt)]
                    else:
                        end = end_dt.astimezone(tz)
                        tz_dates[(tz, end_dt)] = end
                    dt0 = string_to_datetime(leave.date_from).astimezone(tz)
                    dt1 = string_to_datetime(leave.date_to).astimezone(tz)
                    result[resource.id].append((max(start, dt0), min(end, dt1), leave))
            mapped_leaves = {r.id: Intervals(result[r.id]) for r in resources_list}
            leaves = mapped_leaves[resource.id]

            real_attendances = attendances - leaves
            real_leaves = attendances - real_attendances

            # A leave period can be linked to several resource.calendar.leave
            split_leaves = []
            for leave_interval in leaves:
                if leave_interval[2] and len(leave_interval[2]) > 1:
                    split_leaves += [(leave_interval[0], leave_interval[1], l) for l in leave_interval[2]]
                else:
                    split_leaves += [(leave_interval[0], leave_interval[1], leave_interval[2])]
            leaves = split_leaves

            # Attendances
            default_work_entry_type = contract._get_default_work_entry_type()
            for interval in real_attendances:
                work_entry_type_id = interval[2].mapped('work_entry_type_id')[:1] or default_work_entry_type
                # All benefits generated here are using datetimes converted from the employee's timezone
                contract_vals += [{
                    'name': "%s: %s" % (work_entry_type_id.name, employee.name),
                    'date_start': interval[0].astimezone(pytz.utc).replace(tzinfo=None),
                    'date_stop': interval[1].astimezone(pytz.utc).replace(tzinfo=None),
                    'work_entry_type_id': work_entry_type_id.id,
                    'employee_id': employee.id,
                    'contract_id': contract.id,
                    'company_id': contract.company_id.id,
                    'state': 'draft',
                }]

            for interval in real_leaves:
                # Could happen when a leave is configured on the interface on a day for which the
                # employee is not supposed to work, i.e. no attendance_ids on the calendar.
                # In that case, do try to generate an empty work entry, as this would raise a
                # sql constraint error
                if interval[0] == interval[1]:  # if start == stop
                    continue
                leave_entry_type = contract._get_interval_leave_work_entry_type(interval, leaves, bypassing_work_entry_type_codes)
                interval_start = interval[0].astimezone(pytz.utc).replace(tzinfo=None)
                interval_stop = interval[1].astimezone(pytz.utc).replace(tzinfo=None)
                contract_vals += [dict([
                    ('name', "%s%s" % (leave_entry_type.name + ": " if leave_entry_type else "", employee.name)),
                    ('date_start', interval_start),
                    ('date_stop', interval_stop),
                    ('work_entry_type_id', leave_entry_type.id),
                    ('employee_id', employee.id),
                    ('company_id', contract.company_id.id),
                    ('state', 'draft'),
                    ('contract_id', contract.id),
                ] + contract._get_more_vals_leave_interval(interval, leaves))]
        return contract_vals
コード例 #19
0
ファイル: test_resource.py プロジェクト: dai08170/odooerp
 def check(a, b, c):
     a, b, c = self.ints(a), self.ints(b), self.ints(c)
     self.assertEqual(list(Intervals(a) - Intervals(b)), c)
コード例 #20
0
ファイル: hr_contract.py プロジェクト: GSLabIt/odoo
    def _get_contract_work_entries_values(self, date_start, date_stop):
        start_dt = pytz.utc.localize(date_start) if not date_start.tzinfo else date_start
        end_dt = pytz.utc.localize(date_stop) if not date_stop.tzinfo else date_stop

        contract_vals = []
        bypassing_work_entry_type_codes = self._get_bypassing_work_entry_type_codes()

        attendances_by_resource = self._get_attendance_intervals(start_dt, end_dt)

        resource_calendar_leaves = self.env['resource.calendar.leaves'].search(self._get_leave_domain(start_dt, end_dt))
        # {resource: resource_calendar_leaves}
        leaves_by_resource = defaultdict(lambda: self.env['resource.calendar.leaves'])
        for leave in resource_calendar_leaves:
            leaves_by_resource[leave.resource_id.id] |= leave

        tz_dates = {}
        for contract in self:
            employee = contract.employee_id
            calendar = contract.resource_calendar_id
            resource = employee.resource_id
            tz = pytz.timezone(calendar.tz)

            attendances = attendances_by_resource[resource.id]

            # Other calendars: In case the employee has declared time off in another calendar
            # Example: Take a time off, then a credit time.
            # YTI TODO: This mimics the behavior of _leave_intervals_batch, while waiting to be cleaned
            # in master.
            resources_list = [self.env['resource.resource'], resource]
            result = defaultdict(lambda: [])
            for leave in itertools.chain(leaves_by_resource[False], leaves_by_resource[resource.id]):
                for resource in resources_list:
                    # Global time off is not for this calendar, can happen with multiple calendars in self
                    if resource and leave.calendar_id and leave.calendar_id != calendar and not leave.resource_id:
                        continue
                    tz = tz if tz else pytz.timezone((resource or contract).tz)
                    if (tz, start_dt) in tz_dates:
                        start = tz_dates[(tz, start_dt)]
                    else:
                        start = start_dt.astimezone(tz)
                        tz_dates[(tz, start_dt)] = start
                    if (tz, end_dt) in tz_dates:
                        end = tz_dates[(tz, end_dt)]
                    else:
                        end = end_dt.astimezone(tz)
                        tz_dates[(tz, end_dt)] = end
                    dt0 = string_to_datetime(leave.date_from).astimezone(tz)
                    dt1 = string_to_datetime(leave.date_to).astimezone(tz)
                    result[resource.id].append((max(start, dt0), min(end, dt1), leave))
            mapped_leaves = {r.id: Intervals(result[r.id]) for r in resources_list}
            leaves = mapped_leaves[resource.id]

            real_attendances = attendances - leaves
            if contract.has_static_work_entries() or not leaves:
                # Empty leaves means empty real_leaves
                real_leaves = attendances - real_attendances
            else:
                # In the case of attendance based contracts use regular attendances to generate leave intervals
                static_attendances = calendar._attendance_intervals_batch(
                    start_dt, end_dt, resources=resource, tz=tz)[resource.id]
                real_leaves = static_attendances & leaves

            if not contract.has_static_work_entries():
                # An attendance based contract might have an invalid planning, by definition it may not happen with
                # static work entries.
                # Creating overlapping slots for example might lead to a single work entry.
                # In that case we still create both work entries to indicate a problem (conflicting W E).
                split_attendances = []
                for attendance in real_attendances:
                    if attendance[2] and len(attendance[2]) > 1:
                        split_attendances += [(attendance[0], attendance[1], a) for a in attendance[2]]
                    else:
                        split_attendances += [attendance]
                real_attendances = split_attendances

            # A leave period can be linked to several resource.calendar.leave
            split_leaves = []
            for leave_interval in leaves:
                if leave_interval[2] and len(leave_interval[2]) > 1:
                    split_leaves += [(leave_interval[0], leave_interval[1], l) for l in leave_interval[2]]
                else:
                    split_leaves += [(leave_interval[0], leave_interval[1], leave_interval[2])]
            leaves = split_leaves

            # Attendances
            default_work_entry_type = contract._get_default_work_entry_type()
            for interval in real_attendances:
                work_entry_type = 'work_entry_type_id' in interval[2] and interval[2].work_entry_type_id[:1]\
                    or default_work_entry_type
                # All benefits generated here are using datetimes converted from the employee's timezone
                contract_vals += [dict([
                    ('name', "%s: %s" % (work_entry_type.name, employee.name)),
                    ('date_start', interval[0].astimezone(pytz.utc).replace(tzinfo=None)),
                    ('date_stop', interval[1].astimezone(pytz.utc).replace(tzinfo=None)),
                    ('work_entry_type_id', work_entry_type.id),
                    ('employee_id', employee.id),
                    ('contract_id', contract.id),
                    ('company_id', contract.company_id.id),
                    ('state', 'draft'),
                ] + contract._get_more_vals_attendance_interval(interval))]

            for interval in real_leaves:
                # Could happen when a leave is configured on the interface on a day for which the
                # employee is not supposed to work, i.e. no attendance_ids on the calendar.
                # In that case, do try to generate an empty work entry, as this would raise a
                # sql constraint error
                if interval[0] == interval[1]:  # if start == stop
                    continue
                leave_entry_type = contract._get_interval_leave_work_entry_type(interval, leaves, bypassing_work_entry_type_codes)
                interval_start = interval[0].astimezone(pytz.utc).replace(tzinfo=None)
                interval_stop = interval[1].astimezone(pytz.utc).replace(tzinfo=None)
                contract_vals += [dict([
                    ('name', "%s%s" % (leave_entry_type.name + ": " if leave_entry_type else "", employee.name)),
                    ('date_start', interval_start),
                    ('date_stop', interval_stop),
                    ('work_entry_type_id', leave_entry_type.id),
                    ('employee_id', employee.id),
                    ('company_id', contract.company_id.id),
                    ('state', 'draft'),
                    ('contract_id', contract.id),
                ] + contract._get_more_vals_leave_interval(interval, leaves))]
        return contract_vals
コード例 #21
0
ファイル: test_resource.py プロジェクト: dai08170/odooerp
 def check(a, b):
     a, b = self.ints(a), self.ints(b)
     self.assertEqual(list(Intervals(a)), b)