+
+
+
+
+ fp.direct.order.wizard.form.spec.inherit
+ fp.direct.order.wizard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fusion_plating/fusion_plating_quality/views/fp_part_catalog_views_inherit.xml b/fusion_plating/fusion_plating_quality/views/fp_part_catalog_views_inherit.xml
new file mode 100644
index 00000000..a85deb71
--- /dev/null
+++ b/fusion_plating/fusion_plating_quality/views/fp_part_catalog_views_inherit.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+ fp.part.catalog.form.spec.inherit
+ fp.part.catalog
+
+
+
+
+
+
+
+
+
diff --git a/fusion_plating/fusion_plating_quality/views/sale_order_views_inherit.xml b/fusion_plating/fusion_plating_quality/views/sale_order_views_inherit.xml
new file mode 100644
index 00000000..b6080b18
--- /dev/null
+++ b/fusion_plating/fusion_plating_quality/views/sale_order_views_inherit.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+ sale.order.form.quality.spec.inherit
+ sale.order
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fusion_plating/fusion_plating_quality/wizards/__init__.py b/fusion_plating/fusion_plating_quality/wizards/__init__.py
new file mode 100644
index 00000000..cb5cc38c
--- /dev/null
+++ b/fusion_plating/fusion_plating_quality/wizards/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+# Copyright 2026 Nexa Systems Inc.
+# License OPL-1 (Odoo Proprietary License v1.0)
+# Part of the Fusion Plating product family.
+
+from . import fp_contract_review_client_email_wizard
diff --git a/fusion_plating/fusion_plating_quality/wizards/fp_contract_review_client_email_wizard.py b/fusion_plating/fusion_plating_quality/wizards/fp_contract_review_client_email_wizard.py
new file mode 100644
index 00000000..3e9f22d2
--- /dev/null
+++ b/fusion_plating/fusion_plating_quality/wizards/fp_contract_review_client_email_wizard.py
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+# Copyright 2026 Nexa Systems Inc.
+# License OPL-1 (Odoo Proprietary License v1.0)
+# Part of the Fusion Plating product family.
+
+from odoo import _, api, fields, models
+from odoo.exceptions import UserError
+
+
+class FpContractReviewClientEmailWizard(models.TransientModel):
+ """Email-composer wizard for the Contract Review "Awaiting Client Info"
+ workflow. Pre-fills subject + body from the QA failure reason so the
+ QA Signer (Brett, or any other configured signer) can ping the
+ customer in a single click.
+
+ Sending the wizard:
+ 1. Posts a chatter message of message_type='email' on the review
+ (the smart-button counter on the review form picks this up).
+ 2. Sends the actual email via mail.mail to the customer's email.
+ 3. Stamps `info_requested_date` on the review the first time, so
+ the form clearly shows when the request went out.
+ """
+ _name = 'fp.contract.review.client.email.wizard'
+ _description = 'Contract Review — Email Client (Request Info)'
+
+ review_id = fields.Many2one(
+ 'fp.contract.review',
+ string='Contract Review',
+ required=True,
+ ondelete='cascade',
+ )
+ customer_id = fields.Many2one(
+ 'res.partner',
+ related='review_id.customer_id',
+ readonly=True,
+ )
+ recipient_email = fields.Char(
+ string='To',
+ required=True,
+ help='Customer contact email. Edit if the request needs to go to a '
+ 'specific buyer / engineer.',
+ )
+ recipient_name = fields.Char(
+ string='Recipient Name',
+ )
+ subject = fields.Char(
+ string='Subject',
+ required=True,
+ )
+ body = fields.Html(
+ string='Message',
+ required=True,
+ sanitize=True,
+ )
+
+ @api.model
+ def default_get(self, fields_list):
+ vals = super().default_get(fields_list)
+ review_id = self.env.context.get('default_review_id')
+ if review_id:
+ review = self.env['fp.contract.review'].browse(review_id)
+ company = review.company_id or self.env.company
+ part_label = (review.part_id and review.part_id.display_name) or '-'
+ po_label = review.contract_po_number or review.quote_or_job_number or '-'
+ failure_html = review.qa_failure_reason or _(
+ '(Reason not yet captured — type details here.)
'
+ )
+ if 'subject' in fields_list and not vals.get('subject'):
+ vals['subject'] = _(
+ '%(company)s — Information request for Contract Review '
+ '%(name)s (PO %(po)s)'
+ ) % {
+ 'company': company.name or '',
+ 'name': review.name or '',
+ 'po': po_label,
+ }
+ if 'body' in fields_list and not vals.get('body'):
+ vals['body'] = _(
+ 'Hello %(recipient)s,
'
+ 'We are reviewing your contract for %(part)s '
+ '(PO %(po)s) and need additional information to '
+ 'finalise our QA-005 review.
'
+ 'Items requiring clarification:
'
+ '%(failure)s'
+ 'Please reply with the requested information at '
+ 'your earliest convenience so we can complete the '
+ 'review and proceed with production.
'
+ 'Thank you,
%(company)s — Quality Team
'
+ ) % {
+ 'recipient': (review.customer_id.name or _('there')),
+ 'part': part_label,
+ 'po': po_label,
+ 'failure': failure_html,
+ 'company': company.name or '',
+ }
+ return vals
+
+ def action_send(self):
+ """Send the email + post chatter + stamp request date."""
+ self.ensure_one()
+ if not self.recipient_email:
+ raise UserError(_(
+ 'A recipient email is required. Set the customer\'s email '
+ 'on their contact card or override here.'
+ ))
+ review = self.review_id
+ # Post into the review's chatter as message_type='email' so the
+ # smart-button counter picks it up. message_post handles the
+ # actual mail.mail send when partner_ids / email_to is set.
+ review.message_post(
+ body=self.body,
+ subject=self.subject,
+ message_type='email',
+ subtype_xmlid='mail.mt_comment',
+ partner_ids=review.customer_id.ids if review.customer_id else [],
+ email_layout_xmlid='mail.mail_notification_light',
+ email_add_signature=True,
+ )
+ # Belt-and-braces direct send to the recipient_email when it
+ # differs from the partner's primary email (e.g. a buyer-specific
+ # address typed into the wizard).
+ partner_email = review.customer_id.email if review.customer_id else ''
+ if self.recipient_email and self.recipient_email != partner_email:
+ self.env['mail.mail'].sudo().create({
+ 'subject': self.subject,
+ 'body_html': self.body,
+ 'email_from': self.env.user.email_formatted or
+ (review.company_id and review.company_id.email) or '',
+ 'email_to': self.recipient_email,
+ 'auto_delete': True,
+ 'model': 'fp.contract.review',
+ 'res_id': review.id,
+ }).send()
+ if not review.info_requested_date:
+ review.write({'info_requested_date': fields.Datetime.now()})
+ return {'type': 'ir.actions.act_window_close'}
diff --git a/fusion_plating/fusion_plating_quality/wizards/fp_contract_review_client_email_wizard_views.xml b/fusion_plating/fusion_plating_quality/wizards/fp_contract_review_client_email_wizard_views.xml
new file mode 100644
index 00000000..b4b1666f
--- /dev/null
+++ b/fusion_plating/fusion_plating_quality/wizards/fp_contract_review_client_email_wizard_views.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ fp.contract.review.client.email.wizard.form
+ fp.contract.review.client.email.wizard
+
+
+
+
+