19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:31:00 +01:00
parent a1137a1456
commit e1d89e11e3
2789 changed files with 1093187 additions and 605897 deletions

View file

@ -10,8 +10,23 @@ from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools.misc import format_date, get_lang
COLORS_MAP = {
0: 'lightgrey',
1: 'tomato',
2: 'sandybrown',
3: 'khaki',
4: 'skyblue',
5: 'dimgrey',
6: 'lightcoral',
7: 'steelblue',
8: 'darkslateblue',
9: 'crimson',
10: 'mediumseagreen',
11: 'mediumpurple',
}
class HrHolidaySummaryReport(models.AbstractModel):
class ReportHr_HolidaysReport_Holidayssummary(models.AbstractModel):
_name = 'report.hr_holidays.report_holidayssummary'
_description = 'Holidays Summary Report'
@ -35,7 +50,7 @@ class HrHolidaySummaryReport(models.AbstractModel):
def _get_day(self, start_date):
res = []
start_date = fields.Date.from_string(start_date)
for x in range(0, 60):
for _x in range(0, 60):
color = '#ababab' if self._date_is_day_off(start_date) else ''
res.append({'day_str': babel.dates.get_day_names('abbreviated', locale=get_lang(self.env).code)[start_date.weekday()], 'day': start_date.day, 'color': color})
start_date = start_date + relativedelta(days=1)
@ -65,13 +80,9 @@ class HrHolidaySummaryReport(models.AbstractModel):
res.append({'day': current.day, 'color': ''})
if self._date_is_day_off(current) :
res[index]['color'] = '#ababab'
# count and get leave summary details.
holiday_type = ['confirm','validate'] if holiday_type == 'both' else ['confirm'] if holiday_type == 'Confirmed' else ['validate']
holidays = self.env['hr.leave'].search([
('employee_id', '=', empid), ('state', 'in', holiday_type),
('date_from', '<=', str(end_date)),
('date_to', '>=', str(start_date))
])
holidays = self._get_leaves(start_date, self.env['hr.employee'].browse(empid), holiday_type)
for holiday in holidays:
# Convert date to user timezone, otherwise the report will not be consistent with the
# value displayed in the interface.
@ -79,54 +90,87 @@ class HrHolidaySummaryReport(models.AbstractModel):
date_from = fields.Datetime.context_timestamp(holiday, date_from).date()
date_to = fields.Datetime.from_string(holiday.date_to)
date_to = fields.Datetime.context_timestamp(holiday, date_to).date()
for index in range(0, ((date_to - date_from).days + 1)):
if date_from >= start_date and date_from <= end_date:
res[(date_from-start_date).days]['color'] = holiday.holiday_status_id.color_name
for _index in range((date_to - date_from).days + 1):
if start_date <= date_from <= end_date:
res[(date_from-start_date).days]['color'] = COLORS_MAP[holiday.holiday_status_id.color]
date_from += timedelta(1)
count += holiday.number_of_days
employee = self.env['hr.employee'].browse(empid)
return {'emp': employee.name, 'display': res, 'sum': count}
def _get_employees(self, data):
if 'depts' in data:
return self.env['hr.employee'].search([('department_id', 'in', data['depts'])])
elif 'emp' in data:
return self.env['hr.employee'].browse(data['emp'])
return self.env['hr.employee'].search([])
def _get_data_from_report(self, data):
res = []
Employee = self.env['hr.employee']
if 'depts' in data:
for department in self.env['hr.department'].browse(data['depts']):
employees = self._get_employees(data)
departments = self.env['hr.department'].browse(data['depts'])
for department in departments:
res.append({
'dept': department.name,
'data': [
self._get_leaves_summary(data['date_from'], emp.id, data['holiday_type'])
for emp in Employee.search([('department_id', '=', department.id)])
for emp in employees.filtered(lambda emp: emp.department_id.id == department.id)
],
'color': self._get_day(data['date_from']),
})
elif 'emp' in data:
res.append({'data': [
self._get_leaves_summary(data['date_from'], emp.id, data['holiday_type'])
for emp in Employee.browse(data['emp'])
for emp in self._get_employees(data)
]})
return res
def _get_holidays_status(self):
def _get_leaves(self, date_from, employees, holiday_type, date_to=None):
state = ['confirm', 'validate'] if holiday_type == 'both' else ['confirm'] if holiday_type == 'Confirmed' else ['validate']
if not date_to:
date_to = date_from + relativedelta(days=59)
return self.env['hr.leave'].search([
('employee_id', 'in', employees.ids),
('state', 'in', state),
('date_from', '<=', str(date_to)),
('date_to', '>=', str(date_from))
])
def _get_holidays_status(self, data):
res = []
for holiday in self.env['hr.leave.type'].search([]):
res.append({'color': holiday.color_name, 'name': holiday.name})
employees = self.env['hr.employee']
if {'depts', 'emp'} & data.keys():
employees = self._get_employees(data)
holidays = self._get_leaves(fields.Date.from_string(data['date_from']), employees, data['holiday_type'])
for leave_type in holidays.holiday_status_id:
res.append({'color': COLORS_MAP[leave_type.color], 'name': leave_type.name})
return res
@api.model
def _get_report_values(self, docids, data=None):
if not data.get('form'):
raise UserError(_("Form content is missing, this report cannot be printed."))
holidays_report = self.env['ir.actions.report']._get_report_from_name('hr_holidays.report_holidayssummary')
holidays = self.env['hr.leave'].browse(self.ids)
if data and data.get('form'):
return {
'doc_ids': self.ids,
'doc_model': holidays_report.model,
'docs': holidays,
'get_header_info': self._get_header_info(data['form']['date_from'], data['form']['holiday_type']),
'get_day': self._get_day(data['form']['date_from']),
'get_months': self._get_months(data['form']['date_from']),
'get_data_from_report': self._get_data_from_report(data['form']),
'get_holidays_status': self._get_holidays_status(data['form']),
}
return {
'doc_ids': self.ids,
'doc_model': holidays_report.model,
'docs': holidays,
'get_header_info': self._get_header_info(data['form']['date_from'], data['form']['holiday_type']),
'get_day': self._get_day(data['form']['date_from']),
'get_months': self._get_months(data['form']['date_from']),
'get_data_from_report': self._get_data_from_report(data['form']),
'get_holidays_status': self._get_holidays_status(),
'docs': holidays
}

View file

@ -1,28 +1,13 @@
<?xml version="1.0"?>
<odoo>
<record id="action_report_holidayssummary" model="ir.actions.report">
<field name="name">Time Off Summary</field>
<field name="model">hr.holidays.summary.dept</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">hr_holidays.report_holidayssummary</field>
<field name="report_file">hr_holidays.report_holidayssummary</field>
</record>
<record id="action_report_holidayssummary" model="ir.actions.report">
<field name="paperformat_id" ref="hr_holidays.paperformat_hrsummary"/>
</record>
<record id="action_report_holidayssummary2" model="ir.actions.report">
<field name="name">Time Off Summary</field>
<field name="model">hr.leave.allocation</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">hr_holidays.report_holidayssummary</field>
<field name="report_file">hr_holidays.report_holidayssummary</field>
</record>
<record id="action_report_holidayssummary" model="ir.actions.report">
<field name="paperformat_id" ref="hr_holidays.paperformat_hrsummary"/>
</record>
<record id="action_report_holidayssummary" model="ir.actions.report">
<field name="name">Time Off Summary</field>
<field name="model">hr.leave.allocation</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">hr_holidays.report_holidayssummary</field>
<field name="report_file">hr_holidays.report_holidayssummary</field>
<field name="paperformat_id" ref="hr_holidays.paperformat_hrsummary"/>
</record>
</odoo>

View file

@ -68,8 +68,10 @@
</thead>
<tbody>
<tr t-foreach="get_holidays_status" t-as="status">
&lt;td style=background-color:<t t-esc="status['color']"/>!important &gt;&lt;/td&gt;
<td><t t-esc="status['name']"/></td>
<span t-if="status['color']">
&lt;td style=background-color:<t t-esc="status['color']"/>!important &gt;&lt;/td&gt;
<td><t t-esc="status['name']"/></td>
</span>
</tr>
</tbody>
</table>

View file

@ -4,24 +4,24 @@
from odoo import api, fields, models, tools, _
class LeaveReport(models.Model):
_name = "hr.leave.employee.type.report"
class HrLeaveEmployeeTypeReport(models.Model):
_name = 'hr.leave.employee.type.report'
_description = 'Time Off Summary / Report'
_auto = False
_order = "date_from DESC, employee_id"
employee_id = fields.Many2one('hr.employee', string="Employee", readonly=True)
active_employee = fields.Boolean(readonly=True)
number_of_days = fields.Float('Number of Days', readonly=True, group_operator="sum")
number_of_days = fields.Float('Number of Days', readonly=True, aggregator="sum")
number_of_hours = fields.Float('Number of Hours', readonly=True, aggregator="sum")
department_id = fields.Many2one('hr.department', string='Department', readonly=True)
leave_type = fields.Many2one("hr.leave.type", string="Leave Type", readonly=True)
leave_type = fields.Many2one("hr.leave.type", string="Time Off Type", readonly=True)
holiday_status = fields.Selection([
('taken', 'Taken'), #taken = validated
('left', 'Left'),
('planned', 'Planned')
])
state = fields.Selection([
('draft', 'To Submit'),
('cancel', 'Cancelled'),
('confirm', 'To Approve'),
('refuse', 'Refused'),
@ -33,95 +33,170 @@ class LeaveReport(models.Model):
company_id = fields.Many2one('res.company', string="Company", readonly=True)
def init(self):
tools.drop_view_if_exists(self._cr, 'hr_leave_employee_type_report')
tools.drop_view_if_exists(self.env.cr, 'hr_leave_employee_type_report')
self._cr.execute("""
self.env.cr.execute("""
CREATE or REPLACE view hr_leave_employee_type_report as (
SELECT row_number() over(ORDER BY leaves.employee_id) as id,
leaves.employee_id as employee_id,
leaves.active_employee as active_employee,
leaves.number_of_days as number_of_days,
leaves.department_id as department_id,
leaves.leave_type as leave_type,
leaves.holiday_status as holiday_status,
leaves.state as state,
leaves.date_from as date_from,
leaves.date_to as date_to,
leaves.company_id as company_id
FROM (SELECT
allocation.employee_id as employee_id,
employee.active as active_employee,
CASE
WHEN allocation.id = min_allocation_id.min_id
THEN aggregate_allocation.number_of_days - COALESCE(aggregate_leave.number_of_days, 0)
ELSE 0
END as number_of_days,
allocation.department_id as department_id,
allocation.holiday_status_id as leave_type,
allocation.state as state,
allocation.date_from as date_from,
allocation.date_to as date_to,
'left' as holiday_status,
allocation.employee_company_id as company_id
FROM hr_leave_allocation as allocation
INNER JOIN hr_employee as employee ON (allocation.employee_id = employee.id)
WITH
/* Validated leaves */
validated_leaves as (
SELECT
l.employee_id as employee_id,
l.number_of_days as number_of_days,
l.number_of_hours as number_of_hours,
l.holiday_status_id as leave_type,
l.date_from as date_from,
l.date_to as date_to
FROM hr_leave l
WHERE l.state IN ('validate', 'validate1')
),
/* Obtain the minimum id for a given employee and type of leave */
LEFT JOIN
(SELECT employee_id, holiday_status_id, min(id) as min_id
FROM hr_leave_allocation GROUP BY employee_id, holiday_status_id) min_allocation_id
on (allocation.employee_id=min_allocation_id.employee_id and allocation.holiday_status_id=min_allocation_id.holiday_status_id)
/* FIFO-ordered validated allocations */
ordered_allocations as (
SELECT
allocation.id as allocation_id,
allocation.employee_id as employee_id,
employee.active as active_employee,
allocation.number_of_days as number_of_days,
allocation.number_of_hours_display as number_of_hours,
allocation.department_id as department_id,
allocation.holiday_status_id as leave_type,
allocation.state as state,
allocation.date_from as date_from,
allocation.date_to as date_to,
allocation.employee_company_id as company_id,
ROW_NUMBER() OVER (
PARTITION BY allocation.employee_id, allocation.holiday_status_id
ORDER BY allocation.date_from, allocation.id
) as fifo_rank,
SUM(allocation.number_of_days) OVER (
PARTITION BY allocation.employee_id, allocation.holiday_status_id
ORDER BY allocation.date_from, allocation.id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) as cumulative_allocated_days,
SUM(allocation.number_of_hours_display) OVER (
PARTITION BY allocation.employee_id, allocation.holiday_status_id
ORDER BY allocation.date_from, allocation.id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) as cumulative_allocated_hours
FROM hr_leave_allocation allocation
JOIN hr_employee employee ON (allocation.employee_id = employee.id)
WHERE allocation.state = 'validate'
),
/* Obtain the sum of allocations (validated) */
LEFT JOIN
(SELECT employee_id, holiday_status_id,
sum(CASE WHEN state = 'validate' and active = True THEN number_of_days ELSE 0 END) as number_of_days
FROM hr_leave_allocation
GROUP BY employee_id, holiday_status_id) aggregate_allocation
on (allocation.employee_id=aggregate_allocation.employee_id and allocation.holiday_status_id=aggregate_allocation.holiday_status_id)
/* Leaves applicable to each allocation */
taken_per_allocation as (
SELECT
oa.allocation_id,
SUM(vl.number_of_days) as taken_days,
SUM(vl.number_of_hours) as taken_hours
FROM ordered_allocations oa
LEFT JOIN validated_leaves vl
ON vl.employee_id = oa.employee_id
AND vl.leave_type = oa.leave_type
AND vl.date_from <= COALESCE(oa.date_to, 'infinity')
AND vl.date_to >= oa.date_from
GROUP BY oa.allocation_id
),
/* Obtain the sum of requested leaves (validated) */
LEFT JOIN
(SELECT employee_id, holiday_status_id,
sum(CASE WHEN state IN ('validate', 'validate1') THEN number_of_days ELSE 0 END) as number_of_days
FROM hr_leave
/* FIFO remaining balance per allocation */
fifo_balances as (
SELECT
oa.employee_id as employee_id,
oa.active_employee as active_employee,
GREATEST(oa.number_of_days - GREATEST(
COALESCE(tpa.taken_days, 0) - (oa.cumulative_allocated_days - oa.number_of_days), 0),
0) as number_of_days,
GREATEST(oa.number_of_hours - GREATEST(
COALESCE(tpa.taken_hours, 0) - (oa.cumulative_allocated_hours - oa.number_of_hours), 0),
0) as number_of_hours,
oa.department_id as department_id,
oa.leave_type as leave_type,
oa.state as state,
oa.date_from as date_from,
oa.date_to as date_to,
oa.company_id as company_id
FROM ordered_allocations oa
LEFT JOIN taken_per_allocation tpa
ON tpa.allocation_id = oa.allocation_id
)
GROUP BY employee_id, holiday_status_id) aggregate_leave
on (allocation.employee_id=aggregate_leave.employee_id and allocation.holiday_status_id = aggregate_leave.holiday_status_id)
/* Final unified result */
SELECT
row_number() OVER (ORDER BY leaves.employee_id, leaves.date_from) as id,
leaves.employee_id as employee_id,
leaves.active_employee as active_employee,
leaves.number_of_days as number_of_days,
leaves.number_of_hours as number_of_hours,
leaves.department_id as department_id,
leaves.leave_type as leave_type,
leaves.state as state,
leaves.date_from as date_from,
leaves.date_to as date_to,
leaves.holiday_status as holiday_status,
leaves.company_id as company_id
FROM (
/* Remaining leave balances */
SELECT
fb.employee_id as employee_id,
fb.active_employee as active_employee,
fb.number_of_days as number_of_days,
fb.number_of_hours as number_of_hours,
fb.department_id as department_id,
fb.leave_type as leave_type,
fb.state as state,
fb.date_from as date_from,
fb.date_to as date_to,
'left' as holiday_status,
fb.company_id as company_id
FROM fifo_balances fb
WHERE fb.number_of_days >= 0
UNION ALL SELECT
request.employee_id as employee_id,
employee.active as active_employee,
request.number_of_days as number_of_days,
request.department_id as department_id,
request.holiday_status_id as leave_type,
request.state as state,
request.date_from as date_from,
request.date_to as date_to,
CASE
WHEN request.state IN ('validate1', 'validate') THEN 'taken'
WHEN request.state = 'confirm' THEN 'planned'
END as holiday_status,
request.employee_company_id as company_id
FROM hr_leave as request
INNER JOIN hr_employee as employee ON (request.employee_id = employee.id)
WHERE request.state IN ('confirm', 'validate', 'validate1')) leaves
/* Planned and taken leave requests */
UNION ALL SELECT
request.employee_id as employee_id,
employee.active as active_employee,
request.number_of_days as number_of_days,
request.number_of_hours as number_of_hours,
request.department_id as department_id,
request.holiday_status_id as leave_type,
request.state as state,
request.date_from as date_from,
request.date_to as date_to,
CASE
WHEN request.state IN ('validate', 'validate1') THEN 'taken'
WHEN request.state = 'confirm' THEN 'planned'
END as holiday_status,
request.employee_company_id as company_id
FROM hr_leave as request
JOIN hr_employee as employee ON (request.employee_id = employee.id)
WHERE request.state IN ('confirm', 'validate', 'validate1')
) leaves
);
""")
@api.model
def action_time_off_analysis(self):
domain = []
domain = [('company_id', 'in', self.env.companies.ids)]
if self.env.context.get('active_ids'):
domain = [('employee_id', 'in', self.env.context.get('active_ids', []))]
domain = [('employee_id', 'in', self.env.context.get('active_ids', [])),
('state', '!=', 'cancel')]
return {
'name': _('Time Off Analysis'),
'name': _('Balance'),
'type': 'ir.actions.act_window',
'res_model': 'hr.leave.employee.type.report',
'view_mode': 'pivot',
'search_view_id': [self.env.ref('hr_holidays.view_search_hr_holidays_employee_type_report').id],
'domain': domain,
'help': _("""
<p class="o_view_nocontent_empty_folder">
No Balance yet!
</p>
<p>
Why don't you start by <a type="action" class="text-link" name="%d">Allocating Time off</a> ?
</p>
""", self.env.ref("hr_holidays.hr_leave_allocation_action_form").id),
'context': {
'search_default_year': True,
'search_default_company': True,

View file

@ -7,7 +7,7 @@
<search string="Search Time Off">
<field name="employee_id"/>
<field name="date_from"/>
<filter name="year" date="date_from" default_period="this_year" string="Period"/>
<filter name="year" date="date_from" default_period="year" string="Period"/>
<filter string="Company" name="company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Employee" name="employee" context="{'group_by':'employee_id'}"/>
</search>
@ -22,6 +22,7 @@
<pivot sample="1" disable_linking="1">
<field name="employee_id" type="row"/>
<field name="number_of_days" type="measure"/>
<field name="number_of_hours" type="measure"/>
<field name="leave_type" type="col"/>
<field name="holiday_status" type="col"/>
</pivot>
@ -31,9 +32,7 @@
<record id="action_hr_holidays_by_employee_and_type_report" model="ir.actions.server">
<field name="name">Time off Analysis by Employee and Time Off Type</field>
<field name="model_id" ref="hr_holidays.model_hr_leave_employee_type_report"/>
<field name="binding_model_id" ref="hr.model_hr_employee"/>
<field name="state">code</field>
<field name="groups_id" eval="[(4, ref('base.group_no_one'))]"/>
<field name="code">
action = model.action_time_off_analysis()
</field>

View file

@ -1,127 +1,91 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools, _
from odoo.osv import expression
from odoo import fields, models, tools
class LeaveReport(models.Model):
_name = "hr.leave.report"
class HrLeaveReport(models.Model):
_name = 'hr.leave.report'
_description = 'Time Off Summary / Report'
_inherit = ["hr.manager.department.report"]
_auto = False
_order = "date_from DESC, employee_id"
active = fields.Boolean(readonly=True)
employee_id = fields.Many2one('hr.employee', string="Employee", readonly=True)
leave_id = fields.Many2one('hr.leave', string="Leave Request", readonly=True)
leave_id = fields.Many2one('hr.leave', string="Time Off Request", readonly=True)
allocation_id = fields.Many2one('hr.leave.allocation', string="Allocation Request", readonly=True)
active_employee = fields.Boolean(readonly=True)
name = fields.Char('Description', readonly=True)
number_of_days = fields.Float('Number of Days', readonly=True)
number_of_hours = fields.Float('Number of Hours', readonly=True)
leave_type = fields.Selection([
('allocation', 'Allocation'),
('request', 'Time Off')
], string='Request Type', readonly=True)
department_id = fields.Many2one('hr.department', string='Department', readonly=True)
category_id = fields.Many2one('hr.employee.category', string='Employee Tag', readonly=True)
holiday_status_id = fields.Many2one("hr.leave.type", string="Leave Type", readonly=True)
holiday_status_id = fields.Many2one("hr.leave.type", string="Time Off Type", readonly=True)
state = fields.Selection([
('draft', 'To Submit'),
('cancel', 'Cancelled'),
('confirm', 'To Approve'),
('refuse', 'Refused'),
('validate1', 'Second Approval'),
('validate', 'Approved')
], string='Status', readonly=True)
holiday_type = fields.Selection([
('employee', 'By Employee'),
('category', 'By Employee Tag')
], string='Allocation Mode', readonly=True)
date_from = fields.Datetime('Start Date', readonly=True)
date_to = fields.Datetime('End Date', readonly=True)
company_id = fields.Many2one('res.company', string="Company", readonly=True)
def init(self):
tools.drop_view_if_exists(self._cr, 'hr_leave_report')
tools.drop_view_if_exists(self.env.cr, 'hr_leave_report')
self._cr.execute("""
self.env.cr.execute("""
CREATE or REPLACE view hr_leave_report as (
SELECT row_number() over(ORDER BY leaves.employee_id) as id,
leaves.allocation_id as allocation_id, leaves.leave_id as leave_id,
leaves.leave_id as leave_id,
leaves.allocation_id as allocation_id,
leaves.employee_id as employee_id, leaves.name as name,
leaves.active_employee as active_employee, leaves.active as active,
leaves.number_of_days as number_of_days, leaves.leave_type as leave_type,
leaves.category_id as category_id, leaves.department_id as department_id,
leaves.number_of_hours as number_of_hours,
leaves.department_id as department_id,
leaves.holiday_status_id as holiday_status_id, leaves.state as state,
leaves.holiday_type as holiday_type, leaves.date_from as date_from,
leaves.date_from as date_from,
leaves.date_to as date_to, leaves.company_id
from (select
allocation.active as active,
allocation.id as allocation_id,
null as leave_id,
allocation.id as allocation_id,
allocation.employee_id as employee_id,
employee.active as active_employee,
allocation.private_name as name,
allocation.name as name,
allocation.number_of_days as number_of_days,
allocation.category_id as category_id,
allocation.department_id as department_id,
allocation.number_of_hours_display as number_of_hours,
v.department_id as department_id,
allocation.holiday_status_id as holiday_status_id,
allocation.state as state,
allocation.holiday_type,
allocation.date_from as date_from,
allocation.date_to as date_to,
'allocation' as leave_type,
allocation.employee_company_id as company_id
from hr_leave_allocation as allocation
inner join hr_employee as employee on (allocation.employee_id = employee.id)
LEFT JOIN hr_version v ON v.id = employee.current_version_id
where employee.active IS True
union all select
request.active as active,
null as allocation_id,
request.id as leave_id,
null as allocation_id,
request.employee_id as employee_id,
employee.active as active_employee,
request.private_name as name,
(request.number_of_days * -1) as number_of_days,
request.category_id as category_id,
request.department_id as department_id,
(request.number_of_hours * -1) as number_of_hours,
v.department_id as department_id,
request.holiday_status_id as holiday_status_id,
request.state as state,
request.holiday_type,
request.date_from as date_from,
request.date_to as date_to,
'request' as leave_type,
request.employee_company_id as company_id
from hr_leave as request
inner join hr_employee as employee on (request.employee_id = employee.id)
LEFT JOIN hr_version v ON v.id = employee.current_version_id
where employee.active IS True
) leaves
);
""")
@api.model
def action_time_off_analysis(self):
domain = [('holiday_type', '=', 'employee')]
if self.env.context.get('active_ids'):
domain = expression.AND([
domain,
[('employee_id', 'in', self.env.context.get('active_ids', []))]
])
return {
'name': _('Time Off Analysis'),
'type': 'ir.actions.act_window',
'res_model': 'hr.leave.report',
'view_mode': 'tree,pivot,form',
'search_view_id': [self.env.ref('hr_holidays.view_hr_holidays_filter_report').id],
'domain': domain,
'context': {
'search_default_group_type': True,
'search_default_year': True,
'search_default_validated': True,
'search_default_active_employee': True,
}
}
def action_open_record(self):
self.ensure_one()

View file

@ -1,54 +1,68 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools, SUPERUSER_ID
from odoo import api, fields, models, tools
from odoo.addons.base.models.res_partner import _tz_get
from odoo.exceptions import ValidationError
class LeaveReportCalendar(models.Model):
_name = "hr.leave.report.calendar"
class HrLeaveReportCalendar(models.Model):
_name = 'hr.leave.report.calendar'
_description = 'Time Off Calendar'
_auto = False
_order = "start_datetime DESC, employee_id"
name = fields.Char(string='Name', readonly=True)
name = fields.Char(string='Name', readonly=True, compute="_compute_name")
start_datetime = fields.Datetime(string='From', readonly=True)
stop_datetime = fields.Datetime(string='To', readonly=True)
duration_display = fields.Char(related='leave_id.duration_display', readonly=True)
tz = fields.Selection(_tz_get, string="Timezone", readonly=True)
duration = fields.Float(string='Duration', readonly=True)
employee_id = fields.Many2one('hr.employee', readonly=True)
user_id = fields.Many2one('res.users', readonly=True)
department_id = fields.Many2one('hr.department', readonly=True)
job_id = fields.Many2one('hr.job', readonly=True)
company_id = fields.Many2one('res.company', readonly=True)
state = fields.Selection([
('draft', 'To Submit'),
('cancel', 'Cancelled'), # YTI This state seems to be unused. To remove
('cancel', 'Cancelled'),
('confirm', 'To Approve'),
('refuse', 'Refused'),
('validate1', 'Second Approval'),
('validate', 'Approved')
], readonly=True)
description = fields.Char("Description", readonly=True, groups='hr_holidays.group_hr_holidays_user')
holiday_status_id = fields.Many2one('hr.leave.type', readonly=True, string="Time Off Type",
groups='hr_holidays.group_hr_holidays_user')
is_hatched = fields.Boolean('Hatched', readonly=True)
is_striked = fields.Boolean('Striked', readonly=True)
is_absent = fields.Boolean(related='employee_id.is_absent')
member_of_department = fields.Boolean(related='employee_id.member_of_department')
leave_manager_id = fields.Many2one(related='employee_id.leave_manager_id')
leave_id = fields.Many2one(comodel_name='hr.leave', readonly=True, groups='hr_holidays.group_hr_holidays_user')
is_manager = fields.Boolean("Manager", compute="_compute_is_manager")
def init(self):
tools.drop_view_if_exists(self._cr, 'hr_leave_report_calendar')
self._cr.execute("""CREATE OR REPLACE VIEW hr_leave_report_calendar AS
(SELECT
tools.drop_view_if_exists(self.env.cr, 'hr_leave_report_calendar')
self.env.cr.execute("""CREATE OR REPLACE VIEW hr_leave_report_calendar AS
(SELECT
hl.id AS id,
CONCAT(em.name, ': ', hl.duration_display) AS name,
hl.id AS leave_id,
hl.date_from AS start_datetime,
hl.date_to AS stop_datetime,
hl.employee_id AS employee_id,
hl.state AS state,
hl.department_id AS department_id,
hl.number_of_days as duration,
hl.private_name AS description,
hl.holiday_status_id AS holiday_status_id,
em.company_id AS company_id,
em.job_id AS job_id,
v.job_id AS job_id,
em.user_id AS user_id,
COALESCE(
CASE WHEN hl.holiday_type = 'employee' THEN COALESCE(rr.tz, rc.tz) END,
rr.tz,
rc.tz,
cc.tz,
'UTC'
) AS tz,
@ -57,28 +71,66 @@ class LeaveReportCalendar(models.Model):
FROM hr_leave hl
LEFT JOIN hr_employee em
ON em.id = hl.employee_id
LEFT JOIN hr_version v ON v.id = em.current_version_id
LEFT JOIN resource_resource rr
ON rr.id = em.resource_id
LEFT JOIN resource_calendar rc
ON rc.id = em.resource_calendar_id
ON rc.id = v.resource_calendar_id
LEFT JOIN res_company co
ON co.id = em.company_id
LEFT JOIN resource_calendar cc
ON cc.id = co.resource_calendar_id
WHERE
hl.state IN ('confirm', 'validate', 'validate1')
AND hl.active IS TRUE
WHERE
hl.state IN ('confirm', 'validate', 'validate1', 'refuse')
);
""")
def _read(self, fields):
res = super()._read(fields)
def _compute_display_name(self):
if self.env.context.get('hide_employee_name') and 'employee_id' in self.env.context.get('group_by', []):
name_field = self._fields['name']
for record in self.with_user(SUPERUSER_ID):
self.env.cache.set(record, name_field, record.name.split(':')[-1].strip())
return res
for record in self:
record.display_name = record.name.removeprefix(f"{record.employee_id.name}: ")
else:
super()._compute_display_name()
@api.model
def get_unusual_days(self, date_from, date_to=None):
return self.env.user.employee_id._get_unusual_days(date_from, date_to)
@api.depends('employee_id.name', 'leave_id')
def _compute_name(self):
for leave in self:
leave.name = leave.employee_id.name
if self.env.user.has_group('hr_holidays.group_hr_holidays_user'):
# Include the time off type name
leave.name += f" {leave.leave_id.holiday_status_id.name}"
# Include the time off duration.
leave.name += f": {leave.sudo().leave_id.duration_display}"
@api.depends('leave_manager_id')
def _compute_is_manager(self):
for leave in self:
leave.is_manager = self.env.user.has_group('hr_holidays.group_hr_holidays_user') or leave.leave_manager_id == self.env.user
def action_approve(self):
current_user = self.env.user
if current_user.has_group('hr_holidays.group_hr_holidays_user'):
# If the user is a leave manager, approve the leave
self.leave_id.action_approve()
elif self.leave_manager_id == current_user and self.sudo().holiday_status_id.leave_validation_type in ('manager', 'both'):
# If the user is the employee's time off approver, approve the leave
self.sudo().leave_id.sudo(False).action_approve()
else:
# If the user is not a leave manager, raise an error
raise ValidationError(self.env._("You are not allowed to approve this leave request."))
def action_refuse(self):
current_user = self.env.user
if current_user.has_group('hr_holidays.group_hr_holidays_user'):
# If the user is a leave manager, refuse the leave
self.leave_id.action_refuse()
elif self.leave_manager_id == current_user and self.sudo().holiday_status_id.leave_validation_type in ('manager', 'both'):
# If the user is the employee's time off approver, refuse the leave
self.sudo().leave_id.sudo(False).action_refuse()
else:
# If the user is not a leave manager, raise an error
raise ValidationError(self.env._("You are not allowed to refuse this leave request."))

View file

@ -9,14 +9,40 @@
date_start="start_datetime"
date_stop="stop_datetime"
mode="month"
quick_add="False"
quick_create="0"
color="employee_id"
event_open_popup="True"
js_class="time_off_calendar"
js_class="time_off_report_calendar"
create="0"
show_unusual_days="True">
<field name="name"/>
<field name="employee_id" filters="1" invisible="1"/>
<field name="is_hatched" invisible="1"/>
<field name="state" invisible="1"/>
<field name="leave_manager_id" invisible="1"/>
</calendar>
</field>
</record>
<record id="hr_leave_report_calendar_year_view" model="ir.ui.view">
<field name="name">hr.leave.report.calendar.year.view</field>
<field name="model">hr.leave.report.calendar</field>
<field name="arch" type="xml">
<calendar
string="Time Off"
date_start="start_datetime"
date_stop="stop_datetime"
mode="year"
quick_create="0"
color="employee_id"
event_open_popup="True"
js_class="time_off_report_calendar"
show_unusual_days="True">
<field name="name"/>
<field name="employee_id" filters="1" invisible="1"/>
<field name="is_hatched" invisible="1"/>
<field name="state" invisible="1"/>
<field name="leave_manager_id" invisible="1"/>
</calendar>
</field>
</record>
@ -26,12 +52,33 @@
<field name="model">hr.leave.report.calendar</field>
<field name="arch" type="xml">
<form string="Time Off">
<group>
<field name="name"/>
<field name="start_datetime"/>
<field name="stop_datetime"/>
<field name="employee_id" />
</group>
<sheet class="pt-2" style="min-height: 10rem">
<widget name="web_ribbon" title="Cancelled" bg_color="text-bg-danger" invisible="state != 'cancel'"/>
<widget name="web_ribbon" title="Refused" bg_color="bg-danger" invisible="state != 'refuse'"/>
<widget name="web_ribbon" title="Approved" bg_color="bg-success" invisible="state != 'validate'"/>
<group class="py-0 pe-0 overflow-hidden">
<field name="employee_id" widget="many2one_avatar_employee"/>
<field name="holiday_status_id"/>
<label for="start_datetime" string="Dates" />
<div id="full_date" class="o_row">
<field
string='dates'
name="start_datetime"
widget="daterange"
readonly='1'
options="{'end_date_field': 'stop_datetime', 'show_time': false}"/>
<field name="start_datetime" invisible="1"/>
<div style="margin-left: 4.82rem;">
( <field name="duration_display" readonly="1" class="w-auto"/> )
</div>
</div>
</group>
<field name="description"/>
<footer class="d-flex justify-content-end gap-1">
<button name="action_approve" string="Approve" type="object" close="1" invisible="state not in ['confirm', 'refuse'] or not is_manager"/>
<button name="action_refuse" string="Refuse" type="object" close="1" invisible="state == 'refuse' or not is_manager"/>
</footer>
</sheet>
</form>
</field>
</record>
@ -41,20 +88,21 @@
<field name="model">hr.leave.report.calendar</field>
<field name="arch" type="xml">
<search string="Department search">
<field name="name"/>
<field name="employee_id"/>
<field name="department_id"/>
<field name="job_id"/>
<filter name="my_team" string="My Team" domain="['|', ('employee_id.user_id', '=', uid), ('employee_id.parent_id.user_id', '=', uid)]"/>
<filter string="My Department" name="department"
domain="[('employee_id.member_of_department', '=', True)]"
domain="[('member_of_department', '=', True)]"
help="My Department"/>
<separator/>
<filter string="Off Today" name="off_today" domain="[('start_datetime', '&lt;=', context_today().strftime('%Y-%m-%d')), ('stop_datetime', '&gt;=', context_today().strftime('%Y-%m-%d'))]" help="My Department"/>
<filter string="Off Today" name="off_today" domain="[('is_absent', '=', True)]" help="Employees Off Today"/>
<separator/>
<filter string="Approved" name="validate" domain="[('state', '=', 'validate')]" help="validate"/>
<filter name="refused_leaves" string="Refused" domain="[('state', '=', 'refuse')]"/>
<filter string="Waiting for Approval" name="approve" domain="[('state','in',('confirm','validate1'))]"/>
<filter name="groupby_job_id" string="Job Position" context="{'group_by': 'job_id'}"/>
<filter name="groupby_time_off_type" string="Time Off Type" context="{'group_by': 'holiday_status_id'}"/>
<filter name="groupby_company_id" string="Company" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
<filter name="groupby_department_id" context="{'group_by': 'department_id'}"/>
</search>
@ -64,9 +112,28 @@
<record id="action_hr_holidays_dashboard" model="ir.actions.act_window">
<field name="name">All Time Off</field>
<field name="res_model">hr.leave.report.calendar</field>
<field name="path">time-off-overview</field>
<field name="view_mode">calendar</field>
<field name="search_view_id" ref="hr_leave_report_calendar_view_search"/>
<field name="domain">[('employee_id.active','=',True)]</field>
<field name="context">{'hide_employee_name': 1, 'search_default_my_team': 1}</field>
<field name="context">{'hide_employee_name': 1, 'search_default_my_team': 1, 'search_default_current_year': 1,
'search_default_validate': 1, 'search_default_approve': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Nobody here ? All of the people you're looking for will be working at that time.
</p>
</field>
</record>
<record id="action_my_days_off_dashboard_calendar" model="ir.actions.act_window">
<field name="name">Dashboard</field>
<field name="res_model">hr.leave.report.calendar</field>
<field name="view_mode">calendar</field>
<field name="view_id" ref="hr_leave_report_calendar_year_view"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
You have no time off yet!
</p>
</field>
</record>
</odoo>

View file

@ -11,20 +11,17 @@
<filter domain="[('state','in',('confirm','validate1'))]" string="To Approve" name="approve"/>
<filter string="Approved Requests" domain="[('state', '=', 'validate')]" name="validated"/>
<separator/>
<filter name="active_types" string="Active Types" domain="[('holiday_status_id.active', '=', True)]" help="Filters only on requests that belong to an time off type that is 'active' (active field is True)"/>
<filter string="Time off" domain="[('leave_type', '=', 'request')]" name="leave_type_timeoff"/>
<filter string="Allocations" domain="[('leave_type', '=', 'allocation')]" name="leave_type_allocations"/>
<separator/>
<filter string="My Department" name="department" domain="[('department_id.manager_id.user_id', '=', uid)]" help="My Department"/>
<separator/>
<filter string="Active Employee" name="active_employee" domain="[('active_employee','=',True)]"/>
<separator/>
<filter name="year" date="date_from" default_period="this_year" string="Current Year"/>
<filter name="year" date="date_from" default_period="year" string="Start Date"/>
<separator/>
<filter string="My Requests" name="my_leaves" domain="[('employee_id.user_id', '=', uid)]"/>
<filter string="Archived" name="archived" domain="[('active', '=', False)]"/>
<separator/>
<field name="department_id" operator="child_of"/>
<field name="holiday_status_id"/>
<group expand="0" string="Group By">
<group>
<filter name="group_employee" string="Employee" context="{'group_by':'employee_id'}"/>
<filter name="group_type" string="Type" context="{'group_by':'holiday_status_id'}"/>
<filter name="group_company" string="Company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
@ -36,20 +33,32 @@
</record>
<record id="hr_leave_report_tree" model="ir.ui.view">
<field name="name">report.hr.holidays.report.leave_all.tree</field>
<field name="name">report.hr.holidays.report.leave_all.list</field>
<field name="model">hr.leave.report</field>
<field name="arch" type="xml">
<tree create="0" edit="0" delete="0">
<button name="action_open_record" type="object" icon="fa-external-link" title="Open" />
<field name="employee_id" decoration-muted="not active_employee"/>
<field name="number_of_days" string="Number of Days" sum="Remaining Days"/>
<list create="0" edit="0" delete="0" action="action_open_record" type="object">
<field name="employee_id"/>
<field name="number_of_days" string="Number of Days" sum="Remaining Days" width="110px"/>
<field name="number_of_hours" string="Number of Hours" sum="Remaining Hours" width="120px"/>
<field name="leave_type"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="state"/>
<field name="name"/>
<field name="active_employee" invisible="1"/>
</tree>
</list>
</field>
</record>
<record id="hr_leave_report_graph" model="ir.ui.view">
<field name="name">report.hr.holidays.report.leave_all.graph</field>
<field name="model">hr.leave.report</field>
<field name="arch" type="xml">
<graph string="Time off Summary" sample="1" stacked="1" js_class="hr_holidays_graph">
<field name="employee_id" type="row"/>
<field name="leave_type" type="col"/>
<field name="number_of_days" string="Duration (Days)" type="measure"/>
<field name="number_of_hours" string="Duration (Hours)"/>
</graph>
</field>
</record>
@ -57,65 +66,47 @@
<field name="name">report.hr.holidays.report.leave_all.pivot</field>
<field name="model">hr.leave.report</field>
<field name="arch" type="xml">
<pivot>
<field name="employee_id" decoration-muted="not active_employee"/>
<field name="number_of_days" type="measure"/>
<field name="leave_type"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="state"/>
<field name="name"/>
<field name="active_employee" invisible="1"/>
<pivot string="Time off Summary" sample="1">
<field name="employee_id"/>
<field name="number_of_days"/>
<field name="number_of_hours" type="measure"/>
</pivot>
</field>
</record>
<record id="hr_leave_report_view_form" model="ir.ui.view">
<field name="name">hr.leave.report.view.form</field>
<field name="model">hr.leave.report</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="employee_id"/>
<field name="name"/>
<field name="allocation_id" attrs="{'invisible': [('allocation_id', '=', False)]}"/>
<field name="leave_id" attrs="{'invisible': [('leave_id', '=', False)]}"/>
<field name="leave_type" />
<field name="category_id"/>
<field name="state"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group>
<field name="active_employee"/>
<field name="number_of_days"/>
<field name="department_id"/>
<field name="holiday_status_id"/>
<field name="holiday_type"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="act_hr_employee_holiday_request" model="ir.actions.server">
<field name="name">Time off Analysis</field>
<field name="model_id" ref="hr_holidays.model_hr_leave_report"/>
<field name="binding_model_id" ref="hr.model_hr_employee"/>
<field name="state">code</field>
<field name="groups_id" eval="[(4, ref('hr_holidays.group_hr_holidays_user'))]"/>
<field name="code">
action = model.action_time_off_analysis()
</field>
<record id="action_hr_leave_report" model="ir.actions.act_window">
<field name="name">Time Off by Type</field>
<field name="res_model">hr.leave.report</field>
<field name="view_mode">graph,list,pivot</field>
<field name="search_view_id" ref="view_hr_holidays_filter_report"/>
<field name="domain">[]</field>
<field name="context">{'search_default_group_type': 1, 'search_default_approve': 1, 'search_default_validated': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_empty_folder">
No data to display
No Time off yet!
</p>
<p>
Check back later to overview all time off requests
</p>
</field>
</record>
<record id="hr_leave_report_action" model="ir.actions.act_window">
<field name="name">Time Off Analysis</field>
<field name="res_model">hr.leave.report</field>
<field name="view_mode">graph,pivot</field>
<field name="context">{
'search_default_department_id': [active_id],
'default_department_id': active_id,
'search_default_group_employee': 1,
'search_default_group_type': 1,
'search_default_group_date_from': 'month',
}
</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No data yet!
</p>
</field>
</record>
</odoo>