Files
enterprise-addons/esg_csrd_hr/controllers/main.py
T

1310 lines
72 KiB
Python

import statistics
from datetime import date
from dateutil.relativedelta import relativedelta
from odoo.http import request
from odoo.fields import Domain
from odoo.tools import SQL
from odoo.addons.esg_csrd.controllers.main import EsgCsrdReportController
class EsgCsrdHrReportController(EsgCsrdReportController):
def _get_valid_employee_version_ids(self, start_date, end_date, is_employee_type=None):
if is_employee_type:
employee_type_condition = " AND hv.employee_type = 'employee' "
elif is_employee_type is False:
employee_type_condition = " AND hv.employee_type != 'employee' "
else:
employee_type_condition = ""
request.env.cr.execute(SQL(
"""
SELECT DISTINCT hv.id
FROM hr_version hv
JOIN (
SELECT employee_id, MAX(date_version) AS max_date
FROM hr_version
WHERE date_version <= %(end_date)s
GROUP BY employee_id
) latest_employee_version
ON hv.employee_id = latest_employee_version.employee_id
AND hv.date_version = latest_employee_version.max_date
AND (hv.departure_date IS NULL OR hv.departure_date >= %(start_date)s)
AND hv.active = TRUE
%(employee_type_condition)s
""",
start_date=start_date,
end_date=end_date,
employee_type_condition=SQL(employee_type_condition),
))
return tuple(row[0] for row in request.env.cr.fetchall())
def _get_employment_type_data(self, version_ids, total_employees_count):
data = {
'employees_full_time_count': 0,
'employees_full_time_percentage': 0,
'employees_part_time_count': 0,
'employees_part_time_percentage': 0,
}
if not version_ids:
return data
request.env.cr.execute(SQL(
"""
SELECT COUNT(hv.id)
FROM hr_version hv, resource_calendar rc
WHERE hv.id IN %(ids)s
AND hv.resource_calendar_id = rc.id
AND rc.hours_per_week = rc.full_time_required_hours
""",
ids=version_ids,
))
result = request.env.cr.fetchall()[0][0]
employees_emp_type_not_reported_count = request.env['hr.version'].sudo().search_count(
domain=[('id', 'in', version_ids), ('resource_calendar_id', '=', False)],
)
employees_part_time_count = total_employees_count - result - employees_emp_type_not_reported_count
data.update({
'employees_full_time_count': result,
'employees_full_time_percentage': round((result / total_employees_count) * 100, 2),
'employees_part_time_count': employees_part_time_count,
'employees_part_time_percentage': round((employees_part_time_count / total_employees_count) * 100, 2),
'employees_emp_type_not_reported_count': employees_emp_type_not_reported_count,
'employees_emp_type_not_reported_percentage': round((employees_emp_type_not_reported_count / total_employees_count) * 100, 2),
})
return data
def _get_gender_data(self, version_ids, total_employees_count):
employees_count_per_sex = dict(
request.env['hr.version'].sudo()._read_group(
domain=[('id', 'in', version_ids)],
groupby=['sex'],
aggregates=['id:count'],
)
)
employees_female_count = employees_count_per_sex.get('female', 0)
employees_female_percentage = round((employees_female_count / total_employees_count) * 100, 2)
employees_male_count = employees_count_per_sex.get('male', 0)
employees_male_percentage = round((employees_male_count / total_employees_count) * 100, 2)
employees_other_count = employees_count_per_sex.get('other', 0)
employees_other_percentage = round((employees_other_count / total_employees_count) * 100, 2)
employees_unknown_count = total_employees_count - employees_female_count - employees_male_count - employees_other_count
employees_unknown_percentage = round((employees_unknown_count / total_employees_count) * 100, 2)
return {
'employees_female_count': employees_female_count,
'employees_female_percentage': employees_female_percentage,
'employees_male_count': employees_male_count,
'employees_male_percentage': employees_male_percentage,
'employees_other_count': employees_other_count,
'employees_other_percentage': employees_other_percentage,
'employees_unknown_count': employees_unknown_count,
'employees_unknown_percentage': employees_unknown_percentage,
}
def _get_age_group_data(self, version_ids, total_employees_count, end_date):
HrEmployee = request.env['hr.employee']
date_30_years_ago = end_date - relativedelta(years=30)
date_50_years_ago = end_date - relativedelta(years=50)
employees_age_below_30_count = HrEmployee.sudo().search_count(
domain=Domain([('version_ids', 'in', version_ids), ('birthday', '>', date_30_years_ago)]),
)
employees_age_below_30_percentage = round((employees_age_below_30_count / total_employees_count) * 100, 2)
employees_age_between_30_and_50_count = HrEmployee.sudo().search_count(
domain=Domain([('version_ids', 'in', version_ids), ('birthday', '<=', date_30_years_ago), ('birthday', '>=', date_50_years_ago)]),
)
employees_age_between_30_and_50_percentage = round((employees_age_between_30_and_50_count / total_employees_count) * 100, 2)
employees_age_above_50_count = HrEmployee.sudo().search_count(
domain=Domain([('version_ids', 'in', version_ids), ('birthday', '<', date_50_years_ago)]),
)
employees_age_above_50_percentage = round((employees_age_above_50_count / total_employees_count) * 100, 2)
employees_age_unknown_count = total_employees_count - employees_age_below_30_count - employees_age_between_30_and_50_count - employees_age_above_50_count
employees_age_unknown_percentage = round((employees_age_unknown_count / total_employees_count) * 100, 2)
return {
'employees_age_below_30_count': employees_age_below_30_count,
'employees_age_below_30_percentage': employees_age_below_30_percentage,
'employees_age_between_30_and_50_count': employees_age_between_30_and_50_count,
'employees_age_between_30_and_50_percentage': employees_age_between_30_and_50_percentage,
'employees_age_above_50_count': employees_age_above_50_count,
'employees_age_above_50_percentage': employees_age_above_50_percentage,
'employees_age_unknown_count': employees_age_unknown_count,
'employees_age_unknown_percentage': employees_age_unknown_percentage,
}
def _get_contract_type_data(self, version_ids, total_employees_count, start_date, end_date):
HrVersion = request.env['hr.version']
employees_permanent_contract_count = HrVersion.sudo().search_count(
domain=[('id', 'in', version_ids), ('contract_date_start', '<=', end_date), ('contract_date_end', '=', False)]
)
employees_permanent_contract_percentage = round((employees_permanent_contract_count / total_employees_count) * 100, 2)
employees_fixed_term_contract_count = HrVersion.sudo().search_count(
domain=[('id', 'in', version_ids), ('contract_date_start', '<=', end_date), ('contract_date_end', '>=', start_date)]
)
employees_fixed_term_contract_percentage = round((employees_fixed_term_contract_count / total_employees_count) * 100, 2)
employees_unkown_contract_count = total_employees_count - employees_permanent_contract_count - employees_fixed_term_contract_count
employees_unkown_contract_percentage = round((employees_unkown_contract_count / total_employees_count) * 100, 2)
return {
'employees_permanent_contract_count': employees_permanent_contract_count,
'employees_permanent_contract_percentage': employees_permanent_contract_percentage,
'employees_fixed_term_contract_count': employees_fixed_term_contract_count,
'employees_fixed_term_contract_percentage': employees_fixed_term_contract_percentage,
'employees_unkown_contract_count': employees_unkown_contract_count,
'employees_unkown_contract_percentage': employees_unkown_contract_percentage,
}
def _get_country_employees_data(self, version_ids):
if not version_ids:
return {}
request.env.cr.execute(SQL(
"""
SELECT rc.name, COUNT(he.id)
FROM hr_version hv, hr_employee he, res_partner rp, res_country rc
WHERE hv.id IN %(ids)s
AND hv.employee_id = he.id
AND he.work_contact_id = rp.id
AND rp.country_id = rc.id
GROUP BY rc.name
ORDER BY COUNT(he.id) DESC
""",
ids=version_ids,
))
result = request.env.cr.dictfetchall()
return {
next(iter(item['name'].values()), ''): item['count']
for item in result
}
def _get_department_employees_data(self, version_ids):
return dict(
request.env['hr.version'].sudo()._read_group(
domain=[('id', 'in', version_ids), ('department_id', '!=', False)],
groupby=['department_id'],
aggregates=['__count'],
order='__count desc',
)
)
def _get_employee_turnover_data(self, start_date, end_date):
leaving_employees_count = request.env['hr.employee'].sudo().with_context(active_test=False).search_count(
domain=[
('version_ids', 'any', [
('departure_date', '>=', start_date),
('departure_date', '<=', end_date),
]),
],
)
employees_start_year_count = len(self._get_valid_employee_version_ids(start_date, start_date, is_employee_type=True))
employees_end_year_count = len(self._get_valid_employee_version_ids(end_date, end_date, is_employee_type=True))
employees_avg_count = (employees_start_year_count + employees_end_year_count) // 2
employees_turnover_rate = round((leaving_employees_count / employees_avg_count) * 100, 2) if employees_avg_count > 0 else 0
return {
'leaving_employees_count': leaving_employees_count,
'employees_avg_count': employees_avg_count,
'employees_turnover_rate': employees_turnover_rate,
}
def _get_employee_type_data(self, version_ids):
return dict(
request.env['hr.version'].sudo()._read_group(
domain=[('id', 'in', version_ids)],
groupby=['employee_type'],
aggregates=['id:count'],
)
)
def _get_employee_tenure_data(self, start_date, end_date, total_employees_count):
request.env.cr.execute(SQL(
"""
WITH unique_records AS (
-- Step 1: Filter by type and handle duplicates
SELECT DISTINCT
employee_id,
contract_date_start,
COALESCE(contract_date_end, %(end_date)s) AS contract_date_end
FROM hr_version
WHERE contract_date_start IS NOT NULL
AND employee_type = 'employee'
AND date_version <= %(end_date)s
AND (departure_date IS NULL OR departure_date >= %(start_date)s)
AND active = TRUE
),
employee_tenure AS (
-- Step 2: Sum duration per employee
SELECT employee_id,
SUM(contract_date_end - contract_date_start) AS total_days
FROM unique_records
GROUP BY employee_id
),
tenure_buckets AS (
-- Step 3: Categorize into tenure ranges
-- 1 year = 365 days | 3 years = 1095 days | 5 years = 1825 days
SELECT employee_id,
CASE
WHEN total_days < 365 THEN 'tenure_lt1'
WHEN total_days >= 365 AND total_days < 1095 THEN 'tenure_1_3'
WHEN total_days >= 1095 AND total_days <= 1825 THEN 'tenure_3_5'
ELSE 'tenure_gt5'
END AS tenure_range
FROM employee_tenure
)
-- Step 4: Aggregate counts
SELECT tenure_range,
COUNT(*) AS employee_count
FROM tenure_buckets
GROUP BY tenure_range
""",
start_date=start_date,
end_date=end_date,
))
data = {}
if result := request.env.cr.dictfetchall():
data = {
item['tenure_range']: item['employee_count']
for item in result
}
tenure_lt1 = data.get('tenure_lt1', 0)
tenure_1_3 = data.get('tenure_1_3', 0)
tenure_3_5 = data.get('tenure_3_5', 0)
tenure_gt5 = data.get('tenure_gt5', 0)
tenure_not_reported = total_employees_count - tenure_lt1 - tenure_1_3 - tenure_3_5 - tenure_gt5
tenure_lt1_pct = round((tenure_lt1 / total_employees_count) * 100, 2)
tenure_1_3_pct = round((tenure_1_3 / total_employees_count) * 100, 2)
tenure_3_5_pct = round((tenure_3_5 / total_employees_count) * 100, 2)
tenure_gt5_pct = round((tenure_gt5 / total_employees_count) * 100, 2)
tenure_not_reported_pct = round((tenure_not_reported / total_employees_count) * 100, 2)
return {
'tenure_lt1': tenure_lt1,
'tenure_lt1_pct': tenure_lt1_pct,
'tenure_1_3': tenure_1_3,
'tenure_1_3_pct': tenure_1_3_pct,
'tenure_3_5': tenure_3_5,
'tenure_3_5_pct': tenure_3_5_pct,
'tenure_gt5': tenure_gt5,
'tenure_gt5_pct': tenure_gt5_pct,
'tenure_not_reported': tenure_not_reported,
'tenure_not_reported_pct': tenure_not_reported_pct,
}
def _get_pay_gap_data(self, version_ids):
versions_by_sex = dict(request.env['hr.version'].sudo()._read_group(
domain=[('id', 'in', version_ids)],
groupby=['sex'],
aggregates=['id:recordset'],
))
male_versions = versions_by_sex.get('male', request.env['hr.version'])
female_versions = versions_by_sex.get('female', request.env['hr.version'])
# Normalize wages to a hourly wage
def get_wages(versions):
wages = []
for version in versions:
if wage := version.sudo()._get_normalized_wage():
wages.append(wage)
return wages
male_wages = get_wages(male_versions)
female_wages = get_wages(female_versions)
male_median = statistics.median(male_wages) if male_wages else 0
female_median = statistics.median(female_wages) if female_wages else 0
pay_gap = round((male_median - female_median) / male_median * 100, 2) if male_median and female_median else 0
return {
'male_median_hourly_salary': round(male_median, 2),
'female_median_hourly_salary': round(female_median, 2),
'pay_gap_percentage': pay_gap,
}
def _get_department_pay_gap_data(self, version_ids):
data = {}
for department, version_ids in request.env['hr.version'].sudo()._read_group(
domain=[('id', 'in', version_ids), ('department_id', '!=', False)],
groupby=['department_id'],
aggregates=['id:array_agg'],
):
data[department] = self._get_pay_gap_data(version_ids)
return data
def _get_contract_type_pay_gap_data(self, version_ids):
data = {}
for contract_type, version_ids in request.env['hr.version'].sudo()._read_group(
domain=[('id', 'in', version_ids), ('contract_type_id', '!=', False)],
groupby=['contract_type_id'],
aggregates=['id:array_agg'],
):
data[contract_type] = self._get_pay_gap_data(version_ids)
return data
def _get_country_pay_gap_data(self, version_ids):
data = {}
for country, version_ids in request.env['hr.version'].sudo()._read_group(
domain=[('id', 'in', version_ids), ('employee_id.work_contact_id.country_id', '!=', False)],
groupby=['employee_id.work_contact_id.country_id'],
aggregates=['id:array_agg'],
):
data[country] = self._get_pay_gap_data(version_ids)
return data
def _get_employment_type_per_gender_data(self, version_ids, total_employees_count):
data = {
'{{ male_ft_count }}': 0,
'{{ male_pt_count }}': 0,
'{{ female_ft_count }}': 0,
'{{ female_pt_count }}': 0,
'{{ other_ft_count }}': 0,
'{{ other_pt_count }}': 0,
'{{ gender_not_reported_ft_count }}': 0,
'{{ gender_not_reported_pt_count }}': 0,
}
if not version_ids:
return data
request.env.cr.execute(SQL(
"""
SELECT hv.sex, COUNT(hv.id)
FROM hr_version hv, resource_calendar rc
WHERE hv.id IN %(ids)s
AND hv.resource_calendar_id = rc.id
AND rc.hours_per_week = rc.full_time_required_hours
GROUP BY hv.sex
""",
ids=version_ids,
))
result = request.env.cr.dictfetchall()
ft_count_per_gender = {
item['sex']: item['count']
for item in result
}
male_ft_count = ft_count_per_gender.get('male', 0)
female_ft_count = ft_count_per_gender.get('female', 0)
other_ft_count = ft_count_per_gender.get('other', 0)
gender_not_reported_ft_count = ft_count_per_gender.get(None, 0)
gender_data = self._get_gender_data(version_ids, total_employees_count)
male_pt_count = gender_data['employees_male_count'] - male_ft_count
female_pt_count = gender_data['employees_female_count'] - female_ft_count
other_pt_count = gender_data['employees_other_count'] - other_ft_count
gender_not_reported_pt_count = gender_data['employees_unknown_count'] - gender_not_reported_ft_count
data.update({
'male_ft_count': male_ft_count,
'male_pt_count': male_pt_count,
'female_ft_count': female_ft_count,
'female_pt_count': female_pt_count,
'other_ft_count': other_ft_count,
'other_pt_count': other_pt_count,
'gender_not_reported_ft_count': gender_not_reported_ft_count,
'gender_not_reported_pt_count': gender_not_reported_pt_count
})
return data
def _get_non_salaried_data(self, version_ids):
count_per_employee_type = dict(
request.env['hr.version'].sudo()._read_group(
domain=[('id', 'in', version_ids)],
groupby=['employee_type'],
aggregates=['id:count'],
)
)
return {
'non_employees_non_salary_count': sum(
count_per_employee_type.get(emp_type, 0)
for emp_type in ['student', 'trainee']
),
'non_employees_non_indep_count': sum(
count_per_employee_type.get(emp_type, 0)
for emp_type in ['contractor', 'freelance']
),
'non_employees_non_temp_count': count_per_employee_type.get('worker', 0),
}
def _get_gender_management_data(self, version_ids):
if not version_ids:
return []
request.env.cr.execute(SQL(
"""
WITH RECURSIVE leadership_level AS (
-- 1. Start with employees in the given versions
SELECT
e.id AS employee_id,
0 AS level,
ARRAY[e.id] AS visited_nodes,
hv.sex
FROM hr_employee e
JOIN hr_version hv ON e.id = hv.employee_id
WHERE hv.id IN %(ids)s
UNION ALL
-- 2. Move up the hierarchy
SELECT
e.parent_id AS employee_id,
lh.level + 1,
lh.visited_nodes || e.parent_id,
lh.sex -- Pass the gender of the original employee up the chain
FROM hr_employee e
JOIN leadership_level lh ON e.id = lh.employee_id
WHERE e.parent_id IS NOT NULL
AND NOT e.parent_id = ANY(lh.visited_nodes)
),
max_levels AS (
-- 3. Calculate the maximum level reached for each employee version
SELECT
sex,
MAX(level) as max_lvl
FROM leadership_level
GROUP BY employee_id, sex
)
-- 4. Count genders per level
SELECT
max_lvl,
sex,
COUNT(*) as count
FROM max_levels
GROUP BY max_lvl, sex
ORDER BY max_lvl DESC, sex;
""",
ids=tuple(version_ids)
))
result = request.env.cr.dictfetchall()
data = {}
for item in result:
lvl = item['max_lvl']
gender = item['sex']
count = item['count']
if lvl not in data:
data[lvl] = {'male': 0, 'female': 0, 'other': 0}
if gender:
data[lvl][gender] += count
processed_data = []
for lvl in sorted(data.keys(), reverse=True):
stats = data[lvl]
m = stats['male']
f = stats['female']
ratio = f / m if m > 0 else 0.0
processed_data.append({
'level': lvl,
'male': m,
'female': f,
'other': stats['other'],
'ratio': round(ratio, 2)
})
return processed_data
def _get_template_variables(self, article):
template_variables = super()._get_template_variables(article)
if not (
request.env.user.has_group('esg.esg_group_manager')
and request.env.user.has_group('hr.group_hr_user')
and (esg_report := article.inherited_esg_report_id)
):
return template_variables
# Reporting Year
report_version_ids = self._get_valid_employee_version_ids(esg_report.start_date, esg_report.end_date, is_employee_type=True)
report_total_employees_count = len(report_version_ids)
template_variables.update({
'{{ total_reporting }}': str(report_total_employees_count),
'{{ ft_reporting }}': '',
'{{ ft_pct_reporting }}': '',
'{{ pt_reporting }}': '',
'{{ pt_pct_reporting }}': '',
'{{ emp_type_not_reported_reporting }}': '',
'{{ emp_type_not_reported_pct_reporting }}': '',
'{{ female_reporting }}': '',
'{{ female_pct_reporting }}': '',
'{{ male_reporting }}': '',
'{{ male_pct_reporting }}': '',
'{{ other_reporting }}': '',
'{{ other_pct_reporting }}': '',
'{{ gender_not_reported_reporting }}': '',
'{{ gender_not_reported_pct_reporting }}': '',
'{{ age_lt30_reporting }}': '',
'{{ age_lt30_pct_reporting }}': '',
'{{ age_30_50_reporting }}': '',
'{{ age_30_50_pct_reporting }}': '',
'{{ age_gt50_reporting }}': '',
'{{ age_gt50_pct_reporting }}': '',
'{{ age_not_reported_reporting }}': '',
'{{ age_not_reported_pct_reporting }}': '',
'{{ permanent_reporting }}': '',
'{{ permanent_pct_reporting }}': '',
'{{ fixed_reporting }}': '',
'{{ fixed_pct_reporting }}': '',
'{{ contract_not_reported_reporting }}': '',
'{{ contract_not_reported_pct_reporting }}': '',
'{{ tenure_lt1_reporting }}': '',
'{{ tenure_lt1_pct_reporting }}': '',
'{{ tenure_1_3_reporting }}': '',
'{{ tenure_1_3_pct_reporting }}': '',
'{{ tenure_3_5_reporting }}': '',
'{{ tenure_3_5_pct_reporting }}': '',
'{{ tenure_gt5_reporting }}': '',
'{{ tenure_gt5_pct_reporting }}': '',
'{{ tenure_not_reported_reporting }}': '',
'{{ tenure_not_reported_pct_reporting }}': '',
'{{ left_reporting }}': '',
'{{ avg_emp_reporting }}': '',
'{{ turnover_reporting }}': '',
'{{ male_ft_reporting }}': '',
'{{ male_pt_reporting }}': '',
'{{ female_ft_reporting }}': '',
'{{ female_pt_reporting }}': '',
'{{ other_ft_reporting }}': '',
'{{ other_pt_reporting }}': '',
'{{ gender_not_reported_ft_reporting }}': '',
'{{ gender_not_reported_pt_reporting }}': '',
'{{ gender_pay_gap_reporting }}': '',
'{{ median_pay_male_reporting }}': '',
'{{ median_pay_female_reporting }}': '',
'{{ total_base }}': '',
'{{ ft_base }}': '',
'{{ ft_pct_base }}': '',
'{{ pt_base }}': '',
'{{ pt_pct_base }}': '',
'{{ emp_type_not_reported_base }}': '',
'{{ emp_type_not_reported_pct_base }}': '',
'{{ female_base }}': '',
'{{ female_pct_base }}': '',
'{{ male_base }}': '',
'{{ male_pct_base }}': '',
'{{ other_base }}': '',
'{{ other_pct_base }}': '',
'{{ gender_not_reported_base }}': '',
'{{ gender_not_reported_pct_base }}': '',
'{{ age_lt30_base }}': '',
'{{ age_lt30_pct_base }}': '',
'{{ age_30_50_base }}': '',
'{{ age_30_50_pct_base }}': '',
'{{ age_gt50_base }}': '',
'{{ age_gt50_pct_base }}': '',
'{{ age_not_reported_base }}': '',
'{{ age_not_reported_pct_base }}': '',
'{{ permanent_base }}': '',
'{{ permanent_pct_base }}': '',
'{{ fixed_base }}': '',
'{{ fixed_pct_base }}': '',
'{{ contract_not_reported_base }}': '',
'{{ contract_not_reported_pct_base }}': '',
'{{ tenure_lt1_base }}': '',
'{{ tenure_lt1_pct_base }}': '',
'{{ tenure_1_3_base }}': '',
'{{ tenure_1_3_pct_base }}': '',
'{{ tenure_3_5_base }}': '',
'{{ tenure_3_5_pct_base }}': '',
'{{ tenure_gt5_base }}': '',
'{{ tenure_gt5_pct_base }}': '',
'{{ tenure_not_reported_base }}': '',
'{{ tenure_not_reported_pct_base }}': '',
'{{ left_base }}': '',
'{{ avg_emp_base }}': '',
'{{ turnover_base }}': '',
'{{ male_ft_base }}': '',
'{{ male_pt_base }}': '',
'{{ female_ft_base }}': '',
'{{ female_pt_base }}': '',
'{{ other_ft_base }}': '',
'{{ other_pt_base }}': '',
'{{ gender_not_reported_ft_base }}': '',
'{{ gender_not_reported_pt_base }}': '',
'{{ gender_pay_gap_base }}': '',
'{{ median_pay_male_base }}': '',
'{{ median_pay_female_base }}': ''
})
# Base Year
base_total_employees_count = 0
base_year_start_date = 0
base_year_end_date = 0
base_version_ids = False
has_base_year = esg_report._has_valid_base_year()
if has_base_year:
base_year_date = esg_report.company_id.sudo().compute_fiscalyear_dates(date(esg_report.base_year, 1, 1))
base_year_start_date = base_year_date['date_from']
base_year_end_date = base_year_date['date_to']
base_version_ids = self._get_valid_employee_version_ids(base_year_start_date, base_year_end_date, is_employee_type=True)
base_total_employees_count = len(base_version_ids)
template_variables['{{ total_base }}'] = str(base_total_employees_count)
# Data for Table: Employment Type
# Reporting Year
if report_version_ids and esg_report.report_type == 'csrd':
report_employment_type_result = self._get_employment_type_data(report_version_ids, report_total_employees_count)
template_variables.update({
'{{ ft_reporting }}': str(report_employment_type_result['employees_full_time_count']),
'{{ ft_pct_reporting }}': str(report_employment_type_result['employees_full_time_percentage']),
'{{ pt_reporting }}': str(report_employment_type_result['employees_part_time_count']),
'{{ pt_pct_reporting }}': str(report_employment_type_result['employees_part_time_percentage']),
'{{ emp_type_not_reported_reporting }}': str(report_employment_type_result['employees_emp_type_not_reported_count']),
'{{ emp_type_not_reported_pct_reporting }}': str(report_employment_type_result['employees_emp_type_not_reported_percentage']),
})
# Base Year
if base_version_ids and esg_report.report_type == 'csrd':
base_employment_type_result = self._get_employment_type_data(base_version_ids, base_total_employees_count)
template_variables.update({
'{{ ft_base }}': str(base_employment_type_result['employees_full_time_count']),
'{{ ft_pct_base }}': str(base_employment_type_result['employees_full_time_percentage']),
'{{ pt_base }}': str(base_employment_type_result['employees_part_time_count']),
'{{ pt_pct_base }}': str(base_employment_type_result['employees_part_time_percentage']),
'{{ emp_type_not_reported_base }}': str(base_employment_type_result['employees_emp_type_not_reported_count']),
'{{ emp_type_not_reported_pct_base }}': str(base_employment_type_result['employees_emp_type_not_reported_percentage']),
})
# Data for Table: Gender
# Reporting Year
if report_version_ids:
report_gender_result = self._get_gender_data(report_version_ids, report_total_employees_count)
template_variables.update({
'{{ female_reporting }}': str(report_gender_result['employees_female_count']),
'{{ female_pct_reporting }}': str(report_gender_result['employees_female_percentage']),
'{{ male_reporting }}': str(report_gender_result['employees_male_count']),
'{{ male_pct_reporting }}': str(report_gender_result['employees_male_percentage']),
'{{ other_reporting }}': str(report_gender_result['employees_other_count']),
'{{ other_pct_reporting }}': str(report_gender_result['employees_other_percentage']),
'{{ gender_not_reported_reporting }}': str(report_gender_result['employees_unknown_count']),
'{{ gender_not_reported_pct_reporting }}': str(report_gender_result['employees_unknown_percentage']),
})
# Base Year
if base_version_ids:
base_gender_result = self._get_gender_data(base_version_ids, base_total_employees_count)
template_variables.update({
'{{ female_base }}': str(base_gender_result['employees_female_count']),
'{{ female_pct_base }}': str(base_gender_result['employees_female_percentage']),
'{{ male_base }}': str(base_gender_result['employees_male_count']),
'{{ male_pct_base }}': str(base_gender_result['employees_male_percentage']),
'{{ other_base }}': str(base_gender_result['employees_other_count']),
'{{ other_pct_base }}': str(base_gender_result['employees_other_percentage']),
'{{ gender_not_reported_base }}': str(base_gender_result['employees_unknown_count']),
'{{ gender_not_reported_pct_base }}': str(base_gender_result['employees_unknown_percentage']),
})
# Data for Table: Age Group
# Reporting Year
if report_version_ids:
report_age_group_result = self._get_age_group_data(report_version_ids, report_total_employees_count, esg_report.end_date)
template_variables.update({
'{{ age_lt30_reporting }}': str(report_age_group_result['employees_age_below_30_count']),
'{{ age_lt30_pct_reporting }}': str(report_age_group_result['employees_age_below_30_percentage']),
'{{ age_30_50_reporting }}': str(report_age_group_result['employees_age_between_30_and_50_count']),
'{{ age_30_50_pct_reporting }}': str(report_age_group_result['employees_age_between_30_and_50_percentage']),
'{{ age_gt50_reporting }}': str(report_age_group_result['employees_age_above_50_count']),
'{{ age_gt50_pct_reporting }}': str(report_age_group_result['employees_age_above_50_percentage']),
'{{ age_not_reported_reporting }}': str(report_age_group_result['employees_age_unknown_count']),
'{{ age_not_reported_pct_reporting }}': str(report_age_group_result['employees_age_unknown_percentage']),
})
# Base Year
if base_version_ids:
base_age_group_result = self._get_age_group_data(base_version_ids, base_total_employees_count, base_year_end_date)
template_variables.update({
'{{ age_lt30_base }}': str(base_age_group_result['employees_age_below_30_count']),
'{{ age_lt30_pct_base }}': str(base_age_group_result['employees_age_below_30_percentage']),
'{{ age_30_50_base }}': str(base_age_group_result['employees_age_between_30_and_50_count']),
'{{ age_30_50_pct_base }}': str(base_age_group_result['employees_age_between_30_and_50_percentage']),
'{{ age_gt50_base }}': str(base_age_group_result['employees_age_above_50_count']),
'{{ age_gt50_pct_base }}': str(base_age_group_result['employees_age_above_50_percentage']),
'{{ age_not_reported_base }}': str(base_age_group_result['employees_age_unknown_count']),
'{{ age_not_reported_pct_base }}': str(base_age_group_result['employees_age_unknown_percentage']),
})
# Data for Table: Contract Type
# Reporting Year
if report_version_ids and esg_report.report_type == 'csrd':
report_contract_type_result = self._get_contract_type_data(report_version_ids, report_total_employees_count, esg_report.start_date, esg_report.end_date)
template_variables.update({
'{{ permanent_reporting }}': str(report_contract_type_result['employees_permanent_contract_count']),
'{{ permanent_pct_reporting }}': str(report_contract_type_result['employees_permanent_contract_percentage']),
'{{ fixed_reporting }}': str(report_contract_type_result['employees_fixed_term_contract_count']),
'{{ fixed_pct_reporting }}': str(report_contract_type_result['employees_fixed_term_contract_percentage']),
'{{ contract_not_reported_reporting }}': str(report_contract_type_result['employees_unkown_contract_count']),
'{{ contract_not_reported_pct_reporting }}': str(report_contract_type_result['employees_unkown_contract_percentage']),
})
# Base Year
if base_version_ids and esg_report.report_type == 'csrd':
base_contract_type_result = self._get_contract_type_data(base_version_ids, base_total_employees_count, base_year_start_date, base_year_end_date)
template_variables.update({
'{{ permanent_base }}': str(base_contract_type_result['employees_permanent_contract_count']),
'{{ permanent_pct_base }}': str(base_contract_type_result['employees_permanent_contract_percentage']),
'{{ fixed_base }}': str(base_contract_type_result['employees_fixed_term_contract_count']),
'{{ fixed_pct_base }}': str(base_contract_type_result['employees_fixed_term_contract_percentage']),
'{{ contract_not_reported_base }}': str(base_contract_type_result['employees_unkown_contract_count']),
'{{ contract_not_reported_pct_base }}': str(base_contract_type_result['employees_unkown_contract_percentage']),
})
# Data for Table: Tenure
# Reporting Year
if report_version_ids and esg_report.report_type == 'csrd':
report_employee_tenure_result = self._get_employee_tenure_data(esg_report.start_date, esg_report.end_date, report_total_employees_count)
template_variables.update({
'{{ tenure_lt1_reporting }}': str(report_employee_tenure_result['tenure_lt1']),
'{{ tenure_lt1_pct_reporting }}': str(report_employee_tenure_result['tenure_lt1_pct']),
'{{ tenure_1_3_reporting }}': str(report_employee_tenure_result['tenure_1_3']),
'{{ tenure_1_3_pct_reporting }}': str(report_employee_tenure_result['tenure_1_3_pct']),
'{{ tenure_3_5_reporting }}': str(report_employee_tenure_result['tenure_3_5']),
'{{ tenure_3_5_pct_reporting }}': str(report_employee_tenure_result['tenure_3_5_pct']),
'{{ tenure_gt5_reporting }}': str(report_employee_tenure_result['tenure_gt5']),
'{{ tenure_gt5_pct_reporting }}': str(report_employee_tenure_result['tenure_gt5_pct']),
'{{ tenure_not_reported_reporting }}': str(report_employee_tenure_result['tenure_not_reported']),
'{{ tenure_not_reported_pct_reporting }}': str(report_employee_tenure_result['tenure_not_reported_pct']),
})
# Base Year
if base_version_ids and esg_report.report_type == 'csrd':
base_employee_tenure_result = self._get_employee_tenure_data(base_year_start_date, base_year_end_date, base_total_employees_count)
template_variables.update({
'{{ tenure_lt1_base }}': str(base_employee_tenure_result['tenure_lt1']),
'{{ tenure_lt1_pct_base }}': str(base_employee_tenure_result['tenure_lt1_pct']),
'{{ tenure_1_3_base }}': str(base_employee_tenure_result['tenure_1_3']),
'{{ tenure_1_3_pct_base }}': str(base_employee_tenure_result['tenure_1_3_pct']),
'{{ tenure_3_5_base }}': str(base_employee_tenure_result['tenure_3_5']),
'{{ tenure_3_5_pct_base }}': str(base_employee_tenure_result['tenure_3_5_pct']),
'{{ tenure_gt5_base }}': str(base_employee_tenure_result['tenure_gt5']),
'{{ tenure_gt5_pct_base }}': str(base_employee_tenure_result['tenure_gt5_pct']),
'{{ tenure_not_reported_base }}': str(base_employee_tenure_result['tenure_not_reported']),
'{{ tenure_not_reported_pct_base }}': str(base_employee_tenure_result['tenure_not_reported_pct']),
})
# Data for Table: Employee Turnover
# Reporting Year
if report_version_ids:
report_employee_turnover_result = self._get_employee_turnover_data(esg_report.start_date, esg_report.end_date)
template_variables.update({
'{{ left_reporting }}': str(report_employee_turnover_result['leaving_employees_count']),
'{{ avg_emp_reporting }}': str(report_employee_turnover_result['employees_avg_count']),
'{{ turnover_reporting }}': str(report_employee_turnover_result['employees_turnover_rate']),
})
# Base Year
if base_version_ids:
base_employee_turnover_result = self._get_employee_turnover_data(base_year_start_date, base_year_end_date)
template_variables.update({
'{{ left_base }}': str(base_employee_turnover_result['leaving_employees_count']),
'{{ avg_emp_base }}': str(base_employee_turnover_result['employees_avg_count']),
'{{ turnover_base }}': str(base_employee_turnover_result['employees_turnover_rate']),
})
# Data for Table: Workforce by Type of Contract
# Reporting Year
if report_version_ids and esg_report.report_type != 'csrd':
report_employment_type_per_gender_result = self._get_employment_type_per_gender_data(report_version_ids, report_total_employees_count)
template_variables.update({
'{{ male_ft_reporting }}': str(report_employment_type_per_gender_result['male_ft_count']),
'{{ male_pt_reporting }}': str(report_employment_type_per_gender_result['male_pt_count']),
'{{ female_ft_reporting }}': str(report_employment_type_per_gender_result['female_ft_count']),
'{{ female_pt_reporting }}': str(report_employment_type_per_gender_result['female_pt_count']),
'{{ other_ft_reporting }}': str(report_employment_type_per_gender_result['other_ft_count']),
'{{ other_pt_reporting }}': str(report_employment_type_per_gender_result['other_pt_count']),
'{{ gender_not_reported_ft_reporting }}': str(report_employment_type_per_gender_result['gender_not_reported_ft_count']),
'{{ gender_not_reported_pt_reporting }}': str(report_employment_type_per_gender_result['gender_not_reported_pt_count']),
})
# Base Year
if base_version_ids and esg_report.report_type != 'csrd':
base_employment_type_per_gender_result = self._get_employment_type_per_gender_data(base_version_ids, base_total_employees_count)
template_variables.update({
'{{ male_ft_base }}': str(base_employment_type_per_gender_result['male_ft_count']),
'{{ male_pt_base }}': str(base_employment_type_per_gender_result['male_pt_count']),
'{{ female_ft_base }}': str(base_employment_type_per_gender_result['female_ft_count']),
'{{ female_pt_base }}': str(base_employment_type_per_gender_result['female_pt_count']),
'{{ other_ft_base }}': str(base_employment_type_per_gender_result['other_ft_count']),
'{{ other_pt_base }}': str(base_employment_type_per_gender_result['other_pt_count']),
'{{ gender_not_reported_ft_base }}': str(base_employment_type_per_gender_result['gender_not_reported_ft_count']),
'{{ gender_not_reported_pt_base }}': str(base_employment_type_per_gender_result['gender_not_reported_pt_count']),
})
# Data for Remuneration - Gender Pay Gap
# Reporting Year
if report_version_ids and esg_report.report_type != 'csrd':
report_pay_gap_result = self._get_pay_gap_data(report_version_ids)
template_variables.update({
'{{ gender_pay_gap_reporting }}': str(report_pay_gap_result['pay_gap_percentage']),
'{{ median_pay_male_reporting }}': str(report_pay_gap_result['male_median_hourly_salary']),
'{{ median_pay_female_reporting }}': str(report_pay_gap_result['female_median_hourly_salary']),
})
# Base Year
if base_version_ids and esg_report.report_type != 'csrd':
base_pay_gap_result = self._get_pay_gap_data(base_version_ids)
template_variables.update({
'{{ gender_pay_gap_base }}': str(base_pay_gap_result['pay_gap_percentage']),
'{{ median_pay_male_base }}': str(base_pay_gap_result['male_median_hourly_salary']),
'{{ median_pay_female_base }}': str(base_pay_gap_result['female_median_hourly_salary']),
})
# Data for Non-Employees
# Reporting Year
report_non_employees_version_ids = self._get_valid_employee_version_ids(esg_report.start_date, esg_report.end_date, is_employee_type=False)
report_total_non_employees_count = len(report_non_employees_version_ids)
template_variables.update({
'{{ total_non_employee_reporting }}': str(report_total_non_employees_count),
'{{ total_non_employee_base }}': '',
'{{ non_employee_female_reporting }}': '',
'{{ non_employee_female_pct_reporting }}': '',
'{{ non_employee_male_reporting }}': '',
'{{ non_employee_male_pct_reporting }}': '',
'{{ non_employee_other_reporting }}': '',
'{{ non_employee_other_pct_reporting }}': '',
'{{ non_employee_gender_not_reported_reporting }}': '',
'{{ non_employee_gender_not_reported_pct_reporting }}': '',
'{{ non_salary_reporting }}': '',
'{{ non_indep_reporting }}': '',
'{{ non_temp_reporting }}': '',
'{{ non_employee_female_base }}': '',
'{{ non_employee_female_pct_base }}': '',
'{{ non_employee_male_base }}': '',
'{{ non_employee_male_pct_base }}': '',
'{{ non_employee_other_base }}': '',
'{{ non_employee_other_pct_base }}': '',
'{{ non_employee_gender_not_reported_base }}': '',
'{{ non_employee_gender_not_reported_pct_base }}': '',
'{{ non_salary_base }}': '',
'{{ non_indep_base }}': '',
'{{ non_temp_base }}': '',
})
# Base Year
base_total_non_employees_count = 0
base_non_employees_version_ids = False
if has_base_year:
base_non_employees_version_ids = self._get_valid_employee_version_ids(base_year_start_date, base_year_end_date, is_employee_type=False)
base_total_non_employees_count = len(base_non_employees_version_ids)
template_variables['{{ total_non_employee_base }}'] = str(base_total_non_employees_count)
# Data for Table: Gender (Non-Employees)
# Reporting Year
if report_non_employees_version_ids and esg_report.report_type == 'csrd':
non_employees_report_gender_result = self._get_gender_data(report_non_employees_version_ids, report_total_non_employees_count)
template_variables.update({
'{{ non_employee_female_reporting }}': str(non_employees_report_gender_result['employees_female_count']),
'{{ non_employee_female_pct_reporting }}': str(non_employees_report_gender_result['employees_female_percentage']),
'{{ non_employee_male_reporting }}': str(non_employees_report_gender_result['employees_male_count']),
'{{ non_employee_male_pct_reporting }}': str(non_employees_report_gender_result['employees_male_percentage']),
'{{ non_employee_other_reporting }}': str(non_employees_report_gender_result['employees_other_count']),
'{{ non_employee_other_pct_reporting }}': str(non_employees_report_gender_result['employees_other_percentage']),
'{{ non_employee_gender_not_reported_reporting }}': str(non_employees_report_gender_result['employees_unknown_count']),
'{{ non_employee_gender_not_reported_pct_reporting }}': str(non_employees_report_gender_result['employees_unknown_percentage']),
})
# Base Year
if base_non_employees_version_ids and esg_report.report_type == 'csrd':
non_employees_base_gender_result = self._get_gender_data(base_non_employees_version_ids, base_total_non_employees_count)
template_variables.update({
'{{ non_employee_female_base }}': str(non_employees_base_gender_result['employees_female_count']),
'{{ non_employee_female_pct_base }}': str(non_employees_base_gender_result['employees_female_percentage']),
'{{ non_employee_male_base }}': str(non_employees_base_gender_result['employees_male_count']),
'{{ non_employee_male_pct_base }}': str(non_employees_base_gender_result['employees_male_percentage']),
'{{ non_employee_other_base }}': str(non_employees_base_gender_result['employees_other_count']),
'{{ non_employee_other_pct_base }}': str(non_employees_base_gender_result['employees_other_percentage']),
'{{ non_employee_gender_not_reported_base }}': str(non_employees_base_gender_result['employees_unknown_count']),
'{{ non_employee_gender_not_reported_pct_base }}': str(non_employees_base_gender_result['employees_unknown_percentage']),
})
# Data for Table: B8.5: Non-Employees
# Reporting Year
if report_non_employees_version_ids and esg_report.report_type != 'csrd':
non_employees_report_non_salaried_result = self._get_non_salaried_data(report_non_employees_version_ids)
template_variables.update({
'{{ non_salary_reporting }}': str(non_employees_report_non_salaried_result['non_employees_non_salary_count']),
'{{ non_indep_reporting }}': str(non_employees_report_non_salaried_result['non_employees_non_indep_count']),
'{{ non_temp_reporting }}': str(non_employees_report_non_salaried_result['non_employees_non_temp_count']),
})
# Base Year
if base_non_employees_version_ids and esg_report.report_type != 'csrd':
non_employees_base_non_salaried_result = self._get_non_salaried_data(base_non_employees_version_ids)
template_variables.update({
'{{ non_salary_base }}': str(non_employees_base_non_salaried_result['non_employees_non_salary_count']),
'{{ non_indep_base }}': str(non_employees_base_non_salaried_result['non_employees_non_indep_count']),
'{{ non_temp_base }}': str(non_employees_base_non_salaried_result['non_employees_non_temp_count']),
})
# Data for Employees + Non-Employees
report_all_version_ids = self._get_valid_employee_version_ids(esg_report.start_date, esg_report.end_date)
report_total_all_count = len(report_all_version_ids)
template_variables.update({
'{{ women_workforce_pct_reporting }}': '',
'{{ women_management_pct_reporting }}': '',
'{{ employees_lt30_pct_reporting }}': '',
'{{ employees_gt50_pct_reporting }}': '',
})
# Data for Table: Diversity Metrics
if report_all_version_ids and esg_report.report_type == 'csrd':
report_gender_result = self._get_gender_data(report_all_version_ids, report_total_all_count)
template_variables.update({
'{{ women_workforce_pct_reporting }}': str(report_gender_result['employees_female_percentage']),
})
report_age_group_result = self._get_age_group_data(report_all_version_ids, report_total_all_count, esg_report.end_date)
template_variables.update({
'{{ employees_lt30_pct_reporting }}': str(report_age_group_result['employees_age_below_30_percentage']),
'{{ employees_gt50_pct_reporting }}': str(report_age_group_result['employees_age_above_50_percentage']),
})
report_gender_management_data = self._get_gender_management_data(report_all_version_ids)
if report_gender_management_data:
total = report_gender_management_data[0]['female'] + report_gender_management_data[0]['male']
template_variables['{{ women_management_pct_reporting }}'] = str(round((report_gender_management_data[0]['female'] / total) * 100, 2)) if total > 0 else '0.0'
return template_variables
def _get_html_template_variables(self, article):
html_template_variables = super()._get_html_template_variables(article)
if not (
request.env.user.has_group('esg.esg_group_manager')
and request.env.user.has_group('hr.group_hr_user')
and (esg_report := article.inherited_esg_report_id)
):
return html_template_variables
# Reporting Year
report_version_ids = self._get_valid_employee_version_ids(esg_report.start_date, esg_report.end_date, is_employee_type=True)
report_total_employees_count = len(report_version_ids)
html_template_variables.update({
'{{ country_employees_table }}': '',
'{{ department_employees_table }}': '',
})
# Base Year
base_total_employees_count = 0
base_year_start_date = 0
base_year_end_date = 0
base_version_ids = False
has_base_year = esg_report._has_valid_base_year()
if has_base_year:
base_year_date = esg_report.company_id.sudo().compute_fiscalyear_dates(date(esg_report.base_year, 1, 1))
base_year_start_date = base_year_date['date_from']
base_year_end_date = base_year_date['date_to']
base_version_ids = self._get_valid_employee_version_ids(base_year_start_date, base_year_end_date, is_employee_type=True)
base_total_employees_count = len(base_version_ids)
# Data for Table: Geography/Region
# Reporting Year
if report_version_ids:
report_country_data = self._get_country_employees_data(report_version_ids)
# Base Year
base_country_data = self._get_country_employees_data(base_version_ids)
rows = []
for report_country_name, report_country_count in report_country_data.items():
base_country_count = base_country_data.get(report_country_name, 0) if base_version_ids else ''
base_country_percentage = round((base_country_count / base_total_employees_count) * 100, 2) if base_version_ids and base_total_employees_count > 0 else ''
rows.append({
'name': report_country_name,
'r_count': report_country_count,
'r_pct': round((report_country_count / report_total_employees_count) * 100, 2),
'b_count': base_country_count,
'b_pct': base_country_percentage,
})
for base_country_name, base_country_count in base_country_data.items():
if base_country_name in report_country_data:
continue
rows.append({
'name': base_country_name,
'r_count': 0,
'r_pct': 0.0,
'b_count': base_country_count,
'b_pct': round((base_country_count / base_total_employees_count) * 100, 2) if base_total_employees_count > 0 else '',
})
rows.extend([
{
'name': 'Not Reported',
'r_count': report_total_employees_count - sum(report_country_data.values()),
'r_pct': round(((report_total_employees_count - sum(report_country_data.values())) / report_total_employees_count) * 100, 2) if report_total_employees_count > 0 else 0,
'b_count': base_total_employees_count - sum(base_country_data.values()) if base_version_ids else '',
'b_pct': round(((base_total_employees_count - sum(base_country_data.values())) / base_total_employees_count) * 100, 2) if base_version_ids and base_total_employees_count > 0 else '',
},
{
'name': 'Total Employees',
'r_count': report_total_employees_count,
'r_pct': '100',
'b_count': base_total_employees_count or '',
'b_pct': '100' if base_total_employees_count > 0 else '',
},
])
employees_number_per_country_table = request.env['ir.qweb']._render('esg_csrd_hr.esg_csrd_hr_report_country_employees_table', {
'rows': rows,
'has_base_year': has_base_year,
})
html_template_variables['{{ country_employees_table }}'] = employees_number_per_country_table
# Data for Table: Job Category/Function
# Reporting Year
if report_version_ids and esg_report.report_type == 'csrd':
report_department_data = self._get_department_employees_data(report_version_ids)
# Base Year
base_department_data = self._get_department_employees_data(base_version_ids)
rows = []
for report_department, report_count in report_department_data.items():
base_count = base_department_data.get(report_department, 0) if base_version_ids else ''
base_percentage = round((base_count / base_total_employees_count) * 100, 2) if base_version_ids and base_total_employees_count > 0 else ''
rows.append({
'name': report_department.name,
'r_count': report_count,
'r_pct': round((report_count / report_total_employees_count) * 100, 2),
'b_count': base_count,
'b_pct': base_percentage,
})
for base_department, base_count in base_department_data.items():
if base_department in report_department_data:
continue
rows.append({
'name': base_department.name,
'r_count': 0,
'r_pct': 0.0,
'b_count': base_count,
'b_pct': round((base_count / base_total_employees_count) * 100, 2) if base_total_employees_count > 0 else '',
})
rows.extend([
{
'name': 'Not Reported',
'r_count': report_total_employees_count - sum(report_department_data.values()),
'r_pct': round(((report_total_employees_count - sum(report_department_data.values())) / report_total_employees_count) * 100, 2) if report_total_employees_count > 0 else 0,
'b_count': base_total_employees_count - sum(base_department_data.values()) if base_version_ids else '',
'b_pct': round(((base_total_employees_count - sum(base_department_data.values())) / base_total_employees_count) * 100, 2) if base_version_ids and base_total_employees_count > 0 else '',
},
{
'name': 'Total Employees',
'r_count': report_total_employees_count,
'r_pct': '100',
'b_count': base_total_employees_count or '',
'b_pct': '100' if base_total_employees_count > 0 else '',
},
])
employees_number_per_department_table = request.env['ir.qweb']._render('esg_csrd_hr.esg_csrd_hr_report_department_employees_table', {
'rows': rows,
'has_base_year': has_base_year,
})
html_template_variables['{{ department_employees_table }}'] = employees_number_per_department_table
# Data for Non-Employees
# Reporting Year
report_non_employees_version_ids = self._get_valid_employee_version_ids(esg_report.start_date, esg_report.end_date, is_employee_type=False)
report_total_non_employees_count = len(report_non_employees_version_ids)
html_template_variables.update({
'{{ total_non_employee_reporting }}': str(report_total_non_employees_count),
'{{ non_employees_types_table }}': '',
'{{ country_non_employees_table }}': '',
'{{ department_non_employees_table }}': '',
})
# Base Year
base_total_non_employees_count = 0
base_non_employees_version_ids = False
if has_base_year:
base_non_employees_version_ids = self._get_valid_employee_version_ids(base_year_start_date, base_year_end_date, is_employee_type=False)
base_total_non_employees_count = len(base_non_employees_version_ids)
html_template_variables['{{ total_non_employee_base }}'] = str(base_total_non_employees_count)
# Data for Table: Employment Type (Non-Employees)
if report_non_employees_version_ids and esg_report.report_type == 'csrd':
report_non_employee_type_data = self._get_employee_type_data(report_non_employees_version_ids)
base_non_employee_type_data = self._get_employee_type_data(base_non_employees_version_ids)
rows = []
employee_type_labels = dict(request.env['hr.version']._fields['employee_type']._description_selection(request.env))
for employee_type, count in report_non_employee_type_data.items():
base_count = base_non_employee_type_data.get(employee_type, 0) if base_non_employees_version_ids else ''
employee_type_label = employee_type_labels.get(employee_type)
rows.append({
'name': employee_type_label,
'r_count': count,
'r_pct': round((count / report_total_non_employees_count) * 100, 2),
'b_count': base_count,
'b_pct': round((base_count / base_total_non_employees_count) * 100, 2) if base_total_non_employees_count > 0 else '',
})
for employee_type, count in base_non_employee_type_data.items():
if employee_type in report_non_employee_type_data:
continue
employee_type_label = employee_type_labels.get(employee_type)
rows.append({
'name': employee_type_label,
'r_count': 0,
'r_pct': 0.0,
'b_count': count,
'b_pct': round((count / base_total_non_employees_count) * 100, 2) if base_total_non_employees_count > 0 else '',
})
rows.append({
'name': 'Total Non-Employees',
'r_count': report_total_non_employees_count,
'r_pct': '100',
'b_count': base_total_non_employees_count or '',
'b_pct': '100' if base_total_non_employees_count > 0 else '',
})
non_employees_types_table = request.env['ir.qweb']._render('esg_csrd_hr.esg_csrd_hr_report_type_non_employees_table', {
'rows': rows,
'has_base_year': has_base_year,
})
html_template_variables['{{ non_employees_types_table }}'] = non_employees_types_table
# Data for Table: Geography/Region (Non-Employees)
# Reporting Year
if report_non_employees_version_ids and esg_report.report_type == 'csrd':
report_country_data = self._get_country_employees_data(report_non_employees_version_ids)
# Base Year
base_country_data = self._get_country_employees_data(base_non_employees_version_ids)
rows = []
for report_country_name, report_country_count in report_country_data.items():
base_country_count = base_country_data.get(report_country_name, 0) if base_non_employees_version_ids else ''
base_country_percentage = round((base_country_count / base_total_non_employees_count) * 100, 2) if base_non_employees_version_ids and base_total_non_employees_count > 0 else ''
rows.append({
'name': report_country_name,
'r_count': report_country_count,
'r_pct': round((report_country_count / report_total_non_employees_count) * 100, 2),
'b_count': base_country_count,
'b_pct': base_country_percentage,
})
for base_country_name, base_country_count in base_country_data.items():
if base_country_name in report_country_data:
continue
rows.append({
'name': base_country_name,
'r_count': 0,
'r_pct': 0.0,
'b_count': base_country_count,
'b_pct': round((base_country_count / base_total_non_employees_count) * 100, 2) if base_total_non_employees_count > 0 else '',
})
rows.extend([
{
'name': 'Not Reported',
'r_count': report_total_non_employees_count - sum(report_country_data.values()),
'r_pct': round(((report_total_non_employees_count - sum(report_country_data.values())) / report_total_non_employees_count) * 100, 2) if report_total_non_employees_count > 0 else 0,
'b_count': base_total_non_employees_count - sum(base_country_data.values()) if base_non_employees_version_ids else '',
'b_pct': round(((base_total_non_employees_count - sum(base_country_data.values())) / base_total_non_employees_count) * 100, 2) if base_non_employees_version_ids and base_total_non_employees_count > 0 else '',
},
{
'name': 'Total Non-Employees',
'r_count': report_total_non_employees_count,
'r_pct': '100',
'b_count': base_total_non_employees_count or '',
'b_pct': '100' if base_total_non_employees_count > 0 else '',
},
])
country_non_employees_table = request.env['ir.qweb']._render('esg_csrd_hr.esg_csrd_hr_report_department_non_employees_table', {
'rows': rows,
'has_base_year': has_base_year,
})
html_template_variables['{{ country_non_employees_table }}'] = country_non_employees_table
# Data for Table: Job Category/Function (Non-Employees)
# Reporting Year
if report_non_employees_version_ids and esg_report.report_type == 'csrd':
report_department_data = self._get_department_employees_data(report_non_employees_version_ids)
# Base Year
base_department_data = self._get_department_employees_data(base_non_employees_version_ids)
rows = []
for report_department, report_count in report_department_data.items():
base_count = base_department_data.get(report_department, 0) if base_non_employees_version_ids else ''
base_percentage = round((base_count / base_total_non_employees_count) * 100, 2) if base_non_employees_version_ids and base_total_non_employees_count > 0 else ''
rows.append({
'name': report_department.name,
'r_count': report_count,
'r_pct': round((report_count / report_total_non_employees_count) * 100, 2),
'b_count': base_count,
'b_pct': base_percentage,
})
for base_department, base_count in base_department_data.items():
if base_department in report_department_data:
continue
rows.append({
'name': base_department.name,
'r_count': 0,
'r_pct': 0.0,
'b_count': base_count,
'b_pct': round((base_count / base_total_non_employees_count) * 100, 2) if base_total_non_employees_count > 0 else '',
})
rows.extend([
{
'name': 'Not Reported',
'r_count': report_total_non_employees_count - sum(report_department_data.values()),
'r_pct': round(((report_total_non_employees_count - sum(report_department_data.values())) / report_total_non_employees_count) * 100, 2) if report_total_non_employees_count > 0 else 0,
'b_count': base_total_non_employees_count - sum(base_department_data.values()) if base_non_employees_version_ids else '',
'b_pct': round(((base_total_non_employees_count - sum(base_department_data.values())) / base_total_non_employees_count) * 100, 2) if base_non_employees_version_ids and base_total_non_employees_count > 0 else '',
},
{
'name': 'Total Non-Employees',
'r_count': report_total_non_employees_count,
'r_pct': '100',
'b_count': base_total_non_employees_count or '',
'b_pct': '100' if base_total_non_employees_count > 0 else '',
},
])
department_non_employees_table = request.env['ir.qweb']._render('esg_csrd_hr.esg_csrd_hr_report_department_non_employees_table', {
'rows': rows,
'has_base_year': has_base_year,
})
html_template_variables['{{ department_non_employees_table }}'] = department_non_employees_table
# Data for Employees + Non-Employees
# Reporting Year
report_all_version_ids = self._get_valid_employee_version_ids(esg_report.start_date, esg_report.end_date)
html_template_variables.update({
'{{ department_all_employees_pay_gap_table }}': '',
'{{ contract_type_all_employees_pay_gap_table }}': '',
'{{ country_all_employees_pay_gap_table }}': '',
'{{ management_level_gender_table }}': '',
})
# Data for Tables: Remuneration Metrics
# Data for Table: Job Category/Function Pay Gap (Employees + Non-Employees)
# Reporting Year
if report_all_version_ids and esg_report.report_type == 'csrd':
report_department_pay_gap_data = self._get_department_pay_gap_data(report_all_version_ids)
rows = []
for report_department, pay_gap_data in report_department_pay_gap_data.items():
rows.append({
'name': report_department.name,
'male_median_hourly_salary': round(pay_gap_data['male_median_hourly_salary'], 2),
'female_median_hourly_salary': round(pay_gap_data['female_median_hourly_salary'], 2),
'pay_gap_percentage': round(pay_gap_data['pay_gap_percentage'], 2),
})
department_all_employees_pay_gap_table = request.env['ir.qweb']._render('esg_csrd_hr.esg_csrd_hr_report_department_pay_gap_table', {
'rows': rows,
})
html_template_variables['{{ department_all_employees_pay_gap_table }}'] = department_all_employees_pay_gap_table
# Data for Table: Contract Type Pay Gap (Employees + Non-Employees)
# Reporting Year
if report_all_version_ids and esg_report.report_type == 'csrd':
report_contract_pay_gap_data = self._get_contract_type_pay_gap_data(report_all_version_ids)
rows = []
for contract_type, pay_gap_data in report_contract_pay_gap_data.items():
rows.append({
'name': contract_type.name,
'male_median_hourly_salary': round(pay_gap_data['male_median_hourly_salary'], 2),
'female_median_hourly_salary': round(pay_gap_data['female_median_hourly_salary'], 2),
'pay_gap_percentage': round(pay_gap_data['pay_gap_percentage'], 2),
})
contract_type_all_employees_pay_gap_table = request.env['ir.qweb']._render('esg_csrd_hr.esg_csrd_hr_report_contract_type_pay_gap_table', {
'rows': rows,
})
html_template_variables['{{ contract_type_all_employees_pay_gap_table }}'] = contract_type_all_employees_pay_gap_table
# Data for Table: Country Pay Gap (Employees + Non-Employees)
# Reporting Year
if report_all_version_ids and esg_report.report_type == 'csrd':
report_country_pay_gap_data = self._get_country_pay_gap_data(report_all_version_ids)
rows = []
for country, pay_gap_data in report_country_pay_gap_data.items():
rows.append({
'name': country.name,
'male_median_hourly_salary': round(pay_gap_data['male_median_hourly_salary'], 2),
'female_median_hourly_salary': round(pay_gap_data['female_median_hourly_salary'], 2),
'pay_gap_percentage': round(pay_gap_data['pay_gap_percentage'], 2),
})
country_all_employees_pay_gap_table = request.env['ir.qweb']._render('esg_csrd_hr.esg_csrd_hr_report_country_pay_gap_table', {
'rows': rows,
})
html_template_variables['{{ country_all_employees_pay_gap_table }}'] = country_all_employees_pay_gap_table
# Gender Ratio at Management Level (Employees + Non-Employees)
# Reporting Year
if report_all_version_ids and esg_report.report_type != 'csrd':
report_gender_management_data = self._get_gender_management_data(report_all_version_ids)
rows = []
for data in report_gender_management_data:
rows.append({
'level': data['level'],
'female': data['female'],
'male': data['male'],
'other': data['other'],
'ratio': data['ratio'],
})
management_level_gender_table = request.env['ir.qweb']._render('esg_csrd_hr.esg_csrd_hr_report_management_level_gender_table', {
'rows': rows,
})
html_template_variables['{{ management_level_gender_table }}'] = management_level_gender_table
return html_template_variables