# -*- coding: utf-8 -*-
# Copyright 2024-2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
from odoo import models, fields, api, _
from odoo.exceptions import UserError
import base64
import logging
_logger = logging.getLogger(__name__)
_DOC_NAMES = {
'x_fc_mod_drawing': 'Drawing',
'x_fc_mod_initial_photos': 'Assessment Photos',
'x_fc_mod_pca_document': 'Payment Commitment Agreement',
'x_fc_mod_proof_of_delivery': 'Proof of Delivery',
'x_fc_mod_completion_photos': 'Completion Photos',
}
class SendToModWizard(models.TransientModel):
_name = 'fusion_claims.send.to.mod.wizard'
_description = 'Send to March of Dimes Wizard'
sale_order_id = fields.Many2one('sale.order', required=True, readonly=True)
send_mode = fields.Selection([
('drawing', 'Submit Drawing and Quotation'),
('quotation', 'Re-send Quotation'),
('completion', 'Submit POD'),
], required=True, readonly=True)
# --- Recipients as contacts ---
recipient_ids = fields.Many2many(
'res.partner', 'send_mod_wizard_recipient_rel',
'wizard_id', 'partner_id', string='Send To',
)
cc_ids = fields.Many2many(
'res.partner', 'send_mod_wizard_cc_rel',
'wizard_id', 'partner_id', string='CC',
)
# --- Drawing mode uploads ---
drawing_file = fields.Binary(string='Drawing')
drawing_filename = fields.Char()
initial_photos_file = fields.Binary(string='Initial Photos')
initial_photos_filename = fields.Char()
# --- Quotation mode toggles ---
include_quotation = fields.Boolean(string='Quotation PDF', default=True)
include_drawing = fields.Boolean(string='Drawing', default=True)
include_initial_photos = fields.Boolean(string='Initial Photos', default=True)
# --- Completion mode uploads ---
completion_photos_file = fields.Binary(string='Completion Photos')
completion_photos_filename = fields.Char()
pod_file = fields.Binary(string='Proof of Delivery')
pod_filename = fields.Char()
additional_notes = fields.Text(string='Notes')
@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
if not self.env.context.get('active_id'):
return res
order = self.env['sale.order'].browse(self.env.context['active_id'])
res['sale_order_id'] = order.id
ICP = self.env['ir.config_parameter'].sudo()
mod_default_email = ICP.get_param('fusion_claims.mod_default_email', 'hvmp@marchofdimes.ca')
mode = self.env.context.get('mod_wizard_mode', 'quotation')
client = order.partner_id
authorizer = order.x_fc_authorizer_id
case_worker = order.x_fc_case_worker
# Find or create a MOD partner for the default email
mod_partner = self._get_mod_partner(mod_default_email)
if mode == 'drawing':
res['send_mode'] = 'drawing'
# To: Client. CC: MOD + Authorizer
res['recipient_ids'] = [(6, 0, [client.id])] if client else [(6, 0, [])]
cc_ids = []
if mod_partner:
cc_ids.append(mod_partner.id)
if authorizer:
cc_ids.append(authorizer.id)
res['cc_ids'] = [(6, 0, cc_ids)]
# Pre-load files
if order.x_fc_mod_drawing:
res['drawing_file'] = order.x_fc_mod_drawing
res['drawing_filename'] = order.x_fc_mod_drawing_filename
if order.x_fc_mod_initial_photos:
res['initial_photos_file'] = order.x_fc_mod_initial_photos
res['initial_photos_filename'] = order.x_fc_mod_initial_photos_filename
elif mode == 'completion':
res['send_mode'] = 'completion'
# To: Case worker. CC: Authorizer
to_ids = []
if case_worker:
to_ids.append(case_worker.id)
elif mod_partner:
to_ids.append(mod_partner.id)
res['recipient_ids'] = [(6, 0, to_ids)]
cc_ids = []
if authorizer:
cc_ids.append(authorizer.id)
res['cc_ids'] = [(6, 0, cc_ids)]
if order.x_fc_mod_completion_photos:
res['completion_photos_file'] = order.x_fc_mod_completion_photos
res['completion_photos_filename'] = order.x_fc_mod_completion_photos_filename
if order.x_fc_mod_proof_of_delivery:
res['pod_file'] = order.x_fc_mod_proof_of_delivery
res['pod_filename'] = order.x_fc_mod_pod_filename
else:
res['send_mode'] = 'quotation'
res['recipient_ids'] = [(6, 0, [client.id])] if client else [(6, 0, [])]
cc_ids = []
if mod_partner:
cc_ids.append(mod_partner.id)
if authorizer:
cc_ids.append(authorizer.id)
res['cc_ids'] = [(6, 0, cc_ids)]
return res
def _get_mod_partner(self, email):
"""Find or create a partner for the MOD default email."""
if not email:
return None
partner = self.env['res.partner'].sudo().search([('email', '=', email)], limit=1)
if not partner:
partner = self.env['res.partner'].sudo().create({
'name': 'March of Dimes Canada (HVMP)',
'email': email,
'is_company': True,
'company_type': 'company',
})
return partner
def action_preview_quotation(self):
self.ensure_one()
return {
'type': 'ir.actions.act_url',
'url': f'/report/pdf/fusion_claims.report_mod_quotation/{self.sale_order_id.id}',
'target': 'new',
}
def _pro_name(self, field_name, order, orig_filename=None):
"""Professional attachment name."""
client = order.partner_id.name or 'Client'
client_clean = client.replace(' ', '_').replace(',', '')
base = _DOC_NAMES.get(field_name, field_name)
ext = 'pdf'
if orig_filename and '.' in orig_filename:
ext = orig_filename.rsplit('.', 1)[-1].lower()
return f'{base} - {client_clean} - {order.name}.{ext}'
def _get_field_att(self, order, field_name):
att = self.env['ir.attachment'].sudo().search([
('res_model', '=', 'sale.order'),
('res_id', '=', order.id),
('res_field', '=', field_name),
], order='id desc', limit=1)
if att:
att.sudo().write({'name': self._pro_name(field_name, order, att.name)})
return att
def action_send(self):
self.ensure_one()
order = self.sale_order_id
client_name = order.partner_id.name or 'Client'
client_clean = client_name.replace(' ', '_').replace(',', '')
to_emails = [p.email for p in self.recipient_ids if p.email]
cc_emails = [p.email for p in self.cc_ids if p.email]
if order.user_id and order.user_id.email:
sr_email = order.user_id.email
if sr_email not in to_emails and sr_email not in cc_emails:
cc_emails.append(sr_email)
if not to_emails:
raise UserError(_("Please add at least one recipient."))
# --- Save files and change status ---
if self.send_mode == 'drawing':
if not self.drawing_file:
raise UserError(_("Drawing is required."))
save = {
'x_fc_mod_drawing': self.drawing_file,
'x_fc_mod_drawing_filename': self.drawing_filename or f'Drawing - {client_name}.pdf',
}
if self.initial_photos_file:
save['x_fc_mod_initial_photos'] = self.initial_photos_file
save['x_fc_mod_initial_photos_filename'] = (
self.initial_photos_filename or f'Assessment Photos - {client_name}.jpg')
order.with_context(skip_all_validations=True).write(save)
order.write({
'x_fc_mod_status': 'quote_submitted',
'x_fc_mod_drawing_submitted_date': fields.Date.today(),
})
elif self.send_mode == 'completion':
if not self.completion_photos_file:
raise UserError(_("Completion Photos are required."))
if not self.pod_file:
raise UserError(_("Proof of Delivery is required."))
order.with_context(skip_all_validations=True).write({
'x_fc_mod_completion_photos': self.completion_photos_file,
'x_fc_mod_completion_photos_filename': (
self.completion_photos_filename or f'Completion Photos - {client_name}.jpg'),
'x_fc_mod_proof_of_delivery': self.pod_file,
'x_fc_mod_pod_filename': (
self.pod_filename or f'Proof of Delivery - {client_name}.pdf'),
})
order.write({
'x_fc_mod_status': 'pod_submitted',
'x_fc_mod_pod_submitted_date': fields.Date.today(),
})
# --- Collect attachments ---
att_ids = []
att_names = []
Att = self.env['ir.attachment'].sudo()
if self.send_mode in ('drawing', 'quotation'):
try:
report = self.env.ref('fusion_claims.action_report_mod_quotation')
pdf, _ = report._render_qweb_pdf(report.id, [order.id])
a = Att.create({
'name': f'Quotation - {client_clean} - {order.name}.pdf',
'type': 'binary', 'datas': base64.b64encode(pdf),
'res_model': 'sale.order', 'res_id': order.id,
'mimetype': 'application/pdf',
})
att_ids.append(a.id)
att_names.append(a.name)
except Exception as e:
_logger.error(f"Quotation PDF failed: {e}")
if self.send_mode == 'drawing' or self.include_drawing:
a = self._get_field_att(order, 'x_fc_mod_drawing')
if a:
att_ids.append(a.id)
att_names.append(a.name)
if self.send_mode == 'drawing' or self.include_initial_photos:
a = self._get_field_att(order, 'x_fc_mod_initial_photos')
if a:
att_ids.append(a.id)
att_names.append(a.name)
elif self.send_mode == 'completion':
a = self._get_field_att(order, 'x_fc_mod_completion_photos')
if a:
att_ids.append(a.id)
att_names.append(a.name)
a = self._get_field_att(order, 'x_fc_mod_proof_of_delivery')
if a:
att_ids.append(a.id)
att_names.append(a.name)
if not att_ids:
raise UserError(_("No documents to send."))
# --- Build email ---
sender = (order.user_id or self.env.user).name
if self.send_mode in ('drawing', 'quotation'):
title = 'Accessibility Modification Proposal'
summary = (
f'Dear {client_name},
'
f'Please find attached the quotation, drawing and assessment photos for your '
f'accessibility modification project. Please review and let us know if you '
f'have any questions.')
else:
title = 'Completion Photos and Proof of Delivery'
summary = (
f'Please find attached the completion photos and signed proof of delivery '
f'for {client_name}.')
body = order._mod_email_build(
title=title, summary=summary,
email_type='info' if self.send_mode != 'completion' else 'success',
sections=[('Project Details', order._build_mod_case_detail_rows(
include_amounts=self.send_mode in ('drawing', 'quotation')))],
note=self.additional_notes or None,
attachments_note=', '.join(att_names),
sender_name=sender,
)
prefixes = {
'drawing': 'Quotation and Drawing',
'quotation': 'Quotation and Documents',
'completion': 'Completion Photos and Proof of Delivery',
}
ref = order.x_fc_case_reference
subj = f'{prefixes[self.send_mode]} - {client_name} - {order.name}'
if ref:
subj = f'{prefixes[self.send_mode]} - {ref} - {client_name}'
try:
self.env['mail.mail'].sudo().create({
'subject': subj,
'body_html': body,
'email_to': ', '.join(to_emails),
'email_cc': ', '.join(cc_emails) if cc_emails else '',
'model': 'sale.order',
'res_id': order.id,
'attachment_ids': [(6, 0, att_ids)],
}).send()
to_str = ', '.join(to_emails)
cc_str = ', '.join(cc_emails) if cc_emails else None
order._email_chatter_log(
f'{prefixes[self.send_mode]} sent', to_str, cc_str,
[f'Documents: {", ".join(att_names)}'],
)
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': 'Sent',
'message': f'Documents sent to {to_str}',
'type': 'success', 'sticky': False,
'next': {'type': 'ir.actions.act_window_close'},
},
}
except Exception as e:
_logger.error(f"Send failed for {order.name}: {e}")
raise UserError(_(f"Failed to send: {e}"))