- New STAGE_OPTION_GROUP_ID constant (=75) added to config and consumed
by both the in-card readonly and the header. Because the field carries
optionGroupId, /api/data auto-includes the Stage option group in its
parallel fetch (via the existing optionGroupIds derivation).
- SubmissionContextHeader: dropped the small inline 'Stage Organizing'
line. Replaced with an uppercase 'Current stage' eyebrow next to the
progress dots, followed by a display-font 20/24px leaf-toned label
underneath. Resolves the stored option value ('Organizing') to its
Civi option label ('Stage 1 — Convene & Prepare') via the new
resolveStageLabel helper; falls back to the raw value if the option
group hasn't loaded.
- Stage 0 'Check-in (organizing)' section: re-added a current_stage
readonly field at the top. With optionGroupId set, FieldRenderer's
readonly branch resolves the value to the Civi label for display.
669 lines
25 KiB
TypeScript
669 lines
25 KiB
TypeScript
/**
|
|
* Form definition for the Org Engagement Check-in form.
|
|
*
|
|
* Field references and types are sourced from the live CiviCRM DEV
|
|
* (`client.crm.fci.coop`) inventory pulled 2026-05-09 via APIv4
|
|
* `CustomField.get` filtered to the six custom groups bound to the
|
|
* `Check-in (organizing)` activity type:
|
|
* - Check_in_data__organizing_ (Stage 0)
|
|
* - Stage_1, Stage_2, Stage_3, Stage_4, Stage_5
|
|
*
|
|
* `civiField` uses APIv4's `<group_name>.<field_name>` syntax.
|
|
*
|
|
* Fields with `optionGroupId` set have their `options` populated at runtime
|
|
* via `/api/data` — the server fetches OptionValue rows for those option
|
|
* groups and embeds them in the FormDataPayload. The hardcoded `options`
|
|
* arrays here are placeholder fallbacks if the live fetch fails.
|
|
*/
|
|
|
|
import type { FieldConfig, FormConfig, StageSectionConfig, VisibilityRule } from "@/types/form";
|
|
|
|
// Active stage values (stored values in CiviCRM's `Stage` option group).
|
|
const S = {
|
|
Inquiry: "Inquiry",
|
|
Organizing: "Organizing",
|
|
Feasibility: "Feasibility",
|
|
BusinessFeasibility: "Business feasibility",
|
|
StoreImplementation: "Store Implementation",
|
|
Stabilize: "Stabilize newly opened co-op",
|
|
} as const;
|
|
|
|
const visibleAtOrAfter = (...stages: string[]): VisibilityRule => ({
|
|
field: "current_stage",
|
|
op: "in",
|
|
values: stages,
|
|
});
|
|
|
|
/**
|
|
* Option group ID for the `Stage` option group on `client.crm.fci.coop`.
|
|
* Used to resolve the current stage value (e.g. "Organizing") to its Civi
|
|
* label (e.g. "Stage 1 — Convene & Prepare") for display.
|
|
*/
|
|
export const STAGE_OPTION_GROUP_ID = 75;
|
|
|
|
// CiviCRM custom group machine names per the APIv4 inventory.
|
|
const G0 = "Check_in_data__organizing_";
|
|
const G1 = "Stage_1";
|
|
const G2 = "Stage_2";
|
|
const G3 = "Stage_3";
|
|
const G4 = "Stage_4";
|
|
const G5 = "Stage_5";
|
|
|
|
// Stage 0 — Check-in (always visible)
|
|
const stage0: StageSectionConfig = {
|
|
rank: 0,
|
|
id: "stage_0",
|
|
label: "Check-in (organizing)",
|
|
intro:
|
|
"Core check-in fields. These are visible at every stage and capture the data we follow over time across the lifecycle of the co-op.",
|
|
fields: [
|
|
// current_stage is a UI-only readonly field. Its value is the Civi
|
|
// option *value* (e.g. "Organizing") sourced by /api/data from the
|
|
// most recent stage-bearing Check-in (organizing) activity. Setting
|
|
// `optionGroupId` makes the readonly renderer resolve the value to
|
|
// its Civi option *label* (e.g. "Stage 1 — Convene & Prepare") for
|
|
// display. The form never writes Stage back to the activity.
|
|
{
|
|
name: "current_stage",
|
|
label: "Current stage",
|
|
type: "readonly",
|
|
optionGroupId: STAGE_OPTION_GROUP_ID,
|
|
},
|
|
{
|
|
name: "Peer_Group_Participation",
|
|
label: "Peer Group Participation",
|
|
type: "select",
|
|
civiField: `${G0}.Peer_Group_Participation`,
|
|
optionGroupId: 140,
|
|
help: "Is this co-op currently participating in Peer Learning Groups?",
|
|
visibleWhen: []
|
|
},
|
|
{
|
|
name: "Internal_Startup_Assessment",
|
|
label: "Internal Startup Assessment",
|
|
type: "select",
|
|
civiField: `${G0}.Internal_Startup_Assessment`,
|
|
optionGroupId: 132,
|
|
visibleWhen: []
|
|
},
|
|
{
|
|
name: "Internal_Startup_Assessment_Date",
|
|
label: "Internal Startup Assessment Date",
|
|
type: "date",
|
|
civiField: `${G0}.Internal_Startup_Assessment_Date`,
|
|
help: "Date of the latest Internal Startup Assessment rating.",
|
|
visibleWhen: []
|
|
},
|
|
{
|
|
name: "Member_Goal_for_current_Stage",
|
|
label: "Member Goal for current Stage",
|
|
type: "number",
|
|
civiField: `${G0}.Member_Goal_for_current_Stage`,
|
|
help: "What is your member / owner goal for your current co-op development Stage?",
|
|
},
|
|
{
|
|
name: "Members__current_",
|
|
label: "Members (current)",
|
|
type: "number",
|
|
civiField: `${G0}.Members__current_`,
|
|
help: "How many paid members does the co-op currently have? Include partially paid (e.g. installment plans).",
|
|
},
|
|
{
|
|
name: "Total_members_at_opening",
|
|
label: "Total members at opening",
|
|
type: "number",
|
|
civiField: `${G0}.Total_members_at_opening`,
|
|
visibleWhen: visibleAtOrAfter(S.Stabilize),
|
|
},
|
|
// {
|
|
// name: "Volunteers_Helping_In_Store",
|
|
// label: "Volunteers Helping In Store",
|
|
// type: "select",
|
|
// civiField: `${G0}.Volunteers_Helping_In_Store`,
|
|
// optionGroupId: 141,
|
|
// help: "Does this co-op use volunteers for day-to-day operations? (Not occasional events.)",
|
|
// },
|
|
// {
|
|
// name: "Work_from_Members",
|
|
// label: "Work from Members",
|
|
// type: "select",
|
|
// civiField: `${G0}.Work_from_Members`,
|
|
// optionGroupId: 142,
|
|
// help: "Does this co-op require work from members (e.g. annual hours)?",
|
|
// },
|
|
{
|
|
name: "Total_square_ft",
|
|
label: "Total square ft",
|
|
type: "number",
|
|
civiField: `${G0}.Total_square_ft`,
|
|
step: 1,
|
|
visibleWhen: visibleAtOrAfter(S.BusinessFeasibility, S.StoreImplementation, S.Stabilize),
|
|
},
|
|
{
|
|
name: "Retail_sq_ft",
|
|
label: "Retail sq ft",
|
|
type: "number",
|
|
civiField: `${G0}.Retail_sq_ft`,
|
|
step: 1,
|
|
visibleWhen: visibleAtOrAfter(S.BusinessFeasibility, S.StoreImplementation, S.Stabilize),
|
|
},
|
|
// {
|
|
// name: "Latest_Sources_and_Uses_doc",
|
|
// label: "Latest Sources and Uses doc",
|
|
// type: "file",
|
|
// civiField: `${G0}.Latest_Sources_and_Uses_doc`,
|
|
// },
|
|
// {
|
|
// name: "Latest_Pro_Forma_doc",
|
|
// label: "Latest Pro Forma doc",
|
|
// type: "file",
|
|
// civiField: `${G0}.Latest_Pro_Forma_doc`,
|
|
// },
|
|
// { name: "Projected_Year_1_Sales", label: "Projected Year 1 Sales", type: "currency", civiField: `${G0}.Projected_Year_1_Sales` },
|
|
// { name: "Projected_Year_2_Sales", label: "Projected Year 2 Sales", type: "currency", civiField: `${G0}.Projected_Year_2_Sales` },
|
|
// { name: "Projected_Year_3_Sales", label: "Projected Year 3 Sales", type: "currency", civiField: `${G0}.Projected_Year_3_Sales` },
|
|
// {
|
|
// name: "Projected_sales_at_maturity",
|
|
// label: "Projected sales once established",
|
|
// type: "currency",
|
|
// civiField: `${G0}.Projected_sales_at_maturity`,
|
|
// },
|
|
{
|
|
name: "FTEs",
|
|
label: "Projected FTEs",
|
|
type: "number",
|
|
visibleWhen: visibleAtOrAfter(S.BusinessFeasibility, S.StoreImplementation, S.Stabilize),
|
|
civiField: `${G0}.FTEs`,
|
|
step: 0.1,
|
|
help: "Number of full-time equivalent (FTE) employees planned to work at the store.",
|
|
},
|
|
{
|
|
name: "Actual_FTE",
|
|
label: "Actual FTE",
|
|
type: "number",
|
|
civiField: `${G0}.Actual_FTE`,
|
|
step: 0.1,
|
|
visibleWhen: visibleAtOrAfter(S.Stabilize),
|
|
},
|
|
{
|
|
name: "Total_cost_of_project",
|
|
label: "Total cost of project",
|
|
type: "currency",
|
|
visibleWhen: visibleAtOrAfter(S.Stabilize),
|
|
civiField: `${G0}.Total_cost_of_project`,
|
|
},
|
|
{
|
|
name: "Member_equity_total",
|
|
label: "Member equity total needed",
|
|
type: "currency",
|
|
civiField: `${G0}.Member_equity_total`
|
|
},
|
|
{
|
|
name: "Member_equity_raised",
|
|
label: "Member equity raised",
|
|
type: "currency",
|
|
visibleWhen: visibleAtOrAfter(S.Stabilize),
|
|
civiField: `${G0}.Member_equity_raised`
|
|
},
|
|
{
|
|
name: "Member_loans_total",
|
|
label: "Member loans total needed",
|
|
type: "currency",
|
|
civiField: `${G0}.Member_loans_total`
|
|
},
|
|
{
|
|
name: "Member_loans_raised",
|
|
label: "Member loans raised",
|
|
type: "currency",
|
|
visibleWhen: visibleAtOrAfter(S.Stabilize),
|
|
civiField: `${G0}.Member_loans_raised`
|
|
},
|
|
// {
|
|
// name: "Member_preferred_shares_total",
|
|
// label: "Member preferred shares total needed",
|
|
// type: "currency",
|
|
// civiField: `${G0}.Member_preferred_shares_total`,
|
|
// },
|
|
{
|
|
name: "Member_preferred_shares_raised",
|
|
label: "Member preferred shares raised",
|
|
type: "currency",
|
|
visibleWhen: visibleAtOrAfter(S.Stabilize),
|
|
civiField: `${G0}.Member_preferred_shares_raised`,
|
|
},
|
|
{ name: "Bank_debt_total", label: "Bank debt total needed", type: "currency", civiField: `${G0}.Bank_debt_total` },
|
|
{ name: "Bank_debt_raised", label: "Bank debt raised", type: "currency", visibleWhen: visibleAtOrAfter(S.Stabilize), civiField: `${G0}.Bank_debt_raised` },
|
|
{ name: "Grant_Donations_Needed", label: "Grants / Donations Needed", type: "currency", civiField: `${G0}.Grant_Donations_Needed` },
|
|
{ name: "Grants_Donations_Raised", label: "Grants / Donations Raised", type: "currency", visibleWhen: visibleAtOrAfter(S.Stabilize), civiField: `${G0}.Grants_Donations_Raised` },
|
|
{ name: "Other_sources_total", label: "Other sources total needed", type: "currency", civiField: `${G0}.Other_sources_total` },
|
|
{ name: "Other_sources_raised", label: "Other sources raised", type: "currency", visibleWhen: visibleAtOrAfter(S.Stabilize), civiField: `${G0}.Other_sources_raised` },
|
|
{ name: "Date_Closed_Folded", label: "Date Closed / Folded", type: "date", civiField: `${G0}.Date_Closed_Folded` },
|
|
] as FieldConfig[],
|
|
};
|
|
|
|
// Stage 1 — Convene & Prepare (visible from Organizing onward)
|
|
const stage1: StageSectionConfig = {
|
|
rank: 1,
|
|
id: "stage_1",
|
|
label: "Stage 1 — Convene & Prepare",
|
|
visibleWhen: visibleAtOrAfter(S.Organizing, S.Feasibility, S.BusinessFeasibility, S.StoreImplementation, S.Stabilize),
|
|
fields: [
|
|
{
|
|
name: "Preliminary_Market_Assessment",
|
|
label: "Preliminary Market Assessment",
|
|
type: "date",
|
|
required: true,
|
|
civiField: `${G1}.Preliminary_Market_Assessment`,
|
|
help: "What date was your Preliminary Market Assessment completed?",
|
|
},
|
|
{
|
|
name: "Preliminary_Market_Assessment_Upload",
|
|
label: "Preliminary Market Assessment — Upload",
|
|
type: "file",
|
|
civiField: `${G1}.Preliminary_Market_Assessment_Upload`,
|
|
},
|
|
{
|
|
name: "Preliminary_Sources_Uses",
|
|
label: "Preliminary Sources And Uses",
|
|
type: "date",
|
|
civiField: `${G1}.Preliminary_Sources_Uses`,
|
|
help: "When was your Preliminary Sources & Uses completed?",
|
|
},
|
|
{
|
|
name: "Preliminary_Sources_Uses_Upload",
|
|
label: "Preliminary Sources and Uses — Upload",
|
|
type: "file",
|
|
civiField: `${G1}.Preliminary_Sources_Uses_Upload`,
|
|
},
|
|
{
|
|
name: "Vision",
|
|
label: "Vision",
|
|
type: "date",
|
|
civiField: `${G1}.Vision`,
|
|
help: "What date was your vision document completed?",
|
|
},
|
|
{ name: "Vision_Upload", label: "Vision — Upload", type: "file", civiField: `${G1}.Vision_Upload` },
|
|
{
|
|
name: "Business_Concept",
|
|
label: "Business Concept",
|
|
type: "date",
|
|
civiField: `${G1}.Business_Concept`,
|
|
help: "When was your business concept finalized?",
|
|
},
|
|
{
|
|
name: "Business_Concept_Upload",
|
|
label: "Business Concept — Upload",
|
|
type: "file",
|
|
civiField: `${G1}.Business_Concept_Upload`,
|
|
},
|
|
] as FieldConfig[],
|
|
};
|
|
|
|
// Stage 2 — Feasibility
|
|
const stage2: StageSectionConfig = {
|
|
rank: 2,
|
|
id: "stage_2",
|
|
label: "Stage 2 — Grow & Plan",
|
|
visibleWhen: visibleAtOrAfter(S.Feasibility, S.BusinessFeasibility, S.StoreImplementation, S.Stabilize),
|
|
fields: [
|
|
{ name: "Market_Study_Date", label: "Market Study Date", type: "date", civiField: `${G2}.Market_Study_Date` },
|
|
{ name: "Market_Study_Upload", label: "Market Study — Upload", type: "file", civiField: `${G2}.Market_Study_Upload` },
|
|
{ name: "Pro_Forma_date_completed", label: "Pro Forma — date completed", type: "date", civiField: `${G2}.Pro_Forma_date_completed` },
|
|
{ name: "Pro_Forma_Upload", label: "Pro Forma — Upload", type: "file", civiField: `${G2}.Pro_Forma_Upload` },
|
|
{
|
|
name: "Pro_Forma_Viability",
|
|
label: "Pro Forma Viability",
|
|
type: "select",
|
|
civiField: `${G2}.Pro_Forma_Viability`,
|
|
optionGroupId: 133,
|
|
help: "Rate the viability of the project.",
|
|
},
|
|
{
|
|
name: "Business_Plan",
|
|
label: "Business Plan",
|
|
type: "date",
|
|
civiField: `${G2}.Business_Plan`,
|
|
help: "What date was your most recent business plan completed?",
|
|
},
|
|
{ name: "Business_Plan_Upload", label: "Business Plan — Upload", type: "file", civiField: `${G2}.Business_Plan_Upload` },
|
|
{
|
|
name: "Board_Self_Assessment",
|
|
label: "Board Self Assessment",
|
|
type: "date",
|
|
civiField: `${G2}.Board_Self_Assessment`,
|
|
help: "When was your last Board Self Assessment completed?",
|
|
},
|
|
{
|
|
name: "Board_Self_Assessment_Upload",
|
|
label: "Board Self Assessment — Upload",
|
|
type: "file",
|
|
civiField: `${G2}.Board_Self_Assessment_Upload`,
|
|
},
|
|
{
|
|
name: "Governance_System_Used",
|
|
label: "Governance System Used",
|
|
type: "text",
|
|
civiField: `${G2}.Governance_System_Used`,
|
|
help: "What governance system does your board use, or how do you make decisions?",
|
|
},
|
|
] as FieldConfig[],
|
|
};
|
|
|
|
// Stage 3 — Connect & Gather
|
|
const stage3: StageSectionConfig = {
|
|
rank: 3,
|
|
id: "stage_3",
|
|
label: "Stage 3 — Connect & Gather",
|
|
visibleWhen: visibleAtOrAfter(S.BusinessFeasibility, S.StoreImplementation, S.Stabilize),
|
|
fields: [
|
|
{
|
|
name: "Site_Letter_of_Intent_Date",
|
|
label: "Site: Letter of Intent Date",
|
|
type: "date",
|
|
civiField: `${G3}.Site_Letter_of_Intent_Date`,
|
|
},
|
|
{
|
|
name: "Site_Letter_of_Intent_Upload",
|
|
label: "Site: Letter of Intent — Upload",
|
|
type: "file",
|
|
civiField: `${G3}.Site_Letter_of_Intent_Upload`,
|
|
},
|
|
{
|
|
name: "Own_the_building_property",
|
|
label: "Own the building / property",
|
|
type: "select",
|
|
civiField: `${G3}.Own_the_building_property`,
|
|
optionGroupId: 139,
|
|
help: "Will the co-op own the building or property?",
|
|
},
|
|
{
|
|
name: "Capital_Campaign_Owner_Participation_",
|
|
label: "Capital Campaign: Owner Participation %",
|
|
type: "percent",
|
|
civiField: `${G3}.Capital_Campaign_Owner_Participation_`,
|
|
},
|
|
{
|
|
name: "Capital_Campaign_Average_Owner_Investment",
|
|
label: "Capital Campaign: Average Owner Investment",
|
|
type: "currency",
|
|
civiField: `${G3}.Capital_Campaign_Average_Owner_Investment`,
|
|
},
|
|
{
|
|
name: "Capital_Stack",
|
|
label: "Capital Stack",
|
|
type: "multiselect",
|
|
civiField: `${G3}.Capital_Stack`,
|
|
optionGroupId: 134,
|
|
help: "Select all the types of funding that are part of your Sources.",
|
|
},
|
|
{
|
|
name: "Project_Manager_Date_of_Hire",
|
|
label: "Project Manager — Date of Hire",
|
|
type: "date",
|
|
civiField: `${G3}.Project_Manager_Date_of_Hire`,
|
|
},
|
|
{
|
|
name: "Store_Design_Plan_Completion",
|
|
label: "Store Design — Plan Completion",
|
|
type: "date",
|
|
civiField: `${G3}.Store_Design_Plan_Completion`,
|
|
},
|
|
{ name: "Store_Designer", label: "Store Designer", type: "text", civiField: `${G3}.Store_Designer` },
|
|
{ name: "NCG_Member", label: "NCG Member", type: "select", civiField: `${G3}.NCG_Member`, optionGroupId: 136 },
|
|
{ name: "NCG_Corridor", label: "NCG Corridor", type: "select", civiField: `${G3}.NCG_Corridor`, optionGroupId: 143 },
|
|
{ name: "INFRA_Member", label: "INFRA Member", type: "select", civiField: `${G3}.INFRA_Member`, optionGroupId: 137 },
|
|
{
|
|
name: "Other_Distributors",
|
|
label: "Other Distributors",
|
|
type: "select",
|
|
civiField: `${G3}.Other_Distributors`,
|
|
optionGroupId: 138,
|
|
help: "Are you using or considering one of these other distributors as your primary?",
|
|
},
|
|
] as FieldConfig[],
|
|
};
|
|
|
|
// Stage 4 — Excite & Build
|
|
const stage4: StageSectionConfig = {
|
|
rank: 4,
|
|
id: "stage_4",
|
|
label: "Stage 4 — Excite & Build",
|
|
visibleWhen: visibleAtOrAfter(S.StoreImplementation, S.Stabilize),
|
|
fields: [
|
|
{ name: "Projected_Opening_Date", label: "Projected Opening Date", type: "date", civiField: `${G4}.Projected_Opening_Date` },
|
|
{
|
|
name: "Construction_Completion_Date",
|
|
label: "Construction Completion Date",
|
|
type: "date",
|
|
civiField: `${G4}.Construction_Completion_Date`,
|
|
},
|
|
{
|
|
name: "Missions_Transition_Plan_Date",
|
|
label: "Mission Transition Plan — Date",
|
|
type: "date",
|
|
civiField: `${G4}.Missions_Transition_Plan_Date`,
|
|
},
|
|
{
|
|
name: "Mission_Transition_Plan_Upload",
|
|
label: "Mission Transition Plan — Upload",
|
|
type: "file",
|
|
civiField: `${G4}.Mission_Transition_Plan_Upload`,
|
|
},
|
|
{ name: "General_Manager_Name", label: "General Manager — Name", type: "text", civiField: `${G4}.General_Manager_Name` },
|
|
{ name: "General_Manager_Phone", label: "General Manager Phone", type: "phone", civiField: `${G4}.General_Manager_Phone` },
|
|
{ name: "GM_Email", label: "General Manager Email", type: "email", civiField: `${G4}.GM_Email` },
|
|
{
|
|
name: "General_Manager_Date_of_Hire",
|
|
label: "General Manager — Date of Hire",
|
|
type: "date",
|
|
civiField: `${G4}.General_Manager_Date_of_Hire`,
|
|
},
|
|
{
|
|
name: "General_Manager_Background",
|
|
label: "General Manager Background",
|
|
type: "select",
|
|
civiField: `${G4}.General_Manager_Background`,
|
|
optionGroupId: 135,
|
|
help: "What professional background does your General Manager have?",
|
|
},
|
|
{
|
|
name: "GM_Support_Training",
|
|
label: "GM Support and Training",
|
|
type: "text",
|
|
civiField: `${G4}.GM_Support_Training`,
|
|
help: "What training and/or onboarding support is being provided to your GM?",
|
|
},
|
|
{
|
|
name: "GM_Support_Team",
|
|
label: "GM Support Team",
|
|
type: "text",
|
|
civiField: `${G4}.GM_Support_Team`,
|
|
help: "Who is providing support and training to your GM?",
|
|
},
|
|
] as FieldConfig[],
|
|
};
|
|
|
|
// Stage 5 — Fulfill & Stabilize. Field machine names have historical
|
|
// inconsistencies; we list each one explicitly rather than building
|
|
// programmatically to keep the mapping auditable.
|
|
/*
|
|
const STAGE_5_FIELDS: FieldConfig[] = [
|
|
// Y1 Monthly Sales Targets — note the irregular machine names!
|
|
{ name: "Y1_Monthly_Sales_Targets", label: "Y1 Monthly Sales Target: M1", type: "currency", civiField: `${G5}.Y1_Monthly_Sales_Targets` },
|
|
{ name: "Y1_Monthly_Sales_Targets_M2", label: "Y1 Monthly Sales Target: M2", type: "currency", civiField: `${G5}.Y1_Monthly_Sales_Targets_M2` },
|
|
{ name: "Y1_Monthly_Sales_Target_M2", label: "Y1 Monthly Sales Target: M3", type: "currency", civiField: `${G5}.Y1_Monthly_Sales_Target_M2`, help: "(Civi field name says M2 but represents M3.)" },
|
|
{ name: "Y1_Monthly_Sales_Target_M4", label: "Y1 Monthly Sales Target: M4", type: "currency", civiField: `${G5}.Y1_Monthly_Sales_Target_M4` },
|
|
{ name: "Y1_Monthly_Sales_Target_M5", label: "Y1 Monthly Sales Target: M5", type: "currency", civiField: `${G5}.Y1_Monthly_Sales_Target_M5` },
|
|
{ name: "Y1_Monthly_Sales_Target_M6", label: "Y1 Monthly Sales Target: M6", type: "currency", civiField: `${G5}.Y1_Monthly_Sales_Target_M6` },
|
|
{ name: "Y1_Monthly_Sales_Target_M7", label: "Y1 Monthly Sales Target: M7", type: "currency", civiField: `${G5}.Y1_Monthly_Sales_Target_M7` },
|
|
{ name: "Y1_Monthly_Sales_Target_M8", label: "Y1 Monthly Sales Target: M8", type: "currency", civiField: `${G5}.Y1_Monthly_Sales_Target_M8` },
|
|
{ name: "Y1_Monthly_Sales_Target_M9", label: "Y1 Monthly Sales Target: M9", type: "currency", civiField: `${G5}.Y1_Monthly_Sales_Target_M9` },
|
|
{ name: "Y1_Monthly_Sales_Target_M10", label: "Y1 Monthly Sales Target: M10", type: "currency", civiField: `${G5}.Y1_Monthly_Sales_Target_M10` },
|
|
{ name: "Y1_Monthly_Sales_Target_M11", label: "Y1 Monthly Sales Target: M11", type: "currency", civiField: `${G5}.Y1_Monthly_Sales_Target_M11` },
|
|
{ name: "Y1_Monthly_Sales_Target_M12", label: "Y1 Monthly Sales Target: M12", type: "currency", civiField: `${G5}.Y1_Monthly_Sales_Target_M12` },
|
|
|
|
// Y1 Actual Sales (M1..M12)
|
|
...[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((m) => ({
|
|
name: `Y1_M${m}_Actual_Sales`,
|
|
label: `Y1 M${m} Actual Sales`,
|
|
type: "currency" as const,
|
|
civiField: `${G5}.Y1_M${m}_Actual_Sales`,
|
|
})),
|
|
|
|
// Y1 Transactions (M1..M12)
|
|
...[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((m) => ({
|
|
name: `Y1_M${m}_Transactions`,
|
|
label: `Y1 M${m} Transactions`,
|
|
type: "number" as const,
|
|
civiField: `${G5}.Y1_M${m}_Transactions`,
|
|
})),
|
|
|
|
// Y1 Quarterly Labor (Q1..Q4) — stored as % of sales
|
|
...[1, 2, 3, 4].map((q) => ({
|
|
name: `Y1_Q${q}_Labor`,
|
|
label: `Y1 Q${q} Labor`,
|
|
type: "percent" as const,
|
|
civiField: `${G5}.Y1_Q${q}_Labor`,
|
|
help: `Labor, as a percent of sales for Y1 Q${q}.`,
|
|
})),
|
|
|
|
// Y1 Quarterly Margin (Q1..Q4)
|
|
...[1, 2, 3, 4].map((q) => ({
|
|
name: `Y1_Q${q}_Margin`,
|
|
label: `Y1 Q${q} Margin`,
|
|
type: "percent" as const,
|
|
civiField: `${G5}.Y1_Q${q}_Margin`,
|
|
help: `Margin, as a percent of sales, for Y1 Q${q}.`,
|
|
})),
|
|
|
|
// Y1 Quarterly Member Sales % (Q1..Q4) — note the trailing underscore in Civi machine names!
|
|
...[1, 2, 3, 4].map((q) => ({
|
|
name: `Y1_Q${q}_Member_Sales_`,
|
|
label: `Y1 Q${q} Member Sales %`,
|
|
type: "percent" as const,
|
|
civiField: `${G5}.Y1_Q${q}_Member_Sales_`,
|
|
help: `Percentage of sales to member-owners in Y1 Q${q}.`,
|
|
})),
|
|
];
|
|
|
|
// Helpers for building the field-name lists in the matrices below.
|
|
const months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
|
const monthCols = months.map((m) => ({ key: `M${m}`, label: `M${m}` }));
|
|
const quarters = [1, 2, 3, 4];
|
|
const quarterCols = quarters.map((q) => ({ key: `Q${q}`, label: `Q${q}` }));
|
|
|
|
// Y1 Monthly Sales Target field names (irregular — built explicitly).
|
|
const Y1_TARGET_FIELDS = [
|
|
"Y1_Monthly_Sales_Targets", // M1
|
|
"Y1_Monthly_Sales_Targets_M2", // M2
|
|
"Y1_Monthly_Sales_Target_M2", // M3 (Civi field name says M2; label says M3)
|
|
"Y1_Monthly_Sales_Target_M4",
|
|
"Y1_Monthly_Sales_Target_M5",
|
|
"Y1_Monthly_Sales_Target_M6",
|
|
"Y1_Monthly_Sales_Target_M7",
|
|
"Y1_Monthly_Sales_Target_M8",
|
|
"Y1_Monthly_Sales_Target_M9",
|
|
"Y1_Monthly_Sales_Target_M10",
|
|
"Y1_Monthly_Sales_Target_M11",
|
|
"Y1_Monthly_Sales_Target_M12",
|
|
];
|
|
*/
|
|
const stage5: StageSectionConfig = {
|
|
rank: 5,
|
|
id: "stage_5",
|
|
label: "Stage 5 — Fulfill & Stabilize",
|
|
visibleWhen: visibleAtOrAfter(S.Stabilize),
|
|
fields: [
|
|
{ name: "Date_Opened", label: "Date Opened", type: "date", civiField: `${G5}.Date_Opened` },
|
|
{ name: "Y1_Actual_Sales", label: "Y1 Actual Sales", type: "currency", civiField: `${G5}.Y1_Actual_Sales` }
|
|
] as FieldConfig[],
|
|
// STAGE_5_FIELDS,
|
|
// matrixGroups: [
|
|
// {
|
|
// id: "y1_monthly_metrics",
|
|
// label: "Year 1 — Monthly Tracking",
|
|
// intro: "Targets and actuals by month. Leave blank for months you don't yet have data for.",
|
|
// cols: monthCols,
|
|
// rows: [
|
|
// { label: "Sales Target", type: "currency", fields: Y1_TARGET_FIELDS },
|
|
// {
|
|
// label: "Actual Sales",
|
|
// type: "currency",
|
|
// fields: months.map((m) => `Y1_M${m}_Actual_Sales`),
|
|
// },
|
|
// {
|
|
// label: "Transactions",
|
|
// type: "number",
|
|
// fields: months.map((m) => `Y1_M${m}_Transactions`),
|
|
// },
|
|
// ],
|
|
// },
|
|
// {
|
|
// id: "y1_quarterly_metrics",
|
|
// label: "Year 1 — Quarterly Tracking",
|
|
// intro: "Quarterly operating ratios.",
|
|
// cols: quarterCols,
|
|
// rows: [
|
|
// {
|
|
// label: "Labor",
|
|
// type: "percent",
|
|
// fields: quarters.map((q) => `Y1_Q${q}_Labor`),
|
|
// },
|
|
// {
|
|
// label: "Margin",
|
|
// type: "percent",
|
|
// fields: quarters.map((q) => `Y1_Q${q}_Margin`),
|
|
// },
|
|
// {
|
|
// label: "Member Sales",
|
|
// type: "percent",
|
|
// fields: quarters.map((q) => `Y1_Q${q}_Member_Sales_`),
|
|
// },
|
|
// ],
|
|
// },
|
|
// ],
|
|
};
|
|
|
|
export const formConfig: FormConfig = {
|
|
id: "org_engagement_check_in",
|
|
title: "Co-op Check-in",
|
|
subtitle:
|
|
"Update tracking data for your co-op as you progress through the organizing stages. The questions you'll see depend on where you are in the framework.",
|
|
stageField: "current_stage",
|
|
sections: [stage0, stage1, stage2, stage3, stage4, stage5],
|
|
};
|
|
|
|
/** Convenience: flatten all field configs across all sections for lookup by name. */
|
|
export const allFields = formConfig.sections.flatMap((s) => s.fields);
|
|
|
|
/** Activity type the form writes its submissions as. */
|
|
export const ACTIVITY_TYPE_NAME = "Check-in (organizing)";
|
|
|
|
/**
|
|
* Stage-snapshot field on the activity. The form does NOT write this field;
|
|
* it's set manually by staff on dedicated stage-change check-in activities.
|
|
* /api/data uses this field to derive the org's current stage by finding
|
|
* the most recent activity where it is non-empty.
|
|
*/
|
|
export const ACTIVITY_STAGE_FIELD = `${G0}.Stage`;
|
|
|
|
/**
|
|
* RelationshipType.name_a_b for the relationship that links the form-filler
|
|
* (Individual side) to the organization the form is about (Organization side).
|
|
*
|
|
* On `client.crm.fci.coop` we reuse the existing "Primary Contact"
|
|
* relationship type (id 24) rather than introducing a dedicated
|
|
* "Primary Form Contact" type. If you change this, also update the docs in
|
|
* the parent repo's spec.
|
|
*/
|
|
export const FORM_CONTACT_RELATIONSHIP = "Primary Contact";
|
|
|
|
/** All distinct option_group_ids referenced by fields in this form. */
|
|
export const optionGroupIds = Array.from(
|
|
new Set(
|
|
allFields
|
|
.map((f) => (f as FieldConfig).optionGroupId)
|
|
.filter((id): id is number => typeof id === "number"),
|
|
),
|
|
);
|