Use existing 'Primary Contact' relationship type via FORM_CONTACT_RELATIONSHIP constant

This commit is contained in:
Joel Brock
2026-05-09 20:57:29 -07:00
parent e132789a3e
commit 2e9d3855ba
5 changed files with 46 additions and 54 deletions

View File

@@ -2,7 +2,7 @@
* GET /api/data?cid=<cid>&cs=<cs>
*
* Verifies the checksum, resolves the org from the contact via the
* Primary Form Contact relationship, reads the org's Framework Stage
* Primary Contact relationship, reads the org's Framework Stage
* (`Food_Co_op_Organizing.Stage` on the Organization contact), and walks
* past `Check-in (organizing)` activities for per-field most-recent prefill.
*
@@ -19,7 +19,7 @@
import { NextRequest, NextResponse } from "next/server";
import { civi, verifyChecksum } from "@/lib/civicrm";
import { loadPrefill } from "@/lib/prefill";
import { allFields, optionGroupIds, ACTIVITY_TYPE_NAME } from "@/config/form";
import { allFields, optionGroupIds, ACTIVITY_TYPE_NAME, FORM_CONTACT_RELATIONSHIP } from "@/config/form";
import type { FormDataPayload, SelectOption } from "@/types/form";
const STUB_PAYLOAD: FormDataPayload = {
@@ -118,13 +118,13 @@ export async function GET(req: NextRequest) {
);
}
// Resolve the org from the contact's Primary Form Contact relationship.
// Resolve the org from the contact's "Primary Contact" relationship.
// The relationship is Individual (A) → Organization (B); fetch contact_id_b.
const relRes = await civi<{ contact_id_b: number }>("Relationship", "get", {
select: ["contact_id_b"],
where: [
["contact_id_a", "=", Number(cid)],
["relationship_type_id:name", "=", "Primary Form Contact of"],
["relationship_type_id:name_a_b", "=", FORM_CONTACT_RELATIONSHIP],
["is_active", "=", true],
],
limit: 2,
@@ -132,13 +132,13 @@ export async function GET(req: NextRequest) {
const orgs = relRes.values ?? [];
if (orgs.length === 0) {
return NextResponse.json(
{ error: "No active Primary Form Contact relationship found for your contact." },
{ error: `No active "${FORM_CONTACT_RELATIONSHIP}" relationship found for your contact.` },
{ status: 404 },
);
}
if (orgs.length > 1) {
return NextResponse.json(
{ error: "Your contact has multiple active Primary Form Contact relationships; staff must resolve before this link will work." },
{ error: `Your contact has multiple active "${FORM_CONTACT_RELATIONSHIP}" relationships; staff must resolve before this link will work.` },
{ status: 409 },
);
}

View File

@@ -20,7 +20,7 @@
import { NextResponse } from "next/server";
import { civi, verifyChecksum } from "@/lib/civicrm";
import { ACTIVITY_TYPE_NAME } from "@/config/form";
import { ACTIVITY_TYPE_NAME, FORM_CONTACT_RELATIONSHIP } from "@/config/form";
interface CheckResult {
check: string;
@@ -114,59 +114,40 @@ async function probeContactGet(): Promise<CheckResult> {
async function probeRelationshipType(): Promise<CheckResult> {
try {
const exact = await civi<{ id: number; name_a_b: string; name_b_a: string; label_a_b: string }>(
"RelationshipType",
"get",
{
select: ["id", "name_a_b", "name_b_a", "label_a_b"],
where: [["name_a_b", "=", "Primary Form Contact of"]],
},
);
if ((exact.values ?? []).length === 1) {
const r = exact.values[0];
const exact = await civi<{
id: number;
name_a_b: string;
name_b_a: string;
label_a_b: string;
contact_type_a: string;
contact_type_b: string;
}>("RelationshipType", "get", {
select: ["id", "name_a_b", "name_b_a", "label_a_b", "contact_type_a", "contact_type_b"],
where: [["name_a_b", "=", FORM_CONTACT_RELATIONSHIP]],
});
const rows = exact.values ?? [];
if (rows.length === 1) {
const r = rows[0];
const directionOk = r.contact_type_a === "Individual" && r.contact_type_b === "Organization";
return {
check: "relationship_type_primary_form_contact_of",
ok: true,
detail: `Found id=${r.id}, name_a_b="${r.name_a_b}", name_b_a="${r.name_b_a}".`,
check: `relationship_type_${FORM_CONTACT_RELATIONSHIP.replace(/\W+/g, "_").toLowerCase()}`,
ok: directionOk,
detail: directionOk
? `Found id=${r.id}, name_a_b="${r.name_a_b}", direction Individual → Organization. ✓`
: `Found id=${r.id} but direction is ${r.contact_type_a}${r.contact_type_b} (expected Individual → Organization).`,
};
}
// Not found by exact name — list close matches so we know what's available.
const candidates = await civi<{ id: number; name_a_b: string; label_a_b: string; contact_type_a: string; contact_type_b: string }>(
"RelationshipType",
"get",
{
select: ["id", "name_a_b", "label_a_b", "contact_type_a", "contact_type_b"],
where: [
[
"OR",
[
["name_a_b", "LIKE", "%Form%"],
["name_a_b", "LIKE", "%Primary%"],
["name_a_b", "LIKE", "%Contact%"],
["label_a_b", "LIKE", "%Form%"],
["label_a_b", "LIKE", "%Primary%"],
],
],
],
limit: 25,
},
);
const closeMatches = (candidates.values ?? [])
.map((r) => ` • id=${r.id} name_a_b="${r.name_a_b}" label="${r.label_a_b}" (${r.contact_type_a}${r.contact_type_b})`)
.join("\n");
return {
check: "relationship_type_primary_form_contact_of",
check: `relationship_type_${FORM_CONTACT_RELATIONSHIP.replace(/\W+/g, "_").toLowerCase()}`,
ok: false,
detail:
"No relationship type with name_a_b='Primary Form Contact of' on this site.\n" +
"Existing relationship types matching Form/Primary/Contact:\n" +
(closeMatches || " (none)") +
"\nFix: create a new RelationshipType (Individual A ↔ Organization B) with name_a_b='Primary Form Contact of', or change the expected name in /api/data and /api/submit to match an existing type.",
rows.length === 0
? `No relationship type with name_a_b="${FORM_CONTACT_RELATIONSHIP}" found.`
: `Multiple relationship types named "${FORM_CONTACT_RELATIONSHIP}" (${rows.length}). Should be exactly one.`,
};
} catch (e) {
return {
check: "relationship_type_primary_form_contact_of",
check: `relationship_type_${FORM_CONTACT_RELATIONSHIP.replace(/\W+/g, "_").toLowerCase()}`,
ok: false,
detail: e instanceof Error ? e.message : String(e),
};

View File

@@ -14,7 +14,7 @@
import { NextResponse } from "next/server";
import { civi, verifyChecksum } from "@/lib/civicrm";
import { allFields, ACTIVITY_TYPE_NAME, ACTIVITY_STAGE_FIELD } from "@/config/form";
import { allFields, ACTIVITY_TYPE_NAME, ACTIVITY_STAGE_FIELD, FORM_CONTACT_RELATIONSHIP } from "@/config/form";
import type { SubmitPayload } from "@/types/form";
function isStubMode(): boolean {
@@ -54,7 +54,7 @@ export async function POST(req: Request) {
select: ["contact_id_b"],
where: [
["contact_id_a", "=", Number(cid)],
["relationship_type_id:name", "=", "Primary Form Contact of"],
["relationship_type_id:name_a_b", "=", FORM_CONTACT_RELATIONSHIP],
["is_active", "=", true],
],
limit: 2,