[16.0][ADD] hr_personal_equipment_request_sign_oca: new module#127
[16.0][ADD] hr_personal_equipment_request_sign_oca: new module#127quirino95 wants to merge 1 commit into
Conversation
78020a4 to
0b98f6d
Compare
| @@ -0,0 +1,34 @@ | |||
| <?xml version="1.0" encoding="UTF-8" ?> | |||
| <odoo> | |||
There was a problem hiding this comment.
suggestion (non-blocking):
I think it could be useful to have these as actual data, not just demo data, maybe with a noupdate, what do you think?
There was a problem hiding this comment.
Why not, it helps save time in configuration. Applied changes.
There was a problem hiding this comment.
Both files are still in the demo key in the manifest (and also still in the demo folder)
There was a problem hiding this comment.
Moved files correctly, thank you.
| def _compute_personal_equipment_request_id(self): | ||
| for item in self.filtered( | ||
| lambda x: x.record_ref | ||
| and x.record_ref._name == "hr.personal.equipment.request" | ||
| ): | ||
| item.personal_equipment_request_id = item.record_ref.id | ||
|
|
||
| @api.depends("record_ref") | ||
| def _compute_employee_id(self): | ||
| for item in self.filtered( | ||
| lambda x: x.record_ref | ||
| and x.record_ref._name == "hr.personal.equipment.request" | ||
| ): | ||
| equipment_request = self.env["hr.personal.equipment.request"].browse( | ||
| item.record_ref.id | ||
| ) | ||
| item.employee_id = equipment_request.employee_id |
There was a problem hiding this comment.
suggestion: since these are so similar and so strictly linked, they could be merged into a single compute for both fields
| compute_sudo=True, | ||
| readonly=True, |
There was a problem hiding this comment.
chore: fields that are computed are also readonly by default. If they are stored, they are also compute_sudo by default
| old_employee_id = self.employee_id | ||
| new_employee_id = vals.get("employee_id") | ||
| res = super().write(vals) | ||
| if new_employee_id and new_employee_id != old_employee_id.id: | ||
| self._generate_sign_oca_request() | ||
| return res |
There was a problem hiding this comment.
issue: Careful! self can be a recordset with multiple records
suggestion:
| old_employee_id = self.employee_id | |
| new_employee_id = vals.get("employee_id") | |
| res = super().write(vals) | |
| if new_employee_id and new_employee_id != old_employee_id.id: | |
| self._generate_sign_oca_request() | |
| return res | |
| if employee_id in vals: | |
| old_employees = {rec.id: rec.employee_id for rec in self} | |
| res = super().write(vals) | |
| if employee_id in vals: | |
| for rec in self: | |
| if rec.employee_id and rec.employee_id != old_employees.get(rec.id): | |
| rec._generate_sign_oca_request() | |
| return res |
0b98f6d to
9f12bae
Compare
| personal_equipment_request_sign_oca_template_id = fields.Many2one( | ||
| comodel_name="sign.oca.template", | ||
| related="company_id.personal_equipment_request_sign_oca_template_id", | ||
| string="Personal Equipment Request Sign Oca Template", |
There was a problem hiding this comment.
chore: In a related field, there's no need to re-declare any fields that are the same as the original field (like this string here)
| ) | ||
|
|
||
| @api.depends("record_ref") | ||
| def _compute_fields(self): |
There was a problem hiding this comment.
issue: let's use a more descriptive name.
suggestion: Maybe employee_id could simply be a related field personal_equipment_request_id.employee_id, so this compute could be even simpler?
Also, maybe it could be called ppe_employee_id to make it clear it's linked to the ppe request? (I'm imagining a scenario with multiple sign related modules
There was a problem hiding this comment.
Renamed employee_id in ppe_employee_id (now it's a related field) and _compute_fields in _compute_personal_equipment_request_id.
| for item in self.filtered( | ||
| lambda x: x.record_ref | ||
| and x.record_ref._name == "hr.personal.equipment.request" | ||
| ): | ||
| item.personal_equipment_request_id = item.record_ref.id | ||
| equipment_request = self.env["hr.personal.equipment.request"].browse( | ||
| item.record_ref.id | ||
| ) | ||
| item.employee_id = equipment_request.employee_id |
There was a problem hiding this comment.
question: Should we explicitly set personal_requipment_request_id to False if record_ref is not a ppe request?
| for item in self.filtered( | |
| lambda x: x.record_ref | |
| and x.record_ref._name == "hr.personal.equipment.request" | |
| ): | |
| item.personal_equipment_request_id = item.record_ref.id | |
| equipment_request = self.env["hr.personal.equipment.request"].browse( | |
| item.record_ref.id | |
| ) | |
| item.employee_id = equipment_request.employee_id | |
| for item in self: | |
| if item.record_ref and item._record_ref._name == "hr.personal.equipment.request": | |
| item.personal_equipment_request_id = item.record_ref.id | |
| equipment_request = self.env["hr.personal.equipment.request"].browse( | |
| item.record_ref.id | |
| ) | |
| item.employee_id = equipment_request.employee_id | |
| else: | |
| item.personal_equipment_request_id = False |
There was a problem hiding this comment.
I don't know but is okay to explicit (code is also more readable).
| for item in res: | ||
| item._generate_sign_oca_request() |
There was a problem hiding this comment.
chore: _generate_sign_oca_request is safe for recordsets
| for item in res: | |
| item._generate_sign_oca_request() | |
| res._generate_sign_oca_request() |
| sign_request_count = fields.Integer( | ||
| string="Sign request count", | ||
| compute="_compute_sign_request_count", | ||
| compute_sudo=True, |
There was a problem hiding this comment.
suggestion (non-blocking):
| compute_sudo=True, | |
| store=True, |
| <table class="table table-condensed"> | ||
| <tbody> | ||
| <div> | ||
| <t> |
| <tr> | ||
| <th> | ||
| Product | ||
| </th> | ||
| <th> | ||
| Quantity | ||
| </th> | ||
| <th> | ||
| Indications | ||
| </th> | ||
| </tr> |
There was a problem hiding this comment.
chore: please fix the indentation here
| requests = self.request_1 + self.request_2 | ||
| wizard_form = Form( | ||
| self.env["sign.oca.template.generate.multi"].with_context( | ||
| default_model="hr.personal.equipment.request", active_ids=requests.ids | ||
| ) | ||
| ) | ||
| wizard_form.template_id = self.template | ||
| action = wizard_form.save().generate() | ||
| requests = self.env[action["res_model"]].search(action["domain"]) |
There was a problem hiding this comment.
chore (non-blocking): it's somewhat confusing to reuse the variable requests for two different objects. Also below we have request_1 which is not quite the same thing as self.request_1.
I would be grateful for more descriptive and distinct variable names.
| self.assertIn(self.partner_1, request_1.mapped("signer_ids.partner_id")) | ||
| self.assertIn(self.partner_2, request_2.mapped("signer_ids.partner_id")) |
There was a problem hiding this comment.
question: shouldn't this be
| self.assertIn(self.partner_1, request_1.mapped("signer_ids.partner_id")) | |
| self.assertIn(self.partner_2, request_2.mapped("signer_ids.partner_id")) | |
| self.assertEqual(self.partner_1, request_1.mapped("signer_ids.partner_id")) | |
| self.assertEqual(self.partner_2, request_2.mapped("signer_ids.partner_id")) |
| def test_personal_equipment_request_write(self): | ||
| # flake8: noqa: B950 | ||
| self.company_id.personal_equipment_request_sign_oca_template_id = self.template | ||
| self.request_1.write({"employee_id": False}) |
There was a problem hiding this comment.
suggestion (non-blocking):
I personally prefer using object-oriented assignments whenever possible. Seems easier to read and to write
| self.request_1.write({"employee_id": False}) | |
| self.request_1.employee_id = False |
06148e7 to
9fe93b3
Compare
|
There hasn't been any activity on this pull request in the past 4 months, so it has been marked as stale and it will be closed automatically if no further activity occurs in the next 30 days. |
|
@OCA/document-maintainers Hello, we've been using this in production for a few months now, could you please take a look? Thanks! |
9fe93b3 to
94346c7
Compare
| # flake8: noqa: B950 | ||
| self.company_id.personal_equipment_request_sign_oca_template_id = self.template |
There was a problem hiding this comment.
note: There is an identical line in the previous test that does not need this comment, maybe it can be removed?
| } | ||
| ) | ||
|
|
||
| cls.user_1 = cls.env["res.users"].create( |
There was a problem hiding this comment.
suggestion: Executing the tests logs the following ERROR log:
2026-05-11 08:39:30,494 320 ERROR odoo odoo.addons.mail.models.mail_mail: failed sending mail (id: 44) due to You must either provide a sender address explicitly or configure using the combination of `mail.catchall.domain` and `mail.default.from` ICPs, in the server configuration file or with the --email-from startup parameter.
Traceback (most recent call last):
File "/opt/odoo/addons/mail/models/mail_mail.py", line 555, in _send
msg = IrMailServer.build_email(
File "/opt/odoo/odoo/addons/base/models/ir_mail_server.py", line 489, in build_email
assert email_from, "You must either provide a sender address explicitly or configure "\
AssertionError: You must either provide a sender address explicitly or configure using the combination of `mail.catchall.domain` and `mail.default.from` ICPs, in the server configuration file or with the --email-from startup parameter.
(from https://github.com/OCA/sign/actions/runs/25659492416/job/75316125546?pr=127#step:8:99)
I think it is due to these users' creation, please have a look at mail_new_test_user (https://github.com/odoo/odoo/blob/fa0a4e7bc272105cad210867c4ddd46a5326cd22/addons/mail/tests/common.py#L28).
There was a problem hiding this comment.
note: I see that in other modules of this repo these files are in the demo folder, but I agree it makes sense to have these as data files instead.
Maybe one of @OCA/document-maintainers can clarify if we are missing something?
| <odoo> | ||
| <template id="ppe_sign_report_template"> | ||
| <t t-call="web.html_container"> | ||
| <t t-foreach="docs" t-as="doc" t-if="doc.contains_ppe"> | ||
| <t t-call="web.external_layout"> | ||
| <div class="page"> | ||
| <h3 | ||
| class="text-center" | ||
| >Receipt of Personal Protection Equipment</h3> | ||
| <br /> | ||
| <p> | ||
| I, the undersigned accept the PPE that my employer is providing to me. I further acknowledge that I have been instructed | ||
| in how to wear and maintain it. If it is lost or damaged, I will report the same to my employer promptly for replacement. | ||
| </p> | ||
| <p> | ||
| I agree to wear the equipment when facing the exposure it is designed to protect against. | ||
| I acknowledge that my failure to do so may subject me to disciplinary action. | ||
| </p> |
There was a problem hiding this comment.
suggestion: This is the kind of record that the user might want to edit, please consider adding a noupdate.
| "hr_personal_equipment_request_sign_oca.ppe_sign_report_template" | ||
| ) | ||
| pdf = report._render_qweb_pdf(report.report_name, [self.id])[0] | ||
| request_dict.update({"data": base64.b64encode(pdf)}) |
There was a problem hiding this comment.
issue: If the user assigns another PDF to the template, here we are overwriting that value: this is definitely unexpected.
Since we have implemented a nice template, I think we can just assign it to the data field of the record sign_oca_template_personal_equipment_request_demo.
On a second look, this is probably needed to obtain the dynamic behavior of the template, but it is still unexpected: please at least document it or show a warning in the sign template's form.
This dynamic behavior would be a nice addition to the sign_oca module itself 🚀
For instance: you might add the option to link a view instead of a static PDF in the template, and that view would be rendered using the current record.
| 1. Go to Sign > Settings > Roles and create a role with the following data: | ||
|
|
||
| - Partner type: Expression | ||
| - Expression: {{object.employee_id.user_id.partner_id.id}} | ||
|
|
||
| 2. Go to Sign > Templates and create a template with the following data: | ||
|
|
||
| - Model: "This model allows to create a personal equipment request" | ||
| - In one of the fields, you must set the previously created role. |
There was a problem hiding this comment.
note: In this module these records are already created, please mention the existing records here.
| ppe_employee_id = fields.Many2one( | ||
| comodel_name="hr.employee", | ||
| related="personal_equipment_request_id.employee_id", | ||
| store=True, | ||
| ) |
There was a problem hiding this comment.
suggestion: This would allow to change the PPE request's employee from the sign request and I think we don't want that, what do you think?
If this is not needed, please add a readonly attribute.
| "summary": """Hr Personal Equipment Request Sign Oca""", | ||
| "version": "16.0.1.0.0", | ||
| "license": "AGPL-3", | ||
| "author": "PyTech SRL, Odoo Community Association (OCA)", |
There was a problem hiding this comment.
suggestion: We should adopt this new module so we don't burden the PSC in the future.
|
|


New module to allow to generate manual and automatic signature requests from Personal Equipment Request.
Instead of a "static" template, every time a new Personal Equipment Request is created, PPE receipt document is generated and attached in the signature request.
Also, the PPE receipt report template in this module is overwritten, in order to keep the space for signature always in the same position.