# -*- 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}"))