1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/planning/models/planning_template.py
2024-12-10 09:04:09 +07:00

121 lines
6.0 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import math
import pytz
from datetime import datetime, timedelta, time, date
from odoo import api, fields, models, _
from odoo.tools import format_time, float_round
from odoo.addons.resource.models.utils import float_to_time
from odoo.exceptions import ValidationError
class PlanningTemplate(models.Model):
_name = 'planning.slot.template'
_description = "Shift Template"
_order = "sequence"
@api.model
def _default_start_time(self):
company_interval = self.env.company.resource_calendar_id._work_intervals_batch(
pytz.utc.localize(datetime.combine(datetime.today().date(), time.min)),
pytz.utc.localize(datetime.combine(datetime.today().date(), time.max)),
)[False]
if not company_interval:
return
calendar_tz = pytz.timezone(self.env.company.resource_calendar_id.tz)
user_tz = pytz.timezone(self.env.user.tz) if self.env.user.tz else pytz.utc
end_time = calendar_tz.localize(company_interval._items[0][0].replace(tzinfo=None)).astimezone(user_tz).replace(tzinfo=None).time()
return float_round(end_time.hour + end_time.minute / 60 + end_time.second / 3600, precision_digits=2)
@api.model
def _default_duration(self):
return self.env.company.resource_calendar_id.get_work_hours_count(
pytz.utc.localize(datetime.combine(datetime.today().date(), time.min)),
pytz.utc.localize(datetime.combine(datetime.today().date(), time.max)),
)
active = fields.Boolean('Active', default=True)
name = fields.Char('Hours', compute="_compute_name")
sequence = fields.Integer('Sequence', index=True)
role_id = fields.Many2one('planning.role', string="Role")
start_time = fields.Float('Planned Hours', default=_default_start_time, group_operator=None, default_export_compatible=True)
duration = fields.Float('Duration', default=_default_duration, group_operator=None, default_export_compatible=True)
end_time = fields.Float('End Hour', compute='_compute_name', group_operator=None)
duration_days = fields.Integer('Duration Days', compute='_compute_name')
_sql_constraints = [
('check_start_time_lower_than_24', 'CHECK(start_time < 24)', 'The start hour cannot be greater than 24.'),
('check_start_time_positive', 'CHECK(start_time >= 0)', 'The start hour cannot be negative.'),
('check_duration_positive', 'CHECK(duration >= 0)', 'The duration cannot be negative.')
]
@api.constrains('duration')
def _validate_duration(self):
try:
for shift_template in self:
datetime.today() + shift_template._get_duration()
except OverflowError:
raise ValidationError(_("The selected duration creates a date too far into the future."))
@api.depends('start_time', 'duration')
def _compute_name(self):
calendar = self.env.company.resource_calendar_id
user_tz = pytz.timezone(self.env['planning.slot']._get_tz())
today = date.today()
for shift_template in self:
if not 0 <= shift_template.start_time < 24:
raise ValidationError(_('The start hour must be greater or equal to 0 and lower than 24.'))
start_time = time(hour=int(shift_template.start_time), minute=round(math.modf(shift_template.start_time)[0] / (1 / 60.0)))
start_datetime = user_tz.localize(datetime.combine(today, start_time))
shift_template.duration_days, shift_template.end_time = self._get_company_work_duration_data(calendar, start_datetime, shift_template.duration)
end_time = time(hour=int(shift_template.end_time), minute=round(math.modf(shift_template.end_time)[0] / (1 / 60.0)))
shift_template.name = '%s - %s %s' % (
format_time(shift_template.env, start_time, time_format='short').replace(':00 ', ' '),
format_time(shift_template.env, end_time, time_format='short').replace(':00 ', ' '),
_('(%s days span)', shift_template.duration_days) if shift_template.duration_days > 1 else ''
)
def _get_company_work_duration_data(self, calendar, start_datetime, duration):
""""
Taking company's working calendar into account get the `hours` and
`days` from start_time and duration expressed in time and hours.
:param start_time: reference time
:param duration: reference duration in hours
Returns a tuple (duration, end_time) expressed as days and as hours.
"""
end_datetime = calendar.plan_hours(duration, start_datetime, compute_leaves=True)
if end_datetime is False:
raise ValidationError(_('The duration is too long.'))
if duration == 0 and start_datetime.hour == 0:
end_datetime = end_datetime.replace(hour=0)
return (
math.ceil(calendar.get_work_duration_data(start_datetime, end_datetime)['days']),
timedelta(hours=end_datetime.hour, minutes=end_datetime.minute).total_seconds() / 3600,
)
@api.depends('role_id')
def _compute_display_name(self):
for shift_template in self:
name = '{} {}'.format(
shift_template.name,
shift_template.role_id.name if shift_template.role_id.name is not False else '',
)
shift_template.display_name = name
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
res = []
for data in super(PlanningTemplate, self).read_group(domain, fields, groupby, offset, limit, orderby, lazy):
if 'start_time' in data:
data['start_time'] = float_to_time(data['start_time']).strftime('%H:%M')
res.append(data)
return res
def _get_duration(self):
self.ensure_one()
return timedelta(hours=int(self.duration), minutes=round(math.modf(self.duration)[0] / (1 / 60.0)))