diff --git a/fusion_plating/fusion_plating_configurator/models/sale_order.py b/fusion_plating/fusion_plating_configurator/models/sale_order.py
index d4428a1a..c1079122 100644
--- a/fusion_plating/fusion_plating_configurator/models/sale_order.py
+++ b/fusion_plating/fusion_plating_configurator/models/sale_order.py
@@ -83,6 +83,90 @@ class SaleOrder(models.Model):
tracking=True,
)
+ # ---- Phase D: SO detail view polish ----
+ x_fc_external_note = fields.Html(
+ string='External Notes',
+ help='Customer-visible notes. Appear on the SO acknowledgement '
+ 'and customer portal.',
+ )
+ x_fc_internal_note = fields.Html(
+ string='Internal Notes',
+ help='Internal-only notes for the estimator / planner / shop floor.',
+ )
+ x_fc_ship_via = fields.Char(
+ string='Ship Via',
+ help='Carrier or delivery method name (UPS, FedEx, customer pickup, etc.).',
+ tracking=True,
+ )
+ x_fc_contact_phone = fields.Char(
+ related='partner_id.phone', string='Contact Phone', readonly=True,
+ )
+ x_fc_deadline_countdown = fields.Char(
+ string='Deadline',
+ compute='_compute_deadline_countdown',
+ )
+ x_fc_margin_amount = fields.Monetary(
+ string='Margin',
+ compute='_compute_margin', currency_field='currency_id',
+ )
+ x_fc_margin_percent = fields.Float(
+ string='Margin %',
+ compute='_compute_margin',
+ )
+
+ @api.depends('commitment_date')
+ def _compute_deadline_countdown(self):
+ from datetime import datetime
+ now = fields.Datetime.now()
+ for rec in self:
+ if not rec.commitment_date:
+ rec.x_fc_deadline_countdown = False
+ continue
+ target = rec.commitment_date
+ if isinstance(target, datetime):
+ delta = target - now
+ else:
+ from datetime import datetime as _dt
+ delta = _dt.combine(target, _dt.min.time()) - now
+ secs = int(delta.total_seconds())
+ if secs == 0:
+ rec.x_fc_deadline_countdown = 'due now'
+ continue
+ past = secs < 0
+ secs = abs(secs)
+ days = secs // 86400
+ hours = (secs % 86400) // 3600
+ mins = (secs % 3600) // 60
+ bits = []
+ if days:
+ bits.append('%dd' % days)
+ if hours:
+ bits.append('%dh' % hours)
+ if mins and not days:
+ bits.append('%dm' % mins)
+ phrase = ' '.join(bits) or '<1m'
+ rec.x_fc_deadline_countdown = (
+ 'overdue %s' % phrase if past else 'in %s' % phrase
+ )
+
+ @api.depends('order_line.price_subtotal', 'amount_untaxed')
+ def _compute_margin(self):
+ """Simple margin: untaxed total minus rolled-up cost from coating configs."""
+ for rec in self:
+ cost = 0.0
+ for line in rec.order_line:
+ if line.x_fc_coating_config_id:
+ # If coating_config has a cost field, use it; otherwise 0.
+ cost_per_unit = getattr(
+ line.x_fc_coating_config_id, 'unit_cost', 0.0,
+ ) or 0.0
+ cost += cost_per_unit * (line.product_uom_qty or 0)
+ rec.x_fc_margin_amount = (rec.amount_untaxed or 0) - cost
+ rec.x_fc_margin_percent = (
+ (rec.x_fc_margin_amount / rec.amount_untaxed * 100.0)
+ if rec.amount_untaxed else 0.0
+ )
+
@api.onchange('upload_rfq_file')
def _onchange_upload_rfq_file(self):
"""Create attachment from uploaded binary and link it."""
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 59d01e26..268bf5fd 100644
--- a/fusion_plating/fusion_plating_configurator/views/sale_order_views.xml
+++ b/fusion_plating/fusion_plating_configurator/views/sale_order_views.xml
@@ -84,15 +84,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -122,6 +144,7 @@
+