import json
import logging
from markupsafe import Markup
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
class PosOrder(models.Model):
_inherit = 'pos.order'
x_logged_cancellations = fields.Text(string='Logged Cancellations', default='[]')
@api.model
def _process_order(self, order, existing_order):
# Extract frontend cancelled lines log
cancelled_lines = order.get('x_cancelled_lines', [])
res = super()._process_order(order, existing_order)
pos_order = self.browse(res) if isinstance(res, int) else res
if pos_order and cancelled_lines:
pos_order._log_cancelled_lines_to_chatter(cancelled_lines)
return res
def _log_cancelled_lines_to_chatter(self, cancelled_lines):
self.ensure_one()
try:
logged_ids = json.loads(self.x_logged_cancellations or '[]')
except Exception:
logged_ids = []
new_cancellations = []
for cancel in cancelled_lines:
cancel_id = cancel.get('id')
if cancel_id and cancel_id not in logged_ids:
new_cancellations.append(cancel)
logged_ids.append(cancel_id)
if new_cancellations:
body = "Product Cancellation/Reduction Log:
"
for cancel in new_cancellations:
if cancel.get('action') == 'delete':
action_str = f"Deleted completely (quantity was {cancel.get('qty', 0)})"
else:
action_str = f"Reduced quantity by {cancel.get('cancelled_qty', 0)} (from {cancel.get('qty', 0)} to {float(cancel.get('qty', 0)) - float(cancel.get('cancelled_qty', 0))})"
body += f"- {cancel.get('product_name')}: {action_str} by {cancel.get('employee_name', 'Unknown')}
"
body += "
"
self.message_post(body=Markup(body))
self.write({'x_logged_cancellations': json.dumps(logged_ids)})
def action_pos_order_cancel(self):
# Capture the cashier / employee who cancelled the order
employee_id = self.env.context.get('cancelled_by_employee_id')
employee_name = "Unknown Employee"
if employee_id:
employee = self.env['hr.employee'].browse(employee_id)
if employee.exists():
employee_name = employee.name
else:
employee_name = self.env.user.name
# Flush any pending x_cancelled_lines that were accumulated on the
# frontend but never sent via _process_order (i.e. the order was
# cancelled without payment). The JS passes these as a dict keyed by
# order ID: { "": [...cancelled_lines...] }
cancelled_lines_map = self.env.context.get('cancelled_lines_map', {})
res = super().action_pos_order_cancel()
for order in self:
order.message_post(body=Markup("Order Cancelled by {}").format(employee_name))
# Log any frontend-tracked line deletions/reductions that happened
# before this cancellation. Use str(order.id) as the JS context key
# since JSON object keys are always strings.
pending_lines = cancelled_lines_map.get(str(order.id)) or cancelled_lines_map.get(order.id)
if pending_lines:
order._log_cancelled_lines_to_chatter(pending_lines)
# Prevent Odoo backend warning regarding custom action properties
if isinstance(res, dict) and 'type' not in res:
res['type'] = ''
return res
def _prepare_pos_log(self, body):
if self.employee_id and hasattr(self.employee_id, 'pos_role') and self.employee_id.pos_role:
role_selection = dict(self.env['hr.employee']._fields['pos_role'].selection)
role_name = role_selection.get(self.employee_id.pos_role, "Cashier")
return body + Markup("
") + f"{role_name} {self.employee_id.name}"
return super()._prepare_pos_log(body)