Replace em-dashes and en-dashes with hyphens across 789 shipped source files (py/xml/js/scss) so the delivered module reads as human-written; em-dashes had become a recognizable AI-generated tell. Internal .md dev notes are excluded. The WO-sticker mojibake strippers keep their dash search targets (now written — / –). No logic changes: comments and display strings only; validated with py_compile + lxml parse. Rewrite the 7 customer notification emails to be intake-neutral (ship-in / drop-off / pickup) and repair-aware, and fix the Shipped email documents line (packing slip vs bill of lading; certificate only when issued). Subjects use a hyphen separator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
81 lines
3.2 KiB
Python
81 lines
3.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2026 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
# Part of the Fusion Plating product family.
|
|
#
|
|
# Sub 6 - Recipient resolver.
|
|
#
|
|
# Every outbound-notification path now routes its recipient lookup
|
|
# through `_fp_resolve_notification_recipients`. The helper consults
|
|
# child contacts (and optionally a delivery-location partner's child
|
|
# contacts) for per-stream flags, falling back to the company's own
|
|
# `email` when no contact opts in. This preserves the pre-Sub-6
|
|
# behaviour exactly for customers who haven't configured any flags.
|
|
|
|
from odoo import models
|
|
|
|
|
|
FP_STREAMS = ('certs', 'qc', 'quotes_so', 'invoices')
|
|
|
|
|
|
class ResPartner(models.Model):
|
|
_inherit = 'res.partner'
|
|
|
|
def _fp_resolve_notification_recipients(self, stream, delivery_location=None):
|
|
"""Return a list of email addresses that should receive a given
|
|
notification stream for this customer.
|
|
|
|
Args:
|
|
stream: one of 'certs', 'qc', 'quotes_so', 'invoices'. Any other
|
|
value returns the bare fallback list.
|
|
delivery_location: optional res.partner (typically with type=
|
|
'delivery') whose own child contacts are
|
|
consulted first, at the same priority as the
|
|
company-level contacts.
|
|
|
|
Fallback: if no contact at either level carries a matching flag
|
|
(or the global flag), the result is the company partner's own
|
|
email. This makes the resolver drop-in safe - no customer ever
|
|
silently stops receiving notifications after Sub 6 ships.
|
|
"""
|
|
self.ensure_one()
|
|
recipients = []
|
|
|
|
# Gather candidate contact recordsets: location-scoped first, then
|
|
# company-scoped. Duplicates are dropped by the final dedup pass.
|
|
candidate_sets = []
|
|
if delivery_location and delivery_location != self:
|
|
candidate_sets.append(delivery_location.child_ids)
|
|
candidate_sets.append(self.child_ids)
|
|
|
|
flag_name = f'x_fc_receives_{stream}' if stream in FP_STREAMS else None
|
|
for contacts in candidate_sets:
|
|
for contact in contacts:
|
|
if not contact.email:
|
|
continue
|
|
is_global = getattr(contact, 'x_fc_is_global_contact', False)
|
|
matches_stream = (
|
|
flag_name is not None
|
|
and getattr(contact, flag_name, False)
|
|
)
|
|
if is_global or matches_stream:
|
|
recipients.append(contact.email)
|
|
|
|
if not recipients:
|
|
# Nobody opted in via contacts - fall back to the company
|
|
# email (and the location's email, if distinct).
|
|
if delivery_location and delivery_location != self and delivery_location.email:
|
|
recipients.append(delivery_location.email)
|
|
if self.email:
|
|
recipients.append(self.email)
|
|
|
|
# Case-insensitive dedup, preserve first-seen order.
|
|
seen = set()
|
|
unique = []
|
|
for email in recipients:
|
|
key = email.strip().lower()
|
|
if key and key not in seen:
|
|
seen.add(key)
|
|
unique.append(email)
|
|
return unique
|