mirror of
https://github.com/bringout/oca-technical.git
synced 2026-04-18 08:12:03 +02:00
Initial commit: OCA Technical packages (595 packages)
This commit is contained in:
commit
2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions
17
odoo-bringout-oca-hack-payroll/payroll/models/__init__.py
Normal file
17
odoo-bringout-oca-hack-payroll/payroll/models/__init__.py
Normal 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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
120
odoo-bringout-oca-hack-payroll/payroll/models/base_browsable.py
Normal file
120
odoo-bringout-oca-hack-payroll/payroll/models/base_browsable.py
Normal 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
|
||||
45
odoo-bringout-oca-hack-payroll/payroll/models/hr_contract.py
Normal file
45
odoo-bringout-oca-hack-payroll/payroll/models/hr_contract.py
Normal 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))
|
||||
|
|
@ -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")
|
||||
21
odoo-bringout-oca-hack-payroll/payroll/models/hr_employee.py
Normal file
21
odoo-bringout-oca-hack-payroll/payroll/models/hr_employee.py
Normal 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)
|
||||
|
|
@ -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
|
||||
1186
odoo-bringout-oca-hack-payroll/payroll/models/hr_payslip.py
Normal file
1186
odoo-bringout-oca-hack-payroll/payroll/models/hr_payslip.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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",
|
||||
)
|
||||
103
odoo-bringout-oca-hack-payroll/payroll/models/hr_payslip_line.py
Normal file
103
odoo-bringout-oca-hack-payroll/payroll/models/hr_payslip_line.py
Normal 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)
|
||||
|
|
@ -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"})
|
||||
|
|
@ -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",
|
||||
)
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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.",
|
||||
),
|
||||
]
|
||||
290
odoo-bringout-oca-hack-payroll/payroll/models/hr_salary_rule.py
Normal file
290
odoo-bringout-oca-hack-payroll/payroll/models/hr_salary_rule.py
Normal 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))
|
||||
)
|
||||
|
|
@ -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."
|
||||
)
|
||||
)
|
||||
|
|
@ -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",
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue