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) <noreply@anthropic.com>
This commit is contained in:
parent
40844b2aff
commit
ff47f47a37
5 changed files with 161 additions and 74 deletions
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"<h2>Engagement Letter Ready for Signature</h2>"
|
||||
f"<p>Hi {first_name},</p>"
|
||||
f"<p>Before we begin your FCC Form 499-A revenue audit and revised filing "
|
||||
f"for calendar year(s) <strong>{years_str}</strong>, we need your signature "
|
||||
f"on the engagement letter.</p>"
|
||||
f"<p>Please review and sign the letter by clicking below:</p>"
|
||||
f"<p><a href='{sign_url}' style='display:inline-block;background:#1e3a5f;color:#fff;"
|
||||
f"padding:12px 28px;border-radius:6px;text-decoration:none;font-weight:600;'>"
|
||||
f"Review & Sign Engagement Letter</a></p>"
|
||||
f"<p style='font-size:12px;color:#9ca3af;'>Order: {order_number}</p>"
|
||||
f"<p style='font-size:11px;color:#9ca3af;margin-top:16px;'>"
|
||||
f"Performance West Inc. | 525 Randall Ave Ste 100-1195, Cheyenne, WY 82001 | 1-888-411-0383</p>"
|
||||
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 <noreply@performancewest.net>")
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue