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:
@@ -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")
|
||||
|
||||
|
||||
# ============================================================
|
||||
|
||||
Reference in New Issue
Block a user