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:
parent
6171c64b90
commit
cbfb8d6091
29 changed files with 602 additions and 52 deletions
|
|
@ -1032,6 +1032,8 @@ def handle_process_compliance_service(payload: dict) -> dict:
|
|||
order["customer_name"] = row.get("customer_name")
|
||||
order["customer_phone"] = row.get("customer_phone")
|
||||
order["batch_id"] = row.get("batch_id")
|
||||
order["engagement_esign_signed_at"] = row.get("engagement_esign_signed_at")
|
||||
order["engagement_esign_required"] = row.get("engagement_esign_required")
|
||||
order["filing_mode"] = row.get("filing_mode")
|
||||
order["form_year_override"] = row.get("form_year_override")
|
||||
order["revises_order_number"] = row.get("revises_order_number")
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue