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 @@ +