mirror of
https://github.com/bringout/oca-payroll.git
synced 2026-04-21 08:22:04 +02:00
Initial commit: OCA Payroll packages (5 packages)
This commit is contained in:
commit
d19274f581
407 changed files with 214057 additions and 0 deletions
823
odoo-bringout-oca-payroll-payroll/payroll/models/hr_payslip.py
Normal file
823
odoo-bringout-oca-payroll-payroll/payroll/models/hr_payslip.py
Normal file
|
|
@ -0,0 +1,823 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import math
|
||||
from datetime import date, datetime, time
|
||||
|
||||
import babel
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from pytz import timezone
|
||||
|
||||
from odoo import _, api, fields, models, tools
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools.float_utils import float_compare
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
from .base_browsable import (
|
||||
BaseBrowsableObject,
|
||||
BrowsableObject,
|
||||
InputLine,
|
||||
Payslips,
|
||||
WorkedDays,
|
||||
)
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HrPayslip(models.Model):
|
||||
_name = "hr.payslip"
|
||||
_inherit = ["mail.thread", "mail.activity.mixin"]
|
||||
_description = "Payslip"
|
||||
_order = "id desc"
|
||||
|
||||
struct_id = fields.Many2one(
|
||||
"hr.payroll.structure",
|
||||
string="Structure",
|
||||
readonly=True,
|
||||
states={"draft": [("readonly", False)]},
|
||||
help="Defines the rules that have to be applied to this payslip, "
|
||||
"accordingly to the contract chosen. If you let empty the field "
|
||||
"contract, this field isn't mandatory anymore and thus the rules "
|
||||
"applied will be all the rules set on the structure of all contracts "
|
||||
"of the employee valid for the chosen period",
|
||||
)
|
||||
name = fields.Char(
|
||||
string="Payslip Name", readonly=True, states={"draft": [("readonly", False)]}
|
||||
)
|
||||
number = fields.Char(
|
||||
string="Reference",
|
||||
readonly=True,
|
||||
copy=False,
|
||||
states={"draft": [("readonly", False)]},
|
||||
)
|
||||
employee_id = fields.Many2one(
|
||||
"hr.employee",
|
||||
string="Employee",
|
||||
required=True,
|
||||
readonly=True,
|
||||
states={"draft": [("readonly", False)]},
|
||||
)
|
||||
date_from = fields.Date(
|
||||
readonly=True,
|
||||
required=True,
|
||||
default=lambda self: fields.Date.to_string(date.today().replace(day=1)),
|
||||
states={"draft": [("readonly", False)]},
|
||||
tracking=True,
|
||||
)
|
||||
date_to = fields.Date(
|
||||
readonly=True,
|
||||
required=True,
|
||||
default=lambda self: fields.Date.to_string(
|
||||
(datetime.now() + relativedelta(months=+1, day=1, days=-1)).date()
|
||||
),
|
||||
states={"draft": [("readonly", False)]},
|
||||
tracking=True,
|
||||
)
|
||||
state = fields.Selection(
|
||||
[
|
||||
("draft", "Draft"),
|
||||
("verify", "Waiting"),
|
||||
("done", "Done"),
|
||||
("cancel", "Rejected"),
|
||||
],
|
||||
string="Status",
|
||||
index=True,
|
||||
readonly=True,
|
||||
copy=False,
|
||||
default="draft",
|
||||
tracking=True,
|
||||
help="""* When the payslip is created the status is \'Draft\'
|
||||
\n* If the payslip is under verification, the status is \'Waiting\'.
|
||||
\n* If the payslip is confirmed then status is set to \'Done\'.
|
||||
\n* When user cancel payslip the status is \'Rejected\'.""",
|
||||
)
|
||||
line_ids = fields.One2many(
|
||||
"hr.payslip.line",
|
||||
"slip_id",
|
||||
string="Payslip Lines",
|
||||
readonly=True,
|
||||
states={"draft": [("readonly", False)]},
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
"res.company",
|
||||
string="Company",
|
||||
readonly=True,
|
||||
copy=False,
|
||||
default=lambda self: self.env.company,
|
||||
states={"draft": [("readonly", False)]},
|
||||
)
|
||||
worked_days_line_ids = fields.One2many(
|
||||
"hr.payslip.worked_days",
|
||||
"payslip_id",
|
||||
string="Payslip Worked Days",
|
||||
copy=True,
|
||||
readonly=True,
|
||||
states={"draft": [("readonly", False)]},
|
||||
)
|
||||
input_line_ids = fields.One2many(
|
||||
"hr.payslip.input",
|
||||
"payslip_id",
|
||||
string="Payslip Inputs",
|
||||
readonly=True,
|
||||
states={"draft": [("readonly", False)]},
|
||||
)
|
||||
paid = fields.Boolean(
|
||||
string="Made Payment Order ? ",
|
||||
readonly=True,
|
||||
copy=False,
|
||||
states={"draft": [("readonly", False)]},
|
||||
)
|
||||
note = fields.Text(
|
||||
string="Internal Note",
|
||||
readonly=True,
|
||||
states={"draft": [("readonly", False)]},
|
||||
tracking=True,
|
||||
)
|
||||
contract_id = fields.Many2one(
|
||||
"hr.contract",
|
||||
string="Contract",
|
||||
readonly=True,
|
||||
tracking=True,
|
||||
states={"draft": [("readonly", False)]},
|
||||
)
|
||||
dynamic_filtered_payslip_lines = fields.One2many(
|
||||
"hr.payslip.line",
|
||||
compute="_compute_dynamic_filtered_payslip_lines",
|
||||
)
|
||||
credit_note = fields.Boolean(
|
||||
readonly=True,
|
||||
states={"draft": [("readonly", False)]},
|
||||
help="Indicates this payslip has a refund of another",
|
||||
)
|
||||
payslip_run_id = fields.Many2one(
|
||||
"hr.payslip.run",
|
||||
string="Payslip Batches",
|
||||
readonly=True,
|
||||
copy=False,
|
||||
tracking=True,
|
||||
states={"draft": [("readonly", False)]},
|
||||
)
|
||||
payslip_count = fields.Integer(
|
||||
compute="_compute_payslip_count", string="Payslip Computation Details"
|
||||
)
|
||||
hide_child_lines = fields.Boolean(default=False)
|
||||
hide_invisible_lines = fields.Boolean(
|
||||
string="Show only lines that appear on payslip", default=False
|
||||
)
|
||||
compute_date = fields.Date()
|
||||
refunded_id = fields.Many2one(
|
||||
"hr.payslip", string="Refunded Payslip", readonly=True
|
||||
)
|
||||
allow_cancel_payslips = fields.Boolean(
|
||||
"Allow Canceling Payslips", compute="_compute_allow_cancel_payslips"
|
||||
)
|
||||
prevent_compute_on_confirm = fields.Boolean(
|
||||
"Prevent Compute on Confirm", compute="_compute_prevent_compute_on_confirm"
|
||||
)
|
||||
|
||||
def _compute_allow_cancel_payslips(self):
|
||||
self.allow_cancel_payslips = (
|
||||
self.env["ir.config_parameter"]
|
||||
.sudo()
|
||||
.get_param("payroll.allow_cancel_payslips")
|
||||
)
|
||||
|
||||
def _compute_prevent_compute_on_confirm(self):
|
||||
self.prevent_compute_on_confirm = (
|
||||
self.env["ir.config_parameter"]
|
||||
.sudo()
|
||||
.get_param("payroll.prevent_compute_on_confirm")
|
||||
)
|
||||
|
||||
@api.depends("line_ids", "hide_child_lines", "hide_invisible_lines")
|
||||
def _compute_dynamic_filtered_payslip_lines(self):
|
||||
for payslip in self:
|
||||
lines = payslip.line_ids
|
||||
if payslip.hide_child_lines:
|
||||
lines = lines.filtered(lambda line: not line.parent_rule_id)
|
||||
if payslip.hide_invisible_lines:
|
||||
lines = lines.filtered(lambda line: line.appears_on_payslip)
|
||||
payslip.dynamic_filtered_payslip_lines = lines
|
||||
|
||||
def _compute_payslip_count(self):
|
||||
for payslip in self:
|
||||
payslip.payslip_count = len(payslip.line_ids)
|
||||
|
||||
@api.constrains("date_from", "date_to")
|
||||
def _check_dates(self):
|
||||
if any(self.filtered(lambda payslip: payslip.date_from > payslip.date_to)):
|
||||
raise ValidationError(
|
||||
_("Payslip 'Date From' must be earlier than 'Date To'.")
|
||||
)
|
||||
|
||||
def copy(self, default=None):
|
||||
rec = super().copy(default)
|
||||
for line in self.input_line_ids:
|
||||
line.copy({"payslip_id": rec.id})
|
||||
for line in self.line_ids:
|
||||
line.copy({"slip_id": rec.id, "input_ids": []})
|
||||
return rec
|
||||
|
||||
def action_payslip_draft(self):
|
||||
return self.write({"state": "draft"})
|
||||
|
||||
def action_payslip_done(self):
|
||||
if (
|
||||
not self.env.context.get("without_compute_sheet")
|
||||
and not self.prevent_compute_on_confirm
|
||||
):
|
||||
self.compute_sheet()
|
||||
return self.write({"state": "done"})
|
||||
|
||||
def action_payslip_cancel(self):
|
||||
for payslip in self:
|
||||
if payslip.allow_cancel_payslips:
|
||||
if payslip.refunded_id and payslip.refunded_id.state != "cancel":
|
||||
raise ValidationError(
|
||||
_(
|
||||
"""To cancel the Original Payslip the
|
||||
Refunded Payslip needs to be canceled first!"""
|
||||
)
|
||||
)
|
||||
else:
|
||||
if self.filtered(lambda slip: slip.state == "done"):
|
||||
raise UserError(_("Cannot cancel a payslip that is done."))
|
||||
return self.write({"state": "cancel"})
|
||||
|
||||
def refund_sheet(self):
|
||||
for payslip in self:
|
||||
copied_payslip = payslip.copy(
|
||||
{"credit_note": True, "name": _("Refund: %s") % payslip.name}
|
||||
)
|
||||
number = copied_payslip.number or self.env["ir.sequence"].next_by_code(
|
||||
"salary.slip"
|
||||
)
|
||||
copied_payslip.write({"number": number})
|
||||
copied_payslip.with_context(
|
||||
without_compute_sheet=True
|
||||
).action_payslip_done()
|
||||
formview_ref = self.env.ref("payroll.hr_payslip_view_form", False)
|
||||
treeview_ref = self.env.ref("payroll.hr_payslip_view_tree", False)
|
||||
res = {
|
||||
"name": _("Refund Payslip"),
|
||||
"view_mode": "tree, form",
|
||||
"view_id": False,
|
||||
"res_model": "hr.payslip",
|
||||
"type": "ir.actions.act_window",
|
||||
"target": "current",
|
||||
"domain": "[('id', 'in', %s)]" % copied_payslip.ids,
|
||||
"views": [
|
||||
(treeview_ref and treeview_ref.id or False, "tree"),
|
||||
(formview_ref and formview_ref.id or False, "form"),
|
||||
],
|
||||
"context": {},
|
||||
}
|
||||
payslip.write({"refunded_id": safe_eval(res["domain"])[0][2][0] or False})
|
||||
return res
|
||||
|
||||
def unlink(self):
|
||||
if any(self.filtered(lambda payslip: payslip.state not in ("draft", "cancel"))):
|
||||
raise UserError(
|
||||
_("You cannot delete a payslip which is not draft or cancelled")
|
||||
)
|
||||
return super(HrPayslip, self).unlink()
|
||||
|
||||
def compute_sheet(self):
|
||||
for payslip in self:
|
||||
# delete old payslip lines
|
||||
payslip.line_ids.unlink()
|
||||
# write payslip lines
|
||||
number = payslip.number or self.env["ir.sequence"].next_by_code(
|
||||
"salary.slip"
|
||||
)
|
||||
lines = [(0, 0, line) for line in list(payslip.get_lines_dict().values())]
|
||||
payslip.write(
|
||||
{
|
||||
"line_ids": lines,
|
||||
"number": number,
|
||||
"state": "verify",
|
||||
"compute_date": fields.Date.today(),
|
||||
}
|
||||
)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def get_worked_day_lines(self, contracts, date_from, date_to):
|
||||
"""
|
||||
@param contracts: Browse record of contracts
|
||||
@return: returns a list of dict containing the input that should be
|
||||
applied for the given contract between date_from and date_to
|
||||
"""
|
||||
res = []
|
||||
for contract in contracts.filtered(
|
||||
lambda contract: contract.resource_calendar_id
|
||||
):
|
||||
day_from = datetime.combine(date_from, time.min)
|
||||
day_to = datetime.combine(date_to, time.max)
|
||||
day_contract_start = datetime.combine(contract.date_start, time.min)
|
||||
# Support for the hr_public_holidays module.
|
||||
contract = contract.with_context(
|
||||
employee_id=self.employee_id.id, exclude_public_holidays=True
|
||||
)
|
||||
# only use payslip day_from if it's greather than contract start date
|
||||
if day_from < day_contract_start:
|
||||
day_from = day_contract_start
|
||||
# == compute leave days == #
|
||||
leaves = self._compute_leave_days(contract, day_from, day_to)
|
||||
res.extend(leaves)
|
||||
# == compute worked days == #
|
||||
attendances = self._compute_worked_days(contract, day_from, day_to)
|
||||
res.append(attendances)
|
||||
return res
|
||||
|
||||
def _compute_leave_days(self, contract, day_from, day_to, leave_domain=None):
|
||||
"""
|
||||
Leave days computation
|
||||
@return: returns a list containing the leave inputs for the period
|
||||
of the payslip. One record per leave type.
|
||||
"""
|
||||
leaves_positive = (
|
||||
self.env["ir.config_parameter"].sudo().get_param("payroll.leaves_positive")
|
||||
)
|
||||
leaves = {}
|
||||
calendar = contract.resource_calendar_id
|
||||
tz = timezone(calendar.tz)
|
||||
day_leave_intervals = contract.employee_id.list_leaves(
|
||||
day_from,
|
||||
day_to,
|
||||
calendar=contract.resource_calendar_id,
|
||||
domain=leave_domain,
|
||||
)
|
||||
for day, hours, leave in day_leave_intervals:
|
||||
holiday = leave[:1].holiday_id
|
||||
current_leave_struct = leaves.setdefault(
|
||||
holiday.holiday_status_id,
|
||||
{
|
||||
"name": holiday.holiday_status_id.name or _("Global Leaves"),
|
||||
"sequence": 5,
|
||||
"code": holiday.holiday_status_id.code or "GLOBAL",
|
||||
"number_of_days": 0.0,
|
||||
"number_of_hours": 0.0,
|
||||
"contract_id": contract.id,
|
||||
},
|
||||
)
|
||||
if leaves_positive:
|
||||
current_leave_struct["number_of_hours"] += hours
|
||||
else:
|
||||
current_leave_struct["number_of_hours"] -= hours
|
||||
work_hours = calendar.get_work_hours_count(
|
||||
tz.localize(datetime.combine(day, time.min)),
|
||||
tz.localize(datetime.combine(day, time.max)),
|
||||
compute_leaves=False,
|
||||
)
|
||||
if work_hours:
|
||||
if leaves_positive:
|
||||
current_leave_struct["number_of_days"] += hours / work_hours
|
||||
else:
|
||||
current_leave_struct["number_of_days"] -= hours / work_hours
|
||||
return leaves.values()
|
||||
|
||||
def _compute_worked_days(self, contract, day_from, day_to):
|
||||
"""
|
||||
Worked days computation
|
||||
@return: returns a list containing the total worked_days for the period
|
||||
of the payslip. This returns the FULL work days expected for the resource
|
||||
calendar selected for the employee (it don't substract leaves by default).
|
||||
"""
|
||||
work_data = contract.employee_id._get_work_days_data_batch(
|
||||
day_from,
|
||||
day_to,
|
||||
calendar=contract.resource_calendar_id,
|
||||
compute_leaves=False,
|
||||
)
|
||||
return {
|
||||
"name": _("Normal Working Days paid at 100%"),
|
||||
"sequence": 1,
|
||||
"code": "WORK100",
|
||||
"number_of_days": work_data[contract.employee_id.id]["days"],
|
||||
"number_of_hours": work_data[contract.employee_id.id]["hours"],
|
||||
"contract_id": contract.id,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def get_inputs(self, contracts, date_from, date_to):
|
||||
# TODO: We leave date_from and date_to params here for backwards
|
||||
# compatibility reasons for the ones who inherit this function
|
||||
# in another modules, but they are not used.
|
||||
# Will be removed in next versions.
|
||||
"""
|
||||
Inputs computation.
|
||||
@returns: Returns a dict with the inputs that are fetched from the salary_structure
|
||||
associated rules for the given contracts.
|
||||
"""
|
||||
res = []
|
||||
current_structure = self.struct_id
|
||||
structure_ids = contracts.get_all_structures()
|
||||
if current_structure:
|
||||
structure_ids = list(set(current_structure._get_parent_structure().ids))
|
||||
rule_ids = (
|
||||
self.env["hr.payroll.structure"].browse(structure_ids).get_all_rules()
|
||||
)
|
||||
sorted_rule_ids = [id for id, sequence in sorted(rule_ids, key=lambda x: x[1])]
|
||||
payslip_inputs = (
|
||||
self.env["hr.salary.rule"].browse(sorted_rule_ids).mapped("input_ids")
|
||||
)
|
||||
for contract in contracts:
|
||||
for payslip_input in payslip_inputs:
|
||||
res.append(
|
||||
{
|
||||
"name": payslip_input.name,
|
||||
"code": payslip_input.code,
|
||||
"contract_id": contract.id,
|
||||
}
|
||||
)
|
||||
return res
|
||||
|
||||
def _init_payroll_dict_contracts(self):
|
||||
return {
|
||||
"count": 0,
|
||||
}
|
||||
|
||||
def get_payroll_dict(self, contracts):
|
||||
"""Setup miscellaneous dictionary values.
|
||||
Other modules may overload this method to inject discreet values into
|
||||
the salary rules. Such values will be available to the salary rule
|
||||
under the `payroll.` prefix.
|
||||
|
||||
This method is evaluated once per payslip.
|
||||
:param contracts: Recordset of all hr.contract records in this payslip
|
||||
:return: a dictionary of discreet values and/or Browsable Objects
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
res = {
|
||||
# In salary rules refer to this as: payroll.contracts.count
|
||||
"contracts": BaseBrowsableObject(self._init_payroll_dict_contracts()),
|
||||
}
|
||||
res["contracts"].count = len(contracts)
|
||||
|
||||
return res
|
||||
|
||||
def get_current_contract_dict(self, contract, contracts):
|
||||
"""Contract dependent dictionary values.
|
||||
This method is called just before the salary rules are evaluated for
|
||||
contract.
|
||||
|
||||
This method is evaluated once for every contract in the payslip.
|
||||
|
||||
:param contract: The current hr.contract being processed
|
||||
:param contracts: Recordset of all hr.contract records in this payslip
|
||||
:return: a dictionary of discreet values and/or Browsable Objects
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
# res = super().get_current_contract_dict(contract, contracts)
|
||||
# res.update({
|
||||
# # In salary rules refer to these as:
|
||||
# # current_contract.foo
|
||||
# # current_contract.foo.bar.baz
|
||||
# "foo": 0,
|
||||
# "bar": BaseBrowsableObject(
|
||||
# {
|
||||
# "baz": 0
|
||||
# }
|
||||
# )
|
||||
# })
|
||||
# <do something to update values in res>
|
||||
# return res
|
||||
|
||||
return {}
|
||||
|
||||
def _get_tools_dict(self):
|
||||
# _get_tools_dict() is intended to be inherited by other private modules
|
||||
# to add tools or python libraries available in localdict
|
||||
return {
|
||||
"math": math,
|
||||
"datetime": datetime,
|
||||
"relativedelta": relativedelta,
|
||||
"float_compare": float_compare,
|
||||
} # "math" object is useful for doing calculations
|
||||
|
||||
def _get_baselocaldict(self, contracts):
|
||||
self.ensure_one()
|
||||
worked_days_dict = {
|
||||
line.code: line for line in self.worked_days_line_ids if line.code
|
||||
}
|
||||
input_lines_dict = {
|
||||
line.code: line for line in self.input_line_ids if line.code
|
||||
}
|
||||
localdict = {
|
||||
"payslips": Payslips(self.employee_id.id, self, self.env),
|
||||
"worked_days": WorkedDays(self.employee_id.id, worked_days_dict, self.env),
|
||||
"inputs": InputLine(self.employee_id.id, input_lines_dict, self.env),
|
||||
"payroll": BrowsableObject(
|
||||
self.employee_id.id, self.get_payroll_dict(contracts), self.env
|
||||
),
|
||||
"current_contract": BrowsableObject(self.employee_id.id, {}, self.env),
|
||||
"categories": BrowsableObject(self.employee_id.id, {}, self.env),
|
||||
"rules": BrowsableObject(self.employee_id.id, {}, self.env),
|
||||
"result_rules": BrowsableObject(self.employee_id.id, {}, self.env),
|
||||
"tools": BrowsableObject(
|
||||
self.employee_id.id, self._get_tools_dict(), self.env
|
||||
),
|
||||
}
|
||||
return localdict
|
||||
|
||||
def _get_salary_rules(self):
|
||||
rule_obj = self.env["hr.salary.rule"]
|
||||
sorted_rules = rule_obj
|
||||
for payslip in self:
|
||||
contracts = payslip._get_employee_contracts()
|
||||
if len(contracts) == 1 and payslip.struct_id:
|
||||
structure_ids = list(set(payslip.struct_id._get_parent_structure().ids))
|
||||
else:
|
||||
structure_ids = contracts.get_all_structures()
|
||||
rule_ids = (
|
||||
self.env["hr.payroll.structure"].browse(structure_ids).get_all_rules()
|
||||
)
|
||||
sorted_rule_ids = [
|
||||
id for id, sequence in sorted(rule_ids, key=lambda x: x[1])
|
||||
]
|
||||
sorted_rules |= rule_obj.browse(sorted_rule_ids)
|
||||
return sorted_rules
|
||||
|
||||
def _compute_payslip_line(self, rule, localdict, lines_dict):
|
||||
self.ensure_one()
|
||||
# check if there is already a rule computed with that code
|
||||
previous_amount = rule.code in localdict and localdict[rule.code] or 0.0
|
||||
# compute the rule to get some values for the payslip line
|
||||
values = rule._compute_rule(localdict)
|
||||
key = (rule.code or "id" + str(rule.id)) + "-" + str(localdict["contract"].id)
|
||||
return self._get_lines_dict(
|
||||
rule, localdict, lines_dict, key, values, previous_amount
|
||||
)
|
||||
|
||||
def _get_lines_dict(
|
||||
self, rule, localdict, lines_dict, key, values, previous_amount
|
||||
):
|
||||
total = values["quantity"] * values["rate"] * values["amount"] / 100.0
|
||||
values["total"] = total
|
||||
# set/overwrite the amount computed for this rule in the localdict
|
||||
code = rule.code or rule.id
|
||||
localdict[code] = total
|
||||
localdict["rules"].dict[code] = rule
|
||||
localdict["result_rules"].dict[code] = BaseBrowsableObject(values)
|
||||
# sum the amount for its salary category
|
||||
localdict = self._sum_salary_rule_category(
|
||||
localdict, rule.category_id, total - previous_amount
|
||||
)
|
||||
# create/overwrite the line in the temporary results
|
||||
line_dict = {
|
||||
"salary_rule_id": rule.id,
|
||||
"employee_id": localdict["employee"].id,
|
||||
"contract_id": localdict["contract"].id,
|
||||
"code": rule.code,
|
||||
"category_id": rule.category_id.id,
|
||||
"sequence": rule.sequence,
|
||||
"appears_on_payslip": rule.appears_on_payslip,
|
||||
"parent_rule_id": rule.parent_rule_id.id,
|
||||
"condition_select": rule.condition_select,
|
||||
"condition_python": rule.condition_python,
|
||||
"condition_range": rule.condition_range,
|
||||
"condition_range_min": rule.condition_range_min,
|
||||
"condition_range_max": rule.condition_range_max,
|
||||
"amount_select": rule.amount_select,
|
||||
"amount_fix": rule.amount_fix,
|
||||
"amount_python_compute": rule.amount_python_compute,
|
||||
"amount_percentage": rule.amount_percentage,
|
||||
"amount_percentage_base": rule.amount_percentage_base,
|
||||
"register_id": rule.register_id.id,
|
||||
}
|
||||
line_dict.update(values)
|
||||
lines_dict[key] = line_dict
|
||||
return localdict, lines_dict
|
||||
|
||||
@api.model
|
||||
def _get_payslip_lines(self, _contract_ids, payslip_id):
|
||||
_logger.warning(
|
||||
"Use of _get_payslip_lines() is deprecated. "
|
||||
"Use get_lines_dict() instead."
|
||||
)
|
||||
return self.browse(payslip_id).get_lines_dict()
|
||||
|
||||
def get_lines_dict(self):
|
||||
lines_dict = {}
|
||||
blacklist = []
|
||||
for payslip in self:
|
||||
contracts = payslip._get_employee_contracts()
|
||||
baselocaldict = payslip._get_baselocaldict(contracts)
|
||||
for contract in contracts:
|
||||
# assign "current_contract" dict
|
||||
baselocaldict["current_contract"] = BrowsableObject(
|
||||
payslip.employee_id.id,
|
||||
payslip.get_current_contract_dict(contract, contracts),
|
||||
payslip.env,
|
||||
)
|
||||
# set up localdict with current contract and employee values
|
||||
localdict = dict(
|
||||
baselocaldict,
|
||||
employee=contract.employee_id,
|
||||
contract=contract,
|
||||
payslip=payslip,
|
||||
)
|
||||
for rule in payslip._get_salary_rules():
|
||||
localdict = rule._reset_localdict_values(localdict)
|
||||
# check if the rule can be applied
|
||||
if rule._satisfy_condition(localdict) and rule.id not in blacklist:
|
||||
localdict, _dict = payslip._compute_payslip_line(
|
||||
rule, localdict, lines_dict
|
||||
)
|
||||
lines_dict.update(_dict)
|
||||
else:
|
||||
# blacklist this rule and its children
|
||||
blacklist += [
|
||||
id for id, seq in rule._recursive_search_of_rules()
|
||||
]
|
||||
# call localdict_hook
|
||||
localdict = payslip.localdict_hook(localdict)
|
||||
# reset "current_contract" dict
|
||||
baselocaldict["current_contract"] = {}
|
||||
return lines_dict
|
||||
|
||||
def localdict_hook(self, localdict):
|
||||
# This hook is called when the function _get_lines_dict ends the loop
|
||||
# and before its returns. This method by itself don't add any functionality
|
||||
# and is intedend to be inherited to access localdict from other functions.
|
||||
return localdict
|
||||
|
||||
def get_payslip_vals(
|
||||
self, date_from, date_to, employee_id=False, contract_id=False, struct_id=False
|
||||
):
|
||||
# Initial default values for generated payslips
|
||||
employee = self.env["hr.employee"].browse(employee_id)
|
||||
res = {
|
||||
"value": {
|
||||
"line_ids": [],
|
||||
"input_line_ids": [(2, x) for x in self.input_line_ids.ids],
|
||||
"worked_days_line_ids": [(2, x) for x in self.worked_days_line_ids.ids],
|
||||
"name": "",
|
||||
"contract_id": False,
|
||||
"struct_id": False,
|
||||
}
|
||||
}
|
||||
# If we don't have employee or date data, we return.
|
||||
if (not employee_id) or (not date_from) or (not date_to):
|
||||
return res
|
||||
# We check if contract_id is present, if not we fill with the
|
||||
# first contract of the employee. If not contract present, we return.
|
||||
if not self.env.context.get("contract"):
|
||||
contract_ids = employee.contract_id.ids
|
||||
else:
|
||||
if contract_id:
|
||||
contract_ids = [contract_id]
|
||||
else:
|
||||
contract_ids = employee._get_contracts(
|
||||
date_from=date_from, date_to=date_to
|
||||
).ids
|
||||
if not contract_ids:
|
||||
return res
|
||||
contract = self.env["hr.contract"].browse(contract_ids[0])
|
||||
res["value"].update({"contract_id": contract.id})
|
||||
# We check if struct_id is already filled, otherwise we assign the contract struct.
|
||||
# If contract don't have a struct, we return.
|
||||
if struct_id:
|
||||
res["value"].update({"struct_id": struct_id[0]})
|
||||
else:
|
||||
struct = contract.struct_id
|
||||
if not struct:
|
||||
return res
|
||||
res["value"].update({"struct_id": struct.id})
|
||||
# Computation of the salary input and worked_day_lines
|
||||
contracts = self.env["hr.contract"].browse(contract_ids)
|
||||
worked_days_line_ids = self.get_worked_day_lines(contracts, date_from, date_to)
|
||||
input_line_ids = self.get_inputs(contracts, date_from, date_to)
|
||||
res["value"].update(
|
||||
{
|
||||
"worked_days_line_ids": worked_days_line_ids,
|
||||
"input_line_ids": input_line_ids,
|
||||
}
|
||||
)
|
||||
return res
|
||||
|
||||
def _sum_salary_rule_category(self, localdict, category, amount):
|
||||
self.ensure_one()
|
||||
if category.parent_id:
|
||||
localdict = self._sum_salary_rule_category(
|
||||
localdict, category.parent_id, amount
|
||||
)
|
||||
if category.code:
|
||||
localdict["categories"].dict[category.code] = (
|
||||
localdict["categories"].dict.get(category.code, 0) + amount
|
||||
)
|
||||
return localdict
|
||||
|
||||
def _get_employee_contracts(self):
|
||||
contracts = self.env["hr.contract"]
|
||||
for payslip in self:
|
||||
if payslip.contract_id.ids:
|
||||
contracts |= payslip.contract_id
|
||||
else:
|
||||
contracts |= payslip.employee_id._get_contracts(
|
||||
date_from=payslip.date_from, date_to=payslip.date_to
|
||||
)
|
||||
return contracts
|
||||
|
||||
@api.onchange("struct_id")
|
||||
def onchange_struct_id(self):
|
||||
for payslip in self:
|
||||
if not payslip.struct_id:
|
||||
payslip.input_line_ids.unlink()
|
||||
return
|
||||
input_lines = payslip.input_line_ids.browse([])
|
||||
input_line_ids = payslip.get_inputs(
|
||||
payslip._get_employee_contracts(), payslip.date_from, payslip.date_to
|
||||
)
|
||||
for r in input_line_ids:
|
||||
input_lines += input_lines.new(r)
|
||||
payslip.input_line_ids = input_lines
|
||||
|
||||
@api.onchange("date_from", "date_to")
|
||||
def onchange_dates(self):
|
||||
for payslip in self:
|
||||
if not payslip.date_from or not payslip.date_to:
|
||||
return
|
||||
worked_days_lines = payslip.worked_days_line_ids.browse([])
|
||||
worked_days_line_ids = payslip.get_worked_day_lines(
|
||||
payslip._get_employee_contracts(), payslip.date_from, payslip.date_to
|
||||
)
|
||||
for line in worked_days_line_ids:
|
||||
worked_days_lines += worked_days_lines.new(line)
|
||||
payslip.worked_days_line_ids = worked_days_lines
|
||||
|
||||
@api.onchange("employee_id", "date_from", "date_to")
|
||||
def onchange_employee(self):
|
||||
for payslip in self:
|
||||
# Return if required values are not present.
|
||||
if (
|
||||
(not payslip.employee_id)
|
||||
or (not payslip.date_from)
|
||||
or (not payslip.date_to)
|
||||
):
|
||||
continue
|
||||
# Assign contract_id automatically when the user don't selected one.
|
||||
if not payslip.env.context.get("contract") or not payslip.contract_id:
|
||||
contract_ids = payslip._get_employee_contracts().ids
|
||||
if not contract_ids:
|
||||
continue
|
||||
payslip.contract_id = payslip.env["hr.contract"].browse(contract_ids[0])
|
||||
# Assign struct_id automatically when the user don't selected one.
|
||||
if not payslip.struct_id and not payslip.env.context.get("struct_id"):
|
||||
if not payslip.contract_id.struct_id:
|
||||
continue
|
||||
payslip.struct_id = payslip.contract_id.struct_id
|
||||
# Compute payslip name
|
||||
payslip._compute_name()
|
||||
# Call worked_days_lines computation when employee is changed.
|
||||
payslip.onchange_dates()
|
||||
# Call input_lines computation when employee is changed.
|
||||
payslip.onchange_struct_id()
|
||||
# Assign company_id automatically based on employee selected.
|
||||
payslip.company_id = payslip.employee_id.company_id
|
||||
|
||||
def _compute_name(self):
|
||||
for record in self:
|
||||
record.name = _("Salary Slip of %(name)s for %(dt)s") % {
|
||||
"name": record.employee_id.name,
|
||||
"dt": tools.ustr(
|
||||
babel.dates.format_date(
|
||||
date=datetime.combine(record.date_from, time.min),
|
||||
format="MMMM-y",
|
||||
locale=record.env.context.get("lang") or "en_US",
|
||||
)
|
||||
),
|
||||
}
|
||||
|
||||
@api.onchange("contract_id")
|
||||
def onchange_contract(self):
|
||||
if not self.contract_id:
|
||||
self.struct_id = False
|
||||
self.with_context(contract=True).onchange_employee()
|
||||
return
|
||||
|
||||
def get_salary_line_total(self, code):
|
||||
self.ensure_one()
|
||||
line = self.line_ids.filtered(lambda line: line.code == code)
|
||||
if line:
|
||||
return line[0].total
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
def line_sum_where(self, field_name, value, rules, result_rules):
|
||||
"""
|
||||
The method may be used in salary rule code.
|
||||
It will sum the total of the previously computed rules
|
||||
where the given field has the given value.
|
||||
E.g.: total_seq_10 = payslip.line_sum_where("sequence", 10, rules, result_rules)
|
||||
"""
|
||||
return sum(
|
||||
[
|
||||
result_rules.dict[code].dict["total"]
|
||||
for code, rule in rules.dict.items()
|
||||
if getattr(rule, field_name) == value
|
||||
]
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue