Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions hr_payroll_document/README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

=====================
HR - Payroll Document
=====================
Expand All @@ -17,7 +13,7 @@ HR - Payroll Document
.. |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/license-AGPL--3-blue.png
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpayroll-lightgray.png?logo=github
Expand Down
7 changes: 6 additions & 1 deletion hr_payroll_document/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
"version": "17.0.1.1.0",
"depends": ["hr", "base_vat"],
"maintainers": ["peluko00"],
"external_dependencies": {"python": ["pypdf"]},
"external_dependencies": {
"python": [
"pypdf",
"PyMuPDF",
],
},
"data": [
"wizard/payroll_management_wizard.xml",
"security/ir.model.access.csv",
Expand Down
28 changes: 20 additions & 8 deletions hr_payroll_document/models/hr_employee.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from odoo import _, fields, models
# Copyright 2025 Simone Rubino - PyTech
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import _, api, fields, models
from odoo.exceptions import ValidationError


Expand Down Expand Up @@ -43,10 +46,19 @@ def action_get_payroll_tree_view(self):
)
return action

def write(self, vals):
res = super().write(vals)
if "identification_id" in vals and not self.env["res.partner"].simple_vat_check(
self.env.company.country_id.code, vals["identification_id"]
):
raise ValidationError(_("The field identification ID is not valid"))
return res
def _validate_payroll_identification(self, code=None):
# Override if the identification should be validated in another way
if code is None and len(self) == 1:
code = self.identification_id
if country_code := self.env.company.country_id.code:
is_valid = self.env["res.partner"].simple_vat_check(country_code, code)
else:
is_valid = True
return is_valid

@api.constrains("identification_id")
def _constrain_payroll_identification(self):
# Only check the employees that have an `identification_id`
for employee in self.filtered("identification_id"):
if not employee._validate_payroll_identification():
raise ValidationError(_("The field identification ID is not valid"))
24 changes: 9 additions & 15 deletions hr_payroll_document/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>README.rst</title>
<title>HR - Payroll Document</title>
<style type="text/css">

/*
Expand Down Expand Up @@ -360,21 +360,16 @@
</style>
</head>
<body>
<div class="document">
<div class="document" id="hr-payroll-document">
<h1 class="title">HR - Payroll Document</h1>


<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="hr-payroll-document">
<h1>HR - Payroll Document</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:366831f17e07580dd04532243d06b24e288d5e2ec72505098b58d057f8b3d2a2
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/payroll/tree/17.0/hr_payroll_document"><img alt="OCA/payroll" src="https://img.shields.io/badge/github-OCA%2Fpayroll-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/payroll-17-0/payroll-17-0-hr_payroll_document"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/payroll&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/payroll/tree/17.0/hr_payroll_document"><img alt="OCA/payroll" src="https://img.shields.io/badge/github-OCA%2Fpayroll-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/payroll-17-0/payroll-17-0-hr_payroll_document"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/payroll&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module have a wizard view to manage the different payrolls of
employees which is identified by the identification_id attribute.</p>
<p>By default, the employee’s payroll is encrypted using their
Expand All @@ -393,23 +388,23 @@ <h1>HR - Payroll Document</h1>
</ul>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-1">Bug Tracker</a></h2>
<h1><a class="toc-backref" href="#toc-entry-1">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/payroll/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/payroll/issues/new?body=module:%20hr_payroll_document%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-2">Credits</a></h2>
<h1><a class="toc-backref" href="#toc-entry-2">Credits</a></h1>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-3">Authors</a></h3>
<h2><a class="toc-backref" href="#toc-entry-3">Authors</a></h2>
<ul class="simple">
<li>APSL</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-4">Contributors</a></h3>
<h2><a class="toc-backref" href="#toc-entry-4">Contributors</a></h2>
<ul class="simple">
<li><a class="reference external" href="https://www.apsl.tech">APSL-Nagarro</a>:<ul>
<li>Antoni Marroig &lt;<a class="reference external" href="mailto:amarroig&#64;apsl.net">amarroig&#64;apsl.net</a>&gt;</li>
Expand All @@ -423,7 +418,7 @@ <h3><a class="toc-backref" href="#toc-entry-4">Contributors</a></h3>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-5">Maintainers</a></h3>
<h2><a class="toc-backref" href="#toc-entry-5">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
Expand All @@ -438,6 +433,5 @@ <h3><a class="toc-backref" href="#toc-entry-5">Maintainers</a></h3>
</div>
</div>
</div>
</div>
</body>
</html>
71 changes: 71 additions & 0 deletions hr_payroll_document/tests/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import contextlib
from unittest import mock

from odoo.tests import common
from odoo.tools.misc import file_path as open_file_path

from odoo.addons.mail.tests.common import mail_new_test_user


class TestHrPayrollDocument(common.TransactionCase):
def setUp(self):
super().setUp()
self.env.user.tz = "Europe/Brussels"
self.user_admin = self.env.ref("base.user_admin")

# Fix Company without country
self.env.company.country_id = False

# Test users to use through the various tests
self.user_employee = mail_new_test_user(
self.env, login="david", groups="base.group_user"
)
self.user_employee_id = self.user_employee.id

# Hr Data
self.employee_emp = self.env["hr.employee"].create(
{
"name": "David Employee",
"user_id": self.user_employee_id,
"company_id": 1,
"identification_id": "30831011V",
}
)

self.wizard = self._create_wizard(
"January", "hr_payroll_document/tests/test.pdf"
)

def _create_wizard(self, subject, file_path):
with open(open_file_path(file_path), "rb") as pdf_file:
encoded_string = base64.b64encode(pdf_file.read())
ir_values = {
"name": "test",
"type": "binary",
"datas": encoded_string,
"store_fname": encoded_string,
"res_model": "payroll.management.wizard",
"res_id": 1,
}
self.attachment = self.env["ir.attachment"].create(ir_values)
self.subject = subject
return self.env["payroll.management.wizard"].create(
{"payrolls": [self.attachment.id], "subject": self.subject}
)

@contextlib.contextmanager
def _mock_valid_identification(self, employee, identification_code):
def _mocked_validate_payroll_identification(self, code=None):
if code is None:
code = employee.identification_id
return code == identification_code

with mock.patch.object(
type(employee),
"_validate_payroll_identification",
_mocked_validate_payroll_identification,
) as patch:
patch.side_effect = _mocked_validate_payroll_identification
yield
Binary file added hr_payroll_document/tests/test_broken_image.pdf
Binary file not shown.
77 changes: 19 additions & 58 deletions hr_payroll_document/tests/test_hr_payroll_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,78 +5,39 @@

from odoo import _
from odoo.exceptions import UserError, ValidationError
from odoo.tests import common
from odoo.tools.misc import file_path

from odoo.addons.mail.tests.common import mail_new_test_user
from odoo.addons.hr_payroll_document.tests.common import TestHrPayrollDocument


class TestHRPayrollDocument(common.TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env.user.tz = "Europe/Brussels"
cls.user_admin = cls.env.ref("base.user_admin")

# Fix Company without country
cls.env.company.country_id = False

# Test users to use through the various tests
cls.user_employee = mail_new_test_user(
cls.env, login="david", groups="base.group_user"
)
cls.user_employee_id = cls.user_employee.id

# Hr Data
cls.employee_emp = cls.env["hr.employee"].create(
{
"name": "David Employee",
"user_id": cls.user_employee_id,
"company_id": 1,
"identification_id": "30831011V",
}
)

with open(file_path("hr_payroll_document/tests/test.pdf"), "rb") as pdf_file:
encoded_string = base64.b64encode(pdf_file.read())
ir_values = {
"name": "test",
"type": "binary",
"datas": encoded_string,
"store_fname": encoded_string,
"res_model": "payroll.management.wizard",
"res_id": 1,
}
cls.attachment = cls.env["ir.attachment"].create(ir_values)
cls.subject = "January"
cls.wizard = cls.env["payroll.management.wizard"].create(
{"payrolls": [cls.attachment.id], "subject": cls.subject}
)
class TestHRPayrollDocument(TestHrPayrollDocument):
def setUp(self, *args, **kwargs):
super().setUp(*args, **kwargs)

def fill_company_id(self):
self.env.company.country_id = self.env["res.country"].search(
[("name", "=", "Spain")]
)

def test_extension_error(self):
with open(file_path("hr_payroll_document/tests/test.docx"), "rb") as pdf_file:
encoded_string = base64.b64encode(pdf_file.read())
ir_values = {
"name": "test",
"type": "binary",
"datas": encoded_string,
"store_fname": encoded_string,
"res_model": "payroll.management.wizard",
"res_id": 1,
}
self.attachment = self.env["ir.attachment"].create(ir_values)
self.subject = "January"
self.wizard = self.env["payroll.management.wizard"].create(
{"payrolls": [self.attachment.id], "subject": self.subject}
self.wizard = self._create_wizard(
"January", "hr_payroll_document/tests/test.docx"
)
with self.assertRaises(ValidationError):
self.wizard.send_payrolls()

def test_pdf_broken_image(self):
"""If the PDF cannot be processed with PyPDF, try with another reader."""
self.fill_company_id()
identification_code = "xXXXXXXXXXXXXXXX"
with self._mock_valid_identification(self.employee_emp, identification_code):
self.employee_emp.identification_id = identification_code
self.wizard = self._create_wizard(
"Subject", "hr_payroll_document/tests/test_broken_image.pdf"
)
with self._mock_valid_identification(self.employee_emp, identification_code):
result_action = self.wizard.send_payrolls()
self.assertEqual(result_action["params"]["title"], _("Payrolls sent"))

def test_company_id_required(self):
with self.assertRaises(UserError):
self.wizard.send_payrolls()
Expand Down
Loading