215 lines
7.8 KiB
Python
215 lines
7.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2024-2025 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
|
|
from odoo import models, fields, api, _
|
|
from odoo.exceptions import UserError
|
|
from datetime import datetime, timedelta
|
|
from markupsafe import Markup
|
|
import logging
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ScheduleAssessmentWizard(models.TransientModel):
|
|
"""Wizard to schedule an assessment and create a calendar event."""
|
|
_name = 'fusion_claims.schedule.assessment.wizard'
|
|
_description = 'Schedule Assessment Wizard'
|
|
|
|
sale_order_id = fields.Many2one(
|
|
'sale.order',
|
|
string='Sale Order',
|
|
required=True,
|
|
readonly=True,
|
|
)
|
|
|
|
# Assessment Details
|
|
assessment_date = fields.Date(
|
|
string='Assessment Date',
|
|
required=True,
|
|
default=fields.Date.context_today,
|
|
)
|
|
assessment_time = fields.Float(
|
|
string='Assessment Time',
|
|
required=True,
|
|
default=9.0, # 9:00 AM
|
|
help='Time in 24-hour format (e.g., 14.5 = 2:30 PM)',
|
|
)
|
|
duration = fields.Float(
|
|
string='Duration (hours)',
|
|
required=True,
|
|
default=2.0,
|
|
)
|
|
|
|
# Location and Notes
|
|
location = fields.Char(
|
|
string='Location',
|
|
help='Assessment location (client address, etc.)',
|
|
)
|
|
notes = fields.Text(
|
|
string='Notes',
|
|
help='Additional notes for the assessment',
|
|
)
|
|
|
|
# Attendees
|
|
assessor_id = fields.Many2one(
|
|
'res.users',
|
|
string='Assessor',
|
|
default=lambda self: self.env.user,
|
|
required=True,
|
|
help='User who will conduct the assessment',
|
|
)
|
|
|
|
# Calendar options
|
|
create_calendar_event = fields.Boolean(
|
|
string='Create Calendar Event',
|
|
default=True,
|
|
help='Create an event in Odoo calendar',
|
|
)
|
|
send_reminder = fields.Boolean(
|
|
string='Send Reminder',
|
|
default=True,
|
|
help='Send email reminder before the assessment',
|
|
)
|
|
|
|
@api.model
|
|
def default_get(self, fields_list):
|
|
res = super().default_get(fields_list)
|
|
active_id = self._context.get('active_id')
|
|
if active_id:
|
|
order = self.env['sale.order'].browse(active_id)
|
|
res['sale_order_id'] = order.id
|
|
# Pre-fill location with client address if available
|
|
if order.partner_id:
|
|
partner = order.partner_id
|
|
address_parts = [
|
|
partner.street,
|
|
partner.street2,
|
|
partner.city,
|
|
partner.state_id.name if partner.state_id else None,
|
|
partner.zip,
|
|
]
|
|
res['location'] = ', '.join(filter(None, address_parts))
|
|
return res
|
|
|
|
def action_schedule(self):
|
|
"""Schedule the assessment and optionally create a calendar event."""
|
|
self.ensure_one()
|
|
|
|
order = self.sale_order_id
|
|
|
|
# Validate status
|
|
if order.x_fc_adp_application_status != 'quotation':
|
|
raise UserError("Can only schedule assessment from 'Quotation' status.")
|
|
|
|
# Calculate datetime
|
|
assessment_datetime = datetime.combine(
|
|
self.assessment_date,
|
|
datetime.min.time()
|
|
) + timedelta(hours=self.assessment_time)
|
|
|
|
# Create calendar event if requested
|
|
calendar_event = None
|
|
if self.create_calendar_event:
|
|
calendar_event = self._create_calendar_event(assessment_datetime)
|
|
|
|
# Update sale order
|
|
order.with_context(skip_status_validation=True).write({
|
|
'x_fc_adp_application_status': 'assessment_scheduled',
|
|
'x_fc_assessment_start_date': self.assessment_date,
|
|
})
|
|
|
|
# Post to chatter
|
|
time_str = self._format_time(self.assessment_time)
|
|
event_link = ''
|
|
if calendar_event:
|
|
event_link = f'<p style="margin: 4px 0 0 0;"><a href="/web#id={calendar_event.id}&model=calendar.event&view_type=form" target="_blank"><i class="fa fa-calendar"/> View Calendar Event</a></p>'
|
|
|
|
order.message_post(
|
|
body=Markup(
|
|
'<div style="background: #e8f4fd; border-left: 4px solid #17a2b8; padding: 12px; margin: 8px 0; border-radius: 4px;">'
|
|
'<h4 style="color: #17a2b8; margin: 0 0 8px 0;"><i class="fa fa-calendar"/> Assessment Scheduled</h4>'
|
|
f'<p style="margin: 0;"><strong>Date:</strong> {self.assessment_date.strftime("%B %d, %Y")}</p>'
|
|
f'<p style="margin: 4px 0 0 0;"><strong>Time:</strong> {time_str}</p>'
|
|
f'<p style="margin: 4px 0 0 0;"><strong>Duration:</strong> {self.duration} hour(s)</p>'
|
|
f'<p style="margin: 4px 0 0 0;"><strong>Assessor:</strong> {self.assessor_id.name}</p>'
|
|
f'{f"<p style=\'margin: 4px 0 0 0;\'><strong>Location:</strong> {self.location}</p>" if self.location else ""}'
|
|
f'{f"<p style=\'margin: 4px 0 0 0;\'><strong>Notes:</strong> {self.notes}</p>" if self.notes else ""}'
|
|
f'{event_link}'
|
|
'</div>'
|
|
),
|
|
message_type='notification',
|
|
subtype_xmlid='mail.mt_note',
|
|
)
|
|
|
|
return {'type': 'ir.actions.act_window_close'}
|
|
|
|
def _create_calendar_event(self, start_datetime):
|
|
"""Create a calendar event for the assessment."""
|
|
order = self.sale_order_id
|
|
|
|
end_datetime = start_datetime + timedelta(hours=self.duration)
|
|
|
|
# Build attendee list
|
|
partner_ids = [self.assessor_id.partner_id.id]
|
|
if order.partner_id:
|
|
partner_ids.append(order.partner_id.id)
|
|
|
|
event_vals = {
|
|
'name': f'ADP Assessment - {order.partner_id.name} ({order.name})',
|
|
'start': start_datetime,
|
|
'stop': end_datetime,
|
|
'allday': False,
|
|
'location': self.location or '',
|
|
'description': self._build_event_description(),
|
|
'partner_ids': [(6, 0, partner_ids)],
|
|
'user_id': self.assessor_id.id,
|
|
}
|
|
|
|
# Add alarm if reminder requested
|
|
if self.send_reminder:
|
|
# Find or create a 1-day email reminder
|
|
alarm = self.env['calendar.alarm'].search([
|
|
('alarm_type', '=', 'email'),
|
|
('duration', '=', 1),
|
|
('interval', '=', 'days'),
|
|
], limit=1)
|
|
if not alarm:
|
|
alarm = self.env['calendar.alarm'].create({
|
|
'name': '1 Day Before',
|
|
'alarm_type': 'email',
|
|
'duration': 1,
|
|
'interval': 'days',
|
|
})
|
|
event_vals['alarm_ids'] = [(6, 0, [alarm.id])]
|
|
|
|
return self.env['calendar.event'].create(event_vals)
|
|
|
|
def _build_event_description(self):
|
|
"""Build the calendar event description."""
|
|
order = self.sale_order_id
|
|
lines = [
|
|
f"ADP Assessment for {order.partner_id.name}",
|
|
f"Sale Order: {order.name}",
|
|
"",
|
|
]
|
|
if order.x_fc_reason_for_application:
|
|
reason_label = dict(order._fields['x_fc_reason_for_application'].selection or []).get(
|
|
order.x_fc_reason_for_application, 'N/A'
|
|
)
|
|
lines.append(f"Reason: {reason_label}")
|
|
if self.notes:
|
|
lines.append("")
|
|
lines.append(f"Notes: {self.notes}")
|
|
return '\n'.join(lines)
|
|
|
|
def _format_time(self, time_float):
|
|
"""Convert float time (e.g., 14.5) to readable format (2:30 PM)."""
|
|
hours = int(time_float)
|
|
minutes = int((time_float - hours) * 60)
|
|
period = 'AM' if hours < 12 else 'PM'
|
|
display_hours = hours if hours <= 12 else hours - 12
|
|
if display_hours == 0:
|
|
display_hours = 12
|
|
return f"{display_hours}:{minutes:02d} {period}"
|