From 7b12af0cebf332cae0db9eefd3f7f43c9bd0d93a Mon Sep 17 00:00:00 2001 From: Daniel Reis Date: Mon, 16 Dec 2024 09:56:16 +0000 Subject: [PATCH 1/4] [ADD] hr_master_data --- hr_master_data/README.rst | 106 +++++ hr_master_data/__init__.py | 1 + hr_master_data/__manifest__.py | 23 + hr_master_data/data/demo_data.xml | 90 ++++ hr_master_data/data/hr_master_data.xml | 49 ++ hr_master_data/models/__init__.py | 6 + hr_master_data/models/hr_contract.py | 42 ++ hr_master_data/models/hr_data_group.py | 13 + hr_master_data/models/hr_data_type.py | 21 + hr_master_data/models/hr_data_type_value.py | 15 + hr_master_data/models/hr_data_value_reason.py | 13 + .../models/hr_employee_data_value.py | 42 ++ hr_master_data/pyproject.toml | 3 + hr_master_data/readme/CONFIGURE.md | 7 + hr_master_data/readme/CONTRIBUTORS.md | 1 + hr_master_data/readme/DESCRIPTION.md | 6 + hr_master_data/readme/USAGE.md | 2 + .../security/hr_payroll_security.xml | 11 + hr_master_data/security/ir.model.access.csv | 6 + hr_master_data/static/description/index.html | 447 ++++++++++++++++++ .../views/hr_contract_value_views.xml | 140 ++++++ hr_master_data/views/hr_contract_views.xml | 21 + hr_master_data/views/hr_data_value_views.xml | 93 ++++ 23 files changed, 1158 insertions(+) create mode 100644 hr_master_data/README.rst create mode 100644 hr_master_data/__init__.py create mode 100644 hr_master_data/__manifest__.py create mode 100644 hr_master_data/data/demo_data.xml create mode 100644 hr_master_data/data/hr_master_data.xml create mode 100644 hr_master_data/models/__init__.py create mode 100644 hr_master_data/models/hr_contract.py create mode 100644 hr_master_data/models/hr_data_group.py create mode 100644 hr_master_data/models/hr_data_type.py create mode 100644 hr_master_data/models/hr_data_type_value.py create mode 100644 hr_master_data/models/hr_data_value_reason.py create mode 100644 hr_master_data/models/hr_employee_data_value.py create mode 100644 hr_master_data/pyproject.toml create mode 100644 hr_master_data/readme/CONFIGURE.md create mode 100644 hr_master_data/readme/CONTRIBUTORS.md create mode 100644 hr_master_data/readme/DESCRIPTION.md create mode 100644 hr_master_data/readme/USAGE.md create mode 100644 hr_master_data/security/hr_payroll_security.xml create mode 100644 hr_master_data/security/ir.model.access.csv create mode 100644 hr_master_data/static/description/index.html create mode 100644 hr_master_data/views/hr_contract_value_views.xml create mode 100644 hr_master_data/views/hr_contract_views.xml create mode 100644 hr_master_data/views/hr_data_value_views.xml diff --git a/hr_master_data/README.rst b/hr_master_data/README.rst new file mode 100644 index 000000000..9a6fe5148 --- /dev/null +++ b/hr_master_data/README.rst @@ -0,0 +1,106 @@ +============================ +Employee Master Data History +============================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:29a588be2e853bcfaa92f2f2c0c3d43b2e9f769097eec5f550fc44f437d19bcd + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpayroll-lightgray.png?logo=github + :target: https://github.com/OCA/payroll/tree/18.0/hr_master_data + :alt: OCA/payroll +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/payroll-18-0/payroll-18-0-hr_master_data + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/payroll&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Historical employee master data management. + +Configure the relevant master data attributes, and track their change +along time. For example, professional details (Profession, Category, Pay +Grade), tax information (Social Security Status, Income Tax Status) or +even pay related (Is Time Schedule Exempt, Base Salary, etc.). + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +On Employees / Configuration you can: + +- "Master Data Types": configure the attributes to track, that can be + organized in groups. An attribute value can be a selection from a + list, a number, or a boolean flag. +- "Master Data Change Reasons": configure the differernt reasons that + can be used to track why a history value has changed. + +Usage +===== + +On Employee Contracts, click on the "Master Data" snart button to open +the list of employee master data records. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Daniel Reis + +Contributors +------------ + +- Daniel Reis <> + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-dreispt| image:: https://github.com/dreispt.png?size=40px + :target: https://github.com/dreispt + :alt: dreispt + +Current `maintainer `__: + +|maintainer-dreispt| + +This module is part of the `OCA/payroll `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_master_data/__init__.py b/hr_master_data/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/hr_master_data/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/hr_master_data/__manifest__.py b/hr_master_data/__manifest__.py new file mode 100644 index 000000000..774e33091 --- /dev/null +++ b/hr_master_data/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +{ + "name": "Employee Master Data History", + "summary": "Configure master data attributes and track their changes history.", + "version": "18.0.1.0.0", + "category": "Payroll", + "website": "https://github.com/OCA/payroll", + "license": "LGPL-3", + "author": "Daniel Reis, Odoo Community Association (OCA)", + "depends": ["hr_contract"], + "data": [ + "security/hr_payroll_security.xml", + "security/ir.model.access.csv", + "data/hr_master_data.xml", + "views/hr_data_value_views.xml", + "views/hr_contract_value_views.xml", + "views/hr_contract_views.xml", + ], + "demo": ["data/demo_data.xml"], + "installable": True, + "maintainers": ["dreispt"], +} diff --git a/hr_master_data/data/demo_data.xml b/hr_master_data/data/demo_data.xml new file mode 100644 index 000000000..0f0958a81 --- /dev/null +++ b/hr_master_data/data/demo_data.xml @@ -0,0 +1,90 @@ + + + + + General Labor Law + + + + Collective Contract + + + + Category A + + + + Category B + + + + + Grade 1 + + + + Grade 2 + + + + + SS Position 1 + + + + SS Position 2 + + + + + Tax Position 1 + + + + Tax Position 2 + + + + + + + + 2019-01-01 + + + + + + 2019-01-01 + + + + + + 2019-01-01 + + + + + + 2019-01-01 + + + + + + 2019-01-01 + + + + + 3000.00 + 2019-01-01 + + + + + True + 2019-01-01 + + diff --git a/hr_master_data/data/hr_master_data.xml b/hr_master_data/data/hr_master_data.xml new file mode 100644 index 000000000..e95d58dab --- /dev/null +++ b/hr_master_data/data/hr_master_data.xml @@ -0,0 +1,49 @@ + + + + Professional Data + + + Tax Positions + + + Salary Data + + + + + Collective Contract + id + + + + Professional Category + id + + + + Pay Grade + id + + + + Social Security Position + id + + + + Income Tax Position + id + + + + Base Salary + float + + + + Time Schedule Exempt + bool + + + diff --git a/hr_master_data/models/__init__.py b/hr_master_data/models/__init__.py new file mode 100644 index 000000000..d19c2941b --- /dev/null +++ b/hr_master_data/models/__init__.py @@ -0,0 +1,6 @@ +from . import hr_contract +from . import hr_data_group +from . import hr_data_type +from . import hr_data_type_value +from . import hr_data_value_reason +from . import hr_employee_data_value diff --git a/hr_master_data/models/hr_contract.py b/hr_master_data/models/hr_contract.py new file mode 100644 index 000000000..32926141d --- /dev/null +++ b/hr_master_data/models/hr_contract.py @@ -0,0 +1,42 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import api, fields, models + + +class HrContract(models.Model): + _inherit = "hr.contract" + + @api.depends_context("active_date") + @api.depends("contract_values_ids") + def _compute_active_values_ids(self): + "Returns the active values for the contract at a context given date" + active_date = self.env.context.get("active_date") + for contract in self: + contract.active_values_ids = contract.contract_values_ids.filtered( + lambda x: not active_date + or ( + (not x.date_start or x.date_start <= active_date) + and (not x.date_end or x.date_end >= active_date) + ) + ) + + contract_values_ids = fields.One2many( + comodel_name="hr.employee.data.value", + inverse_name="contract_id", + ) + active_values_ids = fields.One2many( + comodel_name="hr.employee.data.value", + compute="_compute_active_values_ids", + ) + + def action_open_master_data(self): + self.ensure_one() + xmlid = "hr_master_data.action_hr_employee_data_value" + action = self.env["ir.actions.actions"]._for_xml_id(xmlid) + action.update( + { + "domain": [("contract_id", "=", self.id)], + "context": {"default_contract_id": self.id}, + } + ) + return action diff --git a/hr_master_data/models/hr_data_group.py b/hr_master_data/models/hr_data_group.py new file mode 100644 index 000000000..b79702f74 --- /dev/null +++ b/hr_master_data/models/hr_data_group.py @@ -0,0 +1,13 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import fields, models + + +class HrDataGroup(models.Model): + _name = "hr.data.group" + _description = "Master Data Group" + _order = "code,name" + + name = fields.Char() + code = fields.Char() + active = fields.Boolean() diff --git a/hr_master_data/models/hr_data_type.py b/hr_master_data/models/hr_data_type.py new file mode 100644 index 000000000..1afc6e95a --- /dev/null +++ b/hr_master_data/models/hr_data_type.py @@ -0,0 +1,21 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import fields, models + + +class HrDataType(models.Model): + _name = "hr.data.type" + _description = "Master Data Type" + _order = "code,name" + + name = fields.Char(required=True) + code = fields.Char() + value_type = fields.Selection( + selection=[("bool", "Yes/No"), ("float", "Numeric"), ("id", "Selection Value")], + required=True, + ) + group_id = fields.Many2one(comodel_name="hr.data.group") + active = fields.Boolean(default=True) + values_ids = fields.One2many( + comodel_name="hr.data.type.value", inverse_name="type_id" + ) diff --git a/hr_master_data/models/hr_data_type_value.py b/hr_master_data/models/hr_data_type_value.py new file mode 100644 index 000000000..c3e8adbbf --- /dev/null +++ b/hr_master_data/models/hr_data_type_value.py @@ -0,0 +1,15 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import fields, models + + +class HrDataTypeValue(models.Model): + _name = "hr.data.type.value" + _description = "Master Data Value" + _order = "sequence,name" + + name = fields.Char() + code = fields.Char() + sequence = fields.Integer() + type_id = fields.Many2one(comodel_name="hr.data.type") + active = fields.Boolean(default=True) diff --git a/hr_master_data/models/hr_data_value_reason.py b/hr_master_data/models/hr_data_value_reason.py new file mode 100644 index 000000000..c7a844955 --- /dev/null +++ b/hr_master_data/models/hr_data_value_reason.py @@ -0,0 +1,13 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import fields, models + + +class HrDataValueReason(models.Model): + _name = "hr.data.value.reason" + _description = "Master Data Change Reason" + _order = "code,name" + + name = fields.Char() + code = fields.Char() + active = fields.Boolean(default=True) diff --git a/hr_master_data/models/hr_employee_data_value.py b/hr_master_data/models/hr_employee_data_value.py new file mode 100644 index 000000000..776c0a6f5 --- /dev/null +++ b/hr_master_data/models/hr_employee_data_value.py @@ -0,0 +1,42 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import fields, models + + +class HrEmployeeDataValue(models.Model): + _name = "hr.employee.data.value" + _description = "Employee Master Data Value" + _order = "date_start desc" + + employee_id = fields.Many2one( + comodel_name="hr.employee", + related="contract_id.employee_id", + store=True, + readonly=False, + ) + contract_id = fields.Many2one(comodel_name="hr.contract") + company_id = fields.Many2one( + comodel_name="res.company", related="contract_id.company_id", store=True + ) + date_start = fields.Date(required=True) + date_end = fields.Date() + type_id = fields.Many2one(comodel_name="hr.data.type", required=True) + group_id = fields.Many2one(related="type_id.group_id", store=True) + value_type = fields.Selection(related="type_id.value_type") + value_bool = fields.Boolean(string="Yes/No") + value_float = fields.Float(string="Number") + value_id = fields.Many2one( + comodel_name="hr.data.type.value", + domain="[('type_id', '=', type_id)]", + ) + reason_id = fields.Many2one(comodel_name="hr.data.value.reason") + note = fields.Text() + + def get_value(self): + self.ensure_one() + if self.value_type == "bool": + return self.value_bool + elif self.value_type == "float": + return self.value_float + elif self.value_type == "id": + return self.value_id.id diff --git a/hr_master_data/pyproject.toml b/hr_master_data/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/hr_master_data/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/hr_master_data/readme/CONFIGURE.md b/hr_master_data/readme/CONFIGURE.md new file mode 100644 index 000000000..f718853a3 --- /dev/null +++ b/hr_master_data/readme/CONFIGURE.md @@ -0,0 +1,7 @@ +On Employees / Configuration you can: + +- "Master Data Types": configure the attributes to track, that can be + organized in groups. An attribute value can be a selection from a + list, a number, or a boolean flag. +- "Master Data Change Reasons": configure the differernt reasons that + can be used to track why a history value has changed. diff --git a/hr_master_data/readme/CONTRIBUTORS.md b/hr_master_data/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..9104dbede --- /dev/null +++ b/hr_master_data/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Daniel Reis \<\<\>\> diff --git a/hr_master_data/readme/DESCRIPTION.md b/hr_master_data/readme/DESCRIPTION.md new file mode 100644 index 000000000..358053a0f --- /dev/null +++ b/hr_master_data/readme/DESCRIPTION.md @@ -0,0 +1,6 @@ +Historical employee master data management. + +Configure the relevant master data attributes, and track their change +along time. For example, professional details (Profession, Category, Pay +Grade), tax information (Social Security Status, Income Tax Status) or +even pay related (Is Time Schedule Exempt, Base Salary, etc.). diff --git a/hr_master_data/readme/USAGE.md b/hr_master_data/readme/USAGE.md new file mode 100644 index 000000000..0f43d31b0 --- /dev/null +++ b/hr_master_data/readme/USAGE.md @@ -0,0 +1,2 @@ +On Employee Contracts, click on the "Master Data" snart button to open +the list of employee master data records. diff --git a/hr_master_data/security/hr_payroll_security.xml b/hr_master_data/security/hr_payroll_security.xml new file mode 100644 index 000000000..a5c46dd9e --- /dev/null +++ b/hr_master_data/security/hr_payroll_security.xml @@ -0,0 +1,11 @@ + + + + Employee Master Data Values: multi-company + + + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + + diff --git a/hr_master_data/security/ir.model.access.csv b/hr_master_data/security/ir.model.access.csv new file mode 100644 index 000000000..ed5dd7a81 --- /dev/null +++ b/hr_master_data/security/ir.model.access.csv @@ -0,0 +1,6 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_hr_data_group,access_hr_data_group,hr_master_data.model_hr_data_group,hr.group_hr_user,1,1,1,1 +access_hr_data_type,access_hr_data_type,hr_master_data.model_hr_data_type,hr.group_hr_user,1,1,1,1 +access_hr_data_type_value,access_hr_data_type_value,hr_master_data.model_hr_data_type_value,hr.group_hr_user,1,1,1,1 +access_hr_data_value_reason,access_hr_data_value_reason,hr_master_data.model_hr_data_value_reason,hr.group_hr_user,1,1,1,1 +access_hr_employee_data_value,access_hr_employee_data_value,hr_master_data.model_hr_employee_data_value,hr.group_hr_user,1,1,1,1 diff --git a/hr_master_data/static/description/index.html b/hr_master_data/static/description/index.html new file mode 100644 index 000000000..089caad80 --- /dev/null +++ b/hr_master_data/static/description/index.html @@ -0,0 +1,447 @@ + + + + + +Employee Master Data History + + + +
+

Employee Master Data History

+ + +

Beta License: LGPL-3 OCA/payroll Translate me on Weblate Try me on Runboat

+

Historical employee master data management.

+

Configure the relevant master data attributes, and track their change +along time. For example, professional details (Profession, Category, Pay +Grade), tax information (Social Security Status, Income Tax Status) or +even pay related (Is Time Schedule Exempt, Base Salary, etc.).

+

Table of contents

+ +
+

Configuration

+

On Employees / Configuration you can:

+
    +
  • “Master Data Types”: configure the attributes to track, that can be +organized in groups. An attribute value can be a selection from a +list, a number, or a boolean flag.
  • +
  • “Master Data Change Reasons”: configure the differernt reasons that +can be used to track why a history value has changed.
  • +
+
+
+

Usage

+

On Employee Contracts, click on the “Master Data” snart button to open +the list of employee master data records.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Daniel Reis
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

dreispt

+

This module is part of the OCA/payroll project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/hr_master_data/views/hr_contract_value_views.xml b/hr_master_data/views/hr_contract_value_views.xml new file mode 100644 index 000000000..296bfd9a9 --- /dev/null +++ b/hr_master_data/views/hr_contract_value_views.xml @@ -0,0 +1,140 @@ + + + hr.data.value.reason + + + + + + + + + + + hr.employee.data.value + + + + + + + + + + + + + + + + + + hr.employee.data.value + + + + + + + + + + + + + + + + + + hr.employee.data.value + +
+ +
+

+ +

+

+ +

+
+ + + + + + + + + + + + + + + +
+
+
+
+ + + + Master Data Change Reasons + hr.data.value.reason + list + + + + Employee Master Data Values + hr.employee.data.value + list,form + + + + + + +
diff --git a/hr_master_data/views/hr_contract_views.xml b/hr_master_data/views/hr_contract_views.xml new file mode 100644 index 000000000..7d512b6ad --- /dev/null +++ b/hr_master_data/views/hr_contract_views.xml @@ -0,0 +1,21 @@ + + + hr.contract + + +
+ +
+
+
+
diff --git a/hr_master_data/views/hr_data_value_views.xml b/hr_master_data/views/hr_data_value_views.xml new file mode 100644 index 000000000..c733f8105 --- /dev/null +++ b/hr_master_data/views/hr_data_value_views.xml @@ -0,0 +1,93 @@ + + + + hr.data.group + + + + + + + + + + + + hr.data.type + + + + + + + + + + + hr.data.type + +
+ + +
+

+ +

+
+ + + + + + + + + + + + + + +
+
+
+
+ + + + hr.data.type.value + + + + + + + + + + + + + + Master Data Types + hr.data.type + list,form + + + +
From 63c01fae66250868da97accccb27366235b6995d Mon Sep 17 00:00:00 2001 From: Daniel Reis Date: Mon, 16 Dec 2024 09:56:36 +0000 Subject: [PATCH 2/4] [ADD] payroll_rule_history --- payroll_rule_history/README.rst | 102 ++++ payroll_rule_history/__init__.py | 1 + payroll_rule_history/__manifest__.py | 14 + payroll_rule_history/models/__init__.py | 2 + payroll_rule_history/models/hr_payslip.py | 14 + payroll_rule_history/models/hr_salary_rule.py | 61 +++ payroll_rule_history/pyproject.toml | 3 + payroll_rule_history/readme/CONFIGURE.md | 2 + payroll_rule_history/readme/CONTRIBUTORS.md | 1 + payroll_rule_history/readme/DESCRIPTION.md | 4 + payroll_rule_history/readme/USAGE.md | 5 + .../static/description/index.html | 441 ++++++++++++++++++ .../views/hr_salary_rule_views.xml | 85 ++++ 13 files changed, 735 insertions(+) create mode 100644 payroll_rule_history/README.rst create mode 100644 payroll_rule_history/__init__.py create mode 100644 payroll_rule_history/__manifest__.py create mode 100644 payroll_rule_history/models/__init__.py create mode 100644 payroll_rule_history/models/hr_payslip.py create mode 100644 payroll_rule_history/models/hr_salary_rule.py create mode 100644 payroll_rule_history/pyproject.toml create mode 100644 payroll_rule_history/readme/CONFIGURE.md create mode 100644 payroll_rule_history/readme/CONTRIBUTORS.md create mode 100644 payroll_rule_history/readme/DESCRIPTION.md create mode 100644 payroll_rule_history/readme/USAGE.md create mode 100644 payroll_rule_history/static/description/index.html create mode 100644 payroll_rule_history/views/hr_salary_rule_views.xml diff --git a/payroll_rule_history/README.rst b/payroll_rule_history/README.rst new file mode 100644 index 000000000..888daff4f --- /dev/null +++ b/payroll_rule_history/README.rst @@ -0,0 +1,102 @@ +==================== +Payroll Rule History +==================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:c8d99aed2cecf20d88e0f6f2c237f8ab7241197663c78c1779e79371782d43a5 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpayroll-lightgray.png?logo=github + :target: https://github.com/OCA/payroll/tree/18.0/payroll_rule_history + :alt: OCA/payroll +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/payroll-18-0/payroll-18-0-payroll_rule_history + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/payroll&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +History of Payroll Rules + +Set validity dates on Payroll rules, tracking their history and what +version was effective on each moment in time. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +On Payroll / Configuration / Salary Rules, now have available the Start +and End date fields, to set the validity period of a rule. + +Usage +===== + +When calculating a Payslip, only salary rules valid on the payslip end +date are considered. + +This means that older ofr future versions of the rule are ignored for +this particular payslip. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Daniel Reis + +Contributors +------------ + +- Daniel Reis <> + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-dreispt| image:: https://github.com/dreispt.png?size=40px + :target: https://github.com/dreispt + :alt: dreispt + +Current `maintainer `__: + +|maintainer-dreispt| + +This module is part of the `OCA/payroll `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/payroll_rule_history/__init__.py b/payroll_rule_history/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/payroll_rule_history/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/payroll_rule_history/__manifest__.py b/payroll_rule_history/__manifest__.py new file mode 100644 index 000000000..401d49f41 --- /dev/null +++ b/payroll_rule_history/__manifest__.py @@ -0,0 +1,14 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +{ + "name": "Payroll Rule History", + "summary": "Set validity dates on Payroll rules", + "version": "18.0.1.0.0", + "category": "Payroll", + "website": "https://github.com/OCA/payroll", + "license": "LGPL-3", + "author": "Daniel Reis, Odoo Community Association (OCA)", + "depends": ["payroll"], + "data": ["views/hr_salary_rule_views.xml"], + "maintainers": ["dreispt"], +} diff --git a/payroll_rule_history/models/__init__.py b/payroll_rule_history/models/__init__.py new file mode 100644 index 000000000..886fcbdb8 --- /dev/null +++ b/payroll_rule_history/models/__init__.py @@ -0,0 +1,2 @@ +from . import hr_payslip +from . import hr_salary_rule diff --git a/payroll_rule_history/models/hr_payslip.py b/payroll_rule_history/models/hr_payslip.py new file mode 100644 index 000000000..2e3a06409 --- /dev/null +++ b/payroll_rule_history/models/hr_payslip.py @@ -0,0 +1,14 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import models + + +class HrPayslip(models.Model): + _inherit = "hr.payslip" + + def compute_sheet(self): + # Set active_date in the context + # To be used to filter active Master Data Values + if not self.env.context.get("active_date"): + self = self.with_context(active_date=self.date_to) + return super().compute_sheet() diff --git a/payroll_rule_history/models/hr_salary_rule.py b/payroll_rule_history/models/hr_salary_rule.py new file mode 100644 index 000000000..7884d83fd --- /dev/null +++ b/payroll_rule_history/models/hr_salary_rule.py @@ -0,0 +1,61 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import fields, models + + +class HrSalaryRule(models.Model): + _inherit = "hr.salary.rule" + + has_history = fields.Boolean() + # TODO: child rule name and code should sync with the parent rule + base_rule_id = fields.Many2one(comodel_name="hr.salary.rule") + history_rule_ids = fields.One2many( + comodel_name="hr.salary.rule", inverse_name="base_rule_id" + ) + + date_start = fields.Date(string="Start Date") + date_end = fields.Date(string="End Date") + # TODO: period overlap check + + def action_rule_history(self): + self.ensure_one() + return { + # "name": _("Refund Payslip"), + # "view_id": False, + "res_model": "hr.salary.rule", + "type": "ir.actions.act_window", + "view_mode": "list, form", + "target": "current", + "domain": [("base_rule_id", "in", self.id)], + "context": {"default_rule_id": self.id}, + } + + def _get_active_rule(self, date=None): + rules = self.env["hr.salary.rule"] + for rule in self: + rules |= ( + rule.history_rule_ids.filtered( + lambda x: (not date and not x.date_start or x.date_start <= date) + and (not date or not x.date_end or date <= x.date_end) + ) + if rule.has_history and rule.history_rule_ids + else rule + ) + return rules + + # Extension of payroll methods + # ---------------------------- + + def _compute_rule(self, localdict): + # Method to evaluate the results of a salary rule + # With history rules, replace the main rule + # with the history rule active on the payslip date + self = self._get_active_rule(localdict["payslip"].date_to) + return super()._compute_rule(localdict) + + def _recursive_search_of_rules(self): + # Method to return all rules following their parent-child relations + # With history rules, replace therule + # with the history rule active on the payslip date + self = self._get_active_rule(self.env.context.get("active_date")) + return super()._recursive_search_of_rules() diff --git a/payroll_rule_history/pyproject.toml b/payroll_rule_history/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/payroll_rule_history/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/payroll_rule_history/readme/CONFIGURE.md b/payroll_rule_history/readme/CONFIGURE.md new file mode 100644 index 000000000..8f64214ea --- /dev/null +++ b/payroll_rule_history/readme/CONFIGURE.md @@ -0,0 +1,2 @@ +On Payroll / Configuration / Salary Rules, now have available the Start and End date +fields, to set the validity period of a rule. diff --git a/payroll_rule_history/readme/CONTRIBUTORS.md b/payroll_rule_history/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..9104dbede --- /dev/null +++ b/payroll_rule_history/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Daniel Reis \<\<\>\> diff --git a/payroll_rule_history/readme/DESCRIPTION.md b/payroll_rule_history/readme/DESCRIPTION.md new file mode 100644 index 000000000..2bfda8549 --- /dev/null +++ b/payroll_rule_history/readme/DESCRIPTION.md @@ -0,0 +1,4 @@ +History of Payroll Rules + +Set validity dates on Payroll rules, tracking their history and what version was +effective on each moment in time. diff --git a/payroll_rule_history/readme/USAGE.md b/payroll_rule_history/readme/USAGE.md new file mode 100644 index 000000000..9bca34df7 --- /dev/null +++ b/payroll_rule_history/readme/USAGE.md @@ -0,0 +1,5 @@ +When calculating a Payslip, only salary rules valid on the payslip end date are +considered. + +This means that older ofr future versions of the rule are ignored for this particular +payslip. diff --git a/payroll_rule_history/static/description/index.html b/payroll_rule_history/static/description/index.html new file mode 100644 index 000000000..aaf862d66 --- /dev/null +++ b/payroll_rule_history/static/description/index.html @@ -0,0 +1,441 @@ + + + + + +Payroll Rule History + + + +
+

Payroll Rule History

+ + +

Beta License: LGPL-3 OCA/payroll Translate me on Weblate Try me on Runboat

+

History of Payroll Rules

+

Set validity dates on Payroll rules, tracking their history and what +version was effective on each moment in time.

+

Table of contents

+ +
+

Configuration

+

On Payroll / Configuration / Salary Rules, now have available the Start +and End date fields, to set the validity period of a rule.

+
+
+

Usage

+

When calculating a Payslip, only salary rules valid on the payslip end +date are considered.

+

This means that older ofr future versions of the rule are ignored for +this particular payslip.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Daniel Reis
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

dreispt

+

This module is part of the OCA/payroll project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/payroll_rule_history/views/hr_salary_rule_views.xml b/payroll_rule_history/views/hr_salary_rule_views.xml new file mode 100644 index 000000000..9be2d02a1 --- /dev/null +++ b/payroll_rule_history/views/hr_salary_rule_views.xml @@ -0,0 +1,85 @@ + + + hr.salary.rule + + + + + + + + + + + hr.salary.rule + + + + + + + + + + + + + has_history + + + has_history + + + has_history + + + has_history + + + + + + + + + + + hr.salary.rule + + + + + + + + + + {"search_default_is_base_rule": 1} + + From 16423a14e7d4d68059c54fbea69ac7155dabeb93 Mon Sep 17 00:00:00 2001 From: Daniel Reis Date: Thu, 2 Jan 2025 08:13:31 +0000 Subject: [PATCH 3/4] [ADD] payroll_salary_table --- payroll_salary_table/README.rst | 154 ++++++ payroll_salary_table/__init__.py | 1 + payroll_salary_table/__manifest__.py | 19 + payroll_salary_table/models/__init__.py | 5 + payroll_salary_table/models/hr_contract.py | 31 ++ payroll_salary_table/models/hr_payslip.py | 31 ++ .../models/hr_payslip_line.py | 12 + payroll_salary_table/models/hr_salary_rule.py | 32 ++ .../models/hr_salary_table.py | 144 ++++++ payroll_salary_table/pyproject.toml | 3 + payroll_salary_table/readme/CONFIGURE.md | 14 + payroll_salary_table/readme/CONTRIBUTORS.md | 1 + payroll_salary_table/readme/DESCRIPTION.md | 13 + payroll_salary_table/readme/USAGE.md | 27 + .../security/hr_payroll_security.xml | 19 + .../security/ir.model.access.csv | 4 + .../static/description/index.html | 488 ++++++++++++++++++ payroll_salary_table/tests/__init__.py | 1 + .../tests/test_salary_table.py | 63 +++ .../views/hr_salary_table.xml | 86 +++ .../views/hr_salary_table_template.xml | 61 +++ payroll_salary_table/views/menu.xml | 43 ++ 22 files changed, 1252 insertions(+) create mode 100644 payroll_salary_table/README.rst create mode 100644 payroll_salary_table/__init__.py create mode 100644 payroll_salary_table/__manifest__.py create mode 100644 payroll_salary_table/models/__init__.py create mode 100644 payroll_salary_table/models/hr_contract.py create mode 100644 payroll_salary_table/models/hr_payslip.py create mode 100644 payroll_salary_table/models/hr_payslip_line.py create mode 100644 payroll_salary_table/models/hr_salary_rule.py create mode 100644 payroll_salary_table/models/hr_salary_table.py create mode 100644 payroll_salary_table/pyproject.toml create mode 100644 payroll_salary_table/readme/CONFIGURE.md create mode 100644 payroll_salary_table/readme/CONTRIBUTORS.md create mode 100644 payroll_salary_table/readme/DESCRIPTION.md create mode 100644 payroll_salary_table/readme/USAGE.md create mode 100644 payroll_salary_table/security/hr_payroll_security.xml create mode 100644 payroll_salary_table/security/ir.model.access.csv create mode 100644 payroll_salary_table/static/description/index.html create mode 100644 payroll_salary_table/tests/__init__.py create mode 100644 payroll_salary_table/tests/test_salary_table.py create mode 100644 payroll_salary_table/views/hr_salary_table.xml create mode 100644 payroll_salary_table/views/hr_salary_table_template.xml create mode 100644 payroll_salary_table/views/menu.xml diff --git a/payroll_salary_table/README.rst b/payroll_salary_table/README.rst new file mode 100644 index 000000000..5bfb14efb --- /dev/null +++ b/payroll_salary_table/README.rst @@ -0,0 +1,154 @@ +=================== +Payroll Value Table +=================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:2250d57eccea82867ce2915a53b682df463175f6bf233de094e953ccd662f730 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpayroll-lightgray.png?logo=github + :target: https://github.com/OCA/payroll/tree/18.0/payroll_salary_table + :alt: OCA/payroll +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/payroll-18-0/payroll-18-0-payroll_salary_table + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/payroll&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Salary Tables to be used on payroll calculation. + +Set values for applicable for certain master data values, or even for +combinations on them. These can thn be read from salary rules, to have +those values applied to employees that natch those conditions. + +Examples include: + +- Income Tax Deduction Rate, based on the employee's Income Tax + Position and total income amount. + +- Base Salary, based on a combination of Professional category and Pay + Grade. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +At Payroll / Configuration / Salary Tables, define the different salary +tables to use. + +Examples of Salary Tables include: + +- Basic Salary per Professional Category, or Pay Grade, or both. +- Tax Rates per Tax Situation and taxable amount. +- Allowance amounts or rates per Professional Category, or other + relevant data. + +To create a Salary Table provide: + +- Name: a meaningful title. +- Code: an identifier used to reference it from salary rules. +- Sequence: the order of value application, larger numbers override + lower numbers. +- Type 1, 2, 3: select the Master Data Types to use to match table + values. + +Usage +===== + +Salary Tables are maintained at Payroll / Configuration / Salary Tables. +Create a Salary Table Period to create a table valid for a specific +period. + +On a Salary Table Period set: + +- Template: the salary table being used +- Company : the company it applies to (all if left empty) +- Start and End Dates: the validity period (optional) +- Note: any comment and notes +- Lines: enter each combination and the Result value to be used when + matched + +Salary Tables can accessed in salary rules using the ``lookup_table`` +function. Examples: + +:: + + value = lookup_table("BASIC") + rate = lookup_table("TAX", taxable_amount) + +Master Data values can be accessed in salary rules using the +get_data_value function. Examples: + +:: + + result = contract.get_data_value("BASIC") + result_data = contract.get_data_value("TAX_SITUATION") + +Payslip lines can store a Master Data value returned by a salary rule in +the ``result_data`` variable. This can be useful for audit and +reporting. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Daniel Reis + +Contributors +------------ + +- Daniel Reis <> + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-dreispt| image:: https://github.com/dreispt.png?size=40px + :target: https://github.com/dreispt + :alt: dreispt + +Current `maintainer `__: + +|maintainer-dreispt| + +This module is part of the `OCA/payroll `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/payroll_salary_table/__init__.py b/payroll_salary_table/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/payroll_salary_table/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/payroll_salary_table/__manifest__.py b/payroll_salary_table/__manifest__.py new file mode 100644 index 000000000..78e8b1d4f --- /dev/null +++ b/payroll_salary_table/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +{ + "name": "Payroll Value Table", + "version": "18.0.1.0.0", + "category": "Payroll", + "website": "https://github.com/OCA/payroll", + "license": "LGPL-3", + "author": "Daniel Reis, Odoo Community Association (OCA)", + "depends": ["payroll", "hr_master_data"], + "data": [ + "security/hr_payroll_security.xml", + "security/ir.model.access.csv", + "views/hr_salary_table_template.xml", + "views/hr_salary_table.xml", + "views/menu.xml", + ], + "maintainers": ["dreispt"], +} diff --git a/payroll_salary_table/models/__init__.py b/payroll_salary_table/models/__init__.py new file mode 100644 index 000000000..fafe6b3ce --- /dev/null +++ b/payroll_salary_table/models/__init__.py @@ -0,0 +1,5 @@ +from . import hr_contract +from . import hr_payslip +from . import hr_payslip_line +from . import hr_salary_rule +from . import hr_salary_table diff --git a/payroll_salary_table/models/hr_contract.py b/payroll_salary_table/models/hr_contract.py new file mode 100644 index 000000000..17cd55373 --- /dev/null +++ b/payroll_salary_table/models/hr_contract.py @@ -0,0 +1,31 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import models + + +class HrContract(models.Model): + _inherit = "hr.contract" + + def get_data_record(self, code, date=None): + self.ensure_one() + contract = self.with_context(active_date=date) if date else self + value_line = contract.active_values_ids.filtered( + lambda x: x.type_id.code == code + and (not date or not x.date_start or x.date_start <= date) + and (not date or not x.date_end or x.date_end >= date) + ) + return value_line[:1] + + def get_data_value(self, code, date=None): + """ + Lookup on the Employee Master Data to find the value of a Data Type. + Can be used in salary rules. + code is a data type code. + + For example, to get the value for the BASIC code: + value = contract.get_data_value("BASIC") + + The active_date should be set in the context, with the date to be used. + + """ + return self.get_data_record(code, date).get_value() diff --git a/payroll_salary_table/models/hr_payslip.py b/payroll_salary_table/models/hr_payslip.py new file mode 100644 index 000000000..d5cbded3b --- /dev/null +++ b/payroll_salary_table/models/hr_payslip.py @@ -0,0 +1,31 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import models + + +class HrPayslip(models.Model): + _inherit = "hr.payslip" + + def _get_all_salary_table_lines(self): + """ + Return the active Salary Table lines, to be fetched and stored once, + and then be available for queries by salary rules. + """ + SalTableTemplate = self.env["hr.salary.table.template"] + active_date = self[0].date_to + return SalTableTemplate.get_all_salary_table_lines(active_date) + + def _get_global_data(self): + """ + Cached data, computed once an then shared by all the payslip calculations. + To be used in Salary Rules code calculations (_compute_rule_code) + """ + return {"salary_table_lines": self._get_all_salary_table_lines()} + + def compute_sheet(self): + # Set active_date AND global data in the context + # To be used to filter active Master Data Values + global_data = self._get_global_data() + active_date = self[0].date_to + self = self.with_context(active_date=active_date, global_data=global_data) + return super().compute_sheet() diff --git a/payroll_salary_table/models/hr_payslip_line.py b/payroll_salary_table/models/hr_payslip_line.py new file mode 100644 index 000000000..5d53f12ef --- /dev/null +++ b/payroll_salary_table/models/hr_payslip_line.py @@ -0,0 +1,12 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import fields, models + + +class HrPayslipLine(models.Model): + _inherit = "hr.payslip.line" + + data_value_id = fields.Many2one( + comodel_name="hr.data.type.value", + readonly=True, + ) diff --git a/payroll_salary_table/models/hr_salary_rule.py b/payroll_salary_table/models/hr_salary_rule.py new file mode 100644 index 000000000..56f0db75b --- /dev/null +++ b/payroll_salary_table/models/hr_salary_rule.py @@ -0,0 +1,32 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import models + + +class HrSalaryRule(models.Model): + _inherit = "hr.salary.rule" + + def _compute_rule(self, localdict): + # When calculating a Python code rule + # the lookup_tables function is available for the rule code + # to get the applicable value for a salary rule code. + # Example: result = lookup_tables("TAXRATE", taxable_amount) + def lookup_table(rule_code, amount=0): + global_data = self.env.context.get("global_data", {}) + salary_table_lines = global_data.get("salary_table_lines") + contract = localdict["contract"] + line = salary_table_lines.lookup_rule(contract, rule_code, amount) + return line.result or 0.0 + + localdict["lookup_table"] = lookup_table + return super()._compute_rule(localdict) + + def _get_rule_dict(self, localdict): + # After calculating a salary rule, + # the result_data variable ican be stored in the Payslip line. + # This allows salary rules to calculate and stor a Master Data Value, + # that can later be be useful for audit or reporting. + res = super()._get_rule_dict(localdict) + if "result_data" in localdict: + res["data_value_id"] = float(localdict["result_data"]) + return res diff --git a/payroll_salary_table/models/hr_salary_table.py b/payroll_salary_table/models/hr_salary_table.py new file mode 100644 index 000000000..9c7ab2fb2 --- /dev/null +++ b/payroll_salary_table/models/hr_salary_table.py @@ -0,0 +1,144 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import api, fields, models + + +class HrSalaryTableTemplate(models.Model): + _name = "hr.salary.table.template" + _description = "Salary Table" + _order = "sequence" + + name = fields.Char(required=True) + code = fields.Char() + sequence = fields.Integer() + type_1_id = fields.Many2one("hr.data.type") + type_2_id = fields.Many2one("hr.data.type") + type_3_id = fields.Many2one("hr.data.type") + line_ids = fields.One2many( + comodel_name="hr.salary.table", + inverse_name="template_id", + ) + note = fields.Text() + active = fields.Boolean(default=True) + + def get_all_salary_table_lines(self, active_date): + """ + Return all active salary table lines, + that at a later step can be looked up to find specific rule values. + + Used on Payslips, to query and cache ahead of time all salary table lines, + to be available for lookup by the rule calculations. + """ + ids = self.ids or self.search([]).ids + domain = [ + ("table_id.template_id", "in", ids), + "|", + ("table_id.date_start", "<=", active_date), + ("table_id.date_start", "=", False), + "|", + ("table_id.date_end", ">=", active_date), + ("table_id.date_end", "=", False), + ] + return self.env["hr.salary.table.line"].search(domain) + + +class HrSalaryTable(models.Model): + _name = "hr.salary.table" + _description = "Salary Table Period" + + template_id = fields.Many2one("hr.salary.table.template", required=True) + company_id = fields.Many2one("res.company") + date_start = fields.Date() + date_end = fields.Date() + line_ids = fields.One2many( + comodel_name="hr.salary.table.line", + inverse_name="table_id", + ) + note = fields.Text() + + template_code = fields.Char(related="template_id.code") + type_1_id = fields.Many2one("hr.data.type", related="template_id.type_1_id") + type_2_id = fields.Many2one("hr.data.type", related="template_id.type_2_id") + type_3_id = fields.Many2one("hr.data.type", related="template_id.type_3_id") + + +class HrSalaryTableLine(models.Model): + _name = "hr.salary.table.line" + _description = "Salary Table Values" + _order = "table_sequence desc, value_1_id, value_2_id, value_3_id, number_from" + + table_id = fields.Many2one("hr.salary.table") + table_sequence = fields.Integer( + related="table_id.template_id.sequence", + store=True, + ) + + salary_rule_id = fields.Many2one("hr.salary.rule") + salary_rule_code = fields.Char(related="salary_rule_id.code") + value_1_id = fields.Many2one( + "hr.data.type.value", + domain="[('type_id', '=', type_1_id)]", + ) + value_2_id = fields.Many2one( + "hr.data.type.value", + domain="[('type_id', '=', type_2_id)]", + ) + value_3_id = fields.Many2one( + "hr.data.type.value", + domain="[('type_id', '=', type_3_id)]", + ) + number_from = fields.Float() + number_to = fields.Float() + result = fields.Float() + + template_id = fields.Many2one( + "hr.salary.table.template", related="table_id.template_id", store=True + ) + company_id = fields.Many2one( + comodel_name="res.company", related="table_id.company_id", store=True + ) + date_start = fields.Date(related="table_id.date_start", store=True) + date_end = fields.Date(related="table_id.date_end", store=True) + + template_code = fields.Char( + string="Template Code", + related="table_id.template_id.code", + ) + type_1_id = fields.Many2one( + "hr.data.type", related="table_id.template_id.type_1_id" + ) + type_2_id = fields.Many2one( + "hr.data.type", related="table_id.template_id.type_2_id" + ) + type_3_id = fields.Many2one( + "hr.data.type", related="table_id.template_id.type_3_id" + ) + + @api.model + def _get_salary_table_line_domain(self, contract, rule_code=None, amount=0): + domain = [] + if rule_code: + domain.append(("salary_rule_code", "=", rule_code)) + if self.type_1_id: + value_1 = contract.get_data_value(self.type_1_id) + domain.append(("type_1_id", "=", value_1)) + if self.type_2_id: + value_2 = contract.get_data_value(self.type_1_id) + domain.append(("type_2_id", "=", value_2)) + if self.type_3_id: + value_3 = contract.get_data_value(self.type_1_id) + domain.append(("type_3_id", "=", value_3)) + if amount: + domain += [("number_from", "<=", amount), ("number_to", ">", amount)] + return domain + + def lookup_rule(self, contract, rule_code, amount=0): + """ + Given a recordset of salary table lines, + lookup the active value for a salary rule. + Sort order is important, as the first record is the value to be returned. + + Used by payslip rule calculations. + """ + domain = self._get_salary_table_line_domain(contract, rule_code, amount) + return self.filtered_domain(domain)[:1] diff --git a/payroll_salary_table/pyproject.toml b/payroll_salary_table/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/payroll_salary_table/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/payroll_salary_table/readme/CONFIGURE.md b/payroll_salary_table/readme/CONFIGURE.md new file mode 100644 index 000000000..8c9b594f0 --- /dev/null +++ b/payroll_salary_table/readme/CONFIGURE.md @@ -0,0 +1,14 @@ +At Payroll / Configuration / Salary Tables, define the different salary tables to use. + +Examples of Salary Tables include: + +- Basic Salary per Professional Category, or Pay Grade, or both. +- Tax Rates per Tax Situation and taxable amount. +- Allowance amounts or rates per Professional Category, or other relevant data. + +To create a Salary Table provide: + +- Name: a meaningful title. +- Code: an identifier used to reference it from salary rules. +- Sequence: the order of value application, larger numbers override lower numbers. +- Type 1, 2, 3: select the Master Data Types to use to match table values. diff --git a/payroll_salary_table/readme/CONTRIBUTORS.md b/payroll_salary_table/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..9104dbede --- /dev/null +++ b/payroll_salary_table/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Daniel Reis \<\<\>\> diff --git a/payroll_salary_table/readme/DESCRIPTION.md b/payroll_salary_table/readme/DESCRIPTION.md new file mode 100644 index 000000000..bb322188b --- /dev/null +++ b/payroll_salary_table/readme/DESCRIPTION.md @@ -0,0 +1,13 @@ +Salary Tables to be used on payroll calculation. + +Set values for applicable for certain master data values, +or even for combinations on them. +These can thn be read from salary rules, to have those values applied to employees that +natch those conditions. + +Examples include: + +- Income Tax Deduction Rate, based on the employee's Income Tax Position + and total income amount. + +- Base Salary, based on a combination of Professional category and Pay Grade. diff --git a/payroll_salary_table/readme/USAGE.md b/payroll_salary_table/readme/USAGE.md new file mode 100644 index 000000000..220196f76 --- /dev/null +++ b/payroll_salary_table/readme/USAGE.md @@ -0,0 +1,27 @@ +Salary Tables are maintained at Payroll / Configuration / Salary Tables. +Create a Salary Table Period to create a table valid for a specific period. + +On a Salary Table Period set: + +- Template: the salary table being used +- Company : the company it applies to (all if left empty) +- Start and End Dates: the validity period (optional) +- Note: any comment and notes +- Lines: enter each combination and the Result value to be used when matched + +Salary Tables can accessed in salary rules using the ``lookup_table`` function. +Examples: +``` +value = lookup_table("BASIC") +rate = lookup_table("TAX", taxable_amount) +``` + +Master Data values can be accessed in salary rules using the get_data_value function. +Examples: +``` +result = contract.get_data_value("BASIC") +result_data = contract.get_data_value("TAX_SITUATION") +``` + +Payslip lines can store a Master Data value returned by a salary rule +in the ``result_data`` variable. This can be useful for audit and reporting. diff --git a/payroll_salary_table/security/hr_payroll_security.xml b/payroll_salary_table/security/hr_payroll_security.xml new file mode 100644 index 000000000..fcf6e6996 --- /dev/null +++ b/payroll_salary_table/security/hr_payroll_security.xml @@ -0,0 +1,19 @@ + + + + Salary Table: multi-company + + + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + + + Salary Table Line: multi-company + + + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + + diff --git a/payroll_salary_table/security/ir.model.access.csv b/payroll_salary_table/security/ir.model.access.csv new file mode 100644 index 000000000..d6ad31a3a --- /dev/null +++ b/payroll_salary_table/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_hr_salary_table_template_user,hr.salary.table.template user,model_hr_salary_table_template,payroll.group_payroll_user,1,1,1,1 +access_hr_salary_table_user,hr.salary.table user,model_hr_salary_table,payroll.group_payroll_user,1,1,1,1 +access_hr_salary_table_line_user,hr.salary.table.line user,model_hr_salary_table_line,payroll.group_payroll_user,1,1,1,1 diff --git a/payroll_salary_table/static/description/index.html b/payroll_salary_table/static/description/index.html new file mode 100644 index 000000000..474e6acfe --- /dev/null +++ b/payroll_salary_table/static/description/index.html @@ -0,0 +1,488 @@ + + + + + +Payroll Value Table + + + +
+

Payroll Value Table

+ + +

Beta License: LGPL-3 OCA/payroll Translate me on Weblate Try me on Runboat

+

Salary Tables to be used on payroll calculation.

+

Set values for applicable for certain master data values, or even for +combinations on them. These can thn be read from salary rules, to have +those values applied to employees that natch those conditions.

+

Examples include:

+
    +
  • Income Tax Deduction Rate, based on the employee’s Income Tax +Position and total income amount.
  • +
  • Base Salary, based on a combination of Professional category and Pay +Grade.
  • +
+

Table of contents

+ +
+

Configuration

+

At Payroll / Configuration / Salary Tables, define the different salary +tables to use.

+

Examples of Salary Tables include:

+
    +
  • Basic Salary per Professional Category, or Pay Grade, or both.
  • +
  • Tax Rates per Tax Situation and taxable amount.
  • +
  • Allowance amounts or rates per Professional Category, or other +relevant data.
  • +
+

To create a Salary Table provide:

+
    +
  • Name: a meaningful title.
  • +
  • Code: an identifier used to reference it from salary rules.
  • +
  • Sequence: the order of value application, larger numbers override +lower numbers.
  • +
  • Type 1, 2, 3: select the Master Data Types to use to match table +values.
  • +
+
+
+

Usage

+

Salary Tables are maintained at Payroll / Configuration / Salary Tables. +Create a Salary Table Period to create a table valid for a specific +period.

+

On a Salary Table Period set:

+
    +
  • Template: the salary table being used
  • +
  • Company : the company it applies to (all if left empty)
  • +
  • Start and End Dates: the validity period (optional)
  • +
  • Note: any comment and notes
  • +
  • Lines: enter each combination and the Result value to be used when +matched
  • +
+

Salary Tables can accessed in salary rules using the lookup_table +function. Examples:

+
+value = lookup_table("BASIC")
+rate = lookup_table("TAX", taxable_amount)
+
+

Master Data values can be accessed in salary rules using the +get_data_value function. Examples:

+
+result = contract.get_data_value("BASIC")
+result_data = contract.get_data_value("TAX_SITUATION")
+
+

Payslip lines can store a Master Data value returned by a salary rule in +the result_data variable. This can be useful for audit and +reporting.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Daniel Reis
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

dreispt

+

This module is part of the OCA/payroll project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/payroll_salary_table/tests/__init__.py b/payroll_salary_table/tests/__init__.py new file mode 100644 index 000000000..230e4ece2 --- /dev/null +++ b/payroll_salary_table/tests/__init__.py @@ -0,0 +1 @@ +from . import test_salary_table diff --git a/payroll_salary_table/tests/test_salary_table.py b/payroll_salary_table/tests/test_salary_table.py new file mode 100644 index 000000000..61a37b9c1 --- /dev/null +++ b/payroll_salary_table/tests/test_salary_table.py @@ -0,0 +1,63 @@ +from odoo.addons.payroll.tests.common import TestPayslipBase + + +class TestPayslipSalaryTable(TestPayslipBase): + def setUp(self): + super().setUp() + + self.salary_table_template = self.env["hr.salary.table.template"].create( + {"name": "Basic Salary Table", "code": "BASIC"} + ) + self.salary_table = self.env["hr.salary.table"].create( + {"template_id": self.salary_table_template.id} + ) + table_id = self.salary_table.id + + line_values = [ + { + "table_id": table_id, + "salary_rule_id": self.rule_basic.id, + "number_from": 0, + "number_to": 1, + "result": 100, + }, + { + "table_id": table_id, + "salary_rule_id": self.rule_basic.id, + "number_from": 1, + "number_to": 2, + "result": 200, + }, + { + "table_id": table_id, + "salary_rule_id": self.rule_basic.id, + "number_from": 2, + "number_to": 3, + "result": 300, + }, + ] + self.env["hr.salary.table.line"].create(line_values) + + def test_salary_table(self): + payslip = self.Payslip.create( + { + "employee_id": self.richard_emp.id, + "contract_id": self.richard_contract.id, + } + ) + payslip.onchange_employee() + + self.rule_basic.amount_python_compute = "result = lookup_table('BASIC', 0)" + payslip.compute_sheet() + line = payslip.line_ids.filtered(lambda record: record.code == "BASIC") + self.assertEqual(line.amount, 100) + + self.rule_basic.amount_python_compute = "result = lookup_table('BASIC', 1)" + payslip.compute_sheet() + line = payslip.line_ids.filtered(lambda record: record.code == "BASIC") + self.assertEqual(line.amount, 200) + + self.rule_basic.amount_python_compute = "result = lookup_table('BASIC', 2)" + payslip.compute_sheet() + line = payslip.line_ids.filtered(lambda record: record.code == "BASIC") + self.assertEqual(line.amount, 300) diff --git a/payroll_salary_table/views/hr_salary_table.xml b/payroll_salary_table/views/hr_salary_table.xml new file mode 100644 index 000000000..d1549b5d6 --- /dev/null +++ b/payroll_salary_table/views/hr_salary_table.xml @@ -0,0 +1,86 @@ + + + + hr.salary.table.list + hr.salary.table + + + + + + + + + + + + + hr.salary.table.form + hr.salary.table + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + hr.salary.table.line.list + hr.salary.table.line + + + + + + + + + + + + + + + + +
diff --git a/payroll_salary_table/views/hr_salary_table_template.xml b/payroll_salary_table/views/hr_salary_table_template.xml new file mode 100644 index 000000000..90e08c4d9 --- /dev/null +++ b/payroll_salary_table/views/hr_salary_table_template.xml @@ -0,0 +1,61 @@ + + + + hr.salary.table.template.list + hr.salary.table.template + + + + + + + + + + + + + + + hr.salary.table.form.template + hr.salary.table.template + +
+ + +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
diff --git a/payroll_salary_table/views/menu.xml b/payroll_salary_table/views/menu.xml new file mode 100644 index 000000000..b3cedfeba --- /dev/null +++ b/payroll_salary_table/views/menu.xml @@ -0,0 +1,43 @@ + + + + Salary Tables + hr.salary.table.template + list,form + + + + Salary Table Periods + hr.salary.table + list,form + + + + Salary Table Lines + hr.salary.table.line + list,form + + + + + + + + From dbdac3726416d77a3284666f4d1d40fd9338be7c Mon Sep 17 00:00:00 2001 From: Daniel Reis Date: Sat, 5 Apr 2025 22:02:03 +0100 Subject: [PATCH 4/4] [ADD] payroll_rule_tag --- payroll_rule_tag/README.rst | 109 +++++ payroll_rule_tag/__init__.py | 1 + payroll_rule_tag/__manifest__.py | 17 + payroll_rule_tag/models/__init__.py | 3 + payroll_rule_tag/models/hr_payslip.py | 35 ++ payroll_rule_tag/models/hr_salary_rule.py | 9 + payroll_rule_tag/models/hr_salary_rule_tag.py | 12 + payroll_rule_tag/pyproject.toml | 3 + payroll_rule_tag/readme/CONFIGURE.md | 2 + payroll_rule_tag/readme/CONTRIBUTORS.md | 1 + payroll_rule_tag/readme/DESCRIPTION.md | 4 + payroll_rule_tag/readme/USAGE.md | 11 + payroll_rule_tag/security/ir.model.access.csv | 2 + .../static/description/index.html | 446 ++++++++++++++++++ payroll_rule_tag/tests/__init__.py | 1 + .../tests/test_salary_rule_tag.py | 53 +++ payroll_rule_tag/views/hr_salary_rule.xml | 11 + payroll_rule_tag/views/hr_salary_rule_tag.xml | 32 ++ 18 files changed, 752 insertions(+) create mode 100644 payroll_rule_tag/README.rst create mode 100644 payroll_rule_tag/__init__.py create mode 100644 payroll_rule_tag/__manifest__.py create mode 100644 payroll_rule_tag/models/__init__.py create mode 100644 payroll_rule_tag/models/hr_payslip.py create mode 100644 payroll_rule_tag/models/hr_salary_rule.py create mode 100644 payroll_rule_tag/models/hr_salary_rule_tag.py create mode 100644 payroll_rule_tag/pyproject.toml create mode 100644 payroll_rule_tag/readme/CONFIGURE.md create mode 100644 payroll_rule_tag/readme/CONTRIBUTORS.md create mode 100644 payroll_rule_tag/readme/DESCRIPTION.md create mode 100644 payroll_rule_tag/readme/USAGE.md create mode 100644 payroll_rule_tag/security/ir.model.access.csv create mode 100644 payroll_rule_tag/static/description/index.html create mode 100644 payroll_rule_tag/tests/__init__.py create mode 100644 payroll_rule_tag/tests/test_salary_rule_tag.py create mode 100644 payroll_rule_tag/views/hr_salary_rule.xml create mode 100644 payroll_rule_tag/views/hr_salary_rule_tag.xml diff --git a/payroll_rule_tag/README.rst b/payroll_rule_tag/README.rst new file mode 100644 index 000000000..7206ea7de --- /dev/null +++ b/payroll_rule_tag/README.rst @@ -0,0 +1,109 @@ +================ +Payroll Rule Tag +================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:5ee9008067f86ba06d52efcdc02644a97437611ee4feaf74fcadaf545845b2db + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpayroll-lightgray.png?logo=github + :target: https://github.com/OCA/payroll/tree/18.0/payroll_rule_tag + :alt: OCA/payroll +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/payroll-18-0/payroll-18-0-payroll_rule_tag + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/payroll&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Assign tags to Salary Rules. + +These tags can be used to compute totals, and can work as a more flexble +alternative to salary rule Categories. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Salary rule Tags can be defined from the Payroll / Configuration menu, +and are assigned to Salary Rules using the "Tags" field in the +Computation form area. + +Usage +===== + +On Salary Rules form, one or more tags can be selected and assigne to +the rule, on the "Tags" field. + +In salary rules with Python code, totals for a tag can be accessed using +the ``tags`` browseable collection with the uppercase tag name. + +Example: + +:: + + result = tags.TAXABLE + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Daniel Reis + +Contributors +------------ + +- Daniel Reis <> + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-dreispt| image:: https://github.com/dreispt.png?size=40px + :target: https://github.com/dreispt + :alt: dreispt + +Current `maintainer `__: + +|maintainer-dreispt| + +This module is part of the `OCA/payroll `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/payroll_rule_tag/__init__.py b/payroll_rule_tag/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/payroll_rule_tag/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/payroll_rule_tag/__manifest__.py b/payroll_rule_tag/__manifest__.py new file mode 100644 index 000000000..7c1650650 --- /dev/null +++ b/payroll_rule_tag/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +{ + "name": "Payroll Rule Tag", + "version": "18.0.1.0.0", + "category": "Payroll", + "website": "https://github.com/OCA/payroll", + "license": "LGPL-3", + "author": "Daniel Reis, Odoo Community Association (OCA)", + "depends": ["payroll"], + "data": [ + "security/ir.model.access.csv", + "views/hr_salary_rule.xml", + "views/hr_salary_rule_tag.xml", + ], + "maintainers": ["dreispt"], +} diff --git a/payroll_rule_tag/models/__init__.py b/payroll_rule_tag/models/__init__.py new file mode 100644 index 000000000..9d335d427 --- /dev/null +++ b/payroll_rule_tag/models/__init__.py @@ -0,0 +1,3 @@ +from . import hr_payslip +from . import hr_salary_rule +from . import hr_salary_rule_tag diff --git a/payroll_rule_tag/models/hr_payslip.py b/payroll_rule_tag/models/hr_payslip.py new file mode 100644 index 000000000..be878fb0c --- /dev/null +++ b/payroll_rule_tag/models/hr_payslip.py @@ -0,0 +1,35 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import models + +from odoo.addons.payroll.models.base_browsable import BrowsableObject + + +class HrPayslip(models.Model): + _inherit = "hr.payslip" + + def _get_baselocaldict(self, contracts): + localdict = super()._get_baselocaldict(contracts) + localdict["tags"] = BrowsableObject(self.employee_id.id, {}, self.env) + return localdict + + def _get_lines_dict( + self, rule, localdict, lines_dict, key, values, previous_amount + ): + localdict, lines_dict = super()._get_lines_dict( + rule, localdict, lines_dict, key, values, previous_amount + ) + # sum the amount for salary rule tags + total = lines_dict[key]["total"] + for tag in rule.tag_ids: + localdict = self._sum_salary_rule_tag( + localdict, tag.name, total - previous_amount + ) + return localdict, lines_dict + + def _sum_salary_rule_tag(self, localdict, tag_name, amount): + self.ensure_one() + if tag_name: + code = tag_name.upper() + localdict["tags"].dict[code] = localdict["tags"].dict.get(code, 0) + amount + return localdict diff --git a/payroll_rule_tag/models/hr_salary_rule.py b/payroll_rule_tag/models/hr_salary_rule.py new file mode 100644 index 000000000..9812a3d91 --- /dev/null +++ b/payroll_rule_tag/models/hr_salary_rule.py @@ -0,0 +1,9 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import fields, models + + +class HrSalaryRule(models.Model): + _inherit = "hr.salary.rule" + + tag_ids = fields.Many2many("hr.salary.rule.tag", string="Tags") diff --git a/payroll_rule_tag/models/hr_salary_rule_tag.py b/payroll_rule_tag/models/hr_salary_rule_tag.py new file mode 100644 index 000000000..d90c93029 --- /dev/null +++ b/payroll_rule_tag/models/hr_salary_rule_tag.py @@ -0,0 +1,12 @@ +# Copyright 2025 Open Source Integrators (www.opensourceintegrators.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import fields, models + + +class HrSalaryRuleTag(models.Model): + _name = "hr.salary.rule.tag" + _description = "Salary Rule Tag" + _order = "name" + + name = fields.Char(required=True) + salary_rules_ids = fields.Many2many("hr.salary.rule") diff --git a/payroll_rule_tag/pyproject.toml b/payroll_rule_tag/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/payroll_rule_tag/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/payroll_rule_tag/readme/CONFIGURE.md b/payroll_rule_tag/readme/CONFIGURE.md new file mode 100644 index 000000000..4c2bf0f74 --- /dev/null +++ b/payroll_rule_tag/readme/CONFIGURE.md @@ -0,0 +1,2 @@ +Salary rule Tags can be defined from the Payroll / Configuration menu, +and are assigned to Salary Rules using the "Tags" field in the Computation form area. diff --git a/payroll_rule_tag/readme/CONTRIBUTORS.md b/payroll_rule_tag/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..9104dbede --- /dev/null +++ b/payroll_rule_tag/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Daniel Reis \<\<\>\> diff --git a/payroll_rule_tag/readme/DESCRIPTION.md b/payroll_rule_tag/readme/DESCRIPTION.md new file mode 100644 index 000000000..3ac4e963f --- /dev/null +++ b/payroll_rule_tag/readme/DESCRIPTION.md @@ -0,0 +1,4 @@ +Assign tags to Salary Rules. + +These tags can be used to compute totals, and can work +as a more flexble alternative to salary rule Categories. diff --git a/payroll_rule_tag/readme/USAGE.md b/payroll_rule_tag/readme/USAGE.md new file mode 100644 index 000000000..3632c836a --- /dev/null +++ b/payroll_rule_tag/readme/USAGE.md @@ -0,0 +1,11 @@ +On Salary Rules form, one or more tags can be selected and assigne to the rule, +on the "Tags" field. + +In salary rules with Python code, totals for a tag can be accessed +using the ``tags`` browseable collection with the uppercase tag name. + +Example: +``` +result = tags.TAXABLE +``` + diff --git a/payroll_rule_tag/security/ir.model.access.csv b/payroll_rule_tag/security/ir.model.access.csv new file mode 100644 index 000000000..9b8bd6521 --- /dev/null +++ b/payroll_rule_tag/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_hr_salary_rule_tag_user,hr.salary.rule.tag user,model_hr_salary_rule_tag,payroll.group_payroll_user,1,1,1,1 diff --git a/payroll_rule_tag/static/description/index.html b/payroll_rule_tag/static/description/index.html new file mode 100644 index 000000000..e1c55cfa9 --- /dev/null +++ b/payroll_rule_tag/static/description/index.html @@ -0,0 +1,446 @@ + + + + + +Payroll Rule Tag + + + +
+

Payroll Rule Tag

+ + +

Beta License: LGPL-3 OCA/payroll Translate me on Weblate Try me on Runboat

+

Assign tags to Salary Rules.

+

These tags can be used to compute totals, and can work as a more flexble +alternative to salary rule Categories.

+

Table of contents

+ +
+

Configuration

+

Salary rule Tags can be defined from the Payroll / Configuration menu, +and are assigned to Salary Rules using the “Tags” field in the +Computation form area.

+
+
+

Usage

+

On Salary Rules form, one or more tags can be selected and assigne to +the rule, on the “Tags” field.

+

In salary rules with Python code, totals for a tag can be accessed using +the tags browseable collection with the uppercase tag name.

+

Example:

+
+result = tags.TAXABLE
+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Daniel Reis
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

dreispt

+

This module is part of the OCA/payroll project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/payroll_rule_tag/tests/__init__.py b/payroll_rule_tag/tests/__init__.py new file mode 100644 index 000000000..88340e830 --- /dev/null +++ b/payroll_rule_tag/tests/__init__.py @@ -0,0 +1 @@ +from . import test_salary_rule_tag diff --git a/payroll_rule_tag/tests/test_salary_rule_tag.py b/payroll_rule_tag/tests/test_salary_rule_tag.py new file mode 100644 index 000000000..050eb3c1e --- /dev/null +++ b/payroll_rule_tag/tests/test_salary_rule_tag.py @@ -0,0 +1,53 @@ +from odoo.addons.payroll.tests.common import TestPayslipBase + + +class TestPayslipSalaryTable(TestPayslipBase): + def setUp(self): + super().setUp() + + self.tag_fixed = self.env["hr.salary.rule.tag"].create({"name": "FixedPay"}) + self.tag_taxable = self.env["hr.salary.rule.tag"].create({"name": "Taxable"}) + self.rule_basic.tag_ids = self.tag_fixed | self.tag_taxable + self.rule_commission.tag_ids = self.tag_taxable + + self.rule_fixed = self.SalaryRule.create( + { + "name": "Fixed Pay Total", + "code": "FIXED_TOTAL", + "sequence": 20, + "amount_select": "code", + "amount_python_compute": "result = tags.FIXEDPAY", + } + ) + self.rule_taxable = self.SalaryRule.create( + { + "name": "Taxable Total", + "code": "TAXABLE_TOTAL", + "sequence": 21, + "amount_select": "code", + "amount_python_compute": "result = tags.TAXABLE", + } + ) + self.developer_pay_structure.rule_ids |= self.rule_fixed | self.rule_taxable + + def _get_amount(self, payslip, code): + line = payslip.line_ids.filtered(lambda record: record.code == code) + return line.amount + + def test_salary_rule_tags(self): + payslip = self.Payslip.create( + { + "employee_id": self.richard_emp.id, + "contract_id": self.richard_contract.id, + } + ) + payslip.onchange_employee() + payslip.compute_sheet() + + basic = self._get_amount(payslip, "BASIC") + commission = self._get_amount(payslip, "SALE") + fixed = self._get_amount(payslip, "FIXED_TOTAL") + taxable = self._get_amount(payslip, "TAXABLE_TOTAL") + + self.assertEqual(fixed, basic) + self.assertEqual(taxable, basic + commission) diff --git a/payroll_rule_tag/views/hr_salary_rule.xml b/payroll_rule_tag/views/hr_salary_rule.xml new file mode 100644 index 000000000..a6fb105ff --- /dev/null +++ b/payroll_rule_tag/views/hr_salary_rule.xml @@ -0,0 +1,11 @@ + + + hr.salary.rule + + + + + + + + diff --git a/payroll_rule_tag/views/hr_salary_rule_tag.xml b/payroll_rule_tag/views/hr_salary_rule_tag.xml new file mode 100644 index 000000000..4f7c79ddc --- /dev/null +++ b/payroll_rule_tag/views/hr_salary_rule_tag.xml @@ -0,0 +1,32 @@ + + + hr.salary.rule.tag + + + + + + + + + hr.salary.rule.tag + +
+ + +
+
+ + + Salary Rule Tags + hr.salary.rule.tag + list,form + + + +