changes
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
{
|
||||
'name': 'Fusion Plating — Customer Portal',
|
||||
'version': '19.0.4.1.0',
|
||||
'version': '19.0.4.3.0',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'Customer-facing portal for plating shops: online RFQ, job status, '
|
||||
'CoC downloads, invoice access.',
|
||||
|
||||
@@ -424,7 +424,10 @@ class FpCustomerPortal(CustomerPortal):
|
||||
'icon': '📑',
|
||||
})
|
||||
|
||||
# SHIPPING (idx 4) — packing list + tracking
|
||||
# SHIPPING (idx 4) — packing list + tracking. Two separate
|
||||
# docs so each can be pending/ready independently. Previously
|
||||
# combined into one entry; that broke when tracking_ref landed
|
||||
# before the packing slip (KeyError 'url').
|
||||
if job.packing_list_attachment_id:
|
||||
groups[4]['docs'].append({
|
||||
'label': 'Packing Slip',
|
||||
@@ -438,11 +441,26 @@ class FpCustomerPortal(CustomerPortal):
|
||||
})
|
||||
else:
|
||||
groups[4]['docs'].append({
|
||||
'label': 'Packing Slip · Tracking #',
|
||||
'sub': 'Available when shipped' + (' — ' + job.tracking_ref if job.tracking_ref else ''),
|
||||
'pending': not job.tracking_ref,
|
||||
'label': 'Packing Slip',
|
||||
'sub': 'Available once shipped',
|
||||
'pending': True,
|
||||
'icon': '📦',
|
||||
})
|
||||
if job.tracking_ref:
|
||||
groups[4]['docs'].append({
|
||||
'label': 'Tracking #%s' % job.tracking_ref,
|
||||
'sub': 'Click to track on the carrier site',
|
||||
'url': job.x_fc_tracking_url or '#',
|
||||
'icon_class': 'o_fp_doc_icon_shipping',
|
||||
'icon': '🚚',
|
||||
})
|
||||
else:
|
||||
groups[4]['docs'].append({
|
||||
'label': 'Tracking #',
|
||||
'sub': 'Available when shipped',
|
||||
'pending': True,
|
||||
'icon': '🚚',
|
||||
})
|
||||
|
||||
return groups
|
||||
|
||||
|
||||
@@ -102,6 +102,79 @@ class FpPortalJob(models.Model):
|
||||
tracking_ref = fields.Char(
|
||||
string='Tracking Reference',
|
||||
)
|
||||
x_fc_tracking_url = fields.Char(
|
||||
string='Tracking URL',
|
||||
compute='_compute_x_fc_tracking_url',
|
||||
help='Resolved carrier tracking URL with the tracking number '
|
||||
'substituted. Used by the portal template to render the '
|
||||
'tracking_ref as a clickable link. Walks portal job → '
|
||||
'fp.job → sale_order → fp.receiving → carrier.',
|
||||
)
|
||||
|
||||
@api.depends('tracking_ref')
|
||||
def _compute_x_fc_tracking_url(self):
|
||||
Job = self.env.get('fp.job')
|
||||
for rec in self:
|
||||
url = ''
|
||||
if rec.tracking_ref and Job is not None:
|
||||
job = Job.sudo().search(
|
||||
[('portal_job_id', '=', rec.id)], limit=1,
|
||||
)
|
||||
so = job.sale_order_id if job else False
|
||||
recv = (
|
||||
so.x_fc_receiving_ids[:1]
|
||||
if so and 'x_fc_receiving_ids' in so._fields else False
|
||||
)
|
||||
carrier = (
|
||||
recv.x_fc_carrier_id
|
||||
if recv and 'x_fc_carrier_id' in recv._fields else False
|
||||
)
|
||||
tpl = (carrier.tracking_url or '') if carrier else ''
|
||||
if tpl:
|
||||
placeholder = '<shipmenttrackingnumber>'
|
||||
if placeholder in tpl:
|
||||
url = tpl.replace(placeholder, rec.tracking_ref)
|
||||
else:
|
||||
url = tpl + rec.tracking_ref
|
||||
rec.x_fc_tracking_url = url
|
||||
|
||||
# ---- Tracking history exposure ----------------------------------------
|
||||
# Pulls fusion.tracking.event records from the outbound shipment linked
|
||||
# via fp.job → fp.receiving → x_fc_outbound_shipment_id. Used by the
|
||||
# portal job page to render a timeline of carrier scan events.
|
||||
x_fc_tracking_event_ids = fields.Many2many(
|
||||
'fusion.tracking.event',
|
||||
string='Tracking Events',
|
||||
compute='_compute_x_fc_tracking_event_ids',
|
||||
)
|
||||
|
||||
@api.depends('tracking_ref')
|
||||
def _compute_x_fc_tracking_event_ids(self):
|
||||
Job = self.env.get('fp.job')
|
||||
Event = self.env.get('fusion.tracking.event')
|
||||
empty = self.env['fusion.tracking.event'] if Event is not None else None
|
||||
for rec in self:
|
||||
events = empty
|
||||
if Event is not None and Job is not None and rec.tracking_ref:
|
||||
job = Job.sudo().search(
|
||||
[('portal_job_id', '=', rec.id)], limit=1,
|
||||
)
|
||||
so = job.sale_order_id if job else False
|
||||
recv = (
|
||||
so.x_fc_receiving_ids[:1]
|
||||
if so and 'x_fc_receiving_ids' in so._fields else False
|
||||
)
|
||||
ship = (
|
||||
recv.x_fc_outbound_shipment_id
|
||||
if recv and 'x_fc_outbound_shipment_id' in recv._fields
|
||||
else False
|
||||
)
|
||||
if ship:
|
||||
events = ship.tracking_event_ids.sorted(
|
||||
key=lambda e: e.event_datetime or fields.Datetime.now(),
|
||||
reverse=True,
|
||||
)
|
||||
rec.x_fc_tracking_event_ids = events
|
||||
coc_attachment_id = fields.Many2one(
|
||||
'ir.attachment',
|
||||
string='Certificate of Conformance',
|
||||
|
||||
@@ -536,7 +536,17 @@
|
||||
</div>
|
||||
<div t-if="job.tracking_ref">
|
||||
<span class="o_fp_fact_label">Tracking </span>
|
||||
<span class="o_fp_fact_value" t-out="job.tracking_ref"/>
|
||||
<span class="o_fp_fact_value">
|
||||
<a t-if="job.x_fc_tracking_url"
|
||||
t-att-href="job.x_fc_tracking_url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<t t-out="job.tracking_ref"/>
|
||||
</a>
|
||||
<t t-else="">
|
||||
<t t-out="job.tracking_ref"/>
|
||||
</t>
|
||||
</span>
|
||||
</div>
|
||||
<div t-if="ship_to and ship_to.id != job.partner_id.commercial_partner_id.id">
|
||||
<span class="o_fp_fact_label">Ship to </span>
|
||||
@@ -594,6 +604,50 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tracking history (if shipment has events) -->
|
||||
<div t-if="job.x_fc_tracking_event_ids" class="o_fp_card"
|
||||
style="margin-top:1.25rem">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div style="font-weight:600;color:#111827;font-size:1rem">
|
||||
Tracking History
|
||||
</div>
|
||||
<span t-if="job.tracking_ref"
|
||||
style="font-size:.75rem;color:#6b7280;font-family:monospace">
|
||||
<a t-if="job.x_fc_tracking_url"
|
||||
t-att-href="job.x_fc_tracking_url"
|
||||
target="_blank" rel="noopener noreferrer">
|
||||
<t t-out="job.tracking_ref"/>
|
||||
</a>
|
||||
<t t-else="" t-out="job.tracking_ref"/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="o_fp_timeline">
|
||||
<t t-foreach="job.x_fc_tracking_event_ids" t-as="evt">
|
||||
<div class="o_fp_timeline_item o_fp_timeline_done">
|
||||
<div class="o_fp_timeline_dot">●</div>
|
||||
<div class="o_fp_timeline_title"
|
||||
t-out="evt.event_description or 'Tracking update'"/>
|
||||
<div class="o_fp_timeline_time">
|
||||
<t t-if="evt.event_datetime"
|
||||
t-out="evt.event_datetime"
|
||||
t-options='{"widget": "datetime"}'/>
|
||||
<t t-elif="evt.event_date"
|
||||
t-out="evt.event_date"
|
||||
t-options='{"widget": "date"}'/>
|
||||
<t t-if="evt.event_site">
|
||||
<span style="color:#9ca3af"> ·
|
||||
<t t-out="evt.event_site"/>
|
||||
<t t-if="evt.event_province">,
|
||||
<t t-out="evt.event_province"/>
|
||||
</t>
|
||||
</span>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Customer notes (if any) -->
|
||||
<div t-if="job.notes" class="o_fp_card" style="margin-top:1.25rem">
|
||||
<div style="font-weight:600;color:#111827;font-size:1rem;margin-bottom:.6rem">Notes</div>
|
||||
|
||||
Reference in New Issue
Block a user