new-site/scripts/workers/services/telecom/auto_filing.py
justin f8cd37ac8c Initial commit — Performance West telecom compliance platform
Includes: API (Express/TypeScript), Astro site, Python workers,
document generators, FCC compliance tools, Canada CRTC formation,
Ansible infrastructure, and deployment scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 06:54:22 -05:00

307 lines
10 KiB
Python

"""Global auto-filing toggle + admin-review workflow.
Gates the Playwright submission step of every FCC/USAC/BDC filing handler
so that — by default — no filing is submitted to the FCC without a
Performance West admin reviewing the generated packet first.
Design
------
* Settings live in ERPNext ``Compliance Settings`` (single DocType) under
the ``auto_filing_enabled`` Check field. Default = False (safer).
* A per-order override field (``custom_auto_filing_override`` on the Sales
Order) lets the admin approve a specific filing one-shot after review
without flipping the global toggle.
* When auto-filing is OFF and there's no per-order override, the handler:
1. Produces and uploads the packet as normal.
2. Creates an ERPNext ToDo assigned to ``admin_email`` (default
``ops@performancewest.net``) with a summary + "Approve & File"
CTA link.
3. Sends the admin a short HTML email with the same summary + link.
4. Leaves the order in "Awaiting Admin Review" state — the workflow
picks it back up when the admin clicks Approve.
The Approve-and-File link hits the Express API endpoint
``POST /api/v1/compliance-orders/:order_number/approve-and-file`` which
sets ``custom_auto_filing_override = 1`` on the Sales Order and
re-dispatches the handler. Handler reads the override flag and runs
the Playwright submission even if the global toggle is still off.
Env fallback
------------
If ERPNext is unreachable (e.g. during local development), the module
reads the env var ``AUTO_FILING_ENABLED`` as a truthy override. Admin
email falls back to ``ADMIN_EMAIL`` env var, then
``ops@performancewest.net``.
"""
from __future__ import annotations
import logging
import os
import smtplib
from dataclasses import dataclass
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from typing import Optional
logger = logging.getLogger(__name__)
DEFAULT_ADMIN_EMAIL = "ops@performancewest.net"
# ─── Settings lookup ──────────────────────────────────────────────────────
@dataclass
class AutoFilingDecision:
"""Result of ``check_auto_filing`` — caller branches on ``may_submit``."""
may_submit: bool
reason: str
admin_email: str
global_enabled: bool
order_override: bool
def _env_truthy(value: str | None) -> bool:
if not value:
return False
return value.strip().lower() in ("1", "true", "yes", "on")
def _settings_from_erpnext() -> tuple[bool, str]:
"""Read ``Compliance Settings`` from ERPNext — returns (enabled, admin_email)."""
try:
from scripts.workers.erpnext_client import ERPNextClient
erp = ERPNextClient()
settings = erp.get_resource("Compliance Settings", "Compliance Settings")
if isinstance(settings, list):
settings = settings[0] if settings else {}
enabled = bool(settings.get("auto_filing_enabled", 0))
admin = (
settings.get("admin_email")
or os.environ.get("ADMIN_EMAIL")
or DEFAULT_ADMIN_EMAIL
)
return enabled, admin
except Exception as exc:
logger.debug("auto_filing: ERPNext settings read failed: %s", exc)
return _env_truthy(os.environ.get("AUTO_FILING_ENABLED")), (
os.environ.get("ADMIN_EMAIL") or DEFAULT_ADMIN_EMAIL
)
def check_auto_filing(order_data: dict) -> AutoFilingDecision:
"""Resolve whether the calling handler may submit to the FCC/USAC.
The handler passes the current ``order_data`` (as received from
``job_server.py``). We inspect the per-order override first, then
fall back to the global setting.
"""
order_override = bool(order_data.get("custom_auto_filing_override", 0))
global_enabled, admin_email = _settings_from_erpnext()
if order_override:
return AutoFilingDecision(
may_submit=True,
reason="per-order admin override",
admin_email=admin_email,
global_enabled=global_enabled,
order_override=True,
)
if global_enabled:
return AutoFilingDecision(
may_submit=True,
reason="global auto_filing_enabled",
admin_email=admin_email,
global_enabled=True,
order_override=False,
)
return AutoFilingDecision(
may_submit=False,
reason="auto-filing disabled; admin review required",
admin_email=admin_email,
global_enabled=False,
order_override=False,
)
# ─── Admin review hand-off ────────────────────────────────────────────────
def request_admin_review(
*,
order_number: str,
service_slug: str,
service_name: str,
entity_name: str,
frn: str,
packet_minio_paths: list[str],
admin_email: str,
summary: str = "",
) -> None:
"""Create an ERPNext ToDo + send a short email to the admin.
The email and the ToDo both include a one-click "Approve & File" URL
that re-dispatches the handler for this order with the auto-filing
override set.
"""
api_base = os.environ.get("API_URL", "http://api:3001").rstrip("/")
approve_url = (
f"{api_base}/api/v1/compliance-orders/{order_number}/approve-and-file"
)
todo_description = (
f"[{service_slug}] Admin review required for {order_number}\n\n"
f"Carrier: {entity_name}\n"
f"FRN: {frn or 'N/A'}\n"
f"Service: {service_name}\n\n"
f"Auto-filing is disabled. The generated packet is staged in MinIO — "
f"review it, then POST to the approve-and-file URL (or click the "
f"button in the admin email) to submit the filing to the FCC.\n\n"
f"Files in MinIO:\n"
+ "\n".join(f"{p}" for p in packet_minio_paths)
+ f"\n\nApprove & File:\n {approve_url}\n"
)
if summary:
todo_description += f"\nHandler notes:\n{summary}\n"
_create_todo(todo_description, admin_email)
_send_admin_email(
to_email=admin_email,
order_number=order_number,
service_name=service_name,
entity_name=entity_name,
frn=frn,
packet_minio_paths=packet_minio_paths,
approve_url=approve_url,
summary=summary,
)
def _create_todo(description: str, admin_email: str) -> None:
try:
from scripts.workers.erpnext_client import ERPNextClient
ERPNextClient().create_resource(
"ToDo",
{
"description": description,
"priority": "High",
"allocated_to": admin_email,
"status": "Open",
},
)
except Exception as exc:
logger.warning("auto_filing: could not create admin ToDo: %s", exc)
_EMAIL_TMPL = """\
<html>
<body style="font-family:Arial,Helvetica,sans-serif;color:#1f2937;">
<h2 style="color:#1a2744;margin:0 0 12px;">Filing ready for review</h2>
<p><strong>Order:</strong> {order_number}<br>
<strong>Carrier:</strong> {entity_name}<br>
<strong>FRN:</strong> {frn}<br>
<strong>Service:</strong> {service_name}</p>
<p>Auto-filing is <em>disabled</em>. The generated packet is staged in MinIO.
Review the documents, then click below to submit the filing to the FCC.</p>
<p><a href="{approve_url}"
style="display:inline-block;padding:12px 22px;background:#059669;
color:#fff;border-radius:4px;font-weight:600;text-decoration:none;">
Approve &amp; File →
</a></p>
<h3 style="margin:22px 0 6px;color:#1a2744;">Packet files</h3>
<ul style="margin:0 0 12px 18px;padding:0;">
{files}
</ul>
{summary_block}
<p style="color:#64748b;font-size:12px;margin-top:24px;">
To enable auto-filing globally, set <code>auto_filing_enabled = 1</code>
in the ERPNext Compliance Settings doctype (or set
<code>AUTO_FILING_ENABLED=1</code> in the worker environment).
</p>
</body>
</html>
"""
def _send_admin_email(
*,
to_email: str,
order_number: str,
service_name: str,
entity_name: str,
frn: str,
packet_minio_paths: list[str],
approve_url: str,
summary: str,
) -> None:
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_PASSWORD", "")
smtp_from = os.environ.get("SMTP_FROM", "orders@performancewest.net")
if not smtp_user or not smtp_pass:
logger.warning("auto_filing: SMTP not configured — skipping admin email")
return
files_html = "\n ".join(
f"<li style=\"margin:3px 0;\"><code>{p}</code></li>"
for p in packet_minio_paths
) or "<li><em>(none)</em></li>"
summary_block = (
f"<h3 style=\"margin:22px 0 6px;color:#1a2744;\">Handler notes</h3>"
f"<pre style=\"background:#f8fafc;padding:10px;border-radius:4px;"
f"white-space:pre-wrap;font-size:12px;\">{summary}</pre>"
if summary else ""
)
html = _EMAIL_TMPL.format(
order_number=order_number,
entity_name=entity_name,
frn=frn or "N/A",
service_name=service_name,
approve_url=approve_url,
files=files_html,
summary_block=summary_block,
)
msg = MIMEMultipart("alternative")
msg["Subject"] = f"[Review & File] {order_number}{service_name}"
msg["From"] = smtp_from
msg["To"] = to_email
msg.attach(MIMEText(html, "html"))
try:
with smtplib.SMTP(smtp_host, smtp_port) as server:
if smtp_port != 25:
server.starttls()
server.login(smtp_user, smtp_pass)
server.sendmail(smtp_from, [to_email], msg.as_string())
logger.info("auto_filing: admin review email sent to %s", to_email)
except Exception as exc:
logger.warning("auto_filing: could not send admin email: %s", exc)
# ─── Per-order override writer (used by the API approve-and-file endpoint) ─
def set_order_override(sales_order_name: str) -> bool:
"""Flip ``custom_auto_filing_override`` to 1 on the Sales Order."""
try:
from scripts.workers.erpnext_client import ERPNextClient
ERPNextClient().set_value(
"Sales Order",
sales_order_name,
"custom_auto_filing_override",
1,
)
return True
except Exception as exc:
logger.warning("auto_filing: could not set override: %s", exc)
return False