Stage 0: org-contact custom fields (Food_Co_op_Organizing)
Adds four fields from the Organization Contact's Food_Co_op_Organizing
custom group to the Stage 0 (always-visible) section:
Date Incorporated (date, editable)
Name on Incorporation Certificate (text, editable)
Certificate of Incorporation (readonly; see note)
Equity share (currency, editable)
These live on the Organization Contact record, not on the Check-in
activity, so they read/write through a different code path:
- FieldConfig gains civiContactField, mutually exclusive with civiField
- /api/data extends the org Contact.get select to include them and
merges values into the prefill payload keyed by form-side name
- /api/submit splits incoming values: contact-bound fields go through
Contact.update (run first), activity-bound fields stay in the
Activity.create call (run second)
- FieldRenderer readonly branch now detects file-shaped values
({id, file_name}) and displays the filename rather than [object Object]
Certificate_of_Incorporation is wired readonly only: the form's
file-upload pipeline is not actually wired end-to-end (FileList drops
at JSON.stringify in onSubmit; no /api/upload endpoint exists). A
follow-up will close that gap.
Also adds scripts/inspect-org-custom-fields.mjs, a one-off introspection
script for dumping CustomField metadata when wiring a new group.
This commit is contained in:
+23
-4
@@ -103,8 +103,11 @@ async function runSubmit(cid: string, cs: string, values: Record<string, unknown
|
||||
}
|
||||
const orgId = orgs[0].contact_id_b;
|
||||
|
||||
// Build the activity record. The Stage custom field is deliberately NOT
|
||||
// set here — staff own stage transitions on their own activities.
|
||||
// Split incoming values into:
|
||||
// - activityRecord: fields with civiField → written to the new activity
|
||||
// - orgContactValues: fields with civiContactField → written to the org
|
||||
// contact via Contact.update
|
||||
// Readonly fields are skipped entirely (display-only).
|
||||
const activityRecord: Record<string, unknown> = {
|
||||
"activity_type_id:name": ACTIVITY_TYPE_NAME,
|
||||
"status_id:name": "Completed",
|
||||
@@ -112,11 +115,27 @@ async function runSubmit(cid: string, cs: string, values: Record<string, unknown
|
||||
source_contact_id: Number(cid),
|
||||
subject: "Co-op Survey (form submission)",
|
||||
};
|
||||
const orgContactValues: Record<string, unknown> = {};
|
||||
for (const [name, value] of Object.entries(values)) {
|
||||
const field = FIELD_BY_NAME.get(name);
|
||||
if (!field || !field.civiField) continue;
|
||||
if (!field) continue;
|
||||
if (field.type === "readonly") continue; // never write read-only fields
|
||||
activityRecord[field.civiField] = value;
|
||||
if (field.civiContactField) {
|
||||
orgContactValues[field.civiContactField] = value;
|
||||
} else if (field.civiField) {
|
||||
activityRecord[field.civiField] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the org contact first (if any contact-bound fields changed),
|
||||
// then create the submission activity. Order matters: if the contact
|
||||
// update fails we'd rather not have an orphan activity claiming the
|
||||
// submission succeeded.
|
||||
if (Object.keys(orgContactValues).length > 0) {
|
||||
await civi("Contact", "update", {
|
||||
where: [["id", "=", orgId]],
|
||||
values: orgContactValues,
|
||||
});
|
||||
}
|
||||
|
||||
await civi("Activity", "create", { values: activityRecord });
|
||||
|
||||
Reference in New Issue
Block a user