mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-22 11:12:00 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
|
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import resource
|
||||
from . import resource_mixin
|
||||
from . import res_company
|
||||
from . import res_users
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,45 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
|
||||
resource_calendar_ids = fields.One2many(
|
||||
'resource.calendar', 'company_id', 'Working Hours')
|
||||
resource_calendar_id = fields.Many2one(
|
||||
'resource.calendar', 'Default Working Hours', ondelete='restrict')
|
||||
|
||||
@api.model
|
||||
def _init_data_resource_calendar(self):
|
||||
self.search([('resource_calendar_id', '=', False)])._create_resource_calendar()
|
||||
|
||||
def _create_resource_calendar(self):
|
||||
vals_list = [
|
||||
company._prepare_resource_calendar_values()
|
||||
for company in self
|
||||
]
|
||||
resource_calendars = self.env['resource.calendar'].create(vals_list)
|
||||
for company, calendar in zip(self, resource_calendars):
|
||||
company.resource_calendar_id = calendar
|
||||
|
||||
def _prepare_resource_calendar_values(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': _('Standard 40 hours/week'),
|
||||
'company_id': self.id,
|
||||
}
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
companies = super().create(vals_list)
|
||||
companies_without_calendar = companies.filtered(lambda c: not c.resource_calendar_id)
|
||||
if companies_without_calendar:
|
||||
companies_without_calendar.sudo()._create_resource_calendar()
|
||||
# calendar created from form view: no company_id set because record was still not created
|
||||
for company in companies:
|
||||
if not company.resource_calendar_id.company_id:
|
||||
company.resource_calendar_id.company_id = company.id
|
||||
return companies
|
||||
27
odoo-bringout-oca-ocb-resource/resource/models/res_users.py
Normal file
27
odoo-bringout-oca-ocb-resource/resource/models/res_users.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
resource_ids = fields.One2many(
|
||||
'resource.resource', 'user_id', 'Resources')
|
||||
resource_calendar_id = fields.Many2one(
|
||||
'resource.calendar', 'Default Working Hours',
|
||||
related='resource_ids.calendar_id', readonly=False)
|
||||
|
||||
def write(self, vals):
|
||||
rslt = super().write(vals)
|
||||
|
||||
# If the timezone of the admin user gets set on their first login, also update the timezone of the default working calendar
|
||||
if (vals.get('tz') and len(self) == 1 and not self.env.user.login_date
|
||||
and self.env.user == self.env.ref('base.user_admin', False) and self == self.env.user):
|
||||
if self.resource_calendar_id:
|
||||
self.resource_calendar_id.tz = vals['tz']
|
||||
else:
|
||||
self.env.ref('resource.resource_calendar_std', False).tz = vals['tz']
|
||||
|
||||
return rslt
|
||||
1168
odoo-bringout-oca-ocb-resource/resource/models/resource.py
Normal file
1168
odoo-bringout-oca-ocb-resource/resource/models/resource.py
Normal file
File diff suppressed because it is too large
Load diff
227
odoo-bringout-oca-ocb-resource/resource/models/resource_mixin.py
Normal file
227
odoo-bringout-oca-ocb-resource/resource/models/resource_mixin.py
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import defaultdict
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from pytz import utc
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
def timezone_datetime(time):
|
||||
if not time.tzinfo:
|
||||
time = time.replace(tzinfo=utc)
|
||||
return time
|
||||
|
||||
|
||||
class ResourceMixin(models.AbstractModel):
|
||||
_name = "resource.mixin"
|
||||
_description = 'Resource Mixin'
|
||||
|
||||
resource_id = fields.Many2one(
|
||||
'resource.resource', 'Resource',
|
||||
auto_join=True, index=True, ondelete='restrict', required=True)
|
||||
company_id = fields.Many2one(
|
||||
'res.company', 'Company',
|
||||
default=lambda self: self.env.company,
|
||||
index=True, related='resource_id.company_id', store=True, readonly=False)
|
||||
resource_calendar_id = fields.Many2one(
|
||||
'resource.calendar', 'Working Hours',
|
||||
default=lambda self: self.env.company.resource_calendar_id,
|
||||
index=True, related='resource_id.calendar_id', store=True, readonly=False)
|
||||
tz = fields.Selection(
|
||||
string='Timezone', related='resource_id.tz', readonly=False,
|
||||
help="This field is used in order to define in which timezone the resources will work.")
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
resources_vals_list = []
|
||||
calendar_ids = [vals['resource_calendar_id'] for vals in vals_list if vals.get('resource_calendar_id')]
|
||||
calendars_tz = {calendar.id: calendar.tz for calendar in self.env['resource.calendar'].browse(calendar_ids)}
|
||||
for vals in vals_list:
|
||||
if not vals.get('resource_id'):
|
||||
resources_vals_list.append(
|
||||
self._prepare_resource_values(
|
||||
vals,
|
||||
vals.pop('tz', False) or calendars_tz.get(vals.get('resource_calendar_id'))
|
||||
)
|
||||
)
|
||||
if resources_vals_list:
|
||||
resources = self.env['resource.resource'].create(resources_vals_list)
|
||||
resources_iter = iter(resources.ids)
|
||||
for vals in vals_list:
|
||||
if not vals.get('resource_id'):
|
||||
vals['resource_id'] = next(resources_iter)
|
||||
return super(ResourceMixin, self.with_context(check_idempotence=True)).create(vals_list)
|
||||
|
||||
def _prepare_resource_values(self, vals, tz):
|
||||
resource_vals = {'name': vals.get(self._rec_name)}
|
||||
if tz:
|
||||
resource_vals['tz'] = tz
|
||||
company_id = vals.get('company_id', self.env.company.id)
|
||||
if company_id:
|
||||
resource_vals['company_id'] = company_id
|
||||
calendar_id = vals.get('resource_calendar_id')
|
||||
if calendar_id:
|
||||
resource_vals['calendar_id'] = calendar_id
|
||||
return resource_vals
|
||||
|
||||
def copy_data(self, default=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
|
||||
resource_default = {}
|
||||
if 'company_id' in default:
|
||||
resource_default['company_id'] = default['company_id']
|
||||
if 'resource_calendar_id' in default:
|
||||
resource_default['calendar_id'] = default['resource_calendar_id']
|
||||
resource = self.resource_id.copy(resource_default)
|
||||
|
||||
default['resource_id'] = resource.id
|
||||
default['company_id'] = resource.company_id.id
|
||||
default['resource_calendar_id'] = resource.calendar_id.id
|
||||
return super(ResourceMixin, self).copy_data(default)
|
||||
|
||||
def _get_work_days_data_batch(self, from_datetime, to_datetime, compute_leaves=True, calendar=None, domain=None):
|
||||
"""
|
||||
By default the resource calendar is used, but it can be
|
||||
changed using the `calendar` argument.
|
||||
|
||||
`domain` is used in order to recognise the leaves to take,
|
||||
None means default value ('time_type', '=', 'leave')
|
||||
|
||||
Returns a dict {'days': n, 'hours': h} containing the
|
||||
quantity of working time expressed as days and as hours.
|
||||
"""
|
||||
resources = self.mapped('resource_id')
|
||||
mapped_employees = {e.resource_id.id: e.id for e in self}
|
||||
result = {}
|
||||
|
||||
# naive datetimes are made explicit in UTC
|
||||
from_datetime = timezone_datetime(from_datetime)
|
||||
to_datetime = timezone_datetime(to_datetime)
|
||||
|
||||
mapped_resources = defaultdict(lambda: self.env['resource.resource'])
|
||||
for record in self:
|
||||
mapped_resources[calendar or record.resource_calendar_id] |= record.resource_id
|
||||
|
||||
for calendar, calendar_resources in mapped_resources.items():
|
||||
if not calendar:
|
||||
for calendar_resource in calendar_resources:
|
||||
result[calendar_resource.id] = {'days': 0, 'hours': 0}
|
||||
continue
|
||||
day_total = calendar._get_resources_day_total(from_datetime, to_datetime, calendar_resources)
|
||||
|
||||
# actual hours per day
|
||||
if compute_leaves:
|
||||
intervals = calendar._work_intervals_batch(from_datetime, to_datetime, calendar_resources, domain)
|
||||
else:
|
||||
intervals = calendar._attendance_intervals_batch(from_datetime, to_datetime, calendar_resources)
|
||||
|
||||
for calendar_resource in calendar_resources:
|
||||
result[calendar_resource.id] = calendar._get_days_data(intervals[calendar_resource.id], day_total[calendar_resource.id])
|
||||
|
||||
# convert "resource: result" into "employee: result"
|
||||
return {mapped_employees[r.id]: result[r.id] for r in resources}
|
||||
|
||||
def _get_leave_days_data_batch(self, from_datetime, to_datetime, calendar=None, domain=None):
|
||||
"""
|
||||
By default the resource calendar is used, but it can be
|
||||
changed using the `calendar` argument.
|
||||
|
||||
`domain` is used in order to recognise the leaves to take,
|
||||
None means default value ('time_type', '=', 'leave')
|
||||
|
||||
Returns a dict {'days': n, 'hours': h} containing the number of leaves
|
||||
expressed as days and as hours.
|
||||
"""
|
||||
resources = self.mapped('resource_id')
|
||||
mapped_employees = {e.resource_id.id: e.id for e in self}
|
||||
result = {}
|
||||
|
||||
# naive datetimes are made explicit in UTC
|
||||
from_datetime = timezone_datetime(from_datetime)
|
||||
to_datetime = timezone_datetime(to_datetime)
|
||||
|
||||
mapped_resources = defaultdict(lambda: self.env['resource.resource'])
|
||||
for record in self:
|
||||
mapped_resources[calendar or record.resource_calendar_id] |= record.resource_id
|
||||
|
||||
for calendar, calendar_resources in mapped_resources.items():
|
||||
day_total = calendar._get_resources_day_total(from_datetime, to_datetime, calendar_resources)
|
||||
|
||||
# compute actual hours per day
|
||||
attendances = calendar._attendance_intervals_batch(from_datetime, to_datetime, calendar_resources)
|
||||
leaves = calendar._leave_intervals_batch(from_datetime, to_datetime, calendar_resources, domain)
|
||||
|
||||
for calendar_resource in calendar_resources:
|
||||
result[calendar_resource.id] = calendar._get_days_data(
|
||||
attendances[calendar_resource.id] & leaves[calendar_resource.id],
|
||||
day_total[calendar_resource.id]
|
||||
)
|
||||
|
||||
# convert "resource: result" into "employee: result"
|
||||
return {mapped_employees[r.id]: result[r.id] for r in resources}
|
||||
|
||||
def _adjust_to_calendar(self, start, end):
|
||||
resource_results = self.resource_id._adjust_to_calendar(start, end)
|
||||
# change dict keys from resources to associated records.
|
||||
return {
|
||||
record: resource_results[record.resource_id]
|
||||
for record in self
|
||||
}
|
||||
|
||||
def list_work_time_per_day(self, from_datetime, to_datetime, calendar=None, domain=None):
|
||||
"""
|
||||
By default the resource calendar is used, but it can be
|
||||
changed using the `calendar` argument.
|
||||
|
||||
`domain` is used in order to recognise the leaves to take,
|
||||
None means default value ('time_type', '=', 'leave')
|
||||
|
||||
Returns a list of tuples (day, hours) for each day
|
||||
containing at least an attendance.
|
||||
"""
|
||||
resource = self.resource_id
|
||||
calendar = calendar or self.resource_calendar_id
|
||||
|
||||
# naive datetimes are made explicit in UTC
|
||||
if not from_datetime.tzinfo:
|
||||
from_datetime = from_datetime.replace(tzinfo=utc)
|
||||
if not to_datetime.tzinfo:
|
||||
to_datetime = to_datetime.replace(tzinfo=utc)
|
||||
|
||||
compute_leaves = self.env.context.get('compute_leaves', True)
|
||||
intervals = calendar._work_intervals_batch(from_datetime, to_datetime, resource, domain, compute_leaves=compute_leaves)[resource.id]
|
||||
result = defaultdict(float)
|
||||
for start, stop, meta in intervals:
|
||||
result[start.date()] += (stop - start).total_seconds() / 3600
|
||||
return sorted(result.items())
|
||||
|
||||
def list_leaves(self, from_datetime, to_datetime, calendar=None, domain=None):
|
||||
"""
|
||||
By default the resource calendar is used, but it can be
|
||||
changed using the `calendar` argument.
|
||||
|
||||
`domain` is used in order to recognise the leaves to take,
|
||||
None means default value ('time_type', '=', 'leave')
|
||||
|
||||
Returns a list of tuples (day, hours, resource.calendar.leaves)
|
||||
for each leave in the calendar.
|
||||
"""
|
||||
resource = self.resource_id
|
||||
calendar = calendar or self.resource_calendar_id
|
||||
|
||||
# naive datetimes are made explicit in UTC
|
||||
if not from_datetime.tzinfo:
|
||||
from_datetime = from_datetime.replace(tzinfo=utc)
|
||||
if not to_datetime.tzinfo:
|
||||
to_datetime = to_datetime.replace(tzinfo=utc)
|
||||
|
||||
attendances = calendar._attendance_intervals_batch(from_datetime, to_datetime, resource)[resource.id]
|
||||
leaves = calendar._leave_intervals_batch(from_datetime, to_datetime, resource, domain)[resource.id]
|
||||
result = []
|
||||
for start, stop, leave in (leaves & attendances):
|
||||
hours = (stop - start).total_seconds() / 3600
|
||||
result.append((start.date(), hours, leave))
|
||||
return result
|
||||
Loading…
Add table
Add a link
Reference in a new issue