Add 499-Q intake page, 499-Q handler, and 499-A discontinuance handler
499-Q Quarterly Filing: - Intake page at /order/fcc-499q with simplified revenue form (4 fields: carrier's carrier inter/intra, end-user inter/intra) - Zero-revenue confirmation checkbox - Handler creates admin todo with filing details + sends client email - Registers as fcc-499q in SERVICE_HANDLERS 499-A Discontinuance: - Handler creates admin todo with step-by-step USAC instructions (file zero-revenue 499-A, request account closure, confirm CPNI/RMD) - Sends client confirmation email explaining the process - Compliance checker CTA: when user selects "No — cancel registration" in the 499-A toggle, shows discontinuance option ($299) instead of standard filing - Order page maps form_499a_disc to fcc-499a-discontinuance slug Compliance checker intelligence: - 499-A toggle tracks _499aVariant (null/zero/discontinuance) - CTA adapts: revenue=standard 499-A, zero=zero-revenue, cancel=discontinuance - Reset clears variant flag Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
572f0cbf93
commit
0fc318cb38
6 changed files with 514 additions and 3 deletions
|
|
@ -18,6 +18,8 @@ from .fcc_compliance_checkup import FCCComplianceCheckupHandler
|
|||
from .rmd_filing import RMDFilingHandler
|
||||
from .cpni_certification import CPNIFilingHandler
|
||||
from .form_499a import Form499AHandler, Form499ABundleHandler
|
||||
from .form_499q import Form499QHandler
|
||||
from .form_499a_discontinuance import Form499ADiscontinuanceHandler
|
||||
from .stir_shaken import StirShakenHandler
|
||||
from .bdc_filing import BDCFilingHandler
|
||||
from .fcc_full_compliance import FullComplianceHandler
|
||||
|
|
@ -63,6 +65,8 @@ SERVICE_HANDLERS: dict[str, type] = {
|
|||
"fcc-499a": Form499AHandler,
|
||||
"fcc-499a-zero": Form499AHandler, # same handler, zero_revenue flag set from slug
|
||||
"fcc-499a-499q": Form499ABundleHandler,
|
||||
"fcc-499q": Form499QHandler,
|
||||
"fcc-499a-discontinuance": Form499ADiscontinuanceHandler,
|
||||
"stir-shaken": StirShakenHandler,
|
||||
# BDC triple — same handler, mode auto-resolved from slug
|
||||
"bdc-filing": BDCFilingHandler, # legacy alias (both)
|
||||
|
|
@ -100,6 +104,7 @@ FCC_SERVICE_SLUGS: frozenset[str] = frozenset({
|
|||
"fcc-499a-zero",
|
||||
"fcc-499a-499q",
|
||||
"fcc-499q",
|
||||
"fcc-499a-discontinuance",
|
||||
"stir-shaken",
|
||||
"bdc-filing",
|
||||
"fcc-full-compliance",
|
||||
|
|
|
|||
130
scripts/workers/services/form_499a_discontinuance.py
Normal file
130
scripts/workers/services/form_499a_discontinuance.py
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
"""FCC Form 499-A Discontinuance Filing Handler.
|
||||
|
||||
For carriers who no longer provide telecommunications services and need
|
||||
to close out their USAC 499-A filing obligations. Files a final 499-A
|
||||
with zero revenue and requests discontinuance status from USAC.
|
||||
|
||||
This is typically for:
|
||||
- Pure broadband resale ISPs who were incorrectly filing 499-A
|
||||
- Carriers who have ceased operations
|
||||
- Companies that were acquired and the FRN is being retired
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from .base_handler import BaseComplianceHandler
|
||||
|
||||
logger = logging.getLogger("workers.services.form_499a_discontinuance")
|
||||
|
||||
|
||||
class Form499ADiscontinuanceHandler(BaseComplianceHandler):
|
||||
SERVICE_SLUG = "fcc-499a-discontinuance"
|
||||
SERVICE_NAME = "Form 499-A Discontinuance Filing"
|
||||
|
||||
async def process(self, order_data: dict) -> dict | None:
|
||||
order_number = order_data.get("order_number", "")
|
||||
entity = order_data.get("entity", {})
|
||||
intake_data = order_data.get("intake_data", {})
|
||||
|
||||
filer_id = intake_data.get("filer_id_499") or entity.get("filer_id_499", "")
|
||||
frn = intake_data.get("frn") or entity.get("frn", "")
|
||||
legal_name = entity.get("legal_name") or intake_data.get("entity_name", "")
|
||||
|
||||
logger.info(
|
||||
"Form499ADiscontinuanceHandler: %s for %s (FRN: %s, Filer ID: %s)",
|
||||
order_number, legal_name, frn, filer_id,
|
||||
)
|
||||
|
||||
discontinuance_reason = intake_data.get("discontinuance_reason", "Ceased providing telecommunications services")
|
||||
last_service_date = intake_data.get("last_service_date", "")
|
||||
|
||||
# Create admin todo with discontinuance instructions
|
||||
# (USAC E-File discontinuance is a manual process — file zero-revenue 499-A
|
||||
# then submit discontinuance request via USAC contact form)
|
||||
self._create_admin_todo(
|
||||
order_number,
|
||||
f"FILE 499-A DISCONTINUANCE for {legal_name}\n\n"
|
||||
f"FRN: {frn}\n"
|
||||
f"Filer ID: {filer_id}\n"
|
||||
f"Reason: {discontinuance_reason}\n"
|
||||
f"Last service date: {last_service_date or 'Not specified'}\n\n"
|
||||
f"Steps:\n"
|
||||
f"1. Log in to USAC E-File (https://forms.universalservice.org/)\n"
|
||||
f"2. File a final 499-A with $0 revenue for the current year\n"
|
||||
f"3. In the comments/notes section, state: "
|
||||
f"'This is a final filing. {legal_name} has discontinued all "
|
||||
f"telecommunications services as of {last_service_date or 'current date'}. "
|
||||
f"Please close this filer account.'\n"
|
||||
f"4. Contact USAC at (888) 641-8722 or usac@usac.org to confirm "
|
||||
f"discontinuance and request removal of future filing obligations\n"
|
||||
f"5. Confirm that CPNI, RMD, and other FCC filings are also discontinued\n\n"
|
||||
f"Client email: {entity.get('contact_email') or order_data.get('customer_email', '')}",
|
||||
)
|
||||
|
||||
# Send confirmation to client
|
||||
self._send_confirmation(
|
||||
to=entity.get("contact_email") or order_data.get("customer_email", ""),
|
||||
entity_name=legal_name,
|
||||
order_number=order_number,
|
||||
filer_id=filer_id,
|
||||
)
|
||||
|
||||
return {"status": "submitted_for_processing"}
|
||||
|
||||
def _send_confirmation(
|
||||
self, to: str, entity_name: str, order_number: str, filer_id: str,
|
||||
) -> None:
|
||||
if not to:
|
||||
return
|
||||
try:
|
||||
import smtplib
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
subject = f"Form 499-A Discontinuance Filed — {entity_name}"
|
||||
html = f"""
|
||||
<div style="font-family:Inter,sans-serif;max-width:600px;margin:0 auto;color:#1f2937">
|
||||
<div style="background:#1e3a5f;padding:16px 24px;border-radius:8px 8px 0 0">
|
||||
<h2 style="color:#fff;margin:0;font-size:16px">Form 499-A Discontinuance</h2>
|
||||
</div>
|
||||
<div style="padding:24px;border:1px solid #e5e7eb;border-top:none;border-radius:0 0 8px 8px">
|
||||
<p>We've received your request to discontinue the FCC Form 499-A filing
|
||||
obligation for <strong>{entity_name}</strong> (Filer ID: {filer_id}).</p>
|
||||
|
||||
<p>We will:</p>
|
||||
<ol style="font-size:14px;color:#374151;padding-left:1.25rem">
|
||||
<li>File a final Form 499-A with zero revenue</li>
|
||||
<li>Request USAC to close your filer account</li>
|
||||
<li>Confirm discontinuance of related obligations (CPNI, RMD)</li>
|
||||
</ol>
|
||||
|
||||
<p>You'll receive a confirmation email once the discontinuance is processed
|
||||
by USAC. This typically takes 2-4 weeks.</p>
|
||||
|
||||
<p style="font-size:13px;color:#6b7280;margin-top:1rem">
|
||||
Order: {order_number}<br>
|
||||
Questions? Reply to this email or contact
|
||||
<a href="mailto:ops@performancewest.net">ops@performancewest.net</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["From"] = os.environ.get("SMTP_FROM", "Performance West <noreply@performancewest.net>")
|
||||
msg["To"] = to
|
||||
msg["Subject"] = subject
|
||||
msg.attach(MIMEText(html, "html"))
|
||||
|
||||
with smtplib.SMTP(
|
||||
os.environ.get("SMTP_HOST", "co.carrierone.com"),
|
||||
int(os.environ.get("SMTP_PORT", "587")),
|
||||
timeout=30,
|
||||
) as s:
|
||||
s.starttls()
|
||||
s.login(os.environ.get("SMTP_USER", ""), os.environ.get("SMTP_PASS", ""))
|
||||
s.send_message(msg)
|
||||
except Exception as exc:
|
||||
logger.warning("Discontinuance confirmation email failed: %s", exc)
|
||||
130
scripts/workers/services/form_499q.py
Normal file
130
scripts/workers/services/form_499q.py
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
"""FCC Form 499-Q Quarterly Filing Handler.
|
||||
|
||||
Simplified filing: the client submits quarterly revenue via the intake page,
|
||||
and this handler files it at USAC E-File. Revenue calculations are minimal
|
||||
compared to the 499-A — just four revenue buckets projected for the quarter.
|
||||
|
||||
The 499-Q determines quarterly USF contribution payments.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from .base_handler import BaseComplianceHandler
|
||||
|
||||
logger = logging.getLogger("workers.services.form_499q")
|
||||
|
||||
|
||||
class Form499QHandler(BaseComplianceHandler):
|
||||
SERVICE_SLUG = "fcc-499q"
|
||||
SERVICE_NAME = "FCC Form 499-Q Quarterly Filing"
|
||||
|
||||
async def process(self, order_data: dict) -> dict | None:
|
||||
order_number = order_data.get("order_number", "")
|
||||
entity = order_data.get("entity", {})
|
||||
intake_data = order_data.get("intake_data", {})
|
||||
|
||||
if not intake_data.get("intake_completed"):
|
||||
logger.info(
|
||||
"Form499QHandler: %s intake not completed — waiting for client",
|
||||
order_number,
|
||||
)
|
||||
self._create_admin_todo(
|
||||
order_number,
|
||||
f"499-Q {intake_data.get('quarter', '?')} for "
|
||||
f"{entity.get('legal_name', '?')} — awaiting client intake. "
|
||||
f"Due {intake_data.get('due_date', '?')}.",
|
||||
)
|
||||
return None
|
||||
|
||||
quarter = intake_data.get("quarter", "?")
|
||||
revenue = intake_data.get("revenue", {})
|
||||
filer_id = intake_data.get("filer_id_499") or entity.get("filer_id_499", "")
|
||||
frn = intake_data.get("frn") or entity.get("frn", "")
|
||||
|
||||
logger.info(
|
||||
"Form499QHandler: processing %s %s for %s (total: $%.2f)",
|
||||
order_number, quarter,
|
||||
entity.get("legal_name", "?"),
|
||||
revenue.get("total", 0),
|
||||
)
|
||||
|
||||
# Create admin todo with filing instructions
|
||||
# (Full Playwright automation for USAC E-File 499-Q TBD)
|
||||
self._create_admin_todo(
|
||||
order_number,
|
||||
f"FILE 499-Q {quarter} for {entity.get('legal_name', '?')} "
|
||||
f"(FRN: {frn}, Filer ID: {filer_id})\n\n"
|
||||
f"Revenue:\n"
|
||||
f" Carrier's Carrier Interstate: ${revenue.get('carriers_carrier_interstate', 0):.2f}\n"
|
||||
f" Carrier's Carrier Intrastate: ${revenue.get('carriers_carrier_intrastate', 0):.2f}\n"
|
||||
f" End-User Interstate: ${revenue.get('end_user_interstate', 0):.2f}\n"
|
||||
f" End-User Intrastate: ${revenue.get('end_user_intrastate', 0):.2f}\n"
|
||||
f" Total: ${revenue.get('total', 0):.2f}\n\n"
|
||||
f"Due: {intake_data.get('due_date', '?')}\n"
|
||||
f"Parent 499-A: {intake_data.get('parent_499a_order', '?')}\n\n"
|
||||
f"File at: https://forms.universalservice.org/",
|
||||
)
|
||||
|
||||
# Send confirmation email to client
|
||||
self._send_confirmation_email(
|
||||
to=entity.get("contact_email") or order_data.get("customer_email", ""),
|
||||
entity_name=entity.get("legal_name", ""),
|
||||
order_number=order_number,
|
||||
quarter=quarter,
|
||||
due_date=intake_data.get("due_date", ""),
|
||||
)
|
||||
|
||||
return {"status": "submitted_for_filing"}
|
||||
|
||||
def _send_confirmation_email(
|
||||
self, to: str, entity_name: str, order_number: str,
|
||||
quarter: str, due_date: str,
|
||||
) -> None:
|
||||
if not to:
|
||||
return
|
||||
try:
|
||||
import smtplib
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
subject = f"499-Q {quarter} Filing Received — {entity_name}"
|
||||
html = f"""
|
||||
<div style="font-family:Inter,sans-serif;max-width:600px;margin:0 auto;color:#1f2937">
|
||||
<div style="background:#1e3a5f;padding:16px 24px;border-radius:8px 8px 0 0">
|
||||
<h2 style="color:#fff;margin:0;font-size:16px">Form 499-Q {quarter} — Filing Received</h2>
|
||||
</div>
|
||||
<div style="padding:24px;border:1px solid #e5e7eb;border-top:none;border-radius:0 0 8px 8px">
|
||||
<p>Your FCC Form 499-Q quarterly revenue data for <strong>{entity_name}</strong>
|
||||
({quarter}, due {due_date}) has been received.</p>
|
||||
<p>We'll file this with USAC E-File and send you a confirmation with your
|
||||
filing reference number once complete.</p>
|
||||
<p style="font-size:13px;color:#6b7280;margin-top:1rem">
|
||||
Order: {order_number}<br>
|
||||
Questions? Reply to this email or contact
|
||||
<a href="mailto:ops@performancewest.net">ops@performancewest.net</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["From"] = os.environ.get("SMTP_FROM", "Performance West <noreply@performancewest.net>")
|
||||
msg["To"] = to
|
||||
msg["Subject"] = subject
|
||||
msg.attach(MIMEText(html, "html"))
|
||||
|
||||
with smtplib.SMTP(
|
||||
os.environ.get("SMTP_HOST", "co.carrierone.com"),
|
||||
int(os.environ.get("SMTP_PORT", "587")),
|
||||
timeout=30,
|
||||
) as s:
|
||||
s.starttls()
|
||||
s.login(
|
||||
os.environ.get("SMTP_USER", ""),
|
||||
os.environ.get("SMTP_PASS", ""),
|
||||
)
|
||||
s.send_message(msg)
|
||||
except Exception as exc:
|
||||
logger.warning("499-Q confirmation email failed: %s", exc)
|
||||
235
site/public/order/fcc-499q/index.html
Normal file
235
site/public/order/fcc-499q/index.html
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FCC Form 499-Q Quarterly Filing — Performance West Inc.</title>
|
||||
<script>
|
||||
window.__PW_API = (function() {
|
||||
var h = window.location.hostname;
|
||||
if (h === "localhost" || h === "127.0.0.1") return "http://" + h + ":3001";
|
||||
if (h === "dev.performancewest.net") return "https://api.dev.performancewest.net";
|
||||
return "https://api.performancewest.net";
|
||||
})();
|
||||
</script>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Inter',system-ui,sans-serif;color:#1f2937;background:#f9fafb;line-height:1.6}
|
||||
.wrap{max-width:640px;margin:0 auto;padding:2rem 1.25rem 4rem}
|
||||
h1{font-size:1.5rem;font-weight:700;color:#111827;margin-bottom:.25rem}
|
||||
.subtitle{font-size:.88rem;color:#6b7280;margin-bottom:1.5rem}
|
||||
.card{background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:1.25rem;margin-bottom:1rem}
|
||||
.card h2{font-size:1rem;font-weight:700;color:#1e3a5f;margin-bottom:.75rem}
|
||||
.label{display:block;font-size:.82rem;font-weight:600;color:#374151;margin-bottom:.3rem;margin-top:.75rem}
|
||||
input[type=text],input[type=number]{width:100%;padding:.5rem .7rem;border:1px solid #d1d5db;border-radius:8px;font-size:.88rem}
|
||||
input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px rgba(30,58,95,.15)}
|
||||
.btn{display:inline-flex;align-items:center;justify-content:center;padding:.65rem 1.5rem;border-radius:8px;font-weight:600;font-size:.92rem;cursor:pointer;border:none;transition:all .15s;font-family:inherit}
|
||||
.btn-primary{background:#1e3a5f;color:#fff}.btn-primary:hover{background:#162e4d}
|
||||
.btn-primary:disabled{opacity:.5;cursor:not-allowed}
|
||||
.info{padding:.6rem .85rem;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;font-size:.82rem;color:#1e40af;margin-bottom:1rem}
|
||||
.revenue-row{display:flex;gap:.5rem;align-items:center;margin-bottom:.4rem}
|
||||
.revenue-row label{flex:1;font-size:.84rem;color:#374151}
|
||||
.revenue-row input{flex:0 0 140px}
|
||||
.err{color:#dc2626;font-size:.82rem;margin-top:.5rem;display:none}
|
||||
.success{background:#f0fdf4;border:2px solid #22c55e;border-radius:12px;padding:1.5rem;text-align:center}
|
||||
.success h2{color:#166534}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<h1>FCC Form 499-Q — Quarterly Filing</h1>
|
||||
<p class="subtitle">Report your projected quarterly USF contributions based on actual telecom revenue for the prior quarter.</p>
|
||||
|
||||
<div id="loading" class="info">Loading order details...</div>
|
||||
|
||||
<div id="form-section" class="hidden">
|
||||
<div class="card">
|
||||
<h2 id="quarter-title">Quarterly Revenue</h2>
|
||||
<p class="info" id="period-info"></p>
|
||||
|
||||
<p style="font-size:.84rem;color:#6b7280;margin-bottom:.75rem">Enter your actual telecom revenue for the reporting period. All amounts in US dollars. Enter 0 for lines that don't apply.</p>
|
||||
|
||||
<div id="revenue-fields">
|
||||
<label class="label">Carrier's Carrier / Wholesale Revenue</label>
|
||||
<div class="revenue-row">
|
||||
<label>Interstate & International</label>
|
||||
<input type="number" id="rev-cc-inter" value="0" min="0" step="0.01">
|
||||
</div>
|
||||
<div class="revenue-row">
|
||||
<label>Intrastate</label>
|
||||
<input type="number" id="rev-cc-intra" value="0" min="0" step="0.01">
|
||||
</div>
|
||||
|
||||
<label class="label" style="margin-top:1rem">End-User Revenue</label>
|
||||
<div class="revenue-row">
|
||||
<label>Interstate & International</label>
|
||||
<input type="number" id="rev-eu-inter" value="0" min="0" step="0.01">
|
||||
</div>
|
||||
<div class="revenue-row">
|
||||
<label>Intrastate</label>
|
||||
<input type="number" id="rev-eu-intra" value="0" min="0" step="0.01">
|
||||
</div>
|
||||
|
||||
<label class="label" style="margin-top:1rem">Total Revenue (auto-calculated)</label>
|
||||
<div style="font-size:1.1rem;font-weight:700;color:#1e3a5f;padding:.5rem 0" id="total-rev">$0.00</div>
|
||||
</div>
|
||||
|
||||
<label style="display:flex;align-items:flex-start;gap:.5rem;padding:.5rem;margin-top:.75rem;border:1px solid #e5e7eb;border-radius:8px;cursor:pointer;font-size:.78rem;color:#6b7280;line-height:1.5">
|
||||
<input type="checkbox" id="zero-confirm" style="margin-top:2px;accent-color:#1e3a5f">
|
||||
<span id="zero-label" class="hidden">I confirm that this entity had <strong>$0 telecom revenue</strong> during this quarter.</span>
|
||||
<span id="nonzero-label">I certify that the revenue figures above are accurate to the best of my knowledge.</span>
|
||||
</label>
|
||||
|
||||
<div style="margin-top:1rem;text-align:right">
|
||||
<button class="btn btn-primary" id="btn-submit" disabled>Submit 499-Q Filing</button>
|
||||
</div>
|
||||
<p class="err" id="submit-err"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="success-section" class="hidden">
|
||||
<div class="success">
|
||||
<h2>499-Q Filing Submitted</h2>
|
||||
<p style="color:#166534;margin-top:.5rem" id="success-msg"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="error-section" class="hidden">
|
||||
<div class="card" style="border-color:#fca5a5;background:#fef2f2">
|
||||
<h2 style="color:#991b1b">Error</h2>
|
||||
<p style="color:#7f1d1d" id="error-msg"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var API = window.__PW_API;
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var orderNumber = params.get("order");
|
||||
|
||||
if (!orderNumber) {
|
||||
document.getElementById("loading").classList.add("hidden");
|
||||
document.getElementById("error-section").classList.remove("hidden");
|
||||
document.getElementById("error-msg").textContent = "No order number provided. Please use the link from your reminder email.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Load order details
|
||||
fetch(API + "/api/v1/compliance-orders/" + orderNumber)
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
document.getElementById("loading").classList.add("hidden");
|
||||
|
||||
if (data.error) {
|
||||
document.getElementById("error-section").classList.remove("hidden");
|
||||
document.getElementById("error-msg").textContent = data.error;
|
||||
return;
|
||||
}
|
||||
|
||||
var intake = data.intake_data || {};
|
||||
if (intake.intake_completed) {
|
||||
document.getElementById("success-section").classList.remove("hidden");
|
||||
document.getElementById("success-msg").textContent =
|
||||
"This quarterly filing has already been submitted. You'll receive a confirmation email when it's processed.";
|
||||
return;
|
||||
}
|
||||
|
||||
var quarter = intake.quarter || "Q?";
|
||||
var dueDate = intake.due_date || "TBD";
|
||||
var periodEnd = intake.period_end_date || "";
|
||||
var entityName = intake.entity_name || data.customer_name || "";
|
||||
|
||||
document.getElementById("quarter-title").textContent =
|
||||
quarter + " Quarterly Revenue — " + entityName;
|
||||
document.getElementById("period-info").innerHTML =
|
||||
"<strong>Filing:</strong> " + quarter + " (due " + dueDate + ")" +
|
||||
(periodEnd ? " · <strong>Period ending:</strong> " + periodEnd : "") +
|
||||
(intake.filer_id_499 ? " · <strong>Filer ID:</strong> " + intake.filer_id_499 : "");
|
||||
|
||||
document.getElementById("form-section").classList.remove("hidden");
|
||||
})
|
||||
.catch(function() {
|
||||
document.getElementById("loading").classList.add("hidden");
|
||||
document.getElementById("error-section").classList.remove("hidden");
|
||||
document.getElementById("error-msg").textContent = "Could not load order details. Please try again.";
|
||||
});
|
||||
|
||||
// Revenue calculation
|
||||
var fields = ["rev-cc-inter", "rev-cc-intra", "rev-eu-inter", "rev-eu-intra"];
|
||||
fields.forEach(function(id) {
|
||||
document.getElementById(id).addEventListener("input", updateTotal);
|
||||
});
|
||||
|
||||
function getTotal() {
|
||||
var total = 0;
|
||||
fields.forEach(function(id) { total += parseFloat(document.getElementById(id).value) || 0; });
|
||||
return total;
|
||||
}
|
||||
|
||||
function updateTotal() {
|
||||
var total = getTotal();
|
||||
document.getElementById("total-rev").textContent = "$" + total.toFixed(2);
|
||||
// Show zero confirmation if all fields are 0
|
||||
var isZero = total === 0;
|
||||
document.getElementById("zero-label").classList.toggle("hidden", !isZero);
|
||||
document.getElementById("nonzero-label").classList.toggle("hidden", isZero);
|
||||
}
|
||||
|
||||
document.getElementById("zero-confirm").addEventListener("change", function() {
|
||||
document.getElementById("btn-submit").disabled = !this.checked;
|
||||
});
|
||||
|
||||
// Submit
|
||||
document.getElementById("btn-submit").addEventListener("click", async function() {
|
||||
var btn = this;
|
||||
var errEl = document.getElementById("submit-err");
|
||||
errEl.style.display = "none";
|
||||
|
||||
if (!document.getElementById("zero-confirm").checked) {
|
||||
errEl.textContent = "Please confirm the revenue figures.";
|
||||
errEl.style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = "Submitting...";
|
||||
|
||||
try {
|
||||
var resp = await fetch(API + "/api/v1/compliance-orders/" + orderNumber + "/intake", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
intake_data: {
|
||||
intake_completed: true,
|
||||
completed_at: new Date().toISOString(),
|
||||
revenue: {
|
||||
carriers_carrier_interstate: parseFloat(document.getElementById("rev-cc-inter").value) || 0,
|
||||
carriers_carrier_intrastate: parseFloat(document.getElementById("rev-cc-intra").value) || 0,
|
||||
end_user_interstate: parseFloat(document.getElementById("rev-eu-inter").value) || 0,
|
||||
end_user_intrastate: parseFloat(document.getElementById("rev-eu-intra").value) || 0,
|
||||
total: getTotal(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
var result = await resp.json();
|
||||
if (!resp.ok) throw new Error(result.error || "Submission failed");
|
||||
|
||||
document.getElementById("form-section").classList.add("hidden");
|
||||
document.getElementById("success-section").classList.remove("hidden");
|
||||
document.getElementById("success-msg").textContent =
|
||||
"Your quarterly revenue data has been submitted. We'll file the 499-Q with USAC and send you a confirmation email.";
|
||||
} catch (err) {
|
||||
errEl.textContent = err.message || "Something went wrong.";
|
||||
errEl.style.display = "block";
|
||||
btn.disabled = false;
|
||||
btn.textContent = "Submit 499-Q Filing";
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -145,6 +145,7 @@ var CHECK_TO_SLUG = {
|
|||
cpni:"cpni-certification", cpni_certification:"cpni-certification",
|
||||
"499a":"fcc-499a", form_499a:"fcc-499a",
|
||||
form_499a_zero:"fcc-499a-zero", "499a-zero":"fcc-499a-zero",
|
||||
form_499a_disc:"fcc-499a-discontinuance", "499a-disc":"fcc-499a-discontinuance",
|
||||
"499q":"fcc-499a-499q", form_499q:"fcc-499a-499q",
|
||||
rmd:"rmd-filing", rmd_filing:"rmd-filing",
|
||||
stir:"stir-shaken", stir_shaken:"stir-shaken",
|
||||
|
|
@ -161,6 +162,7 @@ var SLUG_META = {
|
|||
"cpni-certification": {label:"CPNI Annual Certification", price:14900, desc:"47 CFR § 64.2009 annual CPNI cert filed to FCC ECFS", order:"/order/cpni-certification"},
|
||||
"fcc-499a": {label:"FCC Form 499-A Filing", price:49900, desc:"Annual USF contribution filing at USAC E-File", order:"/order/fcc-499a"},
|
||||
"fcc-499a-zero": {label:"FCC Form 499-A (Zero Revenue)", price:17900, desc:"For carriers with no telecom revenue", order:"/order/fcc-499a"},
|
||||
"fcc-499a-discontinuance":{label:"Form 499-A Discontinuance", price:29900, desc:"Close out USAC filer registration", order:"/order/fcc-499a"},
|
||||
"fcc-499a-499q": {label:"FCC 499-A + 499-Q Bundle", price:59900, desc:"Annual + quarterly USF projections", order:"/order/fcc-499a-499q"},
|
||||
"rmd-filing": {label:"RMD Registration / Recertification", price:21900, gov_fee:10000, gov_fee_label:"FCC RMD filing fee", desc:"Robocall Mitigation Database filing + $100 FCC filing fee", order:"/order/rmd-filing"},
|
||||
"stir-shaken": {label:"STIR/SHAKEN Implementation", price:49900, desc:"STIR/SHAKEN certificate + attestation setup", order:"/order/stir-shaken"},
|
||||
|
|
|
|||
|
|
@ -682,6 +682,7 @@ import Base from "../../layouts/Base.astro";
|
|||
if (check.id === "form_499a" && (status === "red" || status === "yellow")) {
|
||||
function resetF499() {
|
||||
check.status = status;
|
||||
check._499aVariant = null;
|
||||
const cr = colorMap[status];
|
||||
card.className = `${cr.bg} ${cr.border} border rounded-xl p-4 flex items-start gap-3`;
|
||||
card.innerHTML = inner;
|
||||
|
|
@ -718,14 +719,16 @@ import Base from "../../layouts/Base.astro";
|
|||
card.querySelector(".flex-1")?.appendChild(followUp);
|
||||
|
||||
followUp.querySelector(".f499-keep")?.addEventListener("click", () => {
|
||||
check._499aVariant = "zero";
|
||||
showF499Result(
|
||||
`${eName} had no telecom revenue but wants to keep the filer registration active. A zero-revenue 499-A must be filed for each year with an active filer ID. We can file these for you.`,
|
||||
"yellow"
|
||||
);
|
||||
});
|
||||
followUp.querySelector(".f499-deregister")?.addEventListener("click", () => {
|
||||
check._499aVariant = "discontinuance";
|
||||
showF499Result(
|
||||
`${eName} wants to cancel the USAC filer registration. All outstanding 499-A filings must be current first (including zero-revenue returns). We can handle the catch-up filings and submit the cancellation request to USAC on your behalf.`,
|
||||
`${eName} wants to cancel the USAC filer registration. We'll file any outstanding zero-revenue 499-A returns and submit the discontinuance request to USAC on your behalf.`,
|
||||
"yellow"
|
||||
);
|
||||
});
|
||||
|
|
@ -777,8 +780,14 @@ import Base from "../../layouts/Base.astro";
|
|||
services.push({ id: "stir_shaken", name: "STIR/SHAKEN Implementation", desc: "", price: 499 });
|
||||
break;
|
||||
case "form_499a":
|
||||
services.push({ id: "form_499a", name: "Form 499-A Filing", desc: "with revenue", price: 499 });
|
||||
services.push({ id: "form_499a_zero", name: "Form 499-A (Zero Revenue)", desc: "no telecom revenue", price: 179, altOf: "form_499a" });
|
||||
if (check._499aVariant === "discontinuance") {
|
||||
services.push({ id: "form_499a_disc", name: "Form 499-A Discontinuance", desc: "close filer account", price: 299 });
|
||||
} else if (check._499aVariant === "zero") {
|
||||
services.push({ id: "form_499a_zero", name: "Form 499-A (Zero Revenue)", desc: "no telecom revenue", price: 179 });
|
||||
} else {
|
||||
services.push({ id: "form_499a", name: "Form 499-A Filing", desc: "with revenue", price: 499 });
|
||||
services.push({ id: "form_499a_zero", name: "Form 499-A (Zero Revenue)", desc: "no telecom revenue", price: 179, altOf: "form_499a" });
|
||||
}
|
||||
if (s === "red") {
|
||||
services.push({ id: "dc_agent", name: "D.C. Registered Agent", desc: "", price: 99, priceLabel: "$99/yr" });
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue