Tower: upload laundry_management 19.0.19.0.4 (via marketplace)
This commit is contained in:
126
addons/laundry_management/wizard/laundry_order_unlock_wizard.py
Normal file
126
addons/laundry_management/wizard/laundry_order_unlock_wizard.py
Normal file
@@ -0,0 +1,126 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError, AccessError
|
||||
|
||||
|
||||
class LaundryOrderUnlockWizard(models.TransientModel):
|
||||
"""Manager-only wizard that grants a TIMED unlock window on a
|
||||
locked laundry.order.
|
||||
|
||||
The wizard does not unlock the order permanently — it sets
|
||||
`manager_unlocked_until` to a future timestamp. The lock compute
|
||||
on `laundry.order.locked` re-evaluates against `now()` on every
|
||||
read, so once the window passes the order auto-relocks. No manual
|
||||
re-lock action is needed; nothing can drift.
|
||||
|
||||
Audit trail:
|
||||
- reason is logged as a chatter note via mail.message
|
||||
- manager_unlocked_by stores the actor
|
||||
- manager_unlock_reason stores the rationale on the order itself
|
||||
"""
|
||||
_name = 'laundry.order.unlock.wizard'
|
||||
_description = 'Unlock Laundry Order for Editing'
|
||||
|
||||
DEFAULT_DURATION = 30 # minutes
|
||||
MAX_DURATION = 240 # minutes (4 hours)
|
||||
|
||||
order_id = fields.Many2one(
|
||||
'laundry.order', string='Order',
|
||||
required=True, readonly=True, ondelete='cascade',
|
||||
)
|
||||
order_name = fields.Char(
|
||||
related='order_id.name', readonly=True,
|
||||
)
|
||||
order_source_type = fields.Selection(
|
||||
related='order_id.source_type', readonly=True,
|
||||
)
|
||||
order_state = fields.Selection(
|
||||
related='order_id.state', readonly=True,
|
||||
)
|
||||
duration_minutes = fields.Integer(
|
||||
string='Unlock For (minutes)',
|
||||
default=DEFAULT_DURATION,
|
||||
required=True,
|
||||
help='How long the order will accept edits before re-locking '
|
||||
'automatically. Capped at %s minutes.' % MAX_DURATION,
|
||||
)
|
||||
reason = fields.Char(
|
||||
string='Reason',
|
||||
required=True,
|
||||
help='Logged in the chatter for audit. Required.',
|
||||
)
|
||||
|
||||
@api.constrains('duration_minutes')
|
||||
def _check_duration(self):
|
||||
for wiz in self:
|
||||
if wiz.duration_minutes < 1:
|
||||
raise UserError(_('Unlock duration must be at least 1 minute.'))
|
||||
if wiz.duration_minutes > self.MAX_DURATION:
|
||||
raise UserError(_(
|
||||
'Unlock duration is capped at %s minutes.',
|
||||
self.MAX_DURATION,
|
||||
))
|
||||
|
||||
def action_unlock(self):
|
||||
self.ensure_one()
|
||||
if not self.env.user.has_group(
|
||||
'laundry_management.group_laundry_manager_override'
|
||||
):
|
||||
raise AccessError(_(
|
||||
'Only users with the "Laundry / Manager Override" '
|
||||
'privilege can unlock locked orders.'
|
||||
))
|
||||
if not self.reason or not self.reason.strip():
|
||||
raise UserError(_('A reason is required to unlock an order.'))
|
||||
|
||||
unlock_until = (
|
||||
fields.Datetime.now() + timedelta(minutes=self.duration_minutes)
|
||||
)
|
||||
# The wizard writes only manager_unlocked_* fields, which are NOT
|
||||
# in LOCKED_HEADER_FIELDS — the lock guard is a no-op for this
|
||||
# write, so we don't need a context bypass.
|
||||
self.order_id.sudo().write({
|
||||
'manager_unlocked_until': unlock_until,
|
||||
'manager_unlocked_by': self.env.user.id,
|
||||
'manager_unlock_reason': self.reason.strip(),
|
||||
})
|
||||
self.order_id.message_post(
|
||||
body=_(
|
||||
'Order unlocked for %(minutes)s minutes by '
|
||||
'<strong>%(user)s</strong>. Re-locks automatically at '
|
||||
'%(until)s.<br/><strong>Reason:</strong> %(reason)s',
|
||||
minutes=self.duration_minutes,
|
||||
user=self.env.user.name,
|
||||
until=fields.Datetime.to_string(unlock_until),
|
||||
reason=self.reason.strip(),
|
||||
),
|
||||
subtype_xmlid='mail.mt_note',
|
||||
)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'laundry.order',
|
||||
'res_id': self.order_id.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
def action_relock_now(self):
|
||||
"""Optional: managers can also re-lock immediately (clears the
|
||||
unlock window early). Same access control as unlock."""
|
||||
self.ensure_one()
|
||||
if not self.env.user.has_group(
|
||||
'laundry_management.group_laundry_manager_override'
|
||||
):
|
||||
raise AccessError(_('Only override managers can re-lock.'))
|
||||
self.order_id.sudo().write({
|
||||
'manager_unlocked_until': False,
|
||||
})
|
||||
self.order_id.message_post(
|
||||
body=_(
|
||||
'Order re-locked early by <strong>%(user)s</strong>.',
|
||||
user=self.env.user.name,
|
||||
),
|
||||
subtype_xmlid='mail.mt_note',
|
||||
)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
Reference in New Issue
Block a user