diff --git a/fusion_plating/fusion_plating_configurator/__manifest__.py b/fusion_plating/fusion_plating_configurator/__manifest__.py
index 9b1d7d28..3d374ed1 100644
--- a/fusion_plating/fusion_plating_configurator/__manifest__.py
+++ b/fusion_plating/fusion_plating_configurator/__manifest__.py
@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Configurator',
- 'version': '19.0.7.1.0',
+ 'version': '19.0.7.2.0',
'category': 'Manufacturing/Plating',
'summary': 'Quotation configurator with part catalog, coating configs, and formula-based pricing engine.',
'description': """
diff --git a/fusion_plating/fusion_plating_configurator/models/sale_order.py b/fusion_plating/fusion_plating_configurator/models/sale_order.py
index 25b8a568..78523a69 100644
--- a/fusion_plating/fusion_plating_configurator/models/sale_order.py
+++ b/fusion_plating/fusion_plating_configurator/models/sale_order.py
@@ -144,6 +144,78 @@ class SaleOrder(models.Model):
])
rec.x_fc_wo_completion = '%d/%d' % (done, total) if total else '0/0'
+ # ---- Phase F: quotes list view polish ----
+ x_fc_follow_up_date = fields.Date(
+ string='Follow-Up Date',
+ help='Date to chase the customer for a decision on this quote.',
+ tracking=True,
+ )
+ x_fc_follow_up_user_id = fields.Many2one(
+ 'res.users', string='Follow-Up Owner',
+ help='Who should chase the customer on the follow-up date.',
+ )
+ x_fc_email_status = fields.Selection(
+ [('draft', 'Draft'),
+ ('sent', 'Sent'),
+ ('opened', 'Opened'),
+ ('won', 'Order Received')],
+ string='Email Status',
+ compute='_compute_email_status',
+ store=True,
+ )
+ x_fc_part_numbers_summary = fields.Char(
+ string='Part Numbers',
+ compute='_compute_part_numbers_summary',
+ )
+
+ @api.depends('state')
+ def _compute_email_status(self):
+ """Map state + mail tracking to a single visible pill.
+
+ - draft SO with no tracked email sent => draft
+ - sent (Odoo state) => sent
+ - sent + mail opened => opened (detected via mail.message)
+ - state=sale/done => won
+ """
+ for rec in self:
+ if rec.state in ('sale', 'done'):
+ rec.x_fc_email_status = 'won'
+ continue
+ if rec.state == 'draft':
+ rec.x_fc_email_status = 'draft'
+ continue
+ # state == 'sent'
+ opened = False
+ if rec.id:
+ msgs = self.env['mail.message'].sudo().search([
+ ('model', '=', 'sale.order'),
+ ('res_id', '=', rec.id),
+ ('message_type', '=', 'email'),
+ ], limit=10)
+ # mail.notification tracks read timestamps
+ for m in msgs:
+ if m.notification_ids.filtered(
+ lambda n: n.is_read
+ ):
+ opened = True
+ break
+ rec.x_fc_email_status = 'opened' if opened else 'sent'
+
+ @api.depends('order_line.x_fc_part_catalog_id.part_number')
+ def _compute_part_numbers_summary(self):
+ for rec in self:
+ parts = rec.order_line.mapped('x_fc_part_catalog_id.part_number')
+ parts = [p for p in parts if p]
+ if not parts:
+ rec.x_fc_part_numbers_summary = False
+ continue
+ if len(parts) <= 2:
+ rec.x_fc_part_numbers_summary = ', '.join(parts)
+ else:
+ rec.x_fc_part_numbers_summary = '%s, %s (+%d more)' % (
+ parts[0], parts[1], len(parts) - 2,
+ )
+
@api.depends('invoice_ids.amount_total', 'invoice_ids.state',
'invoice_ids.move_type')
def _compute_invoiced_amount(self):
diff --git a/fusion_plating/fusion_plating_configurator/views/sale_order_views.xml b/fusion_plating/fusion_plating_configurator/views/sale_order_views.xml
index 3c0d1438..fbbe8c35 100644
--- a/fusion_plating/fusion_plating_configurator/views/sale_order_views.xml
+++ b/fusion_plating/fusion_plating_configurator/views/sale_order_views.xml
@@ -177,6 +177,69 @@
+
+
+ sale.order.list.fp.quotes
+ sale.order
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sale.order.search.fp.quotes
+ sale.order
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
sale.order.search.fp
@@ -222,7 +285,8 @@
list,form,kanban
[('state', 'in', ('draft', 'sent'))]
+ (0, 0, {'view_mode': 'list', 'view_id': ref('view_sale_order_list_fp_quotes')})]"/>
+
{'default_x_fc_delivery_method': 'shipping_partner'}