Derive current stage from most-recent stage-bearing activity
Stage authority moves from Organization.Food_Co_op_Organizing.Stage to the most recent Check-in (organizing) activity whose Stage custom field is set. Staff create these activities manually to mark transitions; org-owner form submissions no longer write the Stage field at all, so they cannot override a staff-set transition. - /api/data: removed the Contact.get for org-side Stage; added an Activity.get filtered to ACTIVITY_TYPE_NAME + ACTIVITY_STAGE_FIELD IS NOT EMPTY, ordered by activity_date_time DESC, id DESC, limit 1. Fallback when no such activity exists: Inquiry (rank 0). Org-name lookup, stage activity, prefill, and option-group fetch all run in parallel via Promise.all. - /api/submit: removed the stageAtSubmission read + the [ACTIVITY_STAGE_FIELD] write on the activity record. The form's activities are stage-null by design now. - config/form.ts: dropped the stage_at_submission readonly field (no longer being set or displayed). Kept ACTIVITY_STAGE_FIELD export — it's now used by /api/data to find stage-bearing activities. Updated the current_stage field comment to reflect the new source. - components/EngagementForm.tsx: dropped stage_at_submission from evalState (no longer referenced by any visibility rule or readonly display). Org.Food_Co_op_Organizing.Stage remains in CiviCRM for staff list views; the middleware no longer reads or writes it. No backfill required — orgs without a stage-bearing activity simply read as Inquiry.
This commit is contained in:
@@ -5,16 +5,17 @@
|
||||
*
|
||||
* Verifies checksum, resolves org from cid (mirror of /api/data), then
|
||||
* creates a new `Check-in (organizing)` activity. The activity's own Stage
|
||||
* field (`Check_in_data__organizing_.Stage`) is auto-populated from the
|
||||
* org's current `Food_Co_op_Organizing.Stage` so each check-in carries a
|
||||
* snapshot of where the org was at submission time.
|
||||
* field is intentionally left null: the form is not the authority on stage.
|
||||
* Staff set Stage on their own check-in activities to mark transitions, and
|
||||
* /api/data derives the org's current stage from the most recent stage-
|
||||
* bearing activity.
|
||||
*
|
||||
* STUB MODE: if CiviCRM env vars are unset, returns success without writing.
|
||||
*/
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { civi, verifyChecksum } from "@/lib/civicrm";
|
||||
import { allFields, ACTIVITY_TYPE_NAME, ACTIVITY_STAGE_FIELD, FORM_CONTACT_RELATIONSHIP } from "@/config/form";
|
||||
import { allFields, ACTIVITY_TYPE_NAME, FORM_CONTACT_RELATIONSHIP } from "@/config/form";
|
||||
import type { SubmitPayload } from "@/types/form";
|
||||
import { appEnv, redact } from "@/lib/env";
|
||||
import { rateLimit, clientIp } from "@/lib/rate-limit";
|
||||
@@ -102,33 +103,19 @@ async function runSubmit(cid: string, cs: string, values: Record<string, unknown
|
||||
}
|
||||
const orgId = orgs[0].contact_id_b;
|
||||
|
||||
// Read the org's current Framework Stage so we can snapshot it on the activity.
|
||||
const orgRes = await civi<{ "Food_Co_op_Organizing.Stage": string | null }>(
|
||||
"Contact",
|
||||
"get",
|
||||
{
|
||||
select: ["Food_Co_op_Organizing.Stage"],
|
||||
where: [["id", "=", orgId]],
|
||||
},
|
||||
);
|
||||
const stageAtSubmission = orgRes.values?.[0]?.["Food_Co_op_Organizing.Stage"] ?? null;
|
||||
|
||||
// Build the activity record. Each form-side field name maps to its
|
||||
// configured `civiField` for the activity-side write.
|
||||
// Build the activity record. The Stage custom field is deliberately NOT
|
||||
// set here — staff own stage transitions on their own activities.
|
||||
const activityRecord: Record<string, unknown> = {
|
||||
"activity_type_id:name": ACTIVITY_TYPE_NAME,
|
||||
"status_id:name": "Completed",
|
||||
target_contact_id: orgId,
|
||||
source_contact_id: Number(cid),
|
||||
subject: "Co-op Check-in (form submission)",
|
||||
[ACTIVITY_STAGE_FIELD]: stageAtSubmission,
|
||||
};
|
||||
for (const [name, value] of Object.entries(values)) {
|
||||
const field = FIELD_BY_NAME.get(name);
|
||||
if (!field || !field.civiField) continue;
|
||||
if (field.type === "readonly") continue; // never write read-only fields
|
||||
if (name === "current_stage") continue; // org-side, never written to activity
|
||||
if (name === "stage_at_submission") continue; // handled above via ACTIVITY_STAGE_FIELD
|
||||
activityRecord[field.civiField] = value;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user