Add engagement authorization, remove price headers from intake pages, fix duplicate emails

- Add clickwrap authorization checkbox to fcc-compliance, state-puc, neca-ocn order pages
- Store engagement_accepted_at/ip/version in compliance_orders (migration 074)
- Add 499-A past-due/multi-year eSign engagement letter generator
- Gate 499-A handler on engagement signature for past-due/multi-year orders
- Remove price/tax/fee headers from all 19 intake pages (post-payment only)
- Fix duplicate confirmation email for compliance_batch orders
- Add USAC past-due fee negotiation research doc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
justin 2026-04-28 02:50:02 -05:00
parent 6171c64b90
commit cbfb8d6091
29 changed files with 602 additions and 52 deletions

View file

@ -170,11 +170,36 @@ class Form499AHandler(BaseServiceHandler):
date_str = datetime.now().strftime("%Y%m%d")
generated: list[str] = []
# Engagement letter gate: past-due or multi-year (2+) refiling orders
# require a signed engagement letter before we begin work.
filing_mode = order_data.get("filing_mode") or "current"
multi_year = order_data.get("multi_year_filings") or []
needs_engagement = (
filing_mode == "past_due"
or (multi_year and len(multi_year) >= 2)
)
if needs_engagement:
esign_signed = order_data.get("engagement_esign_signed_at")
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")
if not already_required:
logger.info(
"Form499AHandler: %s requires engagement letter eSign (mode=%s, years=%s) — generating + pausing",
order_number, filing_mode, multi_year,
)
self._generate_and_send_engagement_letter(order_data)
else:
logger.info(
"Form499AHandler: %s still waiting for engagement eSign — skipping",
order_number,
)
return []
# Multi-year mode (migration 060): when multi_year_filings has 2+
# years, run this handler once per year with each year pinned as
# form_year_override. Persist per-year confirmations to
# compliance_orders.multi_year_confirmations.
multi_year = order_data.get("multi_year_filings") or []
if multi_year and len(multi_year) >= 2:
all_generated: list[str] = []
year_conf_records: list[dict] = []
@ -1410,6 +1435,150 @@ class Form499AHandler(BaseServiceHandler):
except Exception as exc:
logger.error("Could not create admin ToDo: %s", exc)
def _generate_and_send_engagement_letter(self, order_data: dict) -> None:
"""Generate engagement letter PDF and email client a signing link."""
order_number = order_data["name"]
entity = order_data.get("entity", {}) or {}
intake = order_data.get("intake_data") or {}
multi_year = order_data.get("multi_year_filings") or []
customer_email = order_data.get("customer_email", "")
customer_name = order_data.get("customer_name", "")
import tempfile
work_dir = tempfile.mkdtemp(prefix="engagement_")
docx_path = os.path.join(work_dir, f"engagement_{order_number}.docx")
try:
from scripts.document_gen.templates.engagement_letter_499a import (
generate_engagement_letter,
)
generate_engagement_letter(
entity_name=entity.get("legal_name") or intake.get("entity_legal_name", ""),
frn=entity.get("frn") or intake.get("frn", ""),
contact_name=customer_name,
contact_email=customer_email,
filing_years=multi_year if multi_year else None,
order_number=order_number,
output_path=docx_path,
)
# Convert to PDF
pdf_path = self._convert_to_pdf(docx_path)
# Upload to MinIO
from scripts.document_gen import MinioStorage
storage = MinioStorage()
minio_key = f"engagement/{order_number}/engagement_letter.pdf"
storage.upload_file(pdf_path or docx_path, minio_key)
# Update order with engagement letter path + mark eSign required
try:
conn = psycopg2.connect(os.environ.get("DATABASE_URL", ""))
cur = conn.cursor()
cur.execute(
"""UPDATE compliance_orders
SET engagement_esign_required = TRUE,
engagement_letter_minio_key = %s,
payment_status = 'pending_esign'
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)
# Email client the engagement signing link
if customer_email:
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"
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>"
)
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)
# Create admin todo
try:
from scripts.workers.erpnext_client import ERPNextClient
ERPNextClient().create_resource("ToDo", {
"description": (
f"[fcc-499a] {order_number}\n\n"
f"Engagement letter generated and sent for 499-A past-due/multi-year refiling.\n"
f"Entity: {entity.get('legal_name', '')}\n"
f"Years: {years_str}\n"
f"Waiting for client eSign before processing begins."
),
"priority": "Medium",
"role": "Accounting Advisor",
})
except Exception as exc:
logger.warning("Could not create engagement admin ToDo: %s", exc)
except Exception as exc:
logger.error("Engagement letter generation failed for %s: %s", order_number, exc)
# Create admin todo for manual follow-up
try:
from scripts.workers.erpnext_client import ERPNextClient
ERPNextClient().create_resource("ToDo", {
"description": (
f"[fcc-499a] {order_number}\n\n"
f"FAILED to generate engagement letter: {exc}\n"
f"Manual engagement letter needed before processing past-due 499-A."
),
"priority": "High",
"role": "Accounting Advisor",
})
except Exception:
pass
def _box_to_category_ids(box_num: int) -> set[str]:
"""Given a Line 105 box number, return the category ids that tick it.