From ff47f47a37d0b5714f6e9d4325da69ac61c2dd5d Mon Sep 17 00:00:00 2001 From: justin Date: Mon, 4 May 2026 10:53:59 -0500 Subject: [PATCH] Wire CPNI, CALEA, 499-A engagement, and discontinuance to generic eSign Each handler now pauses for officer signature via the eSign portal before filing/submitting. esign_completed callback re-dispatches through standard pipeline with client_approved=true. - CPNI: officer signs certification before ECFS submission (perjury) - CALEA SSI: officer signs plan before delivery - 499-A engagement: replaced custom JWT/email with request_esign() - Discontinuance: officer signs deactivation letter before USAC email - job_server: injects client_approved + order_number into order_data Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/workers/job_server.py | 47 +++++++----- scripts/workers/services/calea_ssi.py | 40 ++++++++++ .../workers/services/cpni_certification.py | 45 ++++++++++- scripts/workers/services/form_499a.py | 75 +++++-------------- .../services/form_499a_discontinuance.py | 28 +++++++ 5 files changed, 161 insertions(+), 74 deletions(-) diff --git a/scripts/workers/job_server.py b/scripts/workers/job_server.py index 52c88a0..5c5a15c 100644 --- a/scripts/workers/job_server.py +++ b/scripts/workers/job_server.py @@ -1136,6 +1136,19 @@ def handle_process_compliance_service(payload: dict) -> dict: order.get("entity", {}).get("frn"), exc) handler = handler_cls() + + # Ensure order_number is always available (some handlers use it instead of name) + if order_number: + order["order_number"] = order_number + + # Inject eSign approval flags from payload (set by handle_esign_completed) + if payload.get("client_approved"): + order["client_approved"] = True + if payload.get("esign_document_type"): + order["esign_document_type"] = payload["esign_document_type"] + if payload.get("esign_signer_email"): + order["esign_signer_email"] = payload["esign_signer_email"] + # Final entity check before dispatch ent = order.get("entity", {}) LOG.info( @@ -1562,9 +1575,9 @@ def handle_esign_completed(payload: dict) -> dict: Called by portal-esign-generic.ts after a client signs any document. Payload: { order_number, document_type, esign_record_id, signer_email } - Looks up the compliance order for this order_number and re-dispatches - the service handler with client_approved=true so it continues past - the signing checkpoint. + Re-dispatches through the standard handle_process_compliance_service + path with client_approved=true injected into the payload so the handler + skips past its signing checkpoint on the second run. """ order_number = payload.get("order_number", "") document_type = payload.get("document_type", "") @@ -1578,7 +1591,7 @@ def handle_esign_completed(payload: dict) -> dict: conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) with conn.cursor() as cur: cur.execute( - "SELECT service_slug FROM compliance_orders WHERE order_number = %s", + "SELECT service_slug, erpnext_sales_order FROM compliance_orders WHERE order_number = %s", (order_number,), ) row = cur.fetchone() @@ -1589,22 +1602,20 @@ def handle_esign_completed(payload: dict) -> dict: return {"warning": f"No compliance order for {order_number}"} service_slug = row[0] + erpnext_so = row[1] or order_number - # Re-dispatch the service handler with approval flag - from scripts.workers.services import SERVICE_HANDLERS - handler_cls = SERVICE_HANDLERS.get(service_slug) - if handler_cls: - LOG.info("[esign_completed] Re-dispatching %s for %s", service_slug, order_number) - handler = handler_cls() - handler.process(order_number, { - "client_approved": True, - "esign_document_type": document_type, - "esign_signer_email": payload.get("signer_email", ""), - }) - else: - LOG.warning("[esign_completed] No handler for slug=%s", service_slug) + LOG.info("[esign_completed] Re-dispatching %s for %s via standard pipeline", service_slug, order_number) - return {"success": True, "order_number": order_number, "document_type": document_type} + # Re-dispatch through the standard compliance service handler. + # client_approved is injected and will be merged into order_data. + return handle_process_compliance_service({ + "order_name": erpnext_so, + "order_number": order_number, + "service_slug": service_slug, + "client_approved": True, + "esign_document_type": document_type, + "esign_signer_email": payload.get("signer_email", ""), + }) except Exception as exc: LOG.error("[esign_completed] Error for %s: %s", order_number, exc) return {"error": str(exc)} diff --git a/scripts/workers/services/calea_ssi.py b/scripts/workers/services/calea_ssi.py index 05d59e9..481f913 100644 --- a/scripts/workers/services/calea_ssi.py +++ b/scripts/workers/services/calea_ssi.py @@ -167,6 +167,46 @@ class CALEASSIHandler(BaseServiceHandler): except Exception as exc: logger.warning("CALEA SSI PDF conversion failed: %s", exc) + # ── Client eSign gate ────────────────────────────────────────── + # The CALEA SSI plan requires officer signature before delivery. + client_approved = order_data.get("client_approved", False) + if not client_approved and generated: + from scripts.document_gen import MinioStorage + storage = MinioStorage() + pdf_minio_key = "" + for path in generated: + if path.endswith(".pdf"): + remote = f"compliance/{order_number}/{os.path.basename(path)}" + try: + storage.upload_file(path, remote) + pdf_minio_key = remote + except Exception as exc: + logger.warning("MinIO upload failed for %s: %s", path, exc) + break + + try: + import psycopg2 + conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) + from scripts.workers.services.telecom.esign_helper import request_esign + request_esign( + conn=conn, + order_number=order_number, + document_type="calea", + document_title="CALEA System Security & Integrity Plan", + entity_name=entity.get("legal_name", ""), + customer_email=order_data.get("customer_email") or entity.get("contact_email", ""), + customer_name=order_data.get("customer_name") or entity.get("contact_name", ""), + document_minio_key=pdf_minio_key, + requires_perjury=False, + metadata={"frn": entity.get("frn", "")}, + ) + conn.close() + except Exception as exc: + logger.warning("Could not create CALEA eSign record: %s", exc) + + logger.info("CALEASSIHandler: paused for client eSign — order %s", order_number) + return generated + # Persist + schedule annual review if entity_id: self._persist_calea_state( diff --git a/scripts/workers/services/cpni_certification.py b/scripts/workers/services/cpni_certification.py index 197a6e7..cc38e2f 100644 --- a/scripts/workers/services/cpni_certification.py +++ b/scripts/workers/services/cpni_certification.py @@ -188,7 +188,50 @@ class CPNIFilingHandler(BaseServiceHandler): ) return generated - # ── 2a. Auto-filing toggle ────────────────────────────────────── + # ── 2a. Client eSign gate ────────────────────────────────────── + # Officer must sign the CPNI certification before FCC submission + # (47 CFR § 64.2009(e) requires officer attestation). + client_approved = order_data.get("client_approved", False) + if not client_approved: + # Upload cert PDF to MinIO for the signing portal preview + from scripts.document_gen import MinioStorage + storage = MinioStorage() + pdf_minio_key = "" + for path in generated: + if path.endswith(".pdf") and "certification" in path.lower(): + remote = f"compliance/{order_number}/{os.path.basename(path)}" + try: + storage.upload_file(path, remote) + pdf_minio_key = remote + except Exception as exc: + logger.warning("MinIO upload failed for %s: %s", path, exc) + break + + # Create eSign record + send signing email + try: + import psycopg2 + conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) + from scripts.workers.services.telecom.esign_helper import request_esign + request_esign( + conn=conn, + order_number=order_number, + document_type="cpni", + document_title="CPNI Annual Certification", + entity_name=entity.get("legal_name", ""), + customer_email=order_data.get("customer_email") or entity.get("contact_email", ""), + customer_name=order_data.get("customer_name") or entity.get("contact_name", ""), + document_minio_key=pdf_minio_key, + requires_perjury=True, + metadata={"frn": entity.get("frn", ""), "docket": CPNI_DOCKET}, + ) + conn.close() + except Exception as exc: + logger.warning("Could not create CPNI eSign record: %s", exc) + + logger.info("CPNIFilingHandler: paused for client eSign — order %s", order_number) + return generated + + # ── 2b. Auto-filing toggle ────────────────────────────────────── decision = check_auto_filing(order_data) if not decision.may_submit: logger.info( diff --git a/scripts/workers/services/form_499a.py b/scripts/workers/services/form_499a.py index 20226c6..6b9be32 100644 --- a/scripts/workers/services/form_499a.py +++ b/scripts/workers/services/form_499a.py @@ -179,7 +179,7 @@ class Form499AHandler(BaseServiceHandler): or (multi_year and len(multi_year) >= 2) ) if needs_engagement: - esign_signed = order_data.get("engagement_esign_signed_at") + esign_signed = order_data.get("engagement_esign_signed_at") or order_data.get("client_approved") if not esign_signed: # Check if we already generated the letter (avoid re-sending on re-dispatch) already_required = order_data.get("engagement_esign_required") @@ -1537,72 +1537,37 @@ class Form499AHandler(BaseServiceHandler): cur.execute( """UPDATE compliance_orders SET engagement_esign_required = TRUE, - engagement_letter_minio_key = %s, - payment_status = 'pending_esign' + engagement_letter_minio_key = %s WHERE order_number = %s""", (minio_key, order_number), ) conn.commit() cur.close() - conn.close() except Exception as exc: logger.warning("Could not update engagement status: %s", exc) + conn = None - # Email client the engagement signing link - if customer_email: + # Create eSign record + send signing email via generic portal + if customer_email and conn: try: - try: - import jwt as pyjwt - except ImportError: - import PyJWT as pyjwt - secret = os.environ.get("CUSTOMER_JWT_SECRET", "changeme") - domain = os.environ.get("DOMAIN", "performancewest.net") - token = pyjwt.encode( - {"order_id": order_number, "order_type": "compliance", "email": customer_email}, - secret, algorithm="HS256", - ) - sign_url = f"https://{domain}/portal/engagement-sign?token={token}" - - import smtplib - from email.mime.text import MIMEText - from email.mime.multipart import MIMEMultipart - - first_name = customer_name.split(" ")[0] if customer_name else "there" + from scripts.workers.services.telecom.esign_helper import request_esign years_str = ", ".join(str(y) for y in multi_year) if multi_year else "current year" - subject = f"Engagement Letter — 499-A Revenue Audit for {entity.get('legal_name', order_number)}" - body = ( - f"

Engagement Letter Ready for Signature

" - f"

Hi {first_name},

" - f"

Before we begin your FCC Form 499-A revenue audit and revised filing " - f"for calendar year(s) {years_str}, we need your signature " - f"on the engagement letter.

" - f"

Please review and sign the letter by clicking below:

" - f"

" - f"Review & Sign Engagement Letter

" - f"

Order: {order_number}

" - f"

" - f"Performance West Inc. | 525 Randall Ave Ste 100-1195, Cheyenne, WY 82001 | 1-888-411-0383

" + request_esign( + conn=conn, + order_number=order_number, + document_type="499a-engagement", + document_title=f"Engagement Letter — 499-A Filing ({years_str})", + entity_name=entity.get("legal_name") or intake.get("entity_legal_name", ""), + customer_email=customer_email, + customer_name=customer_name, + document_minio_key=minio_key, + requires_perjury=False, + metadata={"frn": entity.get("frn", ""), "filing_years": multi_year}, ) - - msg = MIMEMultipart("alternative") - msg["Subject"] = subject - msg["From"] = os.environ.get("SMTP_FROM", "Performance West ") - msg["To"] = customer_email - msg.attach(MIMEText(body, "html")) - - smtp_host = os.environ.get("SMTP_HOST", "co.carrierone.com") - smtp_port = int(os.environ.get("SMTP_PORT", "587")) - smtp_user = os.environ.get("SMTP_USER", "") - smtp_pass = os.environ.get("SMTP_PASS", "") - with smtplib.SMTP(smtp_host, smtp_port) as server: - server.starttls() - if smtp_user and smtp_pass: - server.login(smtp_user, smtp_pass) - server.send_message(msg) - logger.info("Engagement letter email sent to %s for %s", customer_email, order_number) except Exception as exc: - logger.warning("Could not send engagement email for %s: %s", order_number, exc) + logger.warning("Could not create engagement eSign record for %s: %s", order_number, exc) + finally: + conn.close() # Create admin todo try: diff --git a/scripts/workers/services/form_499a_discontinuance.py b/scripts/workers/services/form_499a_discontinuance.py index 5175789..2d4320c 100644 --- a/scripts/workers/services/form_499a_discontinuance.py +++ b/scripts/workers/services/form_499a_discontinuance.py @@ -98,6 +98,34 @@ class Form499ADiscontinuanceHandler(BaseServiceHandler): except Exception as exc: logger.warning("Discontinuance letter generation failed: %s", exc) + # ── Client eSign gate ────────────────────────────────────────── + # Officer must sign the deactivation letter before we send to USAC. + client_approved = order_data.get("client_approved", False) + if not client_approved and letter_path: + minio_key = f"compliance/{order_number}/usac_deactivation_letter_{datetime.now().strftime('%Y%m%d')}.docx" + try: + import psycopg2 + conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) + from scripts.workers.services.telecom.esign_helper import request_esign + request_esign( + conn=conn, + order_number=order_number, + document_type="discontinuance", + document_title="USAC Filer ID Deactivation Letter", + entity_name=legal_name, + customer_email=entity.get("contact_email") or order_data.get("customer_email", ""), + customer_name=order_data.get("customer_name") or entity.get("contact_name", ""), + document_minio_key=minio_key, + requires_perjury=False, + metadata={"frn": frn, "filer_id": filer_id}, + ) + conn.close() + except Exception as exc: + logger.warning("Could not create discontinuance eSign record: %s", exc) + + logger.info("Form499ADiscontinuanceHandler: paused for client eSign — order %s", order_number) + return [letter_path] if letter_path else [] + # Per FCC 499-A Instructions: discontinuance requires TWO steps: # 1. File the final 499-A (may have actual revenue from the portion # of the year the company operated — NOT required to be zero)