chore(plating): de-dash shipped code + intake-neutral customer emails

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>
This commit is contained in:
gsinghpal
2026-06-05 00:16:19 -04:00
parent c9eb61ee0c
commit 8c76a16366
789 changed files with 4692 additions and 4692 deletions

View File

@@ -2,7 +2,7 @@
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
#
# Fusion Plating Demo Seeder
# Fusion Plating - Demo Seeder
# ================================
# Story-driven demo data: six customers, every workflow stage populated,
# historical depth for dashboards/trends, exception cases for enforcement.
@@ -67,7 +67,7 @@ def _make_badge(lines, width=420, height=220, bg='#0066A1', fg='white',
try:
from PIL import Image, ImageDraw, ImageFont
except ImportError:
LOG(" PIL not available skipping badge generation")
LOG(" PIL not available - skipping badge generation")
return None
import io
img = Image.new('RGB', (width, height), bg)
@@ -198,7 +198,7 @@ if not _kris_user:
_company.x_fc_owner_user_id = _kris_user.id
# Always refresh signature (cheap, looks clean)
_company.x_fc_coc_signature_override = _make_signature('Kris Pathinather')
LOG(f" Accreditation badges + signature generated owner: {_kris_user.name}")
LOG(f" Accreditation badges + signature generated - owner: {_kris_user.name}")
# ============================================================
@@ -277,7 +277,7 @@ westin = ensure_partner('FPD-WESTIN', {
})
delinquent = ensure_partner('FPD-DELINQUENT', {
'name': 'Delinquent Industries (DEMO On Hold)',
'name': 'Delinquent Industries (DEMO - On Hold)',
'email': 'ap@delinquent-demo.com',
'phone': '+1 (416) 555-0199',
'street': '1 Overdue Lane',
@@ -324,8 +324,8 @@ def ensure_process_type(code, name, family='plating'):
'process_family': family,
})
en_midphos = ensure_process_type('EN_MID', 'Electroless Nickel Mid Phos', 'plating')
en_lowphos = ensure_process_type('EN_LOW', 'Electroless Nickel Low Phos', 'plating')
en_midphos = ensure_process_type('EN_MID', 'Electroless Nickel - Mid Phos', 'plating')
en_lowphos = ensure_process_type('EN_LOW', 'Electroless Nickel - Low Phos', 'plating')
passivation = ensure_process_type('PASS_AMS2700', 'Passivation AMS 2700', 'passivation')
hard_chrome = ensure_process_type('HARD_CR', 'Hard Chrome', 'plating')
alk_clean = ensure_process_type('ALK_CLEAN', 'Alkaline Clean', 'pre_treatment')
@@ -392,8 +392,8 @@ PARTS = [
(magellan, 'MG-BRK-112', 'Bell Crank Lever', 'steel', 'moderate', 9.7, 1),
(cyclone, 'CY-STR-101', 'Structural Strut Fitting', 'stainless', 'moderate', 11.5, 1),
(cyclone, 'CY-VLV-44', 'Valve Body Assembly', 'stainless', 'complex', 14.8, 2),
(cyclone, 'CY-PIN-880', 'Clevis Pin Port Side', 'steel', 'simple', 2.4, 1),
(cyclone, 'CY-PIN-881', 'Clevis Pin Starboard', 'steel', 'simple', 2.4, 1),
(cyclone, 'CY-PIN-880', 'Clevis Pin - Port Side', 'steel', 'simple', 2.4, 1),
(cyclone, 'CY-PIN-881', 'Clevis Pin - Starboard', 'steel', 'simple', 2.4, 1),
(honeywell, 'HW-TBO-7001', 'Turbine Output Shaft', 'stainless', 'complex', 28.6, 1),
(honeywell, 'HW-GRB-22', 'Grommet Retainer', 'aluminium', 'simple', 1.8, 1),
(honeywell, 'HW-VNG-114', 'Vane Ring Segment', 'stainless', 'complex', 16.9, 1),
@@ -444,7 +444,7 @@ for partner, pn, name, sub, cx, sa, revs in PARTS:
'parent_part_id': part.id,
'is_latest_revision': (r == revs),
'geometry_source': 'pdf_drawing',
'revision_note': f'Revision {r} minor geometry update',
'revision_note': f'Revision {r} - minor geometry update',
})
created_parts[pn] = part
@@ -518,7 +518,7 @@ LOG("Phase 6: Baths + chemistry logs")
facility = env['fusion.plating.facility'].search([], limit=1)
if not facility:
facility = env['fusion.plating.facility'].create({
'name': 'Main Shop 36 Taber Rd',
'name': 'Main Shop - 36 Taber Rd',
'code': 'MAIN',
'company_id': env.company.id,
})
@@ -574,7 +574,7 @@ for param in (p_nickel, p_ph, p_temp):
if param not in en_midphos.parameter_ids:
en_midphos.parameter_ids = [Command.link(param.id)]
# Per-bath target ranges (override) use bath.target_line_ids
# Per-bath target ranges (override) - use bath.target_line_ids
for bath, params in [(bath_en, [p_nickel, p_ph, p_temp])]:
for p in params:
existing = env['fusion.plating.bath.target'].search([
@@ -586,13 +586,13 @@ for bath, params in [(bath_en, [p_nickel, p_ph, p_temp])]:
'target_min': p.target_min, 'target_max': p.target_max,
})
# Replenishment rule Ni drop → add NiP replenisher
# Replenishment rule - Ni drop → add NiP replenisher
existing_rule = env['fusion.plating.bath.replenishment.rule'].search([
('bath_id', '=', bath_en.id), ('parameter_id', '=', p_nickel.id),
], limit=1)
if not existing_rule:
env['fusion.plating.bath.replenishment.rule'].create({
'name': 'EN Bath Nickel Low Replenishment',
'name': 'EN Bath - Nickel Low Replenishment',
'bath_id': bath_en.id,
'parameter_id': p_nickel.id,
'trigger': 'below_min',
@@ -602,7 +602,7 @@ if not existing_rule:
'min_dose': 50.0,
})
# Historical bath logs 15 readings over 30 days, last 2 out-of-spec (to trigger suggestions)
# Historical bath logs - 15 readings over 30 days, last 2 out-of-spec (to trigger suggestions)
existing_log_count = env['fusion.plating.bath.log'].search_count([('bath_id', '=', bath_en.id)])
if existing_log_count < 12:
for days_ago in range(30, 0, -2): # 15 readings
@@ -720,7 +720,7 @@ def register_payment(invoice):
return False
# Historical job factory creates full SO→MO→Delivery→Invoice→Payment chain
# Historical job factory - creates full SO→MO→Delivery→Invoice→Payment chain
# then backdates everything to simulate months of history
def make_closed_job(partner, coating, part_cat, qty, unit_price, days_ago,
strategy='net_terms', deposit_pct=0.0):
@@ -735,7 +735,7 @@ def make_closed_job(partner, coating, part_cat, qty, unit_price, days_ago,
'x_fc_po_received': True,
'order_line': [Command.create({
'product_id': product.id,
'name': f'{coating.name} {part_cat.name if part_cat else "custom"} (x{qty})',
'name': f'{coating.name} - {part_cat.name if part_cat else "custom"} (x{qty})',
'product_uom_qty': qty, 'price_unit': unit_price,
})],
}
@@ -843,7 +843,7 @@ if closed_count < 10:
print(f" ! history job failed: {e}")
LOG(f" Created 8 historical closed jobs")
else:
LOG(f" Already has {closed_count} closed jobs skipping history phase")
LOG(f" Already has {closed_count} closed jobs - skipping history phase")
# ============================================================
@@ -863,7 +863,7 @@ def quick_so(partner, coating, part_cat, qty, price, strategy, deposit_pct=0.0,
'x_fc_po_received': True,
'order_line': [Command.create({
'product_id': product.id,
'name': f'{coating.name} {part_cat.name if part_cat else "part"} (x{qty})',
'name': f'{coating.name} - {part_cat.name if part_cat else "part"} (x{qty})',
'product_uom_qty': qty, 'price_unit': price,
})],
})
@@ -880,14 +880,14 @@ active_marker = env['sale.order'].search_count([
])
if active_marker == 0:
# Magellan progress billing SO confirmed, 40% invoice already posted
# Magellan progress billing - SO confirmed, 40% invoice already posted
so_mag = quick_so(magellan, coating_chrome, created_parts.get('MG-CYL-550'),
30, 120.00, 'progress', 40.0, 'FPDEMO-ACTIVE-MAGELLAN')
for inv in so_mag.invoice_ids:
try: inv.action_post()
except: pass
# Cyclone deposit SO confirmed, 50% deposit invoice posted & paid,
# Cyclone deposit - SO confirmed, 50% deposit invoice posted & paid,
# MO in progress at WO stage
so_cyc = quick_so(cyclone, coating_en_mid, created_parts.get('CY-VLV-44'),
40, 52.00, 'deposit', 50.0, 'FPDEMO-ACTIVE-CYCLONE')
@@ -902,7 +902,7 @@ if active_marker == 0:
try: mo_cyc.action_confirm()
except: pass
# Honeywell net_terms SO just confirmed, receiving pending
# Honeywell net_terms - SO just confirmed, receiving pending
so_hw = quick_so(honeywell, coating_en_low, created_parts.get('HW-TBO-7001'),
25, 72.00, 'net_terms', 0, 'FPDEMO-ACTIVE-HONEYWELL')
@@ -910,7 +910,7 @@ if active_marker == 0:
so_wn = quick_so(westin, coating_en_low, created_parts.get('WM-HSG-201'),
20, 48.00, 'cod_prepay', 0, 'FPDEMO-WESTIN-DIRECT')
# Amphenol MO done, ready to ship (for live delivery demo)
# Amphenol - MO done, ready to ship (for live delivery demo)
so_am = quick_so(amphenol, coating_en_mid, created_parts.get('VS-ESMC6H00801P01'),
500, 38.00, 'net_terms', 0, 'FPDEMO-AMPHENOL-READY')
mo_am = env['mrp.production'].create({
@@ -925,19 +925,19 @@ if active_marker == 0:
LOG(" 5 active orders across workflow stages")
else:
LOG(" Active demo orders already present skipping")
LOG(" Active demo orders already present - skipping")
# ============================================================
# Phase 11: Exception case Delinquent Industries
# Phase 11: Exception case - Delinquent Industries
# ============================================================
LOG("Phase 11: Exception account hold blocked SO")
LOG("Phase 11: Exception - account hold blocked SO")
block_marker = env['sale.order'].search_count([
('partner_id', '=', delinquent.id), ('x_fc_po_number', '=', 'FPDEMO-HOLD-TEST'),
])
if block_marker == 0:
# Create but leave in DRAFT it will raise UserError when they click confirm
# Create but leave in DRAFT - it will raise UserError when they click confirm
env['sale.order'].create({
'partner_id': delinquent.id,
'x_fc_invoice_strategy': 'net_terms',
@@ -948,7 +948,7 @@ if block_marker == 0:
'product_uom_qty': 10, 'price_unit': 100.0,
})],
})
LOG(" Draft SO for Delinquent Industries click Confirm to demo the hold")
LOG(" Draft SO for Delinquent Industries - click Confirm to demo the hold")
# ============================================================
@@ -958,7 +958,7 @@ LOG("Phase 12: Bake window variety")
bw_count = env['fusion.plating.bake.window'].search_count([])
if bw_count < 3:
# Awaiting recent plate exit, still within window
# Awaiting - recent plate exit, still within window
env['fusion.plating.bake.window'].create({
'bath_id': bath_en.id,
'part_ref': 'MG-WG-8801', 'lot_ref': 'LOT-BW-001',
@@ -990,7 +990,7 @@ if bw_count < 3:
backdate(bw_done, 14)
LOG(" 3 bake windows: 1 awaiting, 1 missed, 1 baked")
else:
LOG(f" Already has {bw_count} bake windows skipping")
LOG(f" Already has {bw_count} bake windows - skipping")
# ============================================================
@@ -1025,9 +1025,9 @@ if stn_count < 5:
})
LOG(f" 5 shop-floor stations created")
else:
LOG(f" Already has {stn_count} stations skipping")
LOG(f" Already has {stn_count} stations - skipping")
# More bake windows add a couple active ones for realism
# More bake windows - add a couple active ones for realism
if env['fusion.plating.bake.window'].search_count([]) < 6:
env['fusion.plating.bake.window'].create({
'bath_id': bath_en.id,
@@ -1056,7 +1056,7 @@ if env['fusion.plating.bake.window'].search_count([]) < 6:
# First-piece-gate seeding retired with the model (19.0.33.2.0).
# Quality holds on active MOs gives the Shop Floor quality-holds panel content
# Quality holds on active MOs - gives the Shop Floor quality-holds panel content
Hold = env['fusion.plating.quality.hold']
if Hold.search_count([]) < 2:
active_mos = env['mrp.production'].search(
@@ -1070,7 +1070,7 @@ if Hold.search_count([]) < 2:
'qty_on_hold': random.randint(3, 8),
'qty_original': int(mo.product_qty or 10),
'hold_reason': hold_reasons[i % len(hold_reasons)],
'description': f'Demo hold flagged during in-process inspection on {mo.name}.',
'description': f'Demo hold - flagged during in-process inspection on {mo.name}.',
'production_id': mo.id,
'workorder_id': wo.id if wo else False,
'portal_job_id': mo.x_fc_portal_job_id.id if mo.x_fc_portal_job_id else False,
@@ -1080,7 +1080,7 @@ if Hold.search_count([]) < 2:
})
LOG(f" {Hold.search_count([])} quality holds seeded")
else:
LOG(f" Already has {Hold.search_count([])} quality holds skipping")
LOG(f" Already has {Hold.search_count([])} quality holds - skipping")
# ============================================================