CRM Feature Plan · crm-v1.2
Smart customer groups inside the admin CRM. Define filter-based dynamic segments that resolve on demand — target by activity, spend, POS purchases, programs, skill level, membership, RFM, and more. Full-page builder UI for a complex enough task.
Overview
Problem
Staff can search and filter the customer list but cannot save those groups or act on them. Every query is one-time.
Solution
Named, persistent filter-based segments. Define conditions once — evaluate live on demand. Segments feed directly into Campaigns as targeting audiences.
UI Architecture
Full-page layout at /crm/segments/new — NOT a dialog or slide-over
┌─────────────────────────────────────────────────────────────────┐ │ ← Segments New Segment [Save] [Cancel]│ ├───────────────────────────┬─────────────────────────────────────┤ │ LEFT PANEL (400px) │ RIGHT PANEL (flex-1) │ │ │ │ │ Name ________________ │ Filter Groups (tabs or accordion) │ │ Description ___________ │ Activity | Spend | POS | Programs │ │ │ Membership | Coaching | Level | RFM│ │ ───────────────────── │ │ │ Active Conditions (N) │ [+ Add condition] │ │ │ │ │ • Last visit > 30 days × │ Each condition row: │ │ • RFM: At Risk × │ [Group/Field ▾] [Op ▾] [Value ___]│ │ • Joined program: League × │ (with sub-options when available) │ │ │ │ │ ───────────────────── │ │ │ 🟢 128 customers match │ │ │ [Preview 5 sample names] │ │ └───────────────────────────┴─────────────────────────────────────┘
Filter Fields
Activity
Last Visit (days ago)
Total Bookings
Activity Rank
Spend
Total Lifetime Spend
POS Purchases
Purchased Specific Item
Purchased from Category
Membership
Has Active Membership
Membership Status
Programs
Has Joined Program
Program Type (sub-filter)
Coaching
Has Coaching Package
Skill Level
Skill Level Range (+ certified option)
Community
Belongs to Community
Profile
Joined (days ago)
Has CRM Tag
RFM
RFM Segment
Churn Risk
Days since most recent confirmed booking or match. Enter 30 = last 30 days.
Displayed in facility currency. Compared in minor units (satang/cents).
Item options loaded from pos_catalog_items for this facility. Matches any confirmed order.
Category options loaded from pos_categories for this facility.
Optionally filter by program type using the sub-option below.
Shown only when 'Has Joined Program' is active. Further filters by program type.
Active coaching package with remaining sessions.
Enter min and max. E.g. 3.0–4.5 = intermediate players.
Community options loaded from communities table for this facility.
lt 30 = joined in last 30 days (new customers).
Tag options loaded from crm_tags for this facility.
Examples
High-value inactive
Total spend > ฿20k AND last visit > 45 days
Use case: Win-back campaignAt-risk members
Has active membership AND churn risk = high/critical
Use case: Retention offerSports shop buyers
Purchased from category = Equipment AND last visit < 30 days
Use case: Upsell new gearLeague players
Has joined program (type = league)
Use case: League announcementIntermediate certified players
Level range 3.0–5.0 AND certified only
Use case: Advanced clinic inviteCoaching package holders
Has coaching package = yes
Use case: Coach schedule updateCommunity VIP group
Belongs to community = VIP Members
Use case: Exclusive event inviteNew players, no membership
Joined < 30 days AND has membership = no
Use case: Membership conversionScreens
Database
CREATE TABLE crm_segments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), facility_id UUID NOT NULL REFERENCES facilities(id) ON DELETE CASCADE, name TEXT NOT NULL CHECK (char_length(name) BETWEEN 1 AND 80), description TEXT, filters JSONB NOT NULL DEFAULT '[]', created_by UUID REFERENCES customers(id), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (facility_id, name) );
Filters JSONB example
[
{
"field": "last_visit_days",
"operator": "gt",
"value": 30
},
{
"field": "rfm_segment",
"operator": "in",
"value": [
"at_risk"
]
},
{
"field": "has_joined_program",
"operator": "eq",
"value": true,
"meta": {
"program_type": [
"league"
]
}
},
{
"field": "level_range",
"operator": "between",
"value": [
3,
5
],
"meta": {
"certified_only": true
}
}
]API
Acceptance Criteria