Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

View file

@ -0,0 +1,17 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import hr_contract
from . import hr_employee
from . import hr_payroll_structure
from . import hr_salary_rule
from . import hr_salary_rule_category
from . import hr_rule_input
from . import hr_contribution_register
from . import base_browsable
from . import hr_payslip
from . import hr_payslip_line
from . import hr_payslip_input
from . import hr_payslip_worked_days
from . import hr_payslip_run
from . import res_config_settings
from . import hr_rule_parameter

View file

@ -0,0 +1,120 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields
class BaseBrowsableObject(object):
def __init__(self, vals_dict):
self.__dict__["base_fields"] = ["base_fields", "dict"]
self.dict = vals_dict
def __getattr__(self, attr):
return attr in self.dict and self.dict.__getitem__(attr) or 0.0
def __setattr__(self, attr, value):
_fields = self.__dict__["base_fields"]
if attr in _fields:
return super().__setattr__(attr, value)
self.__dict__["dict"][attr] = value
def __str__(self):
return str(self.__dict__)
# These classes are used in the _get_payslip_lines() method
class BrowsableObject(BaseBrowsableObject):
def __init__(self, employee_id, vals_dict, env):
super().__init__(vals_dict)
self.base_fields += ["employee_id", "env"]
self.employee_id = employee_id
self.env = env
class InputLine(BrowsableObject):
"""a class that will be used into the python code, mainly for
usability purposes"""
def sum(self, code, from_date, to_date=None):
if to_date is None:
to_date = fields.Date.today()
self.env.cr.execute(
"""
SELECT sum(amount) as sum
FROM hr_payslip as hp, hr_payslip_input as pi
WHERE hp.employee_id = %s AND hp.state = 'done'
AND hp.date_from >= %s AND hp.date_to <= %s
AND hp.id = pi.payslip_id AND pi.code = %s""",
(self.employee_id, from_date, to_date, code),
)
return self.env.cr.fetchone()[0] or 0.0
class WorkedDays(BrowsableObject):
"""a class that will be used into the python code, mainly for
usability purposes"""
def _sum(self, code, from_date, to_date=None):
if to_date is None:
to_date = fields.Date.today()
self.env.cr.execute(
"""
SELECT sum(number_of_days) as number_of_days,
sum(number_of_hours) as number_of_hours
FROM hr_payslip as hp, hr_payslip_worked_days as pi
WHERE hp.employee_id = %s AND hp.state = 'done'
AND hp.date_from >= %s AND hp.date_to <= %s
AND hp.id = pi.payslip_id AND pi.code = %s""",
(self.employee_id, from_date, to_date, code),
)
return self.env.cr.fetchone()
def sum(self, code, from_date, to_date=None):
res = self._sum(code, from_date, to_date)
return res and res[0] or 0.0
def sum_hours(self, code, from_date, to_date=None):
res = self._sum(code, from_date, to_date)
return res and res[1] or 0.0
class Payslips(BrowsableObject):
"""a class that will be used into the python code, mainly for
usability purposes"""
def sum(self, code, from_date, to_date=None):
if to_date is None:
to_date = fields.Date.today()
self.env.cr.execute(
"""SELECT sum(case when hp.credit_note = False then
(pl.total) else (-pl.total) end)
FROM hr_payslip as hp, hr_payslip_line as pl
WHERE hp.employee_id = %s AND hp.state = 'done'
AND hp.date_from >= %s AND hp.date_to <= %s AND
hp.id = pl.slip_id AND pl.code = %s""",
(self.employee_id, from_date, to_date, code),
)
res = self.env.cr.fetchone()
return res and res[0] or 0.0
def rule_parameter(self, code):
return self.env["hr.rule.parameter"]._get_parameter_from_code(
code, self.dict.date_to
)
def koef_min_rad(self):
# https://stackoverflow.com/questions/765797/convert-timedelta-to-years
year_payslip = self.dict.date_to.year
year_from = self.dict.employee_id.first_contract_date.year
month_payslip = self.dict.date_to.month
month_from = self.dict.employee_id.first_contract_date.month
# 20.09.2006 - 31.03.2023 = 2023-2006 = 17
diff = year_payslip - year_from
# no increment until full year
# month 09 contract start, month 03 current payroll
if (diff > 0) and (month_payslip < month_payslip):
diff = diff - 1
return diff * 0.6

View file

@ -0,0 +1,45 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class HrContract(models.Model):
"""
Employee contract based on the visa, work permits
allows to configure different Salary structure
"""
_inherit = "hr.contract"
_description = "Employee Contract"
struct_id = fields.Many2one("hr.payroll.structure", string="Salary Structure")
schedule_pay = fields.Selection(
[
("monthly", "Monthly"),
("quarterly", "Quarterly"),
("semi-annually", "Semi-annually"),
("annually", "Annually"),
("weekly", "Weekly"),
("bi-weekly", "Bi-weekly"),
("bi-monthly", "Bi-monthly"),
],
string="Scheduled Pay",
index=True,
default="monthly",
help="Defines the frequency of the wage payment.",
)
resource_calendar_id = fields.Many2one(
required=True, help="Employee's working schedule."
)
def get_all_structures(self):
"""
@return: the structures linked to the given contracts, ordered by
hierachy (parent=False first, then first level children and
so on) and without duplicates
"""
structures = self.mapped("struct_id")
if not structures:
return []
# YTI TODO return browse records
return list(set(structures._get_parent_structure().ids))

View file

@ -0,0 +1,20 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class HrContributionRegister(models.Model):
_name = "hr.contribution.register"
_description = "Contribution Register"
company_id = fields.Many2one(
"res.company",
string="Company",
default=lambda self: self.env.company,
)
partner_id = fields.Many2one("res.partner", string="Partner")
name = fields.Char(required=True)
register_line_ids = fields.One2many(
"hr.payslip.line", "register_id", string="Register Line", readonly=True
)
note = fields.Text(string="Description")

View file

@ -0,0 +1,21 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class HrEmployee(models.Model):
_inherit = "hr.employee"
_description = "Employee"
slip_ids = fields.One2many(
"hr.payslip", "employee_id", string="Payslips", readonly=True
)
payslip_count = fields.Integer(
compute="_compute_payslip_count",
string="Payslip Count",
groups="payroll.group_payroll_user",
)
def _compute_payslip_count(self):
for employee in self:
employee.payslip_count = len(employee.slip_ids)

View file

@ -0,0 +1,71 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class HrPayrollStructure(models.Model):
"""
Salary structure used to defined
- Basic
- Allowances
- Deductions
"""
_name = "hr.payroll.structure"
_description = "Salary Structure"
@api.model
def _get_parent(self):
return self.env.ref("hr_payroll.structure_base", False)
name = fields.Char(required=True)
code = fields.Char(string="Reference", required=True)
company_id = fields.Many2one(
"res.company",
string="Company",
required=True,
copy=False,
default=lambda self: self.env.company,
)
note = fields.Text(string="Description")
parent_id = fields.Many2one(
"hr.payroll.structure", string="Parent", default=_get_parent
)
children_ids = fields.One2many(
"hr.payroll.structure", "parent_id", string="Children", copy=True
)
rule_ids = fields.Many2many(
"hr.salary.rule",
"hr_structure_salary_rule_rel",
"struct_id",
"rule_id",
string="Salary Rules",
)
@api.constrains("parent_id")
def _check_parent_id(self):
if not self._check_recursion():
raise ValidationError(_("You cannot create a recursive salary structure."))
@api.returns("self", lambda value: value.id)
def copy(self, default=None):
self.ensure_one()
default = dict(default or {}, code=_("%s (copy)") % self.code)
return super(HrPayrollStructure, self).copy(default)
def get_all_rules(self):
"""
@return: returns a list of tuple (id, sequence) of rules that are maybe
to apply
"""
all_rules = []
for struct in self:
all_rules += struct.rule_ids._recursive_search_of_rules()
return all_rules
def _get_parent_structure(self):
parent = self.mapped("parent_id")
if parent:
parent = parent._get_parent_structure()
return parent + self

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class HrPayslipInput(models.Model):
_name = "hr.payslip.input"
_description = "Payslip Input"
_order = "payslip_id, sequence"
name = fields.Char(string="Description", required=True)
payslip_id = fields.Many2one(
"hr.payslip", string="Pay Slip", required=True, ondelete="cascade", index=True
)
sequence = fields.Integer(required=True, index=True, default=10)
code = fields.Char(
required=True, help="The code that can be used in the salary rules"
)
amount = fields.Float(
help="It is used in computation. For e.g. A rule for sales having "
"1% commission of basic salary for per product can defined in "
"expression like result = inputs.SALEURO.amount * contract.wage*0.01."
)
amount_qty = fields.Float(
"Amount Quantity", help="It can be used in computation for other inputs"
)
contract_id = fields.Many2one(
"hr.contract",
string="Contract",
required=True,
help="The contract for which applied this input",
)

View file

@ -0,0 +1,103 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
class HrPayslipLine(models.Model):
_name = "hr.payslip.line"
_inherit = "hr.salary.rule"
_description = "Payslip Line"
_order = "contract_date_start, contract_id, sequence"
slip_id = fields.Many2one(
"hr.payslip", string="Pay Slip", required=True, ondelete="cascade"
)
date_from = fields.Date("Date From", related="slip_id.date_from", store=True)
payslip_run_id = fields.Many2one(
"hr.payslip.run", related="slip_id.payslip_run_id", string="Payslip Batch"
)
child_ids = fields.One2many(
"hr.payslip.line", "parent_line_id", string="Child Payslip Lines"
)
parent_line_id = fields.Many2one(
"hr.payslip.line",
string="Parent Payslip Line",
compute="_compute_parent_line_id",
store=True,
)
salary_rule_id = fields.Many2one("hr.salary.rule", string="Rule", required=True)
employee_id = fields.Many2one("hr.employee", string="Employee", required=True)
contract_id = fields.Many2one(
"hr.contract", string="Contract", required=True, index=True
)
contract_date_start = fields.Date("Contract Date Start", related="contract_id.date_start", index=True, store=True)
rate = fields.Float(string="Rate (%)", digits="Payroll Rate", default=100.0)
amount = fields.Float(digits="Payroll Amount")
quantity = fields.Float(digits="Payroll", default=1.0)
total = fields.Float(
compute="_compute_total",
string="Total",
digits="Payroll",
store=True,
)
#@api.model
#def search(self, args, offset=0, limit=None, order=None, count=False):
# return super(HrPayslipLine, self).search(args, offset=offset, limit=limit, order=order, count=count)
#@api.constrains('contract_id')
#def validate_contract_id(self):
# for record in self:
# if not record.contract_id:
# raise ValidateError(_("Please set contract for payslip!"))
def validate_rounding(self):
for record in self:
if record.rounding <= 0:
raise ValidationError(_("Please set a strictly positive rounding value."))
@api.depends("parent_rule_id", "contract_id", "slip_id")
def _compute_parent_line_id(self):
for line in self:
if line.parent_rule_id:
parent_line = line.slip_id.line_ids.filtered(
lambda l: l.salary_rule_id == line.parent_rule_id
and l.contract_id == line.contract_id
and l.slip_id == line.slip_id
)
if parent_line and len(parent_line) > 1:
raise UserError(
_("Recursion error. Only one line should be parent of %s")
% line.parent_rule_id.name
)
line.parent_line_id = (
parent_line[0].id if len(parent_line) == 1 else False
)
else:
line.parent_line_id = False
@api.depends("quantity", "amount", "rate")
def _compute_total(self):
for line in self:
line.total = float(line.quantity) * line.amount * line.rate / 100
@api.model_create_multi
def create(self, vals_list):
for values in vals_list:
if "employee_id" not in values or "contract_id" not in values:
payslip = self.env["hr.payslip"].browse(values.get("slip_id"))
values["employee_id"] = (
values.get("employee_id") or payslip.employee_id.id
)
values["contract_id"] = (
values.get("contract_id")
or payslip.contract_id
and payslip.contract_id.id
)
if not values["contract_id"]:
raise UserError(
_("You must set a contract to create a payslip line.")
)
return super(HrPayslipLine, self).create(vals_list)

View file

@ -0,0 +1,71 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from dateutil.relativedelta import relativedelta
from odoo import fields, models
class HrPayslipRun(models.Model):
_name = "hr.payslip.run"
_inherit = ["mail.thread", "mail.activity.mixin"]
_description = "Payslip Batches"
_order = "id desc"
name = fields.Char(
required=True, readonly=True, states={"draft": [("readonly", False)]}
)
slip_ids = fields.One2many(
"hr.payslip",
"payslip_run_id",
string="Payslips",
readonly=True,
states={"draft": [("readonly", False)]},
)
state = fields.Selection(
[("draft", "Draft"), ("close", "Close")],
string="Status",
index=True,
readonly=True,
copy=False,
tracking=1,
default="draft",
)
date_start = fields.Date(
string="Date From",
required=True,
readonly=True,
states={"draft": [("readonly", False)]},
default=lambda self: fields.Date.today().replace(day=1),
)
date_end = fields.Date(
string="Date To",
required=True,
readonly=True,
states={"draft": [("readonly", False)]},
default=lambda self: fields.Date.today().replace(day=1)
+ relativedelta(months=+1, day=1, days=-1),
)
credit_note = fields.Boolean(
string="Credit Note",
readonly=True,
states={"draft": [("readonly", False)]},
help="If its checked, indicates that all payslips generated from here "
"are refund payslips.",
)
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 batch, "
"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",
)
def draft_payslip_run(self):
return self.write({"state": "draft"})
def close_payslip_run(self):
return self.write({"state": "close"})

View file

@ -0,0 +1,26 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class HrPayslipWorkedDays(models.Model):
_name = "hr.payslip.worked_days"
_description = "Payslip Worked Days"
_order = "payslip_id, sequence"
name = fields.Char(string="Description", required=True)
payslip_id = fields.Many2one(
"hr.payslip", string="Pay Slip", required=True, ondelete="cascade", index=True
)
sequence = fields.Integer(required=True, index=True, default=10)
code = fields.Char(
required=True, help="The code that can be used in the salary rules"
)
number_of_days = fields.Float(string="Number of Days")
number_of_hours = fields.Float(string="Number of Hours")
contract_id = fields.Many2one(
"hr.contract",
string="Contract",
required=True,
help="The contract for which applied this input",
)

View file

@ -0,0 +1,16 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class HrRuleInput(models.Model):
_name = "hr.rule.input"
_description = "Salary Rule Input"
name = fields.Char(string="Description", required=True)
code = fields.Char(
required=True, help="The code that can be used in the salary rules"
)
input_id = fields.Many2one(
"hr.salary.rule", string="Salary Rule Input", required=True
)

View file

@ -0,0 +1,74 @@
import ast
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class HrSalaryRuleParameterValue(models.Model):
_name = "hr.rule.parameter.value"
_description = "Salary Rule Parameter Value"
_order = "date_from desc"
rule_parameter_id = fields.Many2one(
"hr.rule.parameter", required=True, ondelete="cascade"
)
code = fields.Char(related="rule_parameter_id.code", store=True, readonly=True)
date_from = fields.Date(string="Date From", required=True)
parameter_value = fields.Text(help="Python Code")
country_id = fields.Many2one(related="rule_parameter_id.country_id")
company_id = fields.Many2one(related="rule_parameter_id.company_id")
_sql_constraints = [
(
"_unique",
"unique (rule_parameter_id, date_from)",
"Two rules parameters with the same code cannot start the same day",
),
]
class HrSalaryRuleParameter(models.Model):
_name = "hr.rule.parameter"
_description = "Salary Rule Parameter"
name = fields.Char(required=True)
code = fields.Char(required=True)
description = fields.Text("Description")
country_id = fields.Many2one(
"res.country",
string="Country",
default=lambda self: self.env.company.country_id,
)
parameter_version_ids = fields.One2many(
"hr.rule.parameter.value", "rule_parameter_id", string="Versions"
)
company_id = fields.Many2one(
"res.company", "Company", required=True, default=lambda self: self.env.company
)
@api.model
def _get_parameter_from_code(self, code, date=None):
if not date:
date = fields.Date.today()
rule_parameter = self.env["hr.rule.parameter.value"].search(
[
("code", "=", code),
("date_from", "<=", date),
("company_id", "=", self.env.company.id),
],
limit=1,
)
if rule_parameter:
return ast.literal_eval(rule_parameter.parameter_value)
else:
raise UserError(
_("No rule parameter with code '%s' was found for %s ") % (code, date)
)
_sql_constraints = [
(
"_unique",
"unique (code, company_id)",
"Two rule parameters cannot have the same code.",
),
]

View file

@ -0,0 +1,290 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools.safe_eval import safe_eval
class HrSalaryRule(models.Model):
_name = "hr.salary.rule"
_order = "sequence, id"
_description = "Salary Rule"
name = fields.Char(required=True, translate=True)
code = fields.Char(
required=True,
help="The code of salary rules can be used as reference in computation "
"of other rules. In that case, it is case sensitive.",
)
sequence = fields.Integer(
required=True, index=True, default=5, help="Use to arrange calculation sequence"
)
quantity = fields.Char(
default="1.0",
help="It is used in computation for percentage and fixed amount. "
"For e.g. A rule for Meal Voucher having fixed amount of "
"1€ per worked day can have its quantity defined in expression "
"like worked_days.WORK100.number_of_days.",
)
category_id = fields.Many2one(
"hr.salary.rule.category", string="Category", required=True
)
active = fields.Boolean(
default=True,
help="If the active field is set to false, it will allow you to hide"
" the salary rule without removing it.",
)
appears_on_payslip = fields.Boolean(
string="Appears on Payslip",
default=True,
help="Used to display the salary rule on payslip.",
)
parent_rule_id = fields.Many2one(
"hr.salary.rule", string="Parent Salary Rule", index=True
)
company_id = fields.Many2one(
"res.company",
string="Company",
default=lambda self: self.env.company,
)
condition_select = fields.Selection(
[("none", "Always True"), ("range", "Range"), ("python", "Python Expression")],
string="Condition Based on",
default="none",
required=True,
)
condition_range = fields.Char(
string="Range Based on",
default="contract.wage",
help="This will be used to compute the % fields values; in general it "
"is on basic, but you can also use categories code fields in "
"lowercase as a variable names (hra, ma, lta, etc.) and the "
"variable basic.",
)
condition_python = fields.Text(
string="Python Condition",
required=True,
default="""
# Available variables:
#----------------------
# payslip: object containing the payslips
# payslip.rule_parameter(code): get the value for the rule parameter specified.
# By default it gets the code for payslip date.
# employee: hr.employee object
# contract: hr.contract object
# rules: object containing the rules code (previously computed)
# categories: object containing the computed salary rule categories
# (sum of amount of all rules belonging to that category).
# worked_days: object containing the computed worked days
# inputs: object containing the computed inputs
# payroll: object containing miscellaneous values related to payroll
# current_contract: object with values calculated from the current contract
# result_rules: object with a dict of qty, rate, amount an total of calculated rules
# Available compute variables:
#-------------------------------
# result: returned value have to be set in the variable 'result'
# Example:
#-------------------------------
result = rules.NET > categories.NET * 0.10
""",
help="Applied this rule for calculation if condition is true. You can "
"specify condition like basic > 1000.",
)
condition_range_min = fields.Float(
string="Minimum Range", help="The minimum amount, applied for this rule."
)
condition_range_max = fields.Float(
string="Maximum Range", help="The maximum amount, applied for this rule."
)
amount_select = fields.Selection(
[
("percentage", "Percentage (%)"),
("fix", "Fixed Amount"),
("code", "Python Code"),
],
string="Amount Type",
index=True,
required=True,
default="fix",
help="The computation method for the rule amount.",
)
amount_fix = fields.Float(string="Fixed Amount", digits="Payroll")
amount_percentage = fields.Float(
string="Percentage (%)",
digits="Payroll Rate",
help="For example, enter 50.0 to apply a percentage of 50%",
)
amount_python_compute = fields.Text(
string="Python Code",
default="""
# Available variables:
#-------------------------------
# payslip: object containing the payslips
# employee: hr.employee object
# contract: hr.contract object
# rules: object containing the rules code (previously computed)
# categories: object containing the computed salary rule categories
# (sum of amount of all rules belonging to that category).
# worked_days: object containing the computed worked days.
# inputs: object containing the computed inputs.
# payroll: object containing miscellaneous values related to payroll
# current_contract: object with values calculated from the current contract
# result_rules: object with a dict of qty, rate, amount an total of calculated rules
# Available compute variables:
#-------------------------------
# result: returned value have to be set in the variable 'result'
# result_rate: the rate that will be applied to "result".
# result_qty: the quantity of units that will be multiplied to "result".
# result_name: if this variable is computed, it will contain the name of the line.
# Example:
#-------------------------------
result = contract.wage * 0.10
""",
)
amount_percentage_base = fields.Char(
string="Percentage based on", help="result will be affected to a variable"
)
child_ids = fields.One2many(
"hr.salary.rule", "parent_rule_id", string="Child Salary Rule", copy=True
)
register_id = fields.Many2one(
"hr.contribution.register",
string="Contribution Register",
help="Eventual third party involved in the salary payment of the employees.",
)
input_ids = fields.One2many("hr.rule.input", "input_id", string="Inputs", copy=True)
note = fields.Text(string="Description")
@api.constrains("parent_rule_id")
def _check_parent_rule_id(self):
if not self._check_recursion(parent="parent_rule_id"):
raise ValidationError(
_("Error! You cannot create recursive hierarchy of Salary Rules.")
)
def _recursive_search_of_rules(self):
"""
@return: returns a list of tuple (id, sequence) which are all the
children of the passed rule_ids
"""
children_rules = []
for rule in self.filtered(lambda rule: rule.child_ids):
children_rules += rule.child_ids._recursive_search_of_rules()
return [(rule.id, rule.sequence) for rule in self] + children_rules
# TODO should add some checks on the type of result (should be float)
def _compute_rule(self, localdict):
"""
:param localdict: dictionary containing the environement in which to
compute the rule
:return: returns a tuple build as the base/amount computed, the quantity
and the rate
:rtype: (float, float, float)
"""
self.ensure_one()
if self.amount_select == "fix":
try:
return (
self.amount_fix,
float(safe_eval(self.quantity, localdict)),
100.0,
False, # result_name is always False if not computed by python.
)
except Exception:
raise UserError(
_("Wrong quantity defined for salary rule %s (%s).")
% (self.name, self.code)
)
elif self.amount_select == "percentage":
try:
return (
float(safe_eval(self.amount_percentage_base, localdict)),
float(safe_eval(self.quantity, localdict)),
self.amount_percentage,
False, # result_name is always False if not computed by python.
)
except Exception:
raise UserError(
_(
"Wrong percentage base or quantity defined for salary "
"rule %s (%s)."
)
% (self.name, self.code)
)
else:
try:
safe_eval(
self.amount_python_compute, localdict, mode="exec", nocopy=True
)
result_qty = 1.0
result_rate = 100.0
result_name = False
if "result_name" in localdict:
result_name = localdict["result_name"]
if "result_qty" in localdict:
result_qty = localdict["result_qty"]
if "result_rate" in localdict:
result_rate = localdict["result_rate"]
return (
float(localdict["result"]),
float(result_qty),
float(result_rate),
result_name,
)
except Exception as ex:
raise UserError(
_(
"""
Wrong python code defined for salary rule %s (%s).
Here is the error received:
%s
"""
)
% (self.name, self.code, repr(ex))
)
def _satisfy_condition(self, localdict):
"""
@param contract_id: id of hr.contract to be tested
@return: returns True if the given rule match the condition for the
given contract. Return False otherwise.
"""
self.ensure_one()
if self.condition_select == "none":
return True
elif self.condition_select == "range":
try:
result = safe_eval(self.condition_range, localdict)
return (
self.condition_range_min <= result <= self.condition_range_max
or False
)
except Exception:
raise UserError(
_("Wrong range condition defined for salary rule %s (%s).")
% (self.name, self.code)
)
else: # python code
try:
safe_eval(self.condition_python, localdict, mode="exec", nocopy=True)
return "result" in localdict and localdict["result"] or False
except Exception as ex:
raise UserError(
_(
"""
Wrong python condition defined for salary rule %s (%s).
Here is the error received:
%s
"""
)
% (self.name, self.code, repr(ex))
)

View file

@ -0,0 +1,37 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class HrSalaryRuleCategory(models.Model):
_name = "hr.salary.rule.category"
_description = "Salary Rule Category"
name = fields.Char(required=True, translate=True)
code = fields.Char(required=True)
parent_id = fields.Many2one(
"hr.salary.rule.category",
string="Parent",
help="Linking a salary category to its parent is used only for the "
"reporting purpose.",
)
children_ids = fields.One2many(
"hr.salary.rule.category", "parent_id", string="Children"
)
note = fields.Text(string="Description")
company_id = fields.Many2one(
"res.company",
string="Company",
default=lambda self: self.env.company,
)
@api.constrains("parent_id")
def _check_parent_id(self):
if not self._check_recursion():
raise ValidationError(
_(
"Error! You cannot create recursive hierarchy of Salary "
"Rule Category."
)
)

View file

@ -0,0 +1,20 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
module_payroll_account = fields.Boolean(string="Payroll Accounting")
leaves_positive = fields.Boolean(
config_parameter="payroll.leaves_positive",
string="Leaves with positive values",
help="Values for leaves (days and hours fields) "
"should be positive instead of negative.",
)
allow_cancel_payslips = fields.Boolean(
config_parameter="payroll.allow_cancel_payslips",
string="Allow cancel confirmed payslips",
help="If enabled, it allow the user to cancel confirmed payslips. Default: Not Enabled",
)