Rental orders no longer show the "Authorizer Required?" question or the Authorizer field. The sale type is automatically set to 'Rentals' when creating or confirming a rental order. Validation logic also skips authorizer checks for rental sale type. Made-with: Cursor
220 lines
7.9 KiB
Python
220 lines
7.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Ready for Delivery Wizard
|
|
Wizard to assign delivery technicians and mark order as Ready for Delivery.
|
|
"""
|
|
|
|
from odoo import models, fields, api, _
|
|
from odoo.exceptions import UserError
|
|
from markupsafe import Markup
|
|
import logging
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ReadyForDeliveryWizard(models.TransientModel):
|
|
_name = 'fusion.ready.for.delivery.wizard'
|
|
_description = 'Ready for Delivery Wizard'
|
|
|
|
sale_order_id = fields.Many2one(
|
|
'sale.order',
|
|
string='Sale Order',
|
|
required=True,
|
|
readonly=True,
|
|
)
|
|
partner_id = fields.Many2one(
|
|
'res.partner',
|
|
string='Client',
|
|
related='sale_order_id.partner_id',
|
|
readonly=True,
|
|
)
|
|
claim_number = fields.Char(
|
|
string='Claim Number',
|
|
related='sale_order_id.x_fc_claim_number',
|
|
readonly=True,
|
|
)
|
|
is_early_delivery = fields.Boolean(
|
|
string='Early Delivery',
|
|
related='sale_order_id.x_fc_early_delivery',
|
|
readonly=True,
|
|
help='This delivery is before ADP approval',
|
|
)
|
|
current_status = fields.Selection(
|
|
related='sale_order_id.x_fc_adp_application_status',
|
|
string='Current Status',
|
|
readonly=True,
|
|
)
|
|
|
|
# Technician Assignment
|
|
technician_ids = fields.Many2many(
|
|
'res.users',
|
|
'ready_delivery_wizard_technician_rel',
|
|
'wizard_id',
|
|
'user_id',
|
|
string='Delivery Technicians',
|
|
required=True,
|
|
help='Select one or more field technicians for this delivery',
|
|
)
|
|
|
|
# Scheduling
|
|
scheduled_datetime = fields.Datetime(
|
|
string='Scheduled Delivery Date/Time',
|
|
help='Optional: When is the delivery scheduled?',
|
|
)
|
|
|
|
# Delivery Address
|
|
delivery_address = fields.Text(
|
|
string='Delivery Address',
|
|
compute='_compute_delivery_address',
|
|
readonly=True,
|
|
)
|
|
|
|
# Notes
|
|
notes = fields.Text(
|
|
string='Delivery Notes',
|
|
help='Any special instructions for the delivery technicians',
|
|
)
|
|
|
|
@api.depends('sale_order_id')
|
|
def _compute_delivery_address(self):
|
|
"""Compute delivery address from partner shipping address."""
|
|
for wizard in self:
|
|
order = wizard.sale_order_id
|
|
if order and order.partner_shipping_id:
|
|
addr = order.partner_shipping_id
|
|
parts = [addr.street, addr.street2, addr.city, addr.state_id.name if addr.state_id else '', addr.zip]
|
|
wizard.delivery_address = ', '.join([p for p in parts if p])
|
|
elif order and order.partner_id:
|
|
addr = order.partner_id
|
|
parts = [addr.street, addr.street2, addr.city, addr.state_id.name if addr.state_id else '', addr.zip]
|
|
wizard.delivery_address = ', '.join([p for p in parts if p])
|
|
else:
|
|
wizard.delivery_address = ''
|
|
|
|
def action_confirm(self):
|
|
"""Confirm and mark as Ready for Delivery."""
|
|
self.ensure_one()
|
|
order = self.sale_order_id
|
|
|
|
if not self.technician_ids:
|
|
raise UserError(_("Please select at least one delivery technician."))
|
|
|
|
# Get technician names for chatter message
|
|
technician_names = ', '.join(self.technician_ids.mapped('name'))
|
|
user_name = self.env.user.name
|
|
|
|
# Update the sale order
|
|
order.with_context(skip_status_validation=True).write({
|
|
'x_fc_adp_application_status': 'ready_delivery',
|
|
'x_fc_delivery_technician_ids': [(6, 0, self.technician_ids.ids)],
|
|
'x_fc_ready_for_delivery_date': fields.Datetime.now(),
|
|
'x_fc_scheduled_delivery_datetime': self.scheduled_datetime,
|
|
})
|
|
|
|
# Build chatter message
|
|
scheduled_str = ''
|
|
if self.scheduled_datetime:
|
|
scheduled_str = f'<li><strong>Scheduled:</strong> {self.scheduled_datetime.strftime("%B %d, %Y at %I:%M %p")}</li>'
|
|
|
|
notes_str = ''
|
|
if self.notes:
|
|
notes_str = f'<hr><p class="mb-0"><strong>Delivery Notes:</strong> {self.notes}</p>'
|
|
|
|
early_badge = ''
|
|
if self.is_early_delivery:
|
|
early_badge = ' <span class="badge bg-warning text-dark">Early Delivery</span>'
|
|
|
|
chatter_body = Markup(f'''
|
|
<div class="alert alert-success" role="alert">
|
|
<h5 class="alert-heading"><i class="fa fa-truck"></i> Ready for Delivery{early_badge}</h5>
|
|
<ul>
|
|
<li><strong>Marked By:</strong> {user_name}</li>
|
|
<li><strong>Technician(s):</strong> {technician_names}</li>
|
|
{scheduled_str}
|
|
<li><strong>Delivery Address:</strong> {self.delivery_address or 'N/A'}</li>
|
|
</ul>
|
|
{notes_str}
|
|
</div>
|
|
''')
|
|
|
|
order.message_post(
|
|
body=chatter_body,
|
|
message_type='notification',
|
|
subtype_xmlid='mail.mt_note',
|
|
)
|
|
|
|
# Send email notifications
|
|
order._send_ready_for_delivery_email(
|
|
technicians=self.technician_ids,
|
|
scheduled_datetime=self.scheduled_datetime,
|
|
notes=self.notes,
|
|
)
|
|
|
|
# Auto-create technician tasks for each assigned technician
|
|
self._create_technician_tasks(order)
|
|
|
|
return {'type': 'ir.actions.act_window_close'}
|
|
|
|
def _create_technician_tasks(self, order):
|
|
"""Create a single delivery task with lead + additional technicians.
|
|
|
|
The first selected technician becomes the lead. Any remaining
|
|
technicians are assigned as additional technicians on the same task.
|
|
|
|
The task model's create() method auto-populates address fields
|
|
from the linked sale order's shipping address when address_street
|
|
is not explicitly provided, so we intentionally omit address here.
|
|
"""
|
|
Task = self.env['fusion.technician.task']
|
|
scheduled_date = False
|
|
time_start = 9.0
|
|
|
|
if self.scheduled_datetime:
|
|
scheduled_date = self.scheduled_datetime.date()
|
|
time_start = self.scheduled_datetime.hour + (self.scheduled_datetime.minute / 60.0)
|
|
else:
|
|
scheduled_date = fields.Date.context_today(self)
|
|
|
|
techs = self.technician_ids
|
|
lead_tech = techs[0]
|
|
additional_techs = techs[1:] if len(techs) > 1 else self.env['res.users']
|
|
|
|
vals = {
|
|
'technician_id': lead_tech.id,
|
|
'additional_technician_ids': [(6, 0, additional_techs.ids)] if additional_techs else False,
|
|
'sale_order_id': order.id,
|
|
'task_type': 'delivery',
|
|
'scheduled_date': scheduled_date,
|
|
'time_start': time_start,
|
|
'time_end': time_start + 1.0,
|
|
'partner_id': order.partner_id.id,
|
|
'description': self.notes or '',
|
|
'pod_required': True,
|
|
}
|
|
task = Task.create(vals)
|
|
_logger.info(
|
|
"Created delivery task %s for %s (+%d additional) on order %s",
|
|
task.name, lead_tech.name, len(additional_techs), order.name,
|
|
)
|
|
|
|
task_url = f'/web#id={task.id}&model=fusion.technician.task&view_type=form'
|
|
time_str = task.time_start_12h or ''
|
|
all_names = ', '.join(techs.mapped('name'))
|
|
task_line = (
|
|
f'<li><a href="{task_url}">{task.name}</a> - '
|
|
f'{all_names} '
|
|
f'({task.scheduled_date.strftime("%b %d, %Y") if task.scheduled_date else "TBD"}'
|
|
f'{" at " + time_str if time_str else ""})</li>'
|
|
)
|
|
summary = Markup(
|
|
'<div class="alert alert-info" style="margin:0;">'
|
|
'<strong><i class="fa fa-wrench"></i> Delivery Task Created</strong>'
|
|
'<ul style="margin-bottom:0;">%s</ul>'
|
|
'</div>'
|
|
) % Markup(task_line)
|
|
order.message_post(
|
|
body=summary,
|
|
message_type='notification',
|
|
subtype_xmlid='mail.mt_note',
|
|
)
|