// app/api/shareholder-changes/parties/route.ts // GET /api/shareholder-changes/parties?company_id= // // Returns the two lists the wizard offers when picking the parties to a share // transfer. There is no shareholder register yet, so a shareholder is somebody // already on file — either a person who is (also) a director, or a company: // // { items: { // directors: [{ id, name, surname, email, id_no, linked }] // linked = director of company_id // companies: [{ id, name, email, enterprise_no, authorised_shares }] // } } // // The company being resolved is EXCLUDED from the company list: a company // cannot be a shareholder in itself, and offering it invites a pointless // round-trip through validation. // // Every director is returned, not only those linked to `company_id`. A // shareholder of a private company need not serve on its board, and since // there is no register of who holds what we cannot pre-filter reliably. The // `linked` flag lets the UI surface the obvious candidates first without // hiding anyone. import { NextRequest, NextResponse } from "next/server"; import { fetchCollection, type PBRecord } from "@/lib/pb"; import { COL } from "@/lib/shareholder-changes"; interface DirectorRec extends PBRecord { first_names?: string; surname?: string; email?: string | null; id_no?: string | null; telephone?: string | null; } interface CompanyRec extends PBRecord { enterprise_name?: string; enterprise_no?: string; company_email?: string | null; authorised_shares?: number | null; } interface JunctionRec extends PBRecord { director?: string; } export async function GET(req: NextRequest) { const url = new URL(req.url); const companyId = url.searchParams.get("company_id") ?? ""; try { // Which directors are on the board of the company being resolved. Used only // as a "likely current shareholder" hint, so a failure here must not break // the picker — degrade to "nobody flagged". const linkedDirectorIds = new Set(); if (companyId) { try { const junction = await fetchCollection(COL.companyDirectors, { filter: `company="${companyId}"`, perPage: 500, }); for (const j of junction.items) { if (j.director) linkedDirectorIds.add(j.director); } } catch (err) { console.error( "[shareholder-changes] could not load company directors:", err instanceof Error ? err.message : err ); } } const [directors, companies] = await Promise.all([ fetchCollection(COL.directors, { perPage: 500, sort: "surname" }), fetchCollection(COL.companies, { perPage: 500, sort: "enterprise_name", }), ]); // A company with no name is a data-quality artefact, not a pickable party. const companyItems = companies.items .filter((c) => (c.enterprise_name ?? "").trim() !== "") .filter((c) => c.id !== companyId) .map((c) => ({ id: c.id, name: (c.enterprise_name ?? "").trim(), email: (c.company_email ?? "").trim() || null, enterprise_no: c.enterprise_no ?? null, authorised_shares: c.authorised_shares ?? null, })); const directorItems = directors.items .filter((d) => [d.first_names, d.surname].some((v) => (v ?? "").trim() !== "")) .map((d) => ({ id: d.id, name: (d.first_names ?? "").trim(), surname: (d.surname ?? "").trim(), email: (d.email ?? "").trim() || null, id_no: d.id_no ?? null, // Carried into the signature block ("Cell - 0827985409"), matching how // section 4 identifies its signers. telephone: (d.telephone ?? "").trim() || null, linked: linkedDirectorIds.has(d.id), })); return NextResponse.json({ items: { directors: directorItems, companies: companyItems, }, }); } catch (err) { return NextResponse.json( { error: err instanceof Error ? err.message : "Unexpected error" }, { status: 500 } ); } }