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

@@ -6,11 +6,11 @@ For each major model in the quote → invoice workflow:
• For the most recent COMPLETED record, shows which compliance-
relevant fields are empty (gap candidates)
• Classifies each gap by severity:
CRITICAL compliance blocker (aerospace / Nadcap / env.)
IMPORTANT workflow / operational risk
NICE would improve reporting
CRITICAL - compliance blocker (aerospace / Nadcap / env.)
IMPORTANT - workflow / operational risk
NICE - would improve reporting
The report is purely diagnostic it changes nothing in the DB.
The report is purely diagnostic - it changes nothing in the DB.
"""
env = env # noqa
from collections import defaultdict
@@ -48,49 +48,49 @@ def show_field_audit(model_name, record, candidate_fields):
)
sym = {'CRITICAL': '🔴', 'IMPORTANT': '🟡', 'NICE': ''}[severity]
marker = '✗ EMPTY' if is_empty else '✓ filled'
val_str = str(val)[:60] if not is_empty else ''
val_str = str(val)[:60] if not is_empty else '-'
print(f' {sym} {severity:<9} {field:<32} {marker:<10} {reason}')
print(f' currently: {val_str!r}')
# =====================================================================
section('1. Customer (res.partner) most recently used customer')
section('1. Customer (res.partner) - most recently used customer')
# =====================================================================
partner = env['sale.order'].search([], order='id desc', limit=1).partner_id
show_field_audit('res.partner', partner, [
('email', 'CRITICAL', 'Notifications + portal access silent fail without it'),
('email', 'CRITICAL', 'Notifications + portal access - silent fail without it'),
('phone', 'IMPORTANT', 'Operator can call for clarification'),
('street', 'CRITICAL', 'Required on BoL + Invoice + delivery no shipping without'),
('street', 'CRITICAL', 'Required on BoL + Invoice + delivery - no shipping without'),
('city', 'CRITICAL', 'Same'),
('zip', 'CRITICAL', 'Same'),
('country_id', 'CRITICAL', 'Determines tax + ITAR / CGP rules'),
('vat', 'IMPORTANT', 'HST/GST registration number needed on invoice'),
('vat', 'IMPORTANT', 'HST/GST registration number - needed on invoice'),
('property_payment_term_id', 'IMPORTANT', 'Net-30 vs Net-60 controls invoice due date'),
('x_fc_account_hold', 'NICE', 'Default False is fine; only set when collections issue'),
('x_fc_send_coc', 'NICE', 'Per-customer CoC delivery preference'),
])
# =====================================================================
section('2. Sale Order (sale.order) most recent SO')
section('2. Sale Order (sale.order) - most recent SO')
# =====================================================================
so = env['sale.order'].search([], order='id desc', limit=1)
show_field_audit('sale.order', so, [
('partner_id', 'CRITICAL', 'Already required by Odoo'),
('client_order_ref', 'CRITICAL', 'Customer PO# every aero customer requires this on every doc'),
('x_fc_po_number', 'CRITICAL', 'Same FP-specific mirror'),
('client_order_ref', 'CRITICAL', 'Customer PO# - every aero customer requires this on every doc'),
('x_fc_po_number', 'CRITICAL', 'Same - FP-specific mirror'),
('x_fc_coating_config_id', 'CRITICAL', 'Drives recipe + price + spec'),
('x_fc_part_catalog_id', 'IMPORTANT', 'Part the order is about needed for traceability'),
('x_fc_delivery_method', 'IMPORTANT', 'Pickup / drop / courier drives logistics'),
('x_fc_part_catalog_id', 'IMPORTANT', 'Part the order is about - needed for traceability'),
('x_fc_delivery_method', 'IMPORTANT', 'Pickup / drop / courier - drives logistics'),
('x_fc_rfq_attachment_id', 'NICE', 'Original customer RFQ for audit trail'),
('x_fc_po_attachment_id', 'IMPORTANT', 'Customer signed PO PDF'),
('payment_term_id', 'IMPORTANT', 'Net terms derived from customer if unset'),
('user_id', 'IMPORTANT', 'Salesperson needed for commission + handoff'),
('payment_term_id', 'IMPORTANT', 'Net terms - derived from customer if unset'),
('user_id', 'IMPORTANT', 'Salesperson - needed for commission + handoff'),
])
# =====================================================================
section('3. Receiving (fp.receiving) most recent record')
section('3. Receiving (fp.receiving) - most recent record')
# =====================================================================
recv = env['fp.receiving'].search([], order='id desc', limit=1)
@@ -100,14 +100,14 @@ show_field_audit('fp.receiving', recv, [
('received_by_id', 'CRITICAL', 'Who counted the parts (audit trail)'),
('received_date', 'CRITICAL', 'When the parts arrived (compliance + start-clock)'),
('expected_qty', 'CRITICAL', 'Without this no qty-match check'),
('received_qty', 'CRITICAL', 'The actual count (compliance discrepancy log)'),
('carrier_name', 'IMPORTANT', 'Who delivered chain-of-custody starts here'),
('received_qty', 'CRITICAL', 'The actual count (compliance - discrepancy log)'),
('carrier_name', 'IMPORTANT', 'Who delivered - chain-of-custody starts here'),
('carrier_tracking', 'IMPORTANT', 'Inbound tracking #'),
('notes', 'NICE', 'Free-form receiver observations'),
])
# =====================================================================
section('4. MRP Production (mrp.production) most recent MO')
section('4. MRP Production (mrp.production) - most recent MO')
# =====================================================================
mo = env['mrp.production'].search([('state', '=', 'done')], order='id desc', limit=1)
@@ -115,16 +115,16 @@ show_field_audit('mrp.production', mo, [
('product_id', 'CRITICAL', 'Already required by Odoo'),
('product_qty', 'CRITICAL', 'Same'),
('x_fc_facility_id', 'CRITICAL', 'Where the job is being made (compliance)'),
('x_fc_recipe_id', 'CRITICAL', 'Which process without it WOs can\'t be generated'),
('x_fc_recipe_id', 'CRITICAL', 'Which process - without it WOs can\'t be generated'),
('x_fc_assigned_manager_id','IMPORTANT','Manager responsible for the job'),
('x_fc_customer_spec_id','IMPORTANT', 'Customer spec controlling the job (e.g. AMS 2404)'),
('x_fc_portal_job_id', 'IMPORTANT', 'Portal-facing job tracker'),
('origin', 'CRITICAL', 'Source SO needed for back-link'),
('origin', 'CRITICAL', 'Source SO - needed for back-link'),
('company_id', 'CRITICAL', 'Multi-company correctness (just fixed)'),
])
# =====================================================================
section('5. Work Orders (mrp.workorder) wet WO from most recent MO')
section('5. Work Orders (mrp.workorder) - wet WO from most recent MO')
# =====================================================================
wet_wo = mo.workorder_ids.filtered(
@@ -132,11 +132,11 @@ wet_wo = mo.workorder_ids.filtered(
)[:1] if mo else env['mrp.workorder']
show_field_audit('mrp.workorder', wet_wo, [
('x_fc_assigned_user_id', 'CRITICAL', 'NOW ENFORCED via button_start gate'),
('x_fc_bath_id', 'CRITICAL', 'NOW ENFORCED chemistry traceability'),
('x_fc_tank_id', 'CRITICAL', 'NOW ENFORCED physical tank audit'),
('x_fc_bath_id', 'CRITICAL', 'NOW ENFORCED - chemistry traceability'),
('x_fc_tank_id', 'CRITICAL', 'NOW ENFORCED - physical tank audit'),
('x_fc_facility_id', 'CRITICAL', 'Which plant ran it (multi-facility shops)'),
('x_fc_thickness_target', 'IMPORTANT', 'Spec target drives QC accept/reject criteria'),
('x_fc_dwell_time_minutes','IMPORTANT','Recipe dwell needed for cycle-time analytics'),
('x_fc_thickness_target', 'IMPORTANT', 'Spec target - drives QC accept/reject criteria'),
('x_fc_dwell_time_minutes','IMPORTANT','Recipe dwell - needed for cycle-time analytics'),
('x_fc_rack_id', 'IMPORTANT', 'Which rack/fixture used (per-rack MTO tracking)'),
('x_fc_started_by_user_id','IMPORTANT','Who actually started it (audit, may differ from assigned)'),
('x_fc_finished_by_user_id','IMPORTANT','Who finished it'),
@@ -149,7 +149,7 @@ section('6. Bath Log (fusion.plating.bath.log)')
baths = env['fusion.plating.bath.log'].search([], order='id desc', limit=1)
show_field_audit('fusion.plating.bath.log', baths, [
('bath_id', 'CRITICAL', 'Which bath the readings came from'),
('shift', 'IMPORTANT', 'Day/swing/night for shift-effect analysis'),
('shift', 'IMPORTANT', 'Day/swing/night - for shift-effect analysis'),
('user_id', 'CRITICAL', 'Operator who took the readings (audit trail)'),
('logged_at', 'CRITICAL', 'When the readings were taken'),
('line_ids', 'CRITICAL', 'The actual chemistry numbers (the whole point)'),
@@ -157,7 +157,7 @@ show_field_audit('fusion.plating.bath.log', baths, [
])
# =====================================================================
section('7. Certificate (fp.certificate) most recent CoC')
section('7. Certificate (fp.certificate) - most recent CoC')
# =====================================================================
coc = env['fp.certificate'].search(
@@ -165,16 +165,16 @@ coc = env['fp.certificate'].search(
show_field_audit('fp.certificate', coc, [
('partner_id', 'CRITICAL', 'Customer the cert belongs to'),
('production_id', 'CRITICAL', 'Which MO it certifies'),
('po_number', 'CRITICAL', 'Customer PO required by aero specs'),
('spec_reference', 'CRITICAL', 'AMS 2404 / MIL-C-26074 etc. what was met'),
('po_number', 'CRITICAL', 'Customer PO - required by aero specs'),
('spec_reference', 'CRITICAL', 'AMS 2404 / MIL-C-26074 etc. - what was met'),
('process_description','IMPORTANT','Human-readable process name'),
('part_number', 'IMPORTANT', 'Part the cert covers'),
('quantity_shipped', 'CRITICAL', 'How many parts certified'),
('thickness_reading_ids','CRITICAL','Fischerscope readings (NOW AUTO-LINKED)'),
('attachment_id', 'CRITICAL', 'The PDF itself (NOW AUTO-RENDERED)'),
('issued_by_id', 'CRITICAL', 'Inspector signature who certified this'),
('issued_by_id', 'CRITICAL', 'Inspector signature - who certified this'),
('issued_date', 'CRITICAL', 'When issued'),
('state', 'CRITICAL', 'draft/issued/voided NOT issued = NOT compliant'),
('state', 'CRITICAL', 'draft/issued/voided - NOT issued = NOT compliant'),
])
# =====================================================================
@@ -185,13 +185,13 @@ reading = env['fp.thickness.reading'].search([], order='id desc', limit=1)
show_field_audit('fp.thickness.reading', reading, [
('production_id', 'CRITICAL', 'Which MO this reading is from'),
('certificate_id', 'CRITICAL', 'Which cert (auto-linked at MO done)'),
('reading_number', 'CRITICAL', 'Sequence (n=1, n=2, n=3 Nadcap requires this)'),
('reading_number', 'CRITICAL', 'Sequence (n=1, n=2, n=3 - Nadcap requires this)'),
('nip_mils', 'CRITICAL', 'The thickness measurement itself'),
('ni_percent', 'IMPORTANT', 'Composition affects bath chemistry diagnosis'),
('ni_percent', 'IMPORTANT', 'Composition - affects bath chemistry diagnosis'),
('p_percent', 'IMPORTANT', 'Same'),
('position_label', 'CRITICAL', 'WHERE on the part (Nadcap requires location)'),
('equipment_model', 'CRITICAL', 'Which gauge calibration trail'),
('calibration_std_ref', 'CRITICAL', 'Which calibration standard Nadcap req'),
('equipment_model', 'CRITICAL', 'Which gauge - calibration trail'),
('calibration_std_ref', 'CRITICAL', 'Which calibration standard - Nadcap req'),
('operator_id', 'CRITICAL', 'Who took the reading'),
('reading_datetime', 'CRITICAL', 'When'),
])
@@ -213,11 +213,11 @@ show_field_audit('fusion.plating.delivery', dlv, [
('coc_attachment_id', 'CRITICAL', 'CoC PDF that goes with the parts'),
('packing_list_attachment_id','IMPORTANT','Packing slip'),
('delivery_address_id','IMPORTANT', 'Override default partner ship-to'),
('pod_id', 'CRITICAL', 'Proof of delivery without it, we can\'t bill'),
('pod_id', 'CRITICAL', 'Proof of delivery - without it, we can\'t bill'),
])
# =====================================================================
section('10. Invoice (account.move) most recent posted invoice')
section('10. Invoice (account.move) - most recent posted invoice')
# =====================================================================
inv = env['account.move'].search(
@@ -225,10 +225,10 @@ inv = env['account.move'].search(
order='id desc', limit=1)
show_field_audit('account.move', inv, [
('partner_id', 'CRITICAL', 'Already required'),
('invoice_date', 'CRITICAL', 'When invoiced drives net-terms clock'),
('invoice_date', 'CRITICAL', 'When invoiced - drives net-terms clock'),
('invoice_date_due', 'CRITICAL', 'When payment due'),
('invoice_payment_term_id','CRITICAL', 'Net-30 etc.'),
('invoice_user_id', 'IMPORTANT', 'Salesperson for commission'),
('invoice_user_id', 'IMPORTANT', 'Salesperson - for commission'),
('partner_bank_id', 'IMPORTANT', 'Where to wire payment'),
('ref', 'CRITICAL', 'Customer PO# / reference (required by AP teams)'),
('invoice_origin', 'CRITICAL', 'Source SO link'),
@@ -236,7 +236,7 @@ show_field_audit('account.move', inv, [
])
# =====================================================================
section('11. Workforce Quality Hold + NCR + CAPA (open + completed)')
section('11. Workforce - Quality Hold + NCR + CAPA (open + completed)')
# =====================================================================
# Sample Quality Hold if any
@@ -244,9 +244,9 @@ qh = env.get('fusion.plating.quality.hold')
if qh is not None:
rec = qh.search([], order='id desc', limit=1)
show_field_audit('fusion.plating.quality.hold', rec, [
('partner_id', 'CRITICAL', 'Customer without it we can\'t notify'),
('partner_id', 'CRITICAL', 'Customer - without it we can\'t notify'),
('mo_id', 'CRITICAL', 'Which MO'),
('hold_reason', 'CRITICAL', 'Selection categorize the issue'),
('hold_reason', 'CRITICAL', 'Selection - categorize the issue'),
('description', 'CRITICAL', 'Inspector\'s narrative'),
('qty_on_hold', 'CRITICAL', 'How many parts affected'),
('inspector_id', 'CRITICAL', 'Who flagged it'),
@@ -262,10 +262,10 @@ if ncr is not None:
('production_id', 'CRITICAL', 'Source MO'),
('description', 'CRITICAL', 'What went wrong'),
('severity', 'CRITICAL', 'Critical / major / minor'),
('containment_action', 'CRITICAL', 'Immediate action Nadcap req'),
('root_cause', 'CRITICAL', 'Why required to close'),
('corrective_action', 'CRITICAL', 'Fix required to close'),
('disposition', 'CRITICAL', 'Use-as-is / scrap / rework decision'),
('containment_action', 'CRITICAL', 'Immediate action - Nadcap req'),
('root_cause', 'CRITICAL', 'Why - required to close'),
('corrective_action', 'CRITICAL', 'Fix - required to close'),
('disposition', 'CRITICAL', 'Use-as-is / scrap / rework - decision'),
('raised_by_id', 'CRITICAL', 'Who raised it'),
('raised_date', 'CRITICAL', 'When'),
])
@@ -278,7 +278,7 @@ if capa is not None:
('owner_id', 'CRITICAL', 'Owner / champion'),
('due_date', 'CRITICAL', 'Deadline'),
('problem_description', 'CRITICAL', 'What\'s the recurring issue'),
('root_cause', 'CRITICAL', 'Why-why analysis required'),
('root_cause', 'CRITICAL', 'Why-why analysis - required'),
('corrective_action', 'CRITICAL', 'Fix the existing'),
('preventive_action', 'CRITICAL', 'Prevent recurrence'),
('verification_evidence', 'CRITICAL', 'Proof the fix worked'),
@@ -299,8 +299,8 @@ if DS is not None:
('parameter_id', 'CRITICAL', 'What pollutant'),
('value_measured', 'CRITICAL', 'The reading itself'),
('limit_value', 'CRITICAL', 'The regulatory limit'),
('exceeds_limit', 'CRITICAL', 'Pass/fail drives mandatory reporting'),
('lab_cert_attachment_id','CRITICAL','Lab cert required for regulator'),
('exceeds_limit', 'CRITICAL', 'Pass/fail - drives mandatory reporting'),
('lab_cert_attachment_id','CRITICAL','Lab cert - required for regulator'),
])
WM = env.get('fusion.plating.waste.manifest')
@@ -315,11 +315,11 @@ if WM is not None:
('quantity', 'CRITICAL', 'How much'),
('uom', 'CRITICAL', 'Unit'),
('shipped_date', 'CRITICAL', 'When shipped'),
('received_date', 'CRITICAL', 'When received at disposal closes the loop'),
('received_date', 'CRITICAL', 'When received at disposal - closes the loop'),
])
# =====================================================================
section('SUMMARY gap counts by severity')
section('SUMMARY - gap counts by severity')
# =====================================================================
print(' See per-model details above. Critical gaps are real')