/** * Per-field-most-recent prefill walk. * * Loads all `Org Engagement Submission` activities for an organization, * sorted DESC by activity_date_time. For each field name we care about, * walk the list and take the first non-null value found. * * This closes the v1 prefill gap that Webform CiviCRM cannot express in * its admin UI: load latest values per-field across multiple activities, * without coupling to update-mode. */ import { civi } from "./civicrm"; import type { FieldConfig } from "@/types/form"; interface ActivityRow { id: number; activity_date_time: string; // CiviCRM returns custom fields under their machine names like // `custom_42` or `Stage_0_Core.peer_group_participation`. [key: string]: unknown; } export interface PrefillResult { /** Form-side keys → most-recent non-null value. */ values: Record; /** Number of submission activities walked. */ activityCount: number; } /** * Walk Org Engagement Submission activities for the org, returning per-field * most-recent values keyed by FieldConfig.name. Only fields with a `civiField` * are looked up; transient fields are ignored. */ export async function loadPrefill( orgId: number, fields: FieldConfig[], activityTypeName = "Org Engagement Submission", ): Promise { const civiSelected = fields.filter((f) => f.civiField); // CiviCRM File custom fields return a file id by default. To surface a // human-readable filename in the prefill (so the prior-attachment // indicator can show it), also request the joined `.file_name` for any // file-type field. const fileFieldRefs = new Set( civiSelected.filter((f) => f.type === "file").map((f) => f.civiField!), ); const selectSet = new Set(["id", "activity_date_time"]); for (const f of civiSelected) selectSet.add(f.civiField!); for (const ref of fileFieldRefs) selectSet.add(`${ref}.file_name`); const select = Array.from(selectSet); const res = await civi("Activity", "get", { select, where: [ ["activity_type_id:name", "=", activityTypeName], ["target_contact_id", "=", orgId], ], orderBy: { activity_date_time: "DESC" }, limit: 200, }); const rows = res.values ?? []; const out: Record = {}; for (const f of civiSelected) { for (const row of rows) { const v = row[f.civiField!]; if (v === null || v === undefined || v === "") continue; if (f.type === "file") { const fname = row[`${f.civiField!}.file_name`]; out[f.name] = { id: v, file_name: typeof fname === "string" ? fname : undefined, }; } else { out[f.name] = v; } break; } } return { values: out, activityCount: rows.length }; }