diff --git a/fusion_plating/fusion_plating_reports/report/report_coc.xml b/fusion_plating/fusion_plating_reports/report/report_coc.xml
index 9c2fc011..d6ac01eb 100644
--- a/fusion_plating/fusion_plating_reports/report/report_coc.xml
+++ b/fusion_plating/fusion_plating_reports/report/report_coc.xml
@@ -25,16 +25,18 @@
-
-
+
+
@@ -315,18 +318,19 @@
-
+
+
-
-
-
+
+
+
@@ -337,13 +341,13 @@
-
-
-
+
+
+
diff --git a/fusion_plating/scripts/fp_demo_seed.py b/fusion_plating/scripts/fp_demo_seed.py
index c1d86596..6c06d7f8 100644
--- a/fusion_plating/scripts/fp_demo_seed.py
+++ b/fusion_plating/scripts/fp_demo_seed.py
@@ -52,6 +52,155 @@ def set_date_field(rec, field, days_ago):
rec.write({field: d})
+# ============================================================
+# Phase 0.5: Company CoC settings (accreditation badges + signature)
+# ============================================================
+# We generate clean PIL-based badge PNGs for Nadcap / AS9100 / CGP
+# so the CoC PDF renders complete without the client having to upload
+# anything. They can still replace them with the real trademarked logos
+# via Settings → Fusion Plating → Accreditation Logos whenever they want.
+
+def _make_badge(lines, width=420, height=220, bg='#0066A1', fg='white',
+ border_color='#003d66', border_px=6, font_size=42,
+ subtitle=None, subtitle_color='white', subtitle_size=18):
+ """Render a rectangular badge with centred stacked text."""
+ try:
+ from PIL import Image, ImageDraw, ImageFont
+ except ImportError:
+ LOG(" PIL not available — skipping badge generation")
+ return None
+ import io
+ img = Image.new('RGB', (width, height), bg)
+ draw = ImageDraw.Draw(img)
+ # Border
+ draw.rectangle([(border_px // 2, border_px // 2),
+ (width - border_px, height - border_px)],
+ outline=border_color, width=border_px)
+ # Pick a sans-serif bold font
+ font = None
+ for candidate in (
+ '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
+ '/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf',
+ '/System/Library/Fonts/Helvetica.ttc',
+ 'DejaVuSans-Bold.ttf',
+ ):
+ try:
+ font = ImageFont.truetype(candidate, font_size)
+ break
+ except Exception:
+ continue
+ if font is None:
+ font = ImageFont.load_default()
+ sub_font = None
+ if subtitle:
+ for candidate in (
+ '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
+ '/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf',
+ ):
+ try:
+ sub_font = ImageFont.truetype(candidate, subtitle_size)
+ break
+ except Exception:
+ continue
+ if sub_font is None:
+ sub_font = font
+ # Stack lines vertically, centred
+ line_gap = int(font_size * 1.2)
+ total_h = line_gap * len(lines) + (subtitle_size + 10 if subtitle else 0)
+ y = (height - total_h) // 2
+ for line in lines:
+ bbox = draw.textbbox((0, 0), line, font=font)
+ tw = bbox[2] - bbox[0]
+ draw.text(((width - tw) / 2, y), line, fill=fg, font=font)
+ y += line_gap
+ if subtitle:
+ y += 4
+ bbox = draw.textbbox((0, 0), subtitle, font=sub_font)
+ tw = bbox[2] - bbox[0]
+ draw.text(((width - tw) / 2, y), subtitle, fill=subtitle_color, font=sub_font)
+ buf = io.BytesIO()
+ img.save(buf, format='PNG', optimize=True)
+ return base64.b64encode(buf.getvalue())
+
+
+def _make_signature(name, width=700, height=180, color='#00338D'):
+ """Render a plausible handwritten-looking signature from a name."""
+ try:
+ from PIL import Image, ImageDraw, ImageFont
+ except ImportError:
+ return None
+ import io
+ img = Image.new('RGBA', (width, height), (255, 255, 255, 0))
+ draw = ImageDraw.Draw(img)
+ # Prefer an italic / oblique font for the script look
+ font = None
+ for candidate in (
+ '/usr/share/fonts/truetype/dejavu/DejaVuSans-Oblique.ttf',
+ '/usr/share/fonts/truetype/liberation/LiberationSans-Italic.ttf',
+ '/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed-BoldOblique.ttf',
+ ):
+ try:
+ font = ImageFont.truetype(candidate, 88)
+ break
+ except Exception:
+ continue
+ if font is None:
+ font = ImageFont.load_default()
+ draw.text((30, 30), name, fill=color, font=font)
+ # Underline flourish
+ bbox = draw.textbbox((30, 30), name, font=font)
+ draw.line(
+ [(30, bbox[3] + 10), (bbox[2] + 80, bbox[3] + 10)],
+ fill=color, width=3,
+ )
+ buf = io.BytesIO()
+ img.save(buf, format='PNG', optimize=True)
+ return base64.b64encode(buf.getvalue())
+
+
+LOG("Phase 0.5: Company CoC settings (badges + signature)")
+_company = env.company
+
+# Build accreditation badges (only if not already set to avoid clobbering
+# real logos the client uploaded via Settings)
+if not _company.x_fc_nadcap_logo:
+ _company.x_fc_nadcap_logo = _make_badge(
+ ['NADCAP', 'ACCREDITED'],
+ bg='#0066A1', border_color='#003d66',
+ subtitle='Administered by PRI',
+ )
+if not _company.x_fc_as9100_logo:
+ _company.x_fc_as9100_logo = _make_badge(
+ ['AS9100D', 'CERTIFIED'],
+ bg='#2B6CB0', border_color='#1a4d80',
+ subtitle='ISO 9001',
+ )
+if not _company.x_fc_cgp_logo:
+ _company.x_fc_cgp_logo = _make_badge(
+ ['CGP', 'REGISTERED'],
+ bg='#C8102E', border_color='#8B0A1F',
+ subtitle="Canada's Controlled Goods Program",
+ subtitle_size=15,
+ )
+_company.x_fc_nadcap_active = True
+_company.x_fc_as9100_active = True
+_company.x_fc_cgp_active = True
+
+# Designate a demo owner: a user named "Kris Pathinather" so the
+# Certified By / Name line on the CoC matches the signature image.
+_kris_user = env['res.users'].search([('login', '=', 'kris.pathinather')], limit=1)
+if not _kris_user:
+ _kris_user = env['res.users'].with_context(no_reset_password=True).create({
+ 'name': 'Kris Pathinather',
+ 'login': 'kris.pathinather',
+ 'email': 'kris@enplating.ca',
+ })
+_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}")
+
+
# ============================================================
# Phase 1: Customers (6 stories)
# ============================================================
@@ -78,6 +227,14 @@ amphenol = ensure_partner('FPD-AMPHENOL', {
'state_id': env.ref('base.state_ca_on').id,
'website': 'amphenolcanada.com',
})
+# Give Amphenol their trademark blue-block logo
+if not amphenol.image_1920:
+ amphenol.image_1920 = _make_badge(
+ ['Amphenol'], width=320, height=200,
+ bg='#005EB8', border_color='#003c75',
+ font_size=46,
+ subtitle='Canada Corp.', subtitle_size=20,
+ )
magellan = ensure_partner('FPD-MAGELLAN', {
'name': 'Magellan Aerospace Ltd',