Conversation
If multiple wizards are sending payrolls at the same time, every wizard acts in their own folder. Also add display name.
|
Hi @peluko00, |
| /> | ||
| <field name="arch" type="xml"> | ||
| <field name="payrolls" position="after"> | ||
| <field name="is_payroll_being_processed" invisible="True" /> |
There was a problem hiding this comment.
chore (non-blocking): my understanding is that this (and also is_send_visible below) don't need to be added manually anymore in v18, so they could be removed to simplify the view.
nimarosa
left a comment
There was a problem hiding this comment.
Thanks for forward porting this, the module itself is useful and the tests are a good shape (arrange/act/assert, trap_jobs, a real mail assertion). CI is green and it merges clean on 18.0, so this is close.
Two things I'd like sorted before it goes in.
The first is the /tmp handling in hr_payroll_document. The commit sells it as thread safe, and per-wizard folders do fix the case it was written for, but the path is still predictable and shared: payroll_management_wizard_5 is the same string in every database served by the same host, so two databases processing wizard id 5 at the same time still land in one folder. On top of that the payslips sit unencrypted in a world-writable directory (encryption is optional via no_payroll_encryption) and nothing ever removes them. tempfile.mkdtemp() plus cleanup gives you all three for less code, and you already point at tempfile in the description.
The second is smaller: this PR changes behaviour in hr_payroll_document (_rec_name, the temp path) without bumping its version. It's at 18.0.1.0.2 on 18.0 now, so 18.0.1.1.0 would be right. That module is maintained by @peluko00, who got pinged but hasn't looked yet, so I'd rather wait for that too before merging.
The rest below is small stuff.
|
|
||
| def _get_temp_path(self): | ||
| self.ensure_one() | ||
| path = f"/tmp/{self._table}_{self.id}/" |
There was a problem hiding this comment.
This is the one I'd really like changed. self._table and self.id are not unique across databases, so two databases on the same host still collide here, which is the case the commit says it fixes. And /tmp is world-writable with a guessable name, so anyone on the box can pre-create the directory (or symlink it) and read payslips that were written without encryption. Nothing deletes any of it either, so the files pile up until the host reboots.
tempfile.mkdtemp() gets you uniqueness, 0700, and a handle you can clean up, all at once:
import tempfile
@contextmanager
def _temp_dir(self):
self.ensure_one()
path = tempfile.mkdtemp(prefix=f"{self._table}_{self.id}_")
try:
yield path
finally:
shutil.rmtree(path, ignore_errors=True)and have send_payrolls hold the context for the whole run so merge_pdfs reuses the same directory.
| if btes[0:4] != b"%PDF": | ||
| raise ValidationError(self.env._("Missing pdf file signature")) | ||
| f = open("/tmp/" + file.name, "wb") | ||
| f = open(self._get_temp_path() + file.name, "wb") |
There was a problem hiding this comment.
You already have temp_path from line 109, use it here. Right now every loop iteration re-runs the mkdir for nothing, and if the path ever becomes per-call (which it would with mkdtemp) this line silently writes somewhere else than line 126 reads.
|
|
||
| { | ||
| "name": "HR - Payroll Document - Queue", | ||
| "summary": "Process a PDF payslip aynchronously.", |
There was a problem hiding this comment.
typo: aynchronously -> asynchronously
| ], | ||
| "depends": [ | ||
| "hr_payroll_document", | ||
| "queue_job", |
There was a problem hiding this comment.
Worth adding mail here. You call message_notify on mail.thread and read notification_type, and today that only works because hr happens to pull mail in. Cheap to make explicit.
| return result | ||
|
|
||
| def send_payrolls_async(self): | ||
| return self.with_delay().send_payrolls() |
There was a problem hiding this comment.
with_delay().send_payrolls() returns a Job object, and /web/dataset/call_button drops anything that isn't a dict (return False, dataset.py). So the user clicks Send Async, the dialog just closes, and nothing tells them the job was queued. Returning a display_notification here would match what the sync button already does:
def send_payrolls_async(self):
self.with_delay().send_payrolls()
return {
"type": "ir.actions.client",
"tag": "display_notification",
"params": {
"title": self.env._("Payrolls queued"),
"message": self.env._("You will be notified when they have been sent."),
"type": "info",
},
}| if not self.is_send_visible: | ||
| # We are already hiding the buttons in the UI, | ||
| # but this public method can still be executed. | ||
| raise exceptions.UserError(_("The selected payrolls cannot be processed.")) |
There was a problem hiding this comment.
self.env._() rather than the module level _, the 18.0 way. The base wizard you're inheriting already uses it everywhere, so this is the only odd one out.
| return self.with_delay().send_payrolls() | ||
|
|
||
| def _get_payrolls_being_processed(self): | ||
| jobs = self.env["queue.job"].search( |
There was a problem hiding this comment.
This search has no bound on it and runs on every form load of the wizard, so it walks every non-terminal job for this method and deserializes records for each one. Adding a company or create_uid leg would help, and matching on attachment ids instead of checksums would let you push the filter into the domain instead of doing it in Python.
The checksum match also reaches across companies (the field is compute_sudo), so uploading the same PDF in company A blocks company B. If that's deliberate a one line comment saying so would save the next reader the trip.
| compute="_compute_is_payroll_being_processed", | ||
| compute_sudo=True, | ||
| ) | ||
| is_send_visible = fields.Boolean( |
There was a problem hiding this comment.
This field is just not is_payroll_being_processed, and the view can express that on its own with invisible="is_payroll_being_processed". One less field on a public model, and one less compute per form load. The guard in send_payrolls can read is_payroll_being_processed directly.
Forward porting of the module already proposed in #252: