mirror of
https://github.com/bringout/oca-ocb-hr.git
synced 2026-04-27 08:12:00 +02:00
19.0 vanilla
This commit is contained in:
parent
a1137a1456
commit
e1d89e11e3
2789 changed files with 1093187 additions and 605897 deletions
|
|
@ -1,96 +1,126 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import pytz
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from collections import defaultdict
|
||||
from odoo.tools.intervals import Intervals
|
||||
from odoo import models, fields, api, exceptions, _
|
||||
from odoo.tools import float_round
|
||||
|
||||
|
||||
class HrEmployee(models.Model):
|
||||
_inherit = "hr.employee"
|
||||
|
||||
attendance_manager_id = fields.Many2one(
|
||||
'res.users', store=True, readonly=False,
|
||||
string="Attendance Approver",
|
||||
domain="[('share', '=', False), ('company_ids', 'in', company_id)]",
|
||||
groups="hr_attendance.group_hr_attendance_officer",
|
||||
help="The user set in Attendance will access the attendance of the employee through the dedicated app and will be able to edit them.")
|
||||
attendance_ids = fields.One2many(
|
||||
'hr.attendance', 'employee_id', groups="hr_attendance.group_hr_attendance_user,hr.group_hr_user")
|
||||
'hr.attendance', 'employee_id', groups="hr_attendance.group_hr_attendance_officer,hr.group_hr_user")
|
||||
last_attendance_id = fields.Many2one(
|
||||
'hr.attendance', compute='_compute_last_attendance_id', store=True,
|
||||
groups="hr_attendance.group_hr_attendance_kiosk,hr_attendance.group_hr_attendance,hr.group_hr_user")
|
||||
groups="hr_attendance.group_hr_attendance_officer,hr.group_hr_user")
|
||||
last_check_in = fields.Datetime(
|
||||
related='last_attendance_id.check_in', store=True,
|
||||
groups="hr_attendance.group_hr_attendance_user,hr.group_hr_user")
|
||||
groups="hr_attendance.group_hr_attendance_officer,hr.group_hr_user", tracking=False)
|
||||
last_check_out = fields.Datetime(
|
||||
related='last_attendance_id.check_out', store=True,
|
||||
groups="hr_attendance.group_hr_attendance_user,hr.group_hr_user")
|
||||
groups="hr_attendance.group_hr_attendance_officer,hr.group_hr_user", tracking=False)
|
||||
attendance_state = fields.Selection(
|
||||
string="Attendance Status", compute='_compute_attendance_state',
|
||||
selection=[('checked_out', "Checked out"), ('checked_in', "Checked in")],
|
||||
groups="hr_attendance.group_hr_attendance_kiosk,hr_attendance.group_hr_attendance,hr.group_hr_user")
|
||||
hours_last_month = fields.Float(
|
||||
compute='_compute_hours_last_month', groups="hr_attendance.group_hr_attendance_user,hr.group_hr_user")
|
||||
groups="hr_attendance.group_hr_attendance_officer,hr.group_hr_user")
|
||||
hours_last_month = fields.Float(compute='_compute_hours_last_month')
|
||||
hours_last_month_overtime = fields.Float(compute='_compute_hours_last_month')
|
||||
hours_today = fields.Float(
|
||||
compute='_compute_hours_today',
|
||||
groups="hr_attendance.group_hr_attendance_kiosk,hr_attendance.group_hr_attendance,hr.group_hr_user")
|
||||
groups="hr_attendance.group_hr_attendance_officer,hr.group_hr_user")
|
||||
hours_previously_today = fields.Float(
|
||||
compute='_compute_hours_today',
|
||||
groups="hr_attendance.group_hr_attendance_officer,hr.group_hr_user")
|
||||
last_attendance_worked_hours = fields.Float(
|
||||
compute='_compute_hours_today',
|
||||
groups="hr_attendance.group_hr_attendance_officer,hr.group_hr_user")
|
||||
hours_last_month_display = fields.Char(
|
||||
compute='_compute_hours_last_month', groups="hr_attendance.group_hr_attendance_user,hr.group_hr_user")
|
||||
compute='_compute_hours_last_month', groups="hr.group_hr_user")
|
||||
overtime_ids = fields.One2many(
|
||||
'hr.attendance.overtime', 'employee_id', groups="hr_attendance.group_hr_attendance_user,hr.group_hr_user")
|
||||
total_overtime = fields.Float(
|
||||
compute='_compute_total_overtime', compute_sudo=True,
|
||||
groups="hr_attendance.group_hr_attendance_kiosk,hr_attendance.group_hr_attendance,hr.group_hr_user")
|
||||
'hr.attendance.overtime.line', 'employee_id', groups="hr_attendance.group_hr_attendance_officer,hr.group_hr_user")
|
||||
total_overtime = fields.Float(compute='_compute_total_overtime', compute_sudo=True)
|
||||
display_extra_hours = fields.Boolean(related='company_id.hr_attendance_display_overtime')
|
||||
|
||||
@api.depends('overtime_ids.duration', 'attendance_ids')
|
||||
ruleset_id = fields.Many2one(readonly=False, related="version_id.ruleset_id", inherited=True, groups="hr.group_hr_manager")
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
officer_group = self.env.ref('hr_attendance.group_hr_attendance_officer', raise_if_not_found=False)
|
||||
group_updates = []
|
||||
for vals in vals_list:
|
||||
if officer_group and vals.get('attendance_manager_id'):
|
||||
group_updates.append((4, vals['attendance_manager_id']))
|
||||
if group_updates:
|
||||
officer_group.sudo().write({'user_ids': group_updates})
|
||||
return super().create(vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
old_officers = self.env['res.users']
|
||||
if 'attendance_manager_id' in vals:
|
||||
old_officers = self.attendance_manager_id
|
||||
# Officer was added
|
||||
if vals['attendance_manager_id']:
|
||||
officer = self.env['res.users'].browse(vals['attendance_manager_id'])
|
||||
officers_group = self.env.ref('hr_attendance.group_hr_attendance_officer', raise_if_not_found=False)
|
||||
if officers_group and not officer.has_group('hr_attendance.group_hr_attendance_officer'):
|
||||
officer.sudo().write({'group_ids': [(4, officers_group.id)]})
|
||||
|
||||
res = super().write(vals)
|
||||
old_officers.sudo()._clean_attendance_officers()
|
||||
|
||||
return res
|
||||
|
||||
@api.depends('overtime_ids.manual_duration', 'overtime_ids', 'overtime_ids.status')
|
||||
def _compute_total_overtime(self):
|
||||
for employee in self:
|
||||
if employee.company_id.hr_attendance_overtime:
|
||||
employee.total_overtime = float_round(sum(employee.overtime_ids.mapped('duration')), 2)
|
||||
else:
|
||||
employee.total_overtime = 0
|
||||
mapped_validated_overtimes = dict(
|
||||
self.env['hr.attendance.overtime.line']._read_group(
|
||||
domain=[('status', '=', 'approved')],
|
||||
groupby=['employee_id'],
|
||||
aggregates=['manual_duration:sum']
|
||||
))
|
||||
|
||||
@api.depends('user_id.im_status', 'attendance_state')
|
||||
def _compute_presence_state(self):
|
||||
"""
|
||||
Override to include checkin/checkout in the presence state
|
||||
Attendance has the second highest priority after login
|
||||
"""
|
||||
super()._compute_presence_state()
|
||||
employees = self.filtered(lambda e: e.hr_presence_state != 'present')
|
||||
employee_to_check_working = self.filtered(lambda e: e.attendance_state == 'checked_out'
|
||||
and e.hr_presence_state == 'to_define')
|
||||
working_now_list = employee_to_check_working._get_employee_working_now()
|
||||
for employee in employees:
|
||||
if employee.attendance_state == 'checked_out' and employee.hr_presence_state == 'to_define' and \
|
||||
employee.id not in working_now_list:
|
||||
employee.hr_presence_state = 'absent'
|
||||
elif employee.attendance_state == 'checked_in':
|
||||
employee.hr_presence_state = 'present'
|
||||
for employee in self:
|
||||
employee.total_overtime = mapped_validated_overtimes.get(employee, 0)
|
||||
|
||||
def _compute_hours_last_month(self):
|
||||
"""
|
||||
Compute hours and overtime hours in the current month, if we are the 15th of october, will compute from 1 oct to 15 oct
|
||||
"""
|
||||
now = fields.Datetime.now()
|
||||
now_utc = pytz.utc.localize(now)
|
||||
for employee in self:
|
||||
tz = pytz.timezone(employee.tz or 'UTC')
|
||||
now_tz = now_utc.astimezone(tz)
|
||||
start_tz = now_tz + relativedelta(months=-1, day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
start_tz = now_tz.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
start_naive = start_tz.astimezone(pytz.utc).replace(tzinfo=None)
|
||||
end_tz = now_tz + relativedelta(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
end_tz = now_tz
|
||||
end_naive = end_tz.astimezone(pytz.utc).replace(tzinfo=None)
|
||||
|
||||
attendances = self.env['hr.attendance'].search([
|
||||
('employee_id', '=', employee.id),
|
||||
'&',
|
||||
('check_in', '<=', end_naive),
|
||||
('check_out', '>=', start_naive),
|
||||
])
|
||||
|
||||
current_month_attendances = employee.attendance_ids.filtered(
|
||||
lambda att: att.check_in >= start_naive and att.check_out and att.check_out <= end_naive
|
||||
)
|
||||
hours = 0
|
||||
for attendance in attendances:
|
||||
check_in = max(attendance.check_in, start_naive)
|
||||
check_out = min(attendance.check_out, end_naive)
|
||||
hours += (check_out - check_in).total_seconds() / 3600.0
|
||||
|
||||
overtime_hours = 0
|
||||
for att in current_month_attendances:
|
||||
hours += att.worked_hours or 0
|
||||
overtime_hours += att.validated_overtime_hours or 0
|
||||
employee.hours_last_month = round(hours, 2)
|
||||
employee.hours_last_month_display = "%g" % employee.hours_last_month
|
||||
# overtime_adjustments = sum(
|
||||
# ot.duration or 0
|
||||
# for ot in employee.overtime_ids.filtered(
|
||||
# lambda ot: ot.date >= start_tz.date() and ot.date <= end_tz.date() and ot.adjustment
|
||||
# )
|
||||
# )
|
||||
employee.hours_last_month_overtime = round(overtime_hours, 2)
|
||||
|
||||
def _compute_hours_today(self):
|
||||
now = fields.Datetime.now()
|
||||
|
|
@ -103,23 +133,31 @@ class HrEmployee(models.Model):
|
|||
start_naive = start_tz.astimezone(pytz.utc).replace(tzinfo=None)
|
||||
|
||||
attendances = self.env['hr.attendance'].search([
|
||||
('employee_id', '=', employee.id),
|
||||
('employee_id', 'in', employee.ids),
|
||||
('check_in', '<=', now),
|
||||
'|', ('check_out', '>=', start_naive), ('check_out', '=', False),
|
||||
])
|
||||
|
||||
], order='check_in asc')
|
||||
hours_previously_today = 0
|
||||
worked_hours = 0
|
||||
attendance_worked_hours = 0
|
||||
for attendance in attendances:
|
||||
delta = (attendance.check_out or now) - max(attendance.check_in, start_naive)
|
||||
worked_hours += delta.total_seconds() / 3600.0
|
||||
attendance_worked_hours = delta.total_seconds() / 3600.0
|
||||
worked_hours += attendance_worked_hours
|
||||
hours_previously_today += attendance_worked_hours
|
||||
employee.last_attendance_worked_hours = attendance_worked_hours
|
||||
hours_previously_today -= attendance_worked_hours
|
||||
employee.hours_previously_today = hours_previously_today
|
||||
employee.hours_today = worked_hours
|
||||
|
||||
@api.depends('attendance_ids')
|
||||
def _compute_last_attendance_id(self):
|
||||
current_datetime = fields.Datetime.now()
|
||||
for employee in self:
|
||||
employee.last_attendance_id = self.env['hr.attendance'].search([
|
||||
('employee_id', '=', employee.id),
|
||||
], limit=1)
|
||||
('employee_id', 'in', employee.ids),
|
||||
('check_in', '<=', current_datetime),
|
||||
], order="check_in desc", limit=1)
|
||||
|
||||
@api.depends('last_attendance_id.check_in', 'last_attendance_id.check_out', 'last_attendance_id')
|
||||
def _compute_attendance_state(self):
|
||||
|
|
@ -127,56 +165,7 @@ class HrEmployee(models.Model):
|
|||
att = employee.last_attendance_id.sudo()
|
||||
employee.attendance_state = att and not att.check_out and 'checked_in' or 'checked_out'
|
||||
|
||||
@api.model
|
||||
def attendance_scan(self, barcode):
|
||||
""" Receive a barcode scanned from the Kiosk Mode and change the attendances of corresponding employee.
|
||||
Returns either an action or a warning.
|
||||
"""
|
||||
employee = self.sudo().search([('barcode', '=', barcode)], limit=1)
|
||||
if employee:
|
||||
return employee._attendance_action('hr_attendance.hr_attendance_action_kiosk_mode')
|
||||
return {'warning': _("No employee corresponding to Badge ID '%(barcode)s.'") % {'barcode': barcode}}
|
||||
|
||||
def attendance_manual(self, next_action, entered_pin=None):
|
||||
self.ensure_one()
|
||||
attendance_user_and_no_pin = self.user_has_groups(
|
||||
'hr_attendance.group_hr_attendance_user,'
|
||||
'!hr_attendance.group_hr_attendance_use_pin')
|
||||
can_check_without_pin = attendance_user_and_no_pin or (self.user_id == self.env.user and entered_pin is None)
|
||||
if can_check_without_pin or entered_pin is not None and entered_pin == self.sudo().pin:
|
||||
return self._attendance_action(next_action)
|
||||
if not self.user_has_groups('hr_attendance.group_hr_attendance_user'):
|
||||
return {'warning': _('To activate Kiosk mode without pin code, you must have access right as an Officer or above in the Attendance app. Please contact your administrator.')}
|
||||
return {'warning': _('Wrong PIN')}
|
||||
|
||||
def _attendance_action(self, next_action):
|
||||
""" Changes the attendance of the employee.
|
||||
Returns an action to the check in/out message,
|
||||
next_action defines which menu the check in/out message should return to. ("My Attendances" or "Kiosk Mode")
|
||||
"""
|
||||
self.ensure_one()
|
||||
employee = self.sudo()
|
||||
action_message = self.env["ir.actions.actions"]._for_xml_id("hr_attendance.hr_attendance_action_greeting_message")
|
||||
action_message['previous_attendance_change_date'] = employee.last_attendance_id and (employee.last_attendance_id.check_out or employee.last_attendance_id.check_in) or False
|
||||
action_message['employee_name'] = employee.name
|
||||
action_message['barcode'] = employee.barcode
|
||||
action_message['next_action'] = next_action
|
||||
action_message['hours_today'] = employee.hours_today
|
||||
action_message['kiosk_delay'] = employee.company_id.attendance_kiosk_delay * 1000
|
||||
|
||||
if employee.user_id:
|
||||
modified_attendance = employee.with_user(employee.user_id).sudo()._attendance_action_change()
|
||||
else:
|
||||
modified_attendance = employee._attendance_action_change()
|
||||
action_message['attendance'] = modified_attendance.read()[0]
|
||||
action_message['show_total_overtime'] = employee.company_id.hr_attendance_overtime
|
||||
action_message['total_overtime'] = employee.total_overtime
|
||||
# Overtime have an unique constraint on the day, no need for limit=1
|
||||
action_message['overtime_today'] = self.env['hr.attendance.overtime'].sudo().search([
|
||||
('employee_id', '=', employee.id), ('date', '=', fields.Date.context_today(self)), ('adjustment', '=', False)]).duration or 0
|
||||
return {'action': action_message}
|
||||
|
||||
def _attendance_action_change(self):
|
||||
def _attendance_action_change(self, geo_information=None):
|
||||
""" Check In/Check Out action
|
||||
Check In: create a new attendance record
|
||||
Check Out: modify check_out field of appropriate attendance record
|
||||
|
|
@ -185,27 +174,164 @@ class HrEmployee(models.Model):
|
|||
action_date = fields.Datetime.now()
|
||||
|
||||
if self.attendance_state != 'checked_in':
|
||||
vals = {
|
||||
'employee_id': self.id,
|
||||
'check_in': action_date,
|
||||
}
|
||||
if geo_information:
|
||||
vals = {
|
||||
'employee_id': self.id,
|
||||
'check_in': action_date,
|
||||
**{'in_%s' % key: geo_information[key] for key in geo_information}
|
||||
}
|
||||
else:
|
||||
vals = {
|
||||
'employee_id': self.id,
|
||||
'check_in': action_date,
|
||||
}
|
||||
return self.env['hr.attendance'].create(vals)
|
||||
attendance = self.env['hr.attendance'].search([('employee_id', '=', self.id), ('check_out', '=', False)], limit=1)
|
||||
if attendance:
|
||||
attendance.check_out = action_date
|
||||
if geo_information:
|
||||
attendance.write({
|
||||
'check_out': action_date,
|
||||
**{'out_%s' % key: geo_information[key] for key in geo_information}
|
||||
})
|
||||
else:
|
||||
attendance.write({
|
||||
'check_out': action_date
|
||||
})
|
||||
else:
|
||||
raise exceptions.UserError(_('Cannot perform check out on %(empl_name)s, could not find corresponding check in. '
|
||||
'Your attendances have probably been modified manually by human resources.') % {'empl_name': self.sudo().name, })
|
||||
raise exceptions.UserError(_(
|
||||
'Cannot perform check out on %(empl_name)s, could not find corresponding check in. '
|
||||
'Your attendances have probably been modified manually by human resources.',
|
||||
empl_name=self.sudo().name))
|
||||
return attendance
|
||||
|
||||
@api.model
|
||||
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
|
||||
if 'pin' in groupby or 'pin' in self.env.context.get('group_by', '') or self.env.context.get('no_group_by'):
|
||||
raise exceptions.UserError(_('Such grouping is not allowed.'))
|
||||
return super(HrEmployee, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
|
||||
def get_overtime_data(self, domain=None, employee_id=None):
|
||||
domain = [] if domain is None else domain
|
||||
validated_overtime = {
|
||||
attendance[0].id: attendance[1]
|
||||
for attendance in self.env["hr.attendance"]._read_group(
|
||||
domain=domain,
|
||||
groupby=['employee_id'],
|
||||
aggregates=['validated_overtime_hours:sum']
|
||||
)
|
||||
}
|
||||
return {"validated_overtime": validated_overtime, "overtime_adjustments": {}}
|
||||
|
||||
def action_open_last_month_attendances(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"name": _("Attendances This Month"),
|
||||
"res_model": "hr.attendance",
|
||||
"views": [[self.env.ref('hr_attendance.hr_attendance_employee_simple_tree_view').id, "list"]],
|
||||
"context": {
|
||||
"create": 0,
|
||||
"search_default_check_in_filter": 1,
|
||||
"employee_id": self.id,
|
||||
"display_extra_hours": self.display_extra_hours,
|
||||
},
|
||||
"domain": [('employee_id', '=', self.id)]
|
||||
}
|
||||
|
||||
@api.depends("user_id.im_status", "attendance_state")
|
||||
def _compute_presence_state(self):
|
||||
"""
|
||||
Override to include checkin/checkout in the presence state
|
||||
Attendance has the second highest priority after login
|
||||
"""
|
||||
super()._compute_presence_state()
|
||||
employees = self.filtered(lambda e: e.hr_presence_state != "present")
|
||||
employee_to_check_working = self.filtered(lambda e: e.sudo().attendance_state == "checked_out"
|
||||
and e.hr_presence_state == "out_of_working_hour")
|
||||
working_now_list = employee_to_check_working._get_employee_working_now()
|
||||
for employee in employees:
|
||||
if employee.sudo().attendance_state == "checked_out" and employee.hr_presence_state == "out_of_working_hour" and \
|
||||
employee.id in working_now_list:
|
||||
employee.hr_presence_state = "absent"
|
||||
elif employee.sudo().attendance_state == "checked_in":
|
||||
employee.hr_presence_state = "present"
|
||||
|
||||
def _compute_presence_icon(self):
|
||||
res = super()._compute_presence_icon()
|
||||
# All employee must chek in or check out. Everybody must have an icon
|
||||
self.filtered(lambda employee: not employee.show_hr_icon_display).show_hr_icon_display = True
|
||||
for employee in self:
|
||||
employee.show_hr_icon_display = employee.company_id.hr_presence_control_attendance or bool(employee.user_id)
|
||||
return res
|
||||
|
||||
def open_barcode_scanner(self):
|
||||
return {
|
||||
"type": "ir.actions.client",
|
||||
"tag": "employee_barcode_scanner",
|
||||
"name": "Badge Scanner"
|
||||
}
|
||||
|
||||
def _get_schedules_by_employee_by_work_type(self, start, stop, version_periods_by_employee):
|
||||
employees_by_calendar = defaultdict(lambda: self.env['hr.employee'])
|
||||
leave_intervals_by_cal_by_resource = defaultdict(lambda: defaultdict(Intervals))
|
||||
attendance_intervals_by_cal = defaultdict(Intervals)
|
||||
lunch_intervals_by_cal = defaultdict(Intervals)
|
||||
|
||||
for employee, intervals in version_periods_by_employee.items():
|
||||
for (_start, _stop, version) in intervals:
|
||||
employees_by_calendar[version.resource_calendar_id] |= employee
|
||||
|
||||
for cal, employees in employees_by_calendar.items():
|
||||
if not cal: # employees are fully flex
|
||||
continue
|
||||
cal_leave_intervals_by_resource = cal._leave_intervals_batch(
|
||||
start,
|
||||
stop,
|
||||
resources=employees.resource_id,
|
||||
)
|
||||
for resource, leave_intervals in cal_leave_intervals_by_resource.items():
|
||||
naive_leave_intervals = Intervals([(
|
||||
i_start.replace(tzinfo=None),
|
||||
i_stop.replace(tzinfo=None),
|
||||
i_model
|
||||
) for (i_start, i_stop, i_model) in leave_intervals])
|
||||
leave_intervals_by_cal_by_resource[cal][resource] = naive_leave_intervals
|
||||
|
||||
cal_attendance_intervals = cal._attendance_intervals_batch(
|
||||
start,
|
||||
stop,
|
||||
)[False]
|
||||
attendance_intervals_by_cal[cal] = Intervals([(
|
||||
i_start.replace(tzinfo=None),
|
||||
i_stop.replace(tzinfo=None),
|
||||
i_model
|
||||
) for (i_start, i_stop, i_model) in cal_attendance_intervals])
|
||||
|
||||
cal_lunch_intervals = cal._attendance_intervals_batch(
|
||||
start,
|
||||
stop,
|
||||
lunch=True
|
||||
)[False]
|
||||
lunch_intervals_by_cal[cal] = Intervals([(
|
||||
i_start.replace(tzinfo=None),
|
||||
i_stop.replace(tzinfo=None),
|
||||
i_model
|
||||
) for (i_start, i_stop, i_model) in cal_lunch_intervals])
|
||||
|
||||
full_schedule_by_employee = {
|
||||
'leave': defaultdict(Intervals),
|
||||
'schedule': defaultdict(lambda: {
|
||||
'work': Intervals([]),
|
||||
'lunch': Intervals([]),
|
||||
}),
|
||||
'fully_flexible': defaultdict(Intervals)
|
||||
}
|
||||
for employee, intervals in version_periods_by_employee.items():
|
||||
for (p_start, p_stop, version) in intervals:
|
||||
interval = Intervals([(p_start.replace(tzinfo=None), p_stop.replace(tzinfo=None), self.env['resource.calendar'])])
|
||||
calendar = version.resource_calendar_id
|
||||
if not calendar:
|
||||
full_schedule_by_employee['fully_flexible'][employee] |= interval
|
||||
continue
|
||||
employee_leaves = leave_intervals_by_cal_by_resource[calendar][employee.resource_id.id]
|
||||
full_schedule_by_employee['leave'][employee] |= employee_leaves & interval
|
||||
employee_attendances = attendance_intervals_by_cal[calendar]
|
||||
full_schedule_by_employee['schedule'][employee]['work'] |= employee_attendances & interval
|
||||
employee_lunches = lunch_intervals_by_cal[calendar]
|
||||
full_schedule_by_employee['schedule'][employee]['lunch'] |= employee_lunches & interval
|
||||
|
||||
return full_schedule_by_employee
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue