Entity cache has no RA/officer data yet. Instead, fetch the FCC lookup (quick mode) and offer RMD contact name + address and CORES principal address as clickable suggestions to auto-fill Officer 1. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
290 lines
13 KiB
Text
290 lines
13 KiB
Text
---
|
|
// OfficerStep — CEO + 2nd/3rd officers with business addresses
|
|
// per 2026 FCC Form 499-A Block 2-C (Lines 219-226).
|
|
//
|
|
// Corporate/LLC: require 3 officers (or explain fewer). Sole proprietor:
|
|
// 1 is fine. Partnership: managing partner + 2 with greatest financial
|
|
// interest. Entity structure drives the required count.
|
|
---
|
|
|
|
<div class="pw-step">
|
|
<h2>Officers</h2>
|
|
<p class="pw-help">
|
|
Lines 219-226 of Form 499-A require name, title, and business address
|
|
for up to 3 officers. Entity structure drives how many we collect —
|
|
a sole proprietor gives 1; a corporation gives 3.
|
|
</p>
|
|
|
|
<!-- Suggested officers from corporate records -->
|
|
<div id="pw-officer-suggestions" class="pw-suggestions" hidden>
|
|
<p class="pw-suggest-label">We found corporate records that may match. Select to auto-fill:</p>
|
|
<div id="pw-officer-suggest-list"></div>
|
|
</div>
|
|
|
|
<p class="pw-help" style="margin-top:0;font-size:0.82rem;color:#94a3b8;">
|
|
Fill in Officer 1 (required). Officers 2 and 3 are optional — leave blank if not applicable.
|
|
</p>
|
|
<select id="pw-officer-count" class="pw-input" hidden>
|
|
<option value="3" selected>3</option>
|
|
</select>
|
|
|
|
<fieldset class="pw-fieldset">
|
|
<legend>Officer 1 — CEO / highest-ranking</legend>
|
|
<div class="pw-row">
|
|
<div><label class="pw-field">Name</label><input type="text" id="pw-o1-n" class="pw-input" required /></div>
|
|
<div><label class="pw-field">Title</label><input type="text" id="pw-o1-t" class="pw-input" value="Chief Executive Officer" /></div>
|
|
</div>
|
|
<div class="pw-row">
|
|
<div><label class="pw-field">Email</label><input type="email" id="pw-o1-e" class="pw-input" required /></div>
|
|
<div><label class="pw-field">Phone</label><input type="tel" id="pw-o1-p" class="pw-input" /></div>
|
|
</div>
|
|
<label class="pw-field">Business street</label>
|
|
<input type="text" id="pw-o1-street" class="pw-input" />
|
|
<div class="pw-row">
|
|
<div><label class="pw-field">City</label><input type="text" id="pw-o1-city" class="pw-input" /></div>
|
|
<div><label class="pw-field">State</label><input type="text" id="pw-o1-state" class="pw-input" maxlength="2" /></div>
|
|
<div><label class="pw-field">ZIP</label><input type="text" id="pw-o1-zip" class="pw-input" maxlength="10" /></div>
|
|
</div>
|
|
</fieldset>
|
|
|
|
<fieldset class="pw-fieldset" id="pw-o2-wrap">
|
|
<legend>Officer 2 <span style="font-weight:400;color:#94a3b8;font-size:0.82rem;">(optional)</span></legend>
|
|
<div class="pw-row">
|
|
<div><label class="pw-field">Name</label><input type="text" id="pw-o2-n" class="pw-input" /></div>
|
|
<div><label class="pw-field">Title</label><input type="text" id="pw-o2-t" class="pw-input" /></div>
|
|
</div>
|
|
<label class="pw-field">Business street</label>
|
|
<input type="text" id="pw-o2-street" class="pw-input" />
|
|
<div class="pw-row">
|
|
<div><label class="pw-field">City</label><input type="text" id="pw-o2-city" class="pw-input" /></div>
|
|
<div><label class="pw-field">State</label><input type="text" id="pw-o2-state" class="pw-input" maxlength="2" /></div>
|
|
<div><label class="pw-field">ZIP</label><input type="text" id="pw-o2-zip" class="pw-input" maxlength="10" /></div>
|
|
</div>
|
|
</fieldset>
|
|
|
|
<fieldset class="pw-fieldset" id="pw-o3-wrap">
|
|
<legend>Officer 3 <span style="font-weight:400;color:#94a3b8;font-size:0.82rem;">(optional)</span></legend>
|
|
<div class="pw-row">
|
|
<div><label class="pw-field">Name</label><input type="text" id="pw-o3-n" class="pw-input" /></div>
|
|
<div><label class="pw-field">Title</label><input type="text" id="pw-o3-t" class="pw-input" /></div>
|
|
</div>
|
|
<label class="pw-field">Business street</label>
|
|
<input type="text" id="pw-o3-street" class="pw-input" />
|
|
<div class="pw-row">
|
|
<div><label class="pw-field">City</label><input type="text" id="pw-o3-city" class="pw-input" /></div>
|
|
<div><label class="pw-field">State</label><input type="text" id="pw-o3-state" class="pw-input" maxlength="2" /></div>
|
|
<div><label class="pw-field">ZIP</label><input type="text" id="pw-o3-zip" class="pw-input" maxlength="10" /></div>
|
|
</div>
|
|
</fieldset>
|
|
|
|
<div id="pw-officer-err" class="pw-err" hidden></div>
|
|
</div>
|
|
|
|
<style>
|
|
.pw-step h2 { margin: 0 0 0.5rem; color: #1a2744; }
|
|
.pw-help { color: #64748b; font-size: 0.9rem; margin-bottom: 1rem; }
|
|
.pw-field { display: block; font-weight: 600; color: #1f2937; margin: 0.4rem 0 0.15rem; font-size: 0.85rem; }
|
|
.pw-input { width: 100%; padding: 0.5rem 0.7rem; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 0.92rem; }
|
|
.pw-fieldset { border: 1px solid #e2e8f0; border-radius: 8px; padding: 0.75rem 1rem 1rem; margin: 1rem 0; }
|
|
.pw-fieldset legend { font-weight: 600; color: #1a2744; padding: 0 0.5rem; }
|
|
.pw-row { display: flex; gap: 0.75rem; flex-wrap: wrap; }
|
|
.pw-row > * { flex: 1 1 140px; }
|
|
.pw-suggestions { background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 8px; padding: 0.75rem; margin-bottom: 1rem; }
|
|
.pw-suggest-label { font-size: 0.82rem; color: #1e40af; font-weight: 600; margin: 0 0 0.5rem; }
|
|
.pw-suggest-btn { display: block; width: 100%; text-align: left; padding: 0.5rem 0.75rem; margin: 0.25rem 0; border: 1px solid #dbeafe; background: #fff; border-radius: 6px; cursor: pointer; font-size: 0.85rem; color: #1f2937; transition: all 0.1s; }
|
|
.pw-suggest-btn:hover { border-color: #3b82f6; background: #f0f4ff; }
|
|
.pw-suggest-btn .sg-name { font-weight: 600; }
|
|
.pw-suggest-btn .sg-detail { font-size: 0.78rem; color: #6b7280; }
|
|
.pw-err { color: #b91c1c; margin-top: 0.75rem; font-size: 0.9rem; }
|
|
</style>
|
|
|
|
<script>
|
|
const g = <T extends HTMLElement>(id: string) => document.getElementById(id) as T;
|
|
const count = g<HTMLSelectElement>("pw-officer-count");
|
|
const o2wrap = g<HTMLElement>("pw-o2-wrap");
|
|
const o3wrap = g<HTMLElement>("pw-o3-wrap");
|
|
const err = g<HTMLDivElement>("pw-officer-err");
|
|
|
|
// All 3 officer sections always visible — 2 & 3 are optional
|
|
function syncCount() {};
|
|
|
|
function officerFields(i: number) {
|
|
return {
|
|
n: g<HTMLInputElement>(`pw-o${i}-n`),
|
|
t: g<HTMLInputElement>(`pw-o${i}-t`),
|
|
e: i === 1 ? g<HTMLInputElement>("pw-o1-e") : null,
|
|
p: i === 1 ? g<HTMLInputElement>("pw-o1-p") : null,
|
|
street: g<HTMLInputElement>(`pw-o${i}-street`),
|
|
city: g<HTMLInputElement>(`pw-o${i}-city`),
|
|
state: g<HTMLInputElement>(`pw-o${i}-state`),
|
|
zip: g<HTMLInputElement>(`pw-o${i}-zip`),
|
|
};
|
|
}
|
|
|
|
// Auto-fill Officer 1 from FCC data if available but not yet filled
|
|
async function prefillFromFCC(entity: any) {
|
|
if (!entity?.frn) return;
|
|
const API = (window as any).__PW_API || "";
|
|
const suggestDiv = g<HTMLElement>("pw-officer-suggestions");
|
|
const listDiv = g<HTMLElement>("pw-officer-suggest-list");
|
|
const f = officerFields(1);
|
|
|
|
// If Officer 1 already has a name, don't overwrite
|
|
if (f.n.value.trim()) return;
|
|
|
|
try {
|
|
const resp = await fetch(`${API}/api/v1/fcc/lookup?frn=${entity.frn}&quick=1`);
|
|
if (!resp.ok) return;
|
|
const d = await resp.json();
|
|
|
|
const suggestions: Array<{name: string; title: string; source: string; address?: string; city?: string; state?: string; zip?: string}> = [];
|
|
|
|
// RMD contact
|
|
if (d.rmd?.contact_name) {
|
|
const addrLines = (d.rmd.business_address || "").split("\n").map((s: string) => s.trim()).filter(Boolean);
|
|
const lastLine = addrLines[addrLines.length - 1] || "";
|
|
const stateZipMatch = lastLine.match(/([A-Z]{2})\s+(\d{5})/);
|
|
suggestions.push({
|
|
name: d.rmd.contact_name,
|
|
title: "Contact (from RMD filing)",
|
|
source: "FCC RMD",
|
|
address: addrLines.length > 1 ? addrLines.slice(0, -1).join(", ") : addrLines[0],
|
|
city: stateZipMatch ? lastLine.replace(stateZipMatch[0], "").replace(/,?\s*$/, "").trim() : "",
|
|
state: stateZipMatch ? stateZipMatch[1] : "",
|
|
zip: stateZipMatch ? stateZipMatch[2] : "",
|
|
});
|
|
}
|
|
|
|
// CORES address (entity-level, not a person name)
|
|
if (d.cores?.address && d.cores.city) {
|
|
suggestions.push({
|
|
name: "",
|
|
title: "Principal address (from CORES)",
|
|
source: "FCC CORES",
|
|
address: d.cores.address,
|
|
city: d.cores.city,
|
|
state: d.cores.state || "",
|
|
zip: d.cores.zip || "",
|
|
});
|
|
}
|
|
|
|
if (suggestions.length === 0) return;
|
|
|
|
listDiv.innerHTML = "";
|
|
for (const s of suggestions) {
|
|
const btn = document.createElement("button");
|
|
btn.type = "button";
|
|
btn.className = "pw-suggest-btn";
|
|
btn.innerHTML = `
|
|
${s.name ? `<span class="sg-name">${s.name}</span>` : ""}
|
|
<span class="sg-detail">${s.title}</span>
|
|
${s.address ? `<span class="sg-detail">${[s.address, s.city, s.state, s.zip].filter(Boolean).join(", ")}</span>` : ""}
|
|
`;
|
|
btn.addEventListener("click", () => {
|
|
if (s.name) f.n.value = s.name;
|
|
if (s.address) f.street.value = s.address;
|
|
if (s.city) f.city.value = s.city;
|
|
if (s.state) f.state.value = s.state;
|
|
if (s.zip) f.zip.value = s.zip;
|
|
suggestDiv.hidden = true;
|
|
});
|
|
listDiv.appendChild(btn);
|
|
}
|
|
suggestDiv.hidden = false;
|
|
} catch {}
|
|
}
|
|
|
|
window.addEventListener("pw:step-shown", (evt: any) => {
|
|
if (evt.detail.step !== "officer") return;
|
|
const s = (window as any).PWIntake.get();
|
|
const o = s.officers || [{}, {}, {}];
|
|
const structure = s.entity?.entity_structure;
|
|
const existingCount = s.entity?.officer_count_claimed;
|
|
count.value = String(existingCount || (structure === "sole_prop" ? 1 : structure === "partnership" ? 3 : 3));
|
|
syncCount();
|
|
|
|
for (let i = 1; i <= 3; i++) {
|
|
const f = officerFields(i);
|
|
const oc = o[i - 1] || {};
|
|
f.n.value = oc.name || (i === 1 ? (s.entity?.ceo_name || s.entity?.contact_name || "") : "");
|
|
f.t.value = oc.title || (i === 1 ? (s.entity?.ceo_title || "Chief Executive Officer") : "");
|
|
if (f.e) f.e.value = oc.email || (i === 1 ? (s.entity?.contact_email || "") : "");
|
|
if (f.p) f.p.value = oc.phone || (i === 1 ? (s.entity?.contact_phone || "") : "");
|
|
f.street.value = oc.street || (i === 1 ? (s.entity?.address_street || "") : "");
|
|
f.city.value = oc.city || (i === 1 ? (s.entity?.address_city || "") : "");
|
|
f.state.value = oc.state || (i === 1 ? (s.entity?.address_state || "") : "");
|
|
f.zip.value = oc.zip || (i === 1 ? (s.entity?.address_zip || "") : "");
|
|
}
|
|
|
|
// Show FCC-sourced suggestions if Officer 1 is empty
|
|
if (s.entity?.frn) {
|
|
prefillFromFCC(s.entity);
|
|
}
|
|
});
|
|
|
|
window.addEventListener("pw:step-next", (evt: any) => {
|
|
const PW = (window as any).PWIntake;
|
|
if (PW.steps[PW.get().step_index] !== "officer") return;
|
|
const officers: any[] = [];
|
|
const missing: string[] = [];
|
|
for (let i = 1; i <= 3; i++) {
|
|
const f = officerFields(i);
|
|
if (i === 1) {
|
|
// Officer 1 is required
|
|
if (!f.n.value.trim()) missing.push("Officer 1 name");
|
|
if (!f.t.value.trim()) missing.push("Officer 1 title");
|
|
if (!f.street.value.trim()) missing.push("Officer 1 street address");
|
|
if (!f.city.value.trim()) missing.push("Officer 1 city");
|
|
}
|
|
// Only include officer if name is filled (skip blank optional officers)
|
|
if (f.n.value.trim()) {
|
|
officers.push({
|
|
name: f.n.value.trim(), title: f.t.value.trim(),
|
|
email: f.e?.value.trim() || "", phone: f.p?.value.trim() || "",
|
|
street: f.street.value.trim(), city: f.city.value.trim(),
|
|
state: f.state.value.trim().toUpperCase(), zip: f.zip.value.trim(),
|
|
});
|
|
}
|
|
}
|
|
if (missing.length) {
|
|
err.hidden = false; err.textContent = `Required: ${missing.join(", ")}`;
|
|
evt.preventDefault(); return;
|
|
}
|
|
err.hidden = true;
|
|
const s = PW.get();
|
|
PW.set({
|
|
officers,
|
|
entity: {
|
|
...s.entity,
|
|
ceo_name: officers[0].name,
|
|
ceo_title: officers[0].title,
|
|
contact_name: officers[0].name,
|
|
contact_email: officers[0].email,
|
|
contact_phone: officers[0].phone,
|
|
officer_1_street: officers[0].street,
|
|
officer_1_city: officers[0].city,
|
|
officer_1_state: officers[0].state,
|
|
officer_1_zip: officers[0].zip,
|
|
officer_2_name: officers[1]?.name || null,
|
|
officer_2_title: officers[1]?.title || null,
|
|
officer_2_street: officers[1]?.street || null,
|
|
officer_2_city: officers[1]?.city || null,
|
|
officer_2_state: officers[1]?.state || null,
|
|
officer_2_zip: officers[1]?.zip || null,
|
|
officer_3_name: officers[2]?.name || null,
|
|
officer_3_title: officers[2]?.title || null,
|
|
officer_3_street: officers[2]?.street || null,
|
|
officer_3_city: officers[2]?.city || null,
|
|
officer_3_state: officers[2]?.state || null,
|
|
officer_3_zip: officers[2]?.zip || null,
|
|
officer_count_claimed: n,
|
|
},
|
|
});
|
|
PW.patchIntakeData({
|
|
officer: officers[0],
|
|
officer_2: officers[1] || null,
|
|
officer_3: officers[2] || null,
|
|
officer_count_claimed: n,
|
|
});
|
|
});
|
|
</script>
|