This commit is contained in:
gsinghpal
2026-05-18 22:33:23 -04:00
parent 25f568f225
commit 091f98e1f9
76 changed files with 4521 additions and 220 deletions

View File

@@ -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.',

View File

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

View File

@@ -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',

View File

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