Files
enterprise-addons/hr_contract_salary_payroll/models/hr_contract_salary_offer.py
T

191 lines
9.2 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.addons.hr_contract_salary.utils.hr_version import requires_hr_version_context, hr_version_context
from odoo.tools import float_compare
from datetime import date
from dateutil.relativedelta import relativedelta
from collections import defaultdict
import uuid
class HrContractSalaryOffer(models.Model):
_inherit = 'hr.contract.salary.offer'
def _get_default_struct_id(self):
return self.env['hr.version']._default_salary_structure().default_struct_id
def _get_default_resource_calendar_id(self):
return self.env.company.resource_calendar_id
@api.model
def default_get(self, fields):
result = super().default_get(fields)
if self.env.context.get('default_is_simulation_offer'):
result['access_token'] = uuid.uuid4().hex
return result
is_simulation_offer = fields.Boolean()
monthly_wage = fields.Monetary(string="Monthly Wage", compute='_compute_monthly_wage', store=True, readonly=False)
final_yearly_costs = fields.Monetary(compute='_compute_final_yearly_costs', store=True, readonly=False)
budget_type = fields.Selection(
selection=[('monthly_gross', 'Gross Per Month'), ('yearly_employer', 'Yearly Employer Cost')],
default='yearly_employer',
required=True
)
country_id = fields.Many2one(related='company_id.country_id')
structure_id = fields.Many2one(
'hr.payroll.structure',
string="Salary Structure",
default=_get_default_struct_id,
domain="[('country_id', 'in', (country_id, False))]",
)
resource_calendar_id = fields.Many2one(
'resource.calendar',
string="Working Schedule",
default=_get_default_resource_calendar_id,
check_company=True
)
simulation_employee_id = fields.Many2one('hr.employee', check_company=True,
domain=[('contract_date_start', '!=', False)])
gross_wage = fields.Monetary(compute='_compute_salary', store=True)
net_wage = fields.Monetary(compute='_compute_salary', store=True)
monthly_benefits = fields.Monetary(compute='_compute_salary', store=True)
yearly_benefits = fields.Monetary(compute='_compute_salary', store=True)
yearly_employer_cost = fields.Monetary(compute='_compute_salary', store=True)
monthly_employer_cost = fields.Monetary(compute='_compute_salary', store=True)
is_full_time = fields.Boolean(compute='_compute_salary', store=True)
@requires_hr_version_context()
def _get_version(self):
self.ensure_one()
version = super()._get_version()
version_vals = {}
resource_calendar = version.resource_calendar_id or self.resource_calendar_id or self._get_default_resource_calendar_id()
if self.is_simulation_offer:
resource_calendar = self.resource_calendar_id or version.resource_calendar_id or self._get_default_resource_calendar_id()
if self.simulation_employee_id:
version = self.simulation_employee_id.version_id
version_vals.update({
'structure_type_id': self.structure_id.type_id.id,
})
version_vals.update({
'resource_calendar_id': resource_calendar.id,
version._get_contract_wage_field(): self.monthly_wage,
'wage_with_holidays': self.monthly_wage,
})
if self.is_simulation_offer:
work_time_rate = (resource_calendar.work_time_rate / 100)
new_wage = self.monthly_wage * work_time_rate
version_vals.update({
'wage_on_signature': new_wage,
'wage': new_wage,
'wage_with_holidays': new_wage,
})
employee = version.employee_id
if employee and employee.active:
self.with_context(tracking_disable=True)._archive_future_versions(version=employee.version_id)
version_vals.update({
'contract_date_start': version.contract_date_start or fields.Date.today().replace(day=1),
'contract_date_end': False,
})
version.write(version_vals)
version._inverse_wage_with_holidays()
return version
@api.onchange('simulation_employee_id')
def _onchange_simulation_employee_id(self):
if self.simulation_employee_id:
version = self.simulation_employee_id.current_version_id
self.monthly_wage = version._get_contract_wage()
self.resource_calendar_id = version.resource_calendar_id
self.structure_id = version.structure_type_id.default_struct_id
@api.depends('final_yearly_costs')
def _compute_monthly_wage(self):
monthly_wage_by_offer = {}
# Invalidating the model is needed to be sure that the contract_template_id has the correct data. Even with
# the rollback, the recordset in cache could still hold some wrong values that were rollbacked
with hr_version_context(self, invalidate=True) as offers:
for offer in offers:
version = offer._get_version()
monthly_wage_by_offer[offer] = version._get_gross_from_employer_costs(offer.final_yearly_costs)
for offer in self:
offer.monthly_wage = monthly_wage_by_offer[offer]
@api.depends('monthly_wage')
def _compute_final_yearly_costs(self):
final_yearly_costs_by_offer = {}
# Invalidating the model is needed to be sure that the contract_template_id has the correct data. Even with
# the rollback, the recordset in cache could still hold some wrong values that were rollbacked
with hr_version_context(self, invalidate=True) as offers:
for offer in offers:
version = offer._get_version()
final_yearly_costs_by_offer[offer] = version._get_employer_costs_from_gross(offer.monthly_wage)
for offer in self:
offer.final_yearly_costs = final_yearly_costs_by_offer[offer]
@api.depends('monthly_wage', 'structure_id', 'resource_calendar_id')
def _compute_salary(self) -> None:
vals_by_offer = {}
# Invalidating the model is needed to be sure that the contract_template_id has the correct data. Even with
# the rollback, the recordset in cache could still hold some wrong values that were rollbacked
with hr_version_context(self, invalidate=True) as offers:
for offer in offers:
version = offer._get_version()
payslip = version._generate_salary_simulation_payslip()
yearly_employer_cost = version._get_employer_costs_from_gross(version._get_contract_wage())
monthly_benefits, yearly_benefits = offer._get_benefits(version)
vals_by_offer[offer] = {
'is_full_time': float_compare(version.work_time_rate, 1.0, 4) == 0,
'gross_wage': payslip._get_line_values(['BASIC'])['BASIC'][payslip.id]['total'],
'net_wage': payslip._get_line_values(['NET'])['NET'][payslip.id]['total'],
'monthly_benefits': monthly_benefits,
'yearly_benefits': yearly_benefits,
'yearly_employer_cost': yearly_employer_cost,
'monthly_employer_cost': round(yearly_employer_cost / 12, 2),
}
for offer in self:
offer.update(vals_by_offer[offer])
def _get_benefits(self, version):
monthly_benefit_category = self.env.ref('hr_contract_salary.hr_contract_salary_resume_category_monthly_benefits')
yearly_benefit_category = self.env.ref('hr_contract_salary.hr_contract_salary_resume_category_yearly_benefits')
resume_lines = self.env['hr.contract.salary.resume'].sudo().with_company(version.company_id).search([
'|',
('structure_type_id', '=', False),
('structure_type_id', '=', version.structure_type_id.id),
('value_type', '=', 'sum'),
('category_id', 'in', (monthly_benefit_category.id, yearly_benefit_category.id))
])
result = defaultdict(int)
for resume_line in resume_lines:
value = 0
for benefit in resume_line.benefit_ids:
if not benefit.fold_field or (benefit.fold_field and version[benefit.fold_field]):
field = benefit.field
value += version[field] if benefit.source == 'field' else version._get_property_input_value(benefit.salary_rule_id.code)
result[resume_line.category_id.id] += round(float(value), 2)
return result[monthly_benefit_category.id], result[yearly_benefit_category.id]
@api.model
def action_cron_remove_simulation_offers(self):
simulation_offers = self.env['hr.contract.salary.offer'].search([('is_simulation_offer', '=', True)])
older_than_one_month_offers = simulation_offers.filtered(
lambda offer: offer.offer_create_date < (date.today() - relativedelta(month=1))
)
return older_than_one_month_offers.unlink()
def action_open_salary_configurator(self):
self.ensure_one()
self.resource_calendar_id = self.resource_calendar_id or self._get_default_resource_calendar_id()
return {
'type': 'ir.actions.act_url',
'url': self.url,
'target': 'new',
}