Wire real CiviCRM DEV custom fields and dynamic option fetching
This commit is contained in:
599
config/form.ts
599
config/form.ts
@@ -1,14 +1,19 @@
|
||||
/**
|
||||
* Form definition for the Org Engagement Check-in form.
|
||||
*
|
||||
* Fields and stages are sourced from the spec at
|
||||
* docs/superpowers/specs/2026-05-08-civi-webform-design.md and the
|
||||
* concrete field list provided 2026-05-09.
|
||||
* 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
|
||||
*
|
||||
* The `civiField` property on each field is currently a placeholder
|
||||
* (`custom_<machine_name>`). Replace these with the actual CiviCRM custom
|
||||
* field references (`custom_<id>` or `<group_name>.<field_name>`) once the
|
||||
* field IDs are known. See README.md.
|
||||
* `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";
|
||||
@@ -29,6 +34,15 @@ const visibleAtOrAfter = (...stages: string[]): VisibilityRule => ({
|
||||
values: stages,
|
||||
});
|
||||
|
||||
// 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",
|
||||
@@ -36,175 +50,473 @@ const stage0: StageSectionConfig = {
|
||||
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: [
|
||||
{ name: "current_stage", label: "Framework Stage", type: "readonly", civiField: "Food_Co_op_Organizing.Stage" },
|
||||
{ name: "stage_0_peer_group_participation", label: "Peer Group Participation", type: "text" },
|
||||
{ name: "stage_0_internal_startup_assessment", label: "Internal Startup Assessment", type: "textarea" },
|
||||
{ name: "stage_0_internal_startup_assessment_date", label: "Internal Startup Assessment Date", type: "date" },
|
||||
{ name: "stage_0_member_goal", label: "Member Goal for current Stage", type: "number" },
|
||||
{ name: "stage_0_members_current", label: "Members (current)", type: "number" },
|
||||
{ name: "stage_0_total_members_at_opening", label: "Total members at opening", type: "number" },
|
||||
{ name: "stage_0_volunteers_helping_in_store", label: "Volunteers Helping In Store", type: "number" },
|
||||
{ name: "stage_0_work_from_members", label: "Work from Members", type: "text", help: "Hours, dollars, or descriptive — your choice." },
|
||||
{ name: "stage_0_total_square_ft", label: "Total square ft", type: "number" },
|
||||
{ name: "stage_0_retail_sq_ft", label: "Retail sq ft", type: "number" },
|
||||
{ name: "stage_0_latest_sources_uses_doc", label: "Latest Sources and Uses doc", type: "file" },
|
||||
{ name: "stage_0_latest_pro_forma_doc", label: "Latest Pro Forma doc", type: "file" },
|
||||
{ name: "stage_0_projected_y1_sales", label: "Projected Year 1 Sales", type: "currency" },
|
||||
{ name: "stage_0_projected_y2_sales", label: "Projected Year 2 Sales", type: "currency" },
|
||||
{ name: "stage_0_projected_y3_sales", label: "Projected Year 3 Sales", type: "currency" },
|
||||
{ name: "stage_0_projected_sales_established", label: "Projected sales once established", type: "currency" },
|
||||
{ name: "stage_0_projected_ftes", label: "Projected FTEs", type: "number", step: 0.1 },
|
||||
{ name: "stage_0_actual_fte", label: "Actual FTE", type: "number", step: 0.1 },
|
||||
{ name: "stage_0_total_cost_of_project", label: "Total cost of project", type: "currency" },
|
||||
{ name: "stage_0_member_equity_total_needed", label: "Member equity total needed", type: "currency" },
|
||||
{ name: "stage_0_member_equity_raised", label: "Member equity raised", type: "currency" },
|
||||
{ name: "stage_0_member_loans_total_needed", label: "Member loans total needed", type: "currency" },
|
||||
{ name: "stage_0_member_loans_raised", label: "Member loans raised", type: "currency" },
|
||||
{ name: "stage_0_member_preferred_shares_total_needed", label: "Member preferred shares total needed", type: "currency" },
|
||||
{ name: "stage_0_member_preferred_shares_raised", label: "Member preferred shares raised", type: "currency" },
|
||||
{ name: "stage_0_bank_debt_total_needed", label: "Bank debt total needed", type: "currency" },
|
||||
{ name: "stage_0_bank_debt_raised", label: "Bank debt raised", type: "currency" },
|
||||
{ name: "stage_0_grant_donations_needed", label: "Grants / Donations Needed", type: "currency" },
|
||||
{ name: "stage_0_grants_donations_raised", label: "Grants / Donations Raised", type: "currency" },
|
||||
{ name: "stage_0_other_sources_total_needed", label: "Other sources total needed", type: "currency" },
|
||||
{ name: "stage_0_other_sources_raised", label: "Other sources raised", type: "currency" },
|
||||
{ name: "stage_0_date_closed_folded", label: "Date Closed / Folded", type: "date" },
|
||||
].map((f) => ({ ...f, civiField: f.civiField ?? `custom_${f.name}` })) as FieldConfig[],
|
||||
// current_stage is a special UI-only field — it shows the org's stage.
|
||||
// It is NOT written back to the activity by the submit endpoint.
|
||||
{
|
||||
name: "current_stage",
|
||||
label: "Framework Stage",
|
||||
type: "readonly",
|
||||
civiField: "Food_Co_op_Organizing.Stage",
|
||||
},
|
||||
{
|
||||
name: "stage_at_submission",
|
||||
label: "This check-in's stage",
|
||||
type: "readonly",
|
||||
civiField: `${G0}.Stage`,
|
||||
help: "Set automatically to the org's current Framework Stage when you submit.",
|
||||
},
|
||||
{
|
||||
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?",
|
||||
},
|
||||
{
|
||||
name: "Internal_Startup_Assessment",
|
||||
label: "Internal Startup Assessment",
|
||||
type: "select",
|
||||
civiField: `${G0}.Internal_Startup_Assessment`,
|
||||
optionGroupId: 132,
|
||||
},
|
||||
{
|
||||
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.",
|
||||
},
|
||||
{
|
||||
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`,
|
||||
},
|
||||
{
|
||||
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,
|
||||
},
|
||||
{
|
||||
name: "Retail_sq_ft",
|
||||
label: "Retail sq ft",
|
||||
type: "number",
|
||||
civiField: `${G0}.Retail_sq_ft`,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
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",
|
||||
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,
|
||||
},
|
||||
{
|
||||
name: "Total_cost_of_project",
|
||||
label: "Total cost of project",
|
||||
type: "currency",
|
||||
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", 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", 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",
|
||||
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", 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", 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", 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",
|
||||
label: "Stage 1 — Convene and Prepare",
|
||||
visibleWhen: visibleAtOrAfter(S.Organizing, S.Feasibility, S.BusinessFeasibility, S.StoreImplementation, S.Stabilize),
|
||||
fields: [
|
||||
{ name: "stage_1_preliminary_market_assessment", label: "Preliminary Market Assessment", type: "textarea" },
|
||||
{ name: "stage_1_preliminary_market_assessment_upload", label: "Preliminary Market Assessment — Upload", type: "file" },
|
||||
{ name: "stage_1_preliminary_sources_uses", label: "Preliminary Sources And Uses", type: "textarea" },
|
||||
{ name: "stage_1_preliminary_sources_uses_upload", label: "Preliminary Sources and Uses — Upload", type: "file" },
|
||||
{ name: "stage_1_vision", label: "Vision", type: "textarea" },
|
||||
{ name: "stage_1_vision_upload", label: "Vision — Upload", type: "file" },
|
||||
{ name: "stage_1_business_concept", label: "Business Concept", type: "textarea" },
|
||||
{ name: "stage_1_business_concept_upload", label: "Business Concept — Upload", type: "file" },
|
||||
].map((f) => ({ ...f, civiField: `custom_${f.name}` })) as FieldConfig[],
|
||||
{
|
||||
name: "Preliminary_Market_Assessment",
|
||||
label: "Preliminary Market Assessment",
|
||||
type: "date",
|
||||
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 — Feasibility",
|
||||
visibleWhen: visibleAtOrAfter(S.Feasibility, S.BusinessFeasibility, S.StoreImplementation, S.Stabilize),
|
||||
fields: [
|
||||
{ name: "stage_2_market_study_date", label: "Market Study Date", type: "date" },
|
||||
{ name: "stage_2_market_study_upload", label: "Market Study — Upload", type: "file" },
|
||||
{ name: "stage_2_pro_forma_date", label: "Pro Forma — date completed", type: "date" },
|
||||
{ name: "stage_2_pro_forma_upload", label: "Pro Forma — Upload", type: "file" },
|
||||
{ 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: "stage_2_pro_forma_viability",
|
||||
name: "Pro_Forma_Viability",
|
||||
label: "Pro Forma Viability",
|
||||
type: "select",
|
||||
options: [
|
||||
{ value: "viable", label: "Viable" },
|
||||
{ value: "marginal", label: "Marginal" },
|
||||
{ value: "not_viable", label: "Not viable" },
|
||||
{ value: "tbd", label: "Not yet determined" },
|
||||
],
|
||||
civiField: `${G2}.Pro_Forma_Viability`,
|
||||
optionGroupId: 133,
|
||||
help: "Rate the viability of the project.",
|
||||
},
|
||||
{ name: "stage_2_business_plan", label: "Business Plan", type: "textarea" },
|
||||
{ name: "stage_2_business_plan_upload", label: "Business Plan — Upload", type: "file" },
|
||||
{ name: "stage_2_board_self_assessment", label: "Board Self Assessment", type: "textarea" },
|
||||
{ name: "stage_2_board_self_assessment_upload", label: "Board Self Assessment — Upload", type: "file" },
|
||||
{ name: "stage_2_governance_system", label: "Governance System Used", type: "text" },
|
||||
].map((f) => ({ ...f, civiField: `custom_${f.name}` })) as FieldConfig[],
|
||||
{
|
||||
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",
|
||||
label: "Stage 3 — Connect and Gather",
|
||||
visibleWhen: visibleAtOrAfter(S.BusinessFeasibility, S.StoreImplementation, S.Stabilize),
|
||||
fields: [
|
||||
{ name: "stage_3_site_loi_date", label: "Site: Letter of Intent Date", type: "date" },
|
||||
{ name: "stage_3_site_loi_upload", label: "Site: Letter of Intent — Upload", type: "file" },
|
||||
{ name: "stage_3_own_building_property", label: "Own the building / property", type: "boolean" },
|
||||
{ name: "stage_3_capital_campaign_owner_participation_pct", label: "Capital Campaign: Owner Participation %", type: "percent" },
|
||||
{ name: "stage_3_capital_campaign_avg_owner_investment", label: "Capital Campaign: Average Owner Investment", type: "currency" },
|
||||
{ name: "stage_3_capital_stack", label: "Capital Stack", type: "textarea" },
|
||||
{ name: "stage_3_project_manager_hire_date", label: "Project Manager — Date of Hire", type: "date" },
|
||||
{ name: "stage_3_store_design_plan_completion", label: "Store Design — Plan Completion", type: "date" },
|
||||
{ name: "stage_3_store_designer", label: "Store Designer", type: "text" },
|
||||
{ name: "stage_3_ncg_member", label: "NCG Member", type: "boolean" },
|
||||
{ name: "stage_3_ncg_corridor", label: "NCG Corridor", type: "text" },
|
||||
{ name: "stage_3_infra_member", label: "INFRA Member", type: "boolean" },
|
||||
{ name: "stage_3_other_distributors", label: "Other Distributors", type: "text" },
|
||||
].map((f) => ({ ...f, civiField: `custom_${f.name}` })) as FieldConfig[],
|
||||
{
|
||||
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",
|
||||
label: "Stage 4 — Excite and Build",
|
||||
visibleWhen: visibleAtOrAfter(S.StoreImplementation, S.Stabilize),
|
||||
fields: [
|
||||
{ name: "stage_4_projected_opening_date", label: "Projected Opening Date", type: "date" },
|
||||
{ name: "stage_4_construction_completion_date", label: "Construction Completion Date", type: "date" },
|
||||
{ name: "stage_4_mission_transition_plan_date", label: "Mission Transition Plan — Date", type: "date" },
|
||||
{ name: "stage_4_mission_transition_plan_upload", label: "Mission Transition Plan — Upload", type: "file" },
|
||||
{ name: "stage_4_gm_name", label: "General Manager — Name", type: "text" },
|
||||
{ name: "stage_4_gm_phone", label: "General Manager Phone", type: "phone" },
|
||||
{ name: "stage_4_gm_email", label: "General Manager Email", type: "email" },
|
||||
{ name: "stage_4_gm_hire_date", label: "General Manager — Date of Hire", type: "date" },
|
||||
{ name: "stage_4_gm_background", label: "General Manager Background", type: "textarea" },
|
||||
{ name: "stage_4_gm_support_training", label: "GM Support and Training", type: "textarea" },
|
||||
{ name: "stage_4_gm_support_team", label: "GM Support Team", type: "textarea" },
|
||||
].map((f) => ({ ...f, civiField: `custom_${f.name}` })) as FieldConfig[],
|
||||
{ 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 has 12 monthly + 4 quarterly trackers across multiple metrics; build programmatically.
|
||||
const months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
||||
const quarters = [1, 2, 3, 4];
|
||||
// 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[] = [
|
||||
{ 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` },
|
||||
|
||||
// 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}.`,
|
||||
})),
|
||||
];
|
||||
|
||||
const stage5: StageSectionConfig = {
|
||||
rank: 5,
|
||||
id: "stage_5",
|
||||
label: "Stage 5 — Fulfill & Stabilize",
|
||||
label: "Stage 5 — Fulfill and Stabilize",
|
||||
visibleWhen: visibleAtOrAfter(S.Stabilize),
|
||||
fields: [
|
||||
{ name: "stage_5_date_opened", label: "Date Opened", type: "date" as const },
|
||||
{ name: "stage_5_y1_actual_sales", label: "Y1 Actual Sales", type: "currency" as const },
|
||||
...months.map((m) => ({
|
||||
name: `stage_5_y1_target_m${m}`,
|
||||
label: `Y1 Monthly Sales Target: M${m}`,
|
||||
type: "currency" as const,
|
||||
})),
|
||||
...months.map((m) => ({
|
||||
name: `stage_5_y1_actual_m${m}`,
|
||||
label: `Y1 M${m} Actual Sales`,
|
||||
type: "currency" as const,
|
||||
})),
|
||||
...months.map((m) => ({
|
||||
name: `stage_5_y1_transactions_m${m}`,
|
||||
label: `Y1 M${m} Transactions`,
|
||||
type: "number" as const,
|
||||
})),
|
||||
...quarters.map((q) => ({
|
||||
name: `stage_5_y1_q${q}_labor`,
|
||||
label: `Y1 Q${q} Labor`,
|
||||
type: "number" as const,
|
||||
step: 0.1,
|
||||
help: "FTEs",
|
||||
})),
|
||||
...quarters.map((q) => ({
|
||||
name: `stage_5_y1_q${q}_margin`,
|
||||
label: `Y1 Q${q} Margin`,
|
||||
type: "percent" as const,
|
||||
})),
|
||||
...quarters.map((q) => ({
|
||||
name: `stage_5_y1_q${q}_member_sales_pct`,
|
||||
label: `Y1 Q${q} Member Sales %`,
|
||||
type: "percent" as const,
|
||||
})),
|
||||
].map((f) => ({ ...f, civiField: `custom_${f.name}` })) as FieldConfig[],
|
||||
fields: STAGE_5_FIELDS,
|
||||
};
|
||||
|
||||
export const formConfig: FormConfig = {
|
||||
@@ -218,3 +530,18 @@ export const formConfig: FormConfig = {
|
||||
|
||||
/** 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 (so the form's stage_at_submission writes here). */
|
||||
export const ACTIVITY_STAGE_FIELD = `${G0}.Stage`;
|
||||
|
||||
/** 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"),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user