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 — Notifications',
'version': '19.0.6.4.0',
'version': '19.0.6.6.0',
'category': 'Manufacturing/Plating',
'summary': 'Auto-email notifications at workflow milestones with configurable templates, PDF attachments, and audit log.',
'author': 'Nexa Systems Inc.',
@@ -22,6 +22,7 @@
'fusion_plating_invoicing',
'fusion_plating_logistics',
'fusion_plating_reports',
'fusion_shipping',
'sale_management',
'account',
'mail',

View File

@@ -35,6 +35,13 @@
<field name="active" eval="True"/>
</record>
<record id="fp_notif_shipment_labeled" model="fp.notification.template">
<field name="name">Shipping Label Generated</field>
<field name="trigger_event">shipment_labeled</field>
<field name="mail_template_id" ref="fp_mail_template_shipment_labeled"/>
<field name="active" eval="True"/>
</record>
<record id="fp_notif_shipped" model="fp.notification.template">
<field name="name">Shipped / Delivered</field>
<field name="trigger_event">shipped</field>

View File

@@ -184,6 +184,70 @@
fp.notification.template's `job_complete` trigger, defined
in fp_notification_template_data.xml. -->
<!-- ============================================================= -->
<!-- 4b. Shipping Label Generated (Info, #2B6CB0) -->
<!-- Fires when fusion.shipment.tracking_number first lands. -->
<!-- Customer gets the tracking link BEFORE the package goes -->
<!-- out the door, so they can monitor from pickup. -->
<!-- ============================================================= -->
<record id="fp_mail_template_shipment_labeled" model="mail.template">
<field name="name">FP: Shipping Label Generated</field>
<field name="model_id" ref="fusion_shipping.model_fusion_shipment"/>
<field name="subject">Tracking #{{ object.tracking_number }} — your order is being prepared for shipment</field>
<field name="email_from">{{ (object.company_id.email or user.email) }}</field>
<field name="email_to">{{ (object.sale_order_id and object.sale_order_id.partner_id.email) or '' }}</field>
<field name="auto_delete" eval="True"/>
<field name="body_html" type="html">
<div style="font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif; max-width: 600px; margin: 0 auto; padding: 32px 24px;">
<div style="height: 4px; background-color: #2B6CB0; margin-bottom: 28px;"></div>
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #2B6CB0; font-weight: 600; margin-bottom: 8px;">
EN Technologies
</div>
<h2 style="margin: 0 0 8px 0; font-size: 22px; font-weight: bold;">Your Order Is Being Prepared for Shipment</h2>
<p style="margin: 0 0 20px 0; font-size: 15px; opacity: 0.65;">
Hi <t t-out="object.sale_order_id.partner_id.name or ''"/>, the shipping label has been generated for your order. Tracking starts as soon as our shipping crew hands the package to the carrier.
</p>
<table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
<tr style="border-bottom: 2px solid rgba(128,128,128,0.35);">
<th style="text-align: left; padding: 8px 4px; font-size: 12px; text-transform: uppercase; opacity: 0.55; font-weight: 600;">Shipment</th>
<th style="text-align: right; padding: 8px 4px; font-size: 12px; text-transform: uppercase; opacity: 0.55; font-weight: 600;">Detail</th>
</tr>
<tr style="border-bottom: 1px solid rgba(128,128,128,0.25);">
<td style="padding: 8px 4px;">Sale Order</td>
<td style="padding: 8px 4px; text-align: right; font-family: monospace;"><t t-out="object.sale_order_id.name or '—'"/></td>
</tr>
<tr style="border-bottom: 1px solid rgba(128,128,128,0.25); background: rgba(128,128,128,0.06);">
<td style="padding: 8px 4px;">Carrier</td>
<td style="padding: 8px 4px; text-align: right;"><t t-out="object.carrier_id.name or '—'"/></td>
</tr>
<tr style="border-bottom: 1px solid rgba(128,128,128,0.25);">
<td style="padding: 8px 4px;">Tracking Number</td>
<td style="padding: 8px 4px; text-align: right; font-family: monospace; font-weight: bold;"><t t-out="object.tracking_number or '—'"/></td>
</tr>
</table>
<div t-if="object.x_fc_tracking_url" style="margin: 24px 0; text-align: center;">
<a t-att-href="object.x_fc_tracking_url"
target="_blank"
rel="noopener noreferrer"
style="display: inline-block; padding: 12px 28px; background-color: #2B6CB0; color: #ffffff; text-decoration: none; font-weight: 600; border-radius: 4px;">
Track Shipment
</a>
</div>
<div style="border-left: 3px solid #2B6CB0; padding: 12px 16px; margin: 20px 0; font-size: 14px;">
<strong>What's next:</strong> Once the carrier collects the package, you'll receive a Shipped confirmation with the Certificate of Conformance attached.
</div>
<div style="margin-top: 32px; font-size: 14px;">
Best regards,<br/>
<strong><t t-out="user.name or ''"/></strong><br/>
EN Technologies Inc.
</div>
<div style="margin-top: 40px; padding-top: 16px; border-top: 1px solid rgba(128,128,128,0.25); font-size: 11px; opacity: 0.5; text-align: center;">
This is an automated notification from EN Technologies production system.
</div>
</div>
</field>
</record>
<!-- ============================================================= -->
<!-- 5. Shipped / Delivered (Success, #38a169) -->
<!-- ============================================================= -->

View File

@@ -15,3 +15,4 @@ from . import account_payment
# fires from fp.job.button_mark_done -> _fp_fire_notification('job_complete').
# from . import mrp_production
from . import fp_delivery
from . import fusion_shipment

View File

@@ -16,6 +16,7 @@ TRIGGER_EVENTS = [
('mo_complete', 'Manufacturing Complete'), # legacy, fired by mrp; kept for back-compat
('job_confirmed', 'Plating Job Confirmed'), # Sub 11 — fp.job lifecycle
('job_complete', 'Plating Job Complete'), # Sub 11 — fp.job.button_mark_done
('shipment_labeled', 'Shipping Label Generated'), # Phase C — fired when tracking_number lands on fusion.shipment
('shipped', 'Shipped / Delivered'),
('invoice_posted', 'Invoice Posted'),
('payment_received', 'Payment Received'),
@@ -36,6 +37,7 @@ FP_TRIGGER_STREAM = {
'mo_complete': 'qc',
'job_confirmed': 'qc',
'job_complete': 'qc',
'shipment_labeled': 'certs',
'shipped': 'certs',
'invoice_posted': 'invoices',
'payment_received': 'invoices',

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
"""Phase C — fire 'shipment_labeled' notification when tracking_number
lands on a fusion.shipment for the first time.
Triggers regardless of how tracking got set: live API call or manual
fallback wizard. Customer gets the tracking link as soon as the label
is generated, not after the package physically ships (that's the
existing 'shipped' event on fp.delivery)."""
import logging
from odoo import models
_logger = logging.getLogger(__name__)
class FusionShipment(models.Model):
_inherit = 'fusion.shipment'
def write(self, vals):
# Identify shipments that gain a tracking number for the first
# time. Done BEFORE super().write so we can compare before/after.
will_fire = self.browse()
if 'tracking_number' in vals and vals.get('tracking_number'):
will_fire = self.filtered(lambda s: not s.tracking_number)
res = super().write(vals)
if not will_fire:
return res
Dispatch = self.env.get('fp.notification.template')
if Dispatch is None:
return res
for ship in will_fire:
partner = (
ship.sale_order_id.partner_id
if ship.sale_order_id else False
)
if not partner:
continue
try:
Dispatch._dispatch(
'shipment_labeled',
ship,
partner,
sale_order=ship.sale_order_id or False,
)
except Exception as e:
_logger.warning(
'Shipment %s: shipment_labeled dispatch failed: %s',
ship.name, e,
)
return res