Files

173 lines
9.6 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from odoo.addons.hr_contract_salary.controllers import main
from odoo.addons.hr_contract_salary.utils.hr_version import hr_version_context
from odoo import http, _
from odoo.exceptions import UserError
from odoo.http import request
from odoo.tools.float_utils import float_compare
class HrContractSalary(main.HrContractSalary):
def _can_submit_offer(self, values):
return not values.get('is_simulation_offer', False) and super()._can_submit_offer(values)
def _check_link_access(self, offer, **kw):
if not request.env.user.has_group('hr.group_hr_manager') and offer.is_simulation_offer:
if not self._check_access_token(offer, kw.get('token')):
return False, _('This link is invalid. Please contact the HR Responsible to get a new one...')
return super()._check_link_access(offer, **kw)
def _get_default_template_values(self, version, offer):
values = super()._get_default_template_values(version, offer)
values.update({
'is_simulation_offer': offer.is_simulation_offer
})
return values
def _get_new_version_values(self, version_vals, employee, benefits, offer):
new_version_vals = super()._get_new_version_values(version_vals, employee, benefits, offer)
new_version_vals['work_entry_source'] = version_vals.get('work_entry_source')
new_version_vals['standard_calendar_id'] = version_vals.get('standard_calendar_id')
if version_vals.get('wage_type') == 'hourly':
new_version_vals['hourly_wage'] = version_vals.get('hourly_wage')
return new_version_vals
def _get_personal_infos(self, version, offer):
if not offer.is_simulation_offer:
return super()._get_personal_infos(version, offer)
mapped_personal_infos, new_dropdown_options, new_initial_values = super()._get_personal_infos(version, offer)
new_mapped_personal_infos = defaultdict(lambda: request.env['hr.contract.salary.personal.info'])
for key, personal_infos in mapped_personal_infos.items():
for personal_info in personal_infos:
if personal_info.impacts_net_salary:
new_mapped_personal_infos[key] |= personal_info
else:
new_initial_values.pop(personal_info.field, None)
new_initial_values.pop(personal_info.field + '_filename', None)
new_dropdown_options.pop(personal_info.field, None)
return new_mapped_personal_infos, new_dropdown_options, new_initial_values
def _get_payslip_line_values(self, payslip, codes):
return payslip._get_line_values(codes)
def _get_compute_results(self, new_version):
schedule_pay_label = dict(request.env['hr.version']._fields['schedule_pay']._description_selection(request.env))
def _get_period_name(category_id, version):
if category_id == request.env.ref("hr_contract_salary.hr_contract_salary_resume_category_monthly_salary"):
# ISSUE:
# The salary configurator was originally designed with the assumption that
# the pay schedule is always "monthly". When we changed the schedule pay
# selection labels (e.g., "monthly" -> "month"), the configurator started
# displaying "month Salary" instead of the expected "Monthly Salary"
# and "Gross (Incl. Comm)" instead of "Gross".
# FIX:
# As a temporary workaround, we return the category_id.name (to fetch the correct translated name depending on the language, e.g. "Monthly Salary" in English)
# when version.schedule_pay == 'monthly' because the configurator is not adapted for other pay schedule than monthly.
# For all other schedule_pay values, we keep using the schedule_pay_label mapping,
# until the configurator is properly adapted to support non-monthly schedules.
if version.schedule_pay == 'monthly':
return category_id.name
period_name = schedule_pay_label.get(version.schedule_pay, "Monthly")
return f"{period_name} Salary"
return category_id.name
result = super()._get_compute_results(new_version)
# generate a payslip corresponding to only this version
payslip = new_version._generate_salary_simulation_payslip()
result['payslip_lines'] = [(
line.name,
f'{line.total:.2f}',
line.code,
'total' if line.code in ['BASIC', 'SALARY', 'GROSS', 'NET', 'GROSSIP'] else float_compare(line.total, 0, precision_digits=2),
new_version.company_id.currency_id.position,
new_version.company_id.currency_id.symbol
) for line in payslip.line_ids.filtered(lambda l: l.appears_on_payslip)]
# Allowed company ids might not be filled or request.env.user.company_ids might be wrong
# since we are in route context, force the company to make sure we load everything
resume_lines = request.env['hr.contract.salary.resume'].sudo().with_company(new_version.company_id).search([
'|',
('structure_type_id', '=', False),
('structure_type_id', '=', new_version.structure_type_id.id),
('value_type', 'in', ['payslip', 'monthly_total'])])
monthly_total = 0
monthly_total_lines = resume_lines.filtered(lambda l: l.value_type == 'monthly_total')
# new categories could be introduced at this step
# recreate resume_categories
resume_categories = request.env['hr.contract.salary.resume'].sudo().with_company(new_version.company_id).search([
'|', '&', '|',
('structure_type_id', '=', False),
('structure_type_id', '=', new_version.structure_type_id.id),
('value_type', 'in', ['fixed', 'version', 'monthly_total', 'sum']),
('id', 'in', resume_lines.ids)]).category_id
result['resume_categories'] = [_get_period_name(c, new_version) for c in sorted(resume_categories, key=lambda x: x.sequence)]
all_codes = (resume_lines - monthly_total_lines).mapped('code')
line_values = self._get_payslip_line_values(payslip, all_codes) if all_codes else False
for resume_line in resume_lines - monthly_total_lines:
value = round(line_values[resume_line.code][payslip.id]['total'], 2)
resume_explanation = False
if resume_line.code == 'GROSS' and new_version.wage_type == 'hourly':
hours = payslip.worked_days_line_ids.number_of_hours
resume_explanation = self.env._('This is the gross calculated for the current month with a total of %s hours.', hours)
result['resume_lines_mapped'][_get_period_name(resume_line.category_id, new_version)][resume_line.code] = (resume_line.name, value, new_version.company_id.currency_id.symbol, resume_explanation, new_version.company_id.currency_id.position, resume_line.uom)
if resume_line.impacts_monthly_total:
monthly_total += value / 12.0 if resume_line.category_id.periodicity == 'yearly' else value
for resume_line in monthly_total_lines:
super_line = result['resume_lines_mapped'][_get_period_name(resume_line.category_id, new_version)][resume_line.code]
line_name = super_line[0]
if resume_line.category_id == request.env.ref("hr_contract_salary.hr_contract_salary_resume_category_total"):
period_name = schedule_pay_label.get(new_version.schedule_pay, "Monthly")
line_name = f"{period_name} Equivalent"
new_value = (line_name, round(super_line[1] + float(monthly_total), 2), super_line[2], False, new_version.company_id.currency_id.position, resume_line.uom)
result['resume_lines_mapped'][_get_period_name(resume_line.category_id, new_version)][resume_line.code] = new_value
return result
def _update_version_payroll_properties(self, version, benefits):
version_benefits = request.env['hr.contract.salary.benefit'].sudo().search([
('structure_type_id', '=', version.structure_type_id.id),
('source', '=', 'rule')])
variants = [
'',
"_manual",
"_radio",
"_slider",
]
for benefit in version_benefits:
fields = ['%s%s' % (benefit.field, k) for k in variants]
value = next((benefits[field] for field in fields if field in benefits), None) or 0.0
version._set_property_input_value(benefit.salary_rule_id.code, float(value))
def create_new_version(self, version_vals, offer_id, benefits, no_write=False, **kw):
new_version, version_diff = super().create_new_version(version_vals, offer_id, benefits, no_write=no_write, **kw)
benefits_values = benefits['version']
with hr_version_context(request):
offer = request.env['hr.contract.salary.offer'].sudo().browse(offer_id)
version = offer._get_version()
new_version.write({
'payroll_properties': dict(version.payroll_properties),
})
self._update_version_payroll_properties(new_version, benefits_values)
return new_version, version_diff
@http.route()
def submit(self, offer_id=None, benefits=None, **kw):
offer = request.env['hr.contract.salary.offer'].sudo().browse(offer_id)
if offer.is_simulation_offer:
raise UserError(self.env._('This offer is a simulation. You cannot submit this salary package.'))
return super().submit(offer_id, benefits, **kw)