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>
329 lines
13 KiB
Python
329 lines
13 KiB
Python
"""Foreign Qualification (Certificate of Authority) handler.
|
|
|
|
Processes per-state COA filings for entities expanding to additional
|
|
states. Supports two modes:
|
|
|
|
- `foreign-qualification-single`: one target state per order.
|
|
- `foreign-qualification-multi`: N target states, one order. The
|
|
handler fans out one `foreign_qualification_registrations` row per
|
|
state and processes them sequentially.
|
|
|
|
FCC carriers ordering multi-state registration get the same flow —
|
|
their entity is identified by telecom_entity_id, the handler reads
|
|
the selected target states from intake_data.target_states.
|
|
|
|
Filing steps per state (high-level):
|
|
1. Obtain Certificate of Good Standing from home state (if required
|
|
by target state's `foreign_qual_requires_coa` flag).
|
|
2. Provision NW Registered Agent address in target state (if the
|
|
order includes RA service).
|
|
3. Submit the Certificate of Authority / Application for Registration
|
|
via the target state's SOS portal (Playwright adapter).
|
|
4. Capture confirmation, persist COA document.
|
|
5. Mark `foreign_qualification_registrations.status = 'approved'`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
import psycopg2
|
|
|
|
from .base_handler import BaseServiceHandler
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
DATABASE_URL = os.environ.get("DATABASE_URL", "")
|
|
|
|
|
|
class ForeignQualificationHandler(BaseServiceHandler):
|
|
SERVICE_SLUG = "foreign-qualification"
|
|
SERVICE_NAME = "Foreign Qualification"
|
|
REQUIRES_LLM = False
|
|
|
|
async def process(self, order_data: dict) -> list[str]:
|
|
order_number = order_data["name"]
|
|
entity = order_data.get("entity", {}) or {}
|
|
intake = order_data.get("intake_data", {}) or {}
|
|
|
|
home_state = (
|
|
intake.get("home_state_code")
|
|
or entity.get("state_of_formation")
|
|
or entity.get("address_state")
|
|
or ""
|
|
).upper()
|
|
entity_type = (
|
|
intake.get("entity_type")
|
|
or entity.get("entity_type")
|
|
or "llc"
|
|
).lower()
|
|
|
|
target_states = intake.get("target_states") or []
|
|
if isinstance(target_states, str):
|
|
target_states = [s.strip().upper() for s in target_states.split(",") if s.strip()]
|
|
else:
|
|
target_states = [str(s).strip().upper() for s in target_states]
|
|
|
|
if not target_states:
|
|
self._create_admin_todo(
|
|
order_number,
|
|
f"{self.SERVICE_NAME}: no target states specified in intake_data. "
|
|
"Admin should add target_states and re-dispatch.",
|
|
)
|
|
return []
|
|
|
|
entity_name = (
|
|
intake.get("entity_legal_name")
|
|
or entity.get("legal_name")
|
|
or ""
|
|
)
|
|
if not entity_name:
|
|
self._create_admin_todo(
|
|
order_number,
|
|
f"{self.SERVICE_NAME}: missing entity legal name. "
|
|
"Admin should set entity_legal_name in intake_data.",
|
|
)
|
|
return []
|
|
|
|
# ── Fan out: one registration row per target state ──────────────
|
|
conn = psycopg2.connect(DATABASE_URL)
|
|
try:
|
|
with conn.cursor() as cur:
|
|
for state_code in target_states:
|
|
# Fetch state fee from state_filing_fees.
|
|
fee_col = (
|
|
"foreign_llc_fee"
|
|
if entity_type in ("llc", "pllc")
|
|
else "foreign_corp_fee"
|
|
)
|
|
cur.execute(
|
|
f"SELECT {fee_col} AS fee FROM state_filing_fees WHERE state_code = %s",
|
|
(state_code,),
|
|
)
|
|
fee_row = cur.fetchone()
|
|
state_fee = int(fee_row[0]) if fee_row and fee_row[0] else 0
|
|
|
|
# Check for existing active registration to avoid dupes.
|
|
cur.execute(
|
|
"""
|
|
SELECT id FROM foreign_qualification_registrations
|
|
WHERE order_number = %s
|
|
AND target_state_code = %s
|
|
AND status NOT IN ('cancelled','rejected')
|
|
LIMIT 1
|
|
""",
|
|
(order_number, state_code),
|
|
)
|
|
if cur.fetchone():
|
|
logger.info(
|
|
"ForeignQualHandler: %s already has active registration "
|
|
"for %s — skipping",
|
|
order_number, state_code,
|
|
)
|
|
continue
|
|
|
|
# Compliance order ID (nullable).
|
|
co_id = order_data.get("compliance_order_id") or order_data.get("id")
|
|
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO foreign_qualification_registrations (
|
|
compliance_order_id, order_number,
|
|
telecom_entity_id,
|
|
home_country, home_state_code,
|
|
target_state_code,
|
|
entity_legal_name, entity_type,
|
|
formed_on, home_state_filing_number, ein,
|
|
principal_address_json,
|
|
include_ra_service,
|
|
state_fee_cents, expedited,
|
|
status
|
|
) VALUES (
|
|
%s, %s,
|
|
%s,
|
|
%s, %s,
|
|
%s,
|
|
%s, %s,
|
|
%s, %s, %s,
|
|
%s::jsonb,
|
|
%s,
|
|
%s, %s,
|
|
'received'
|
|
)
|
|
""",
|
|
(
|
|
co_id, order_number,
|
|
entity.get("id"),
|
|
intake.get("home_country", "US"), home_state,
|
|
state_code,
|
|
entity_name, entity_type,
|
|
entity.get("formed_on"), entity.get("state_filing_number"),
|
|
entity.get("ein"),
|
|
_json_or_null(intake.get("principal_address")),
|
|
intake.get("include_ra_each", True),
|
|
state_fee,
|
|
bool(intake.get("expedited", False)),
|
|
),
|
|
)
|
|
conn.commit()
|
|
logger.info(
|
|
"ForeignQualHandler: created %d registration(s) for %s",
|
|
len(target_states), order_number,
|
|
)
|
|
except Exception as exc:
|
|
logger.exception(
|
|
"ForeignQualHandler: DB error creating registrations for %s: %s",
|
|
order_number, exc,
|
|
)
|
|
conn.rollback()
|
|
self._create_admin_todo(
|
|
order_number,
|
|
f"{self.SERVICE_NAME}: failed to create registration rows — {exc}",
|
|
)
|
|
return []
|
|
finally:
|
|
conn.close()
|
|
|
|
# ── Per-state filing dispatch ───────────────────────────────────
|
|
# For v1, each state filing needs admin review because we haven't
|
|
# built the state-specific Playwright adapters for foreign qual yet.
|
|
# The handler creates a ToDo per state so the admin can file manually
|
|
# or trigger the adapter once it's written.
|
|
filed_count = 0
|
|
for state_code in target_states:
|
|
try:
|
|
filed = await self._process_one_state(
|
|
order_number=order_number,
|
|
entity=entity,
|
|
intake=intake,
|
|
entity_name=entity_name,
|
|
entity_type=entity_type,
|
|
home_state=home_state,
|
|
target_state=state_code,
|
|
)
|
|
if filed:
|
|
filed_count += 1
|
|
except Exception as exc:
|
|
logger.error(
|
|
"ForeignQualHandler: error filing %s in %s: %s",
|
|
order_number, state_code, exc,
|
|
)
|
|
self._update_registration_status(
|
|
order_number, state_code, "admin_review",
|
|
last_error=str(exc),
|
|
)
|
|
|
|
if filed_count == 0:
|
|
logger.info(
|
|
"ForeignQualHandler: no states auto-filed for %s — "
|
|
"all set to admin_review",
|
|
order_number,
|
|
)
|
|
|
|
return [] # artifacts come from per-state filings, not the parent
|
|
|
|
async def _process_one_state(
|
|
self,
|
|
*,
|
|
order_number: str,
|
|
entity: dict,
|
|
intake: dict,
|
|
entity_name: str,
|
|
entity_type: str,
|
|
home_state: str,
|
|
target_state: str,
|
|
) -> bool:
|
|
"""Attempt to file the COA in one target state. Returns True if
|
|
the adapter succeeded, False if we parked in admin_review."""
|
|
from scripts.formation.jurisdictions import get_jurisdiction
|
|
|
|
jc = get_jurisdiction(target_state)
|
|
if not jc.has_adapter():
|
|
logger.info(
|
|
"ForeignQualHandler: no adapter for %s — parking for admin",
|
|
target_state,
|
|
)
|
|
self._update_registration_status(
|
|
order_number, target_state, "admin_review",
|
|
last_error=f"No Playwright adapter for {target_state} foreign-qual",
|
|
)
|
|
self._create_admin_todo(
|
|
order_number,
|
|
f"Foreign qualification in {jc.name} ({target_state}) needs "
|
|
f"manual filing — no adapter. Entity: {entity_name} ({entity_type}) "
|
|
f"from {home_state}.",
|
|
)
|
|
return False
|
|
|
|
# TODO: dispatch to adapter.file_foreign_qualification() once
|
|
# per-state adapters implement the method. For now, all states
|
|
# park at admin_review.
|
|
self._update_registration_status(
|
|
order_number, target_state, "admin_review",
|
|
last_error="Foreign qual adapter not yet implemented",
|
|
)
|
|
self._create_admin_todo(
|
|
order_number,
|
|
f"Foreign qualification in {jc.name} ({target_state}): entity "
|
|
f"{entity_name} ({entity_type}) from {home_state}. "
|
|
f"State fee: ${jc.foreign_qualification_fee_cents(entity_type) or 0 / 100:.0f}. "
|
|
f"RA: {'NWRA requested' if intake.get('include_ra_each', True) else 'not requested'}. "
|
|
f"File via {jc.portal_name or 'portal'} at {jc.portal_url or 'N/A'}.",
|
|
)
|
|
return False
|
|
|
|
def _update_registration_status(
|
|
self,
|
|
order_number: str,
|
|
target_state: str,
|
|
status: str,
|
|
*,
|
|
last_error: Optional[str] = None,
|
|
) -> None:
|
|
try:
|
|
conn = psycopg2.connect(DATABASE_URL)
|
|
with conn.cursor() as cur:
|
|
cur.execute(
|
|
"""
|
|
UPDATE foreign_qualification_registrations
|
|
SET status = %s,
|
|
last_error = %s,
|
|
attempt_count = attempt_count + 1
|
|
WHERE order_number = %s
|
|
AND target_state_code = %s
|
|
""",
|
|
(status, last_error, order_number, target_state),
|
|
)
|
|
conn.commit()
|
|
conn.close()
|
|
except Exception as exc:
|
|
logger.error(
|
|
"ForeignQualHandler: failed to update status for %s/%s: %s",
|
|
order_number, target_state, exc,
|
|
)
|
|
|
|
def _create_admin_todo(self, order_number: str, description: str) -> None:
|
|
try:
|
|
from scripts.workers.erpnext_client import ERPNextClient
|
|
ERPNextClient().create_resource(
|
|
"ToDo",
|
|
{
|
|
"description": (
|
|
f"[{self.SERVICE_SLUG}] {order_number}\n\n{description}"
|
|
),
|
|
"priority": "Medium",
|
|
"role": "Accounting Advisor",
|
|
},
|
|
)
|
|
except Exception as exc:
|
|
logger.error("Could not create admin ToDo: %s", exc)
|
|
|
|
|
|
def _json_or_null(v) -> Optional[str]:
|
|
if v is None:
|
|
return None
|
|
import json
|
|
return json.dumps(v) if isinstance(v, dict) else str(v)
|