123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- # -*- coding: utf-8 -*-
- # Part of Odoo. See LICENSE file for full copyright and licensing details.
- from collections import defaultdict
- from pytz import timezone, utc
- from odoo import api, fields, models, _
- class ResourceCalendarLeaves(models.Model):
- _inherit = "resource.calendar.leaves"
- timesheet_ids = fields.One2many('account.analytic.line', 'global_leave_id', string="Analytic Lines")
- def _work_time_per_day(self):
- """ Get work time per day based on the calendar and its attendances
- 1) Gets all calendars with their characteristics (i.e.
- (a) the leaves in it,
- (b) the resources which have a leave,
- (c) the oldest and
- (d) the latest leave dates
- ) for leaves in self.
- 2) Search the attendances based on the characteristics retrieved for each calendar.
- The attendances found are the ones between the date_from of the oldest leave
- and the date_to of the most recent leave.
- 3) Create a dict as result of this method containing:
- {
- leave: {
- max(date_start of work hours, date_start of the leave):
- the duration in days of the work including the leave
- }
- }
- """
- leaves_read_group = self.env['resource.calendar.leaves']._read_group(
- [('id', 'in', self.ids)],
- ['calendar_id', 'ids:array_agg(id)', 'resource_ids:array_agg(resource_id)', 'min_date_from:min(date_from)', 'max_date_to:max(date_to)'],
- ['calendar_id'],
- )
- # dict of keys: calendar_id
- # and values : { 'date_from': datetime, 'date_to': datetime, resources: self.env['resource.resource'] }
- cal_attendance_intervals_dict = {
- res['calendar_id'][0]: {
- 'date_from': utc.localize(res['min_date_from']),
- 'date_to': utc.localize(res['max_date_to']),
- 'resources': self.env['resource.resource'].browse(res['resource_ids'] if res['resource_ids'] and res['resource_ids'][0] else []),
- 'leaves': self.env['resource.calendar.leaves'].browse(res['ids']),
- } for res in leaves_read_group
- }
- # to easily find the calendar with its id.
- calendars_dict = {calendar.id: calendar for calendar in self.calendar_id}
- # dict of keys: leave.id
- # and values: a dict of keys: date
- # and values: number of days
- results = defaultdict(lambda: defaultdict(float))
- for calendar_id, cal_attendance_intervals_params_entry in cal_attendance_intervals_dict.items():
- calendar = calendars_dict[calendar_id]
- work_hours_intervals = calendar._attendance_intervals_batch(
- cal_attendance_intervals_params_entry['date_from'],
- cal_attendance_intervals_params_entry['date_to'],
- cal_attendance_intervals_params_entry['resources'],
- tz=timezone(calendar.tz)
- )
- for leave in cal_attendance_intervals_params_entry['leaves']:
- work_hours_data = work_hours_intervals[leave.resource_id.id]
- for date_from, date_to, dummy in work_hours_data:
- if date_to > utc.localize(leave.date_from) and date_from < utc.localize(leave.date_to):
- tmp_start = max(date_from, utc.localize(leave.date_from))
- tmp_end = min(date_to, utc.localize(leave.date_to))
- results[leave.id][tmp_start.date()] += (tmp_end - tmp_start).total_seconds() / 3600
- results[leave.id] = sorted(results[leave.id].items())
- return results
- def _timesheet_create_lines(self):
- """ Create timesheet leaves for each employee using the same calendar containing in self.calendar_id
- If the employee has already a time off in the same day then no timesheet should be created.
- """
- work_hours_data = self._work_time_per_day()
- employees_groups = self.env['hr.employee']._read_group(
- [('resource_calendar_id', 'in', self.calendar_id.ids)],
- ['resource_calendar_id', 'ids:array_agg(id)'],
- ['resource_calendar_id'])
- mapped_employee = {
- employee['resource_calendar_id'][0]: self.env['hr.employee'].browse(employee['ids'])
- for employee in employees_groups
- }
- employee_ids_set = set()
- employee_ids_set.update(*[line['ids'] for line in employees_groups])
- min_date = max_date = None
- for values in work_hours_data.values():
- for d, dummy in values:
- if not min_date and not max_date:
- min_date = max_date = d
- elif d < min_date:
- min_date = d
- elif d > max_date:
- max_date = d
- holidays_read_group = self.env['hr.leave']._read_group([
- ('employee_id', 'in', list(employee_ids_set)),
- ('date_from', '<=', max_date),
- ('date_to', '>=', min_date),
- ('state', '=', 'validate'),
- ], ['date_from_list:array_agg(date_from)', 'date_to_list:array_agg(date_to)', 'employee_id'], ['employee_id'])
- holidays_by_employee = {
- line['employee_id'][0]: [
- (date_from.date(), date_to.date()) for date_from, date_to in zip(line['date_from_list'], line['date_to_list'])
- ] for line in holidays_read_group
- }
- vals_list = []
- for leave in self:
- for employee in mapped_employee.get(leave.calendar_id.id, self.env['hr.employee']):
- holidays = holidays_by_employee.get(employee.id)
- work_hours_list = work_hours_data[leave.id]
- for index, (day_date, work_hours_count) in enumerate(work_hours_list):
- if not holidays or all(not (date_from <= day_date and date_to >= day_date) for date_from, date_to in holidays):
- vals_list.append(
- leave._timesheet_prepare_line_values(
- index,
- employee,
- work_hours_list,
- day_date,
- work_hours_count
- )
- )
- return self.env['account.analytic.line'].sudo().create(vals_list)
- def _timesheet_prepare_line_values(self, index, employee_id, work_hours_data, day_date, work_hours_count):
- self.ensure_one()
- return {
- 'name': _("Time Off (%s/%s)", index + 1, len(work_hours_data)),
- 'project_id': employee_id.company_id.internal_project_id.id,
- 'task_id': employee_id.company_id.leave_timesheet_task_id.id,
- 'account_id': employee_id.company_id.internal_project_id.analytic_account_id.id,
- 'unit_amount': work_hours_count,
- 'user_id': employee_id.user_id.id,
- 'date': day_date,
- 'global_leave_id': self.id,
- 'employee_id': employee_id.id,
- 'company_id': employee_id.company_id.id,
- }
- @api.model_create_multi
- def create(self, vals_list):
- results = super(ResourceCalendarLeaves, self).create(vals_list)
- results_with_leave_timesheet = results.filtered(lambda r: not r.resource_id.id and r.calendar_id.company_id.internal_project_id and r.calendar_id.company_id.leave_timesheet_task_id)
- results_with_leave_timesheet and results_with_leave_timesheet._timesheet_create_lines()
- return results
- def write(self, vals):
- date_from, date_to, calendar_id = vals.get('date_from'), vals.get('date_to'), vals.get('calendar_id')
- global_time_off_updated = self.env['resource.calendar.leaves']
- if date_from or date_to or 'calendar_id' in vals:
- global_time_off_updated = self.filtered(lambda r: (date_from is not None and r.date_from != date_from) or (date_to is not None and r.date_to != date_to) or (calendar_id is not None and r.calendar_id.id != calendar_id))
- timesheets = global_time_off_updated.sudo().timesheet_ids
- if timesheets:
- timesheets.write({'global_leave_id': False})
- timesheets.unlink()
- result = super(ResourceCalendarLeaves, self).write(vals)
- if global_time_off_updated:
- global_time_offs_with_leave_timesheet = global_time_off_updated.filtered(lambda r: not r.resource_id and r.calendar_id.company_id.internal_project_id and r.calendar_id.company_id.leave_timesheet_task_id)
- global_time_offs_with_leave_timesheet.sudo()._timesheet_create_lines()
- return result
|