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',