Implement 499-Q quarterly filing lifecycle
After 499-A+Q bundle is filed, the handler now creates actual compliance_orders for each remaining quarterly 499-Q filing: Schedule: Q1 due Feb 1, Q2 due May 1, Q3 due Aug 1, Q4 due Nov 1 Each quarterly order: - Created as paid (covered by bundle price) - Has due_date, quarter, period_end_date in intake_data - Links to parent 499-A order - Tracks reminder status (30d/14d/7d sent flags) Notification worker (quarterly_499q_notify.py): - Runs daily at 8am CT via systemd timer - Sends HTML reminder emails at 30, 14, 7 days before due - Email includes intake link for client to submit quarterly data - Late warning at 7 days: "USAC may estimate higher contributions" - Idempotent: won't re-send same reminder level Added fcc-499q service slug ($0, not sold standalone). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3e04a8fc16
commit
572f0cbf93
5 changed files with 305 additions and 26 deletions
|
|
@ -99,6 +99,7 @@ FCC_SERVICE_SLUGS: frozenset[str] = frozenset({
|
|||
"fcc-499a",
|
||||
"fcc-499a-zero",
|
||||
"fcc-499a-499q",
|
||||
"fcc-499q",
|
||||
"stir-shaken",
|
||||
"bdc-filing",
|
||||
"fcc-full-compliance",
|
||||
|
|
|
|||
|
|
@ -1395,39 +1395,95 @@ class Form499AHandler(BaseServiceHandler):
|
|||
# ------------------------------------------------------------------ #
|
||||
|
||||
def _schedule_499q_calendar(self, order_number: str, entity: dict) -> None:
|
||||
"""Create compliance_orders for each remaining 499-Q quarter.
|
||||
|
||||
Each quarterly order:
|
||||
- Has a due_date so the 499-Q notification cron can send reminders
|
||||
- Links to the parent 499-A order via intake_data.parent_499a_order
|
||||
- Starts in 'awaiting_intake' so the client receives a form link
|
||||
- Is $0 (covered by the bundle price)
|
||||
|
||||
The renewal_worker / 499q_notify cron handles:
|
||||
30 days before → first reminder email with intake link
|
||||
14 days before → second reminder
|
||||
7 days before → urgent reminder
|
||||
Due date → final warning
|
||||
"""
|
||||
if entity.get("is_deminimis"):
|
||||
logger.info(
|
||||
"Form499AHandler: de minimis carrier — skipping 499-Q calendar",
|
||||
)
|
||||
return
|
||||
|
||||
year = datetime.utcnow().year
|
||||
# 499-Q quarters: Q1 due Feb 1, Q2 due May 1, Q3 due Aug 1, Q4 due Nov 1
|
||||
quarters = [
|
||||
{"quarter": "Q1", "due": date(year, 2, 1), "period_end": date(year - 1, 12, 31)},
|
||||
{"quarter": "Q2", "due": date(year, 5, 1), "period_end": date(year, 3, 31)},
|
||||
{"quarter": "Q3", "due": date(year, 8, 1), "period_end": date(year, 6, 30)},
|
||||
{"quarter": "Q4", "due": date(year, 11, 1), "period_end": date(year, 9, 30)},
|
||||
]
|
||||
today = date.today()
|
||||
upcoming = [q for q in quarters if q["due"] >= today]
|
||||
if not upcoming:
|
||||
# All quarters passed this year — schedule Q1 of next year
|
||||
upcoming = [{"quarter": "Q1", "due": date(year + 1, 2, 1),
|
||||
"period_end": date(year, 12, 31)}]
|
||||
|
||||
try:
|
||||
from scripts.workers.erpnext_client import ERPNextClient
|
||||
erp = ERPNextClient()
|
||||
year = datetime.utcnow().year
|
||||
due_dates = [date(year, 2, 1), date(year, 5, 1),
|
||||
date(year, 8, 1), date(year, 11, 1)]
|
||||
today = date.today()
|
||||
due_dates = [d for d in due_dates if d >= today]
|
||||
if not due_dates:
|
||||
due_dates = [date(year + 1, 2, 1)]
|
||||
for d in due_dates:
|
||||
erp.create_resource(
|
||||
"Compliance Calendar",
|
||||
{
|
||||
"entity_name": entity.get("legal_name", ""),
|
||||
"order_reference": order_number,
|
||||
"compliance_type": "FCC Form 499-Q",
|
||||
"description": (
|
||||
f"Quarterly FCC Form 499-Q filing for "
|
||||
f"{entity.get('legal_name', '')} "
|
||||
f"(FRN {entity.get('frn', '')})"
|
||||
conn = psycopg2.connect(os.environ.get("DATABASE_URL", ""))
|
||||
with conn.cursor() as cur:
|
||||
for q in upcoming:
|
||||
q_order = f"{order_number}-{q['quarter']}"
|
||||
# Check if already created (idempotency)
|
||||
cur.execute(
|
||||
"SELECT 1 FROM compliance_orders WHERE order_number = %s",
|
||||
(q_order,),
|
||||
)
|
||||
if cur.fetchone():
|
||||
logger.info("499-Q %s already scheduled", q_order)
|
||||
continue
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO compliance_orders (
|
||||
order_number, service_slug, customer_name,
|
||||
customer_email, customer_phone,
|
||||
telecom_entity_id, service_fee_cents,
|
||||
payment_status, intake_data, created_at, updated_at
|
||||
) VALUES (
|
||||
%s, 'fcc-499q', %s, %s, %s, %s, 0,
|
||||
'paid', %s, now(), now()
|
||||
)
|
||||
""",
|
||||
(
|
||||
q_order,
|
||||
entity.get("legal_name", ""),
|
||||
entity.get("contact_email", ""),
|
||||
entity.get("contact_phone", ""),
|
||||
entity.get("id"),
|
||||
json.dumps({
|
||||
"parent_499a_order": order_number,
|
||||
"quarter": q["quarter"],
|
||||
"due_date": q["due"].isoformat(),
|
||||
"period_end_date": q["period_end"].isoformat(),
|
||||
"filing_year": year,
|
||||
"filer_id_499": entity.get("filer_id_499", ""),
|
||||
"frn": entity.get("frn", ""),
|
||||
"entity_name": entity.get("legal_name", ""),
|
||||
"reminder_sent_30d": False,
|
||||
"reminder_sent_14d": False,
|
||||
"reminder_sent_7d": False,
|
||||
"intake_completed": False,
|
||||
}),
|
||||
),
|
||||
"due_date": d.strftime("%Y-%m-%d"),
|
||||
"recurring": 1,
|
||||
"recurrence_period": "Quarterly",
|
||||
"status": "Upcoming",
|
||||
},
|
||||
)
|
||||
)
|
||||
logger.info(
|
||||
"Form499AHandler: scheduled 499-Q %s due %s for %s",
|
||||
q["quarter"], q["due"], entity.get("legal_name", ""),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except Exception as exc:
|
||||
logger.warning("Form499AHandler: could not schedule 499-Q: %s", exc)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue