feat(schema): add validation for lead source and status; update sample data to ensure compliance with new choices

This commit is contained in:
Ashwin 2026-08-02 09:02:53 +05:30
parent 9dc080ddec
commit dc3aa4d613
7 changed files with 355 additions and 11 deletions

View file

@ -326,6 +326,8 @@ def _validate_sample_data(where: str, sample: dict, raw: dict) -> None:
COUNTRIES,
CURRENCY_CODES,
INDCHOICES,
LEAD_SOURCE,
LEAD_STATUS,
PRIORITY_CHOICE,
SOURCES,
STAGES,
@ -432,6 +434,14 @@ def _validate_sample_data(where: str, sample: dict, raw: dict) -> None:
_reject_unknown(at, row, SAMPLE_LEAD_KEYS)
_require(at, row, "title")
_validate_choice(at, row.get("stage"), "stage", lead_stages)
# Lead.source and Lead.status carry `choices` the applier never checks.
# Both were unvalidated here until three shipped packs were found writing
# source="website" — a value LEAD_SOURCE does not contain, so the demo row
# rendered as a blank dropdown and no source filter could reach it. The
# row looked fine in the database, which is precisely why this needs a
# schema check rather than trust.
_validate_choice(at, row.get("source"), "source", {c for c, _l in LEAD_SOURCE})
_validate_choice(at, row.get("status"), "status", {c for c, _l in LEAD_STATUS})
_validate_choice(at, row.get("currency"), "currency", currencies)

View file

@ -3,7 +3,12 @@ import pytest
from common.packs.loader import get_registry
from common.packs.schema import SAMPLE_ENTITY_KEYS
SHIPPED = {"real-estate", "education-admissions", "professional-services"}
SHIPPED = {
"real-estate",
"education-admissions",
"professional-services",
"events-weddings",
}
def test_all_shipped_packs_load_and_validate():

View file

@ -103,6 +103,29 @@ def test_task_stage_accepts_in_progress():
assert stages[0]["stage_type"] == "in_progress"
def _with_lead(lead: dict) -> dict:
return _minimal() | {"sample_data": {"leads": [{"title": "An enquiry"} | lead]}}
def test_sample_lead_source_outside_lead_source_is_rejected():
# "website" reads as obviously valid and is not: LEAD_SOURCE has no such
# entry, and Django does not enforce choices on .create(). Three shipped
# packs carried this before the guard existed — the rows wrote cleanly and
# surfaced only as a blank source column no filter could match.
with pytest.raises(PackValidationError, match="source"):
validate_manifest(_with_lead({"source": "website"}))
def test_sample_lead_source_from_lead_source_is_accepted():
manifest = validate_manifest(_with_lead({"source": "partner"}))
assert manifest["sample_data"]["leads"][0]["source"] == "partner"
def test_sample_lead_status_outside_lead_status_is_rejected():
with pytest.raises(PackValidationError, match="status"):
validate_manifest(_with_lead({"status": "qualified"}))
def test_registry_lookup_of_unknown_id_returns_none():
assert get_pack("no-such-pack") is None

View file

@ -246,7 +246,7 @@
"custom_fields": { "course_interest": "Data Science Fundamentals", "intake": "Spring 2027", "parent_name": "Karen Whitmore", "parent_phone": "+1 720 555 0102", "previous_qualification": "High school diploma" } },
{ "title": "Weekend spoken English batch", "first_name": "Lucia", "last_name": "Moreau",
"email": "lucia.moreau@example.com", "phone": "+1 305 555 0240",
"source": "website", "stage": "New enquiry",
"source": "other", "stage": "New enquiry",
"opportunity_amount": 180, "currency": "USD",
"custom_fields": { "course_interest": "Spoken English", "intake": "Fall 2026", "previous_qualification": "High school diploma" } },
{ "title": "Employer-sponsored CPA seat", "first_name": "Nathan", "last_name": "Achebe",
@ -271,7 +271,7 @@
"custom_fields": { "course_interest": "MBA Foundation", "intake": "Winter 2027", "previous_qualification": "Bachelor's degree" } },
{ "title": "IELTS for a visa application", "first_name": "Tariq", "last_name": "Rahman",
"email": "tariq.rahman@example.com", "phone": "+1 832 555 0246",
"source": "website", "stage": "Demo class",
"source": "other", "stage": "Demo class",
"opportunity_amount": 450, "currency": "USD",
"custom_fields": { "course_interest": "IELTS Coaching", "intake": "Fall 2026", "previous_qualification": "Master's degree" } },
{ "title": "Bootcamp enquiry from a referral", "first_name": "Ursula", "last_name": "Klein",
@ -316,7 +316,7 @@
"custom_fields": { "course_interest": "Full-Stack Web Development", "intake": "Summer 2027", "previous_qualification": "High school diploma" } },
{ "title": "Withdrew before the demo class", "first_name": "Caleb", "last_name": "Nyong'o",
"email": "caleb.nyongo@example.com", "phone": "+1 470 555 0257",
"source": "website", "stage": "Not joining",
"source": "other", "stage": "Not joining",
"opportunity_amount": 3200, "currency": "USD",
"custom_fields": { "course_interest": "Data Science Fundamentals", "intake": "Spring 2027", "previous_qualification": "Bachelor's in Physics" } }
]

View file

@ -0,0 +1,306 @@
{
"id": "events-weddings",
"version": 1,
"name": "Events & weddings",
"description": "Wedding planners, event agencies and venues taking enquiries through to a delivered event.",
"terminology": {
"lead.singular": "Enquiry",
"lead.plural": "Enquiries",
"opportunity.singular": "Booking",
"opportunity.plural": "Bookings",
"account.singular": "Client",
"account.plural": "Clients"
},
"lead_pipeline": {
"name": "Enquiries",
"description": "First enquiry through to a signed booking.",
"stages": [
{ "name": "New enquiry", "order": 1, "stage_type": "open", "color": "#6B7280" },
{ "name": "Consultation booked", "order": 2, "stage_type": "open", "color": "#3B82F6" },
{ "name": "Proposal sent", "order": 3, "stage_type": "open", "color": "#6366F1" },
{ "name": "Date held", "order": 4, "stage_type": "open", "color": "#8B5CF6" },
{ "name": "Contract sent", "order": 5, "stage_type": "open", "color": "#F59E0B" },
{ "name": "Booked", "order": 6, "stage_type": "won", "color": "#10B981" },
{ "name": "Date lost", "order": 7, "stage_type": "lost", "color": "#EF4444" }
]
},
"case_pipeline": {
"name": "Event delivery",
"description": "Changes and problems raised once a booking is confirmed.",
"stages": [
{ "name": "New issue", "order": 1, "stage_type": "open", "color": "#6B7280" },
{ "name": "With planner", "order": 2, "stage_type": "open", "color": "#3B82F6" },
{ "name": "Awaiting vendor", "order": 3, "stage_type": "open", "color": "#F59E0B" },
{ "name": "Awaiting client", "order": 4, "stage_type": "open", "color": "#EC4899" },
{ "name": "Resolved", "order": 5, "stage_type": "closed", "color": "#10B981" },
{ "name": "Not our remit", "order": 6, "stage_type": "rejected", "color": "#EF4444" }
]
},
"task_pipeline": {
"name": "Planning workflow",
"description": "Run-up work for a booked event.",
"stages": [
{ "name": "To do", "order": 1, "stage_type": "open", "color": "#6B7280" },
{ "name": "Scheduled", "order": 2, "stage_type": "open", "color": "#3B82F6" },
{ "name": "In progress", "order": 3, "stage_type": "in_progress", "color": "#8B5CF6" },
{ "name": "Done", "order": 4, "stage_type": "completed", "color": "#10B981" }
]
},
"custom_fields": [
{ "target": "Lead", "key": "event_type", "label": "Event type", "field_type": "dropdown", "is_filterable": true, "display_order": 1,
"options": [
{ "value": "wedding", "label": "Wedding" },
{ "value": "reception", "label": "Reception" },
{ "value": "engagement", "label": "Engagement party" },
{ "value": "milestone", "label": "Milestone celebration" },
{ "value": "corporate", "label": "Corporate event" },
{ "value": "conference", "label": "Conference" },
{ "value": "gala", "label": "Gala or fundraiser" }
] },
{ "target": "Lead", "key": "event_date", "label": "Event date", "field_type": "date", "is_filterable": true, "display_order": 2 },
{ "target": "Lead", "key": "guest_count", "label": "Guest count", "field_type": "number", "is_filterable": true, "display_order": 3 },
{ "target": "Lead", "key": "venue", "label": "Venue", "field_type": "text", "display_order": 4 },
{ "target": "Lead", "key": "budget", "label": "Budget", "field_type": "number", "display_order": 5 },
{ "target": "Lead", "key": "venue_confirmed", "label": "Venue confirmed", "field_type": "checkbox", "display_order": 6 }
],
"tags": [
{ "name": "Peak date", "color": "red", "description": "Falls in the peak season — the date will not hold for long" },
{ "name": "Destination", "color": "violet", "description": "Event away from the home city" },
{ "name": "Corporate", "color": "blue", "description": "Company event rather than a private celebration" },
{ "name": "Referral", "color": "teal", "description": "Introduced by a past client or a venue" }
],
"products": [
{ "name": "Full planning & design", "sku": "EW-PLAN-FULL", "price": 6500, "category": "Planning",
"description": "End-to-end planning from concept and budget through to the day itself." },
{ "name": "Partial planning", "sku": "EW-PLAN-PARTIAL", "price": 3200, "category": "Planning",
"description": "Planning support from three months out, for a client who has already booked a venue." },
{ "name": "Day-of coordination", "sku": "EW-COORD-DAY", "price": 1500, "category": "Coordination",
"description": "On-site management of the run sheet, vendors and timings on the event day." },
{ "name": "Venue sourcing & site visits", "sku": "EW-VENUE-SRC", "price": 750, "category": "Planning",
"description": "Shortlist, availability checks and accompanied site visits." },
{ "name": "Decor & floral styling", "sku": "EW-DECOR", "price": 4200, "category": "Styling",
"description": "Design concept, florals, table styling and installation." },
{ "name": "Catering coordination (per guest)", "sku": "EW-CATERING-PG", "price": 85, "category": "Catering",
"description": "Menu design, tasting and service coordination; charged per confirmed guest." },
{ "name": "Photography & film package", "sku": "EW-PHOTO-FILM", "price": 2800, "category": "Media",
"description": "Full-day photography and a highlights film, edited and delivered digitally." },
{ "name": "Guest management & RSVP desk", "sku": "EW-GUEST-RSVP", "price": 900, "category": "Coordination",
"description": "Invitations, RSVP tracking, seating plan and a staffed welcome desk." }
],
"sample_data": {
"accounts": [
{ "name": "Whitmore & Castellanos", "industry": "ENTERTAINMENT & LEISURE", "email": "eleanor.whitmore@example.com",
"phone": "+1 415 555 0410", "city": "San Francisco", "country": "US",
"description": "September wedding at Ashcombe Manor, 140 guests. Peak Saturday." },
{ "name": "The Ferris Family", "industry": "ENTERTAINMENT & LEISURE", "email": "margaret.ferris@example.com",
"phone": "+1 617 555 0411", "city": "Boston", "country": "US",
"description": "Golden wedding anniversary dinner, close family only." },
{ "name": "Northwind Analytics", "industry": "TECHNOLOGY", "email": "events@northwindanalytics.example.com",
"phone": "+1 206 555 0412", "website": "https://northwindanalytics.example.com", "city": "Seattle", "country": "US",
"description": "Annual customer conference plus an awards dinner on the second night." },
{ "name": "Reyes & Nandakumar", "industry": "ENTERTAINMENT & LEISURE", "email": "camila.reyes@example.com",
"phone": "+1 305 555 0413", "city": "Miami", "country": "US",
"description": "Destination wedding, three days, guests travelling from two countries." },
{ "name": "Halcyon Foundation", "industry": "SERVICE", "email": "gala@halcyonfoundation.example.com",
"phone": "+1 312 555 0414", "website": "https://halcyonfoundation.example.com", "city": "Chicago", "country": "US",
"description": "Autumn fundraising gala with a seated dinner and an auction." },
{ "name": "Bellweather Group", "industry": "FINANCE", "email": "office@bellweathergroup.example.com",
"phone": "+1 212 555 0415", "city": "New York", "country": "US",
"description": "Leadership retreat, offsite. Brought the planning in-house this year." }
],
"contacts": [
{ "first_name": "Eleanor", "last_name": "Whitmore", "email": "eleanor.whitmore@example.com", "phone": "+1 415 555 0132",
"title": "Partner", "account": "Whitmore & Castellanos", "city": "San Francisco", "country": "US",
"description": "Main point of contact. Prefers evening calls." },
{ "first_name": "Diego", "last_name": "Castellanos", "email": "diego.castellanos@example.com", "phone": "+1 415 555 0133",
"title": "Partner", "account": "Whitmore & Castellanos", "city": "San Francisco", "country": "US",
"description": "Handles the music, bar and transport decisions." },
{ "first_name": "Margaret", "last_name": "Ferris", "email": "margaret.ferris@example.com", "phone": "+1 617 555 0141",
"title": "Host", "account": "The Ferris Family", "city": "Boston", "country": "US",
"description": "Organising the anniversary for her parents. Wants it kept simple." },
{ "first_name": "Aditi", "last_name": "Rao", "email": "aditi.rao@example.com", "phone": "+1 206 555 0152",
"title": "Events Manager", "department": "Marketing", "account": "Northwind Analytics", "city": "Seattle", "country": "US",
"description": "Owns the conference budget and signs off the AV spend." },
{ "first_name": "Camila", "last_name": "Reyes", "email": "camila.reyes@example.com", "phone": "+1 305 555 0163",
"title": "Partner", "account": "Reyes & Nandakumar", "city": "Miami", "country": "US",
"description": "Coordinating the guest travel and the welcome dinner." },
{ "first_name": "Priya", "last_name": "Nandakumar", "email": "priya.nandakumar@example.com", "phone": "+1 305 555 0164",
"title": "Partner", "account": "Reyes & Nandakumar", "city": "Miami", "country": "US",
"description": "Leading on decor, florals and the ceremony running order." },
{ "first_name": "Jonah", "last_name": "Klein", "email": "jonah.klein@example.com", "phone": "+1 312 555 0175",
"title": "Programme Director", "account": "Halcyon Foundation", "city": "Chicago", "country": "US",
"description": "Needs the auction and the speeches to fit inside a two-hour dinner." },
{ "first_name": "Rosalind", "last_name": "Beck", "email": "rosalind.beck@example.com", "phone": "+1 212 555 0186",
"title": "Executive Assistant", "account": "Bellweather Group", "city": "New York", "country": "US",
"description": "Booked the retreat directly with the venue this year." }
],
"deals": [
{ "name": "Whitmore & Castellanos wedding — 12 September", "account": "Whitmore & Castellanos", "stage": "NEGOTIATION",
"amount": 48000, "currency": "USD", "probability": 75, "closed_on": "2026-08-20", "lead_source": "PARTNER",
"description": "Full planning plus decor. Negotiating the bar package and the late-night transport." },
{ "name": "Ferris golden anniversary dinner", "account": "The Ferris Family", "stage": "PROPOSAL",
"amount": 12500, "currency": "USD", "probability": 50, "closed_on": "2026-09-05", "lead_source": "EXISTING CUSTOMER",
"description": "Sixty guests, private dining room, one long table. Proposal sent, awaiting the family." },
{ "name": "Northwind annual conference & awards night", "account": "Northwind Analytics", "stage": "QUALIFICATION",
"amount": 96000, "currency": "USD", "probability": 30, "closed_on": "2026-11-30", "lead_source": "WEBSITE",
"description": "Two days, 400 delegates, awards dinner on the second night. AV is the open question." },
{ "name": "Reyes & Nandakumar destination wedding", "account": "Reyes & Nandakumar", "stage": "CLOSED_WON",
"amount": 78000, "currency": "USD", "probability": 100, "closed_on": "2026-07-15", "lead_source": "EXISTING CUSTOMER",
"description": "Booked. Three days in February, welcome dinner, ceremony and a farewell brunch." },
{ "name": "Halcyon autumn fundraising gala", "account": "Halcyon Foundation", "stage": "PROSPECTING",
"amount": 34000, "currency": "USD", "probability": 10, "closed_on": "2026-10-15", "lead_source": "PUBLIC RELATIONS",
"description": "Early conversation. Date not fixed and the venue shortlist is still open." },
{ "name": "Bellweather leadership retreat", "account": "Bellweather Group", "stage": "CLOSED_LOST",
"amount": 22000, "currency": "USD", "probability": 0, "closed_on": "2026-07-08", "lead_source": "CALL",
"description": "Lost. The venue offered a package directly and they booked it in-house." }
],
"tickets": [
{ "name": "Florist cannot source peonies for the ceremony", "status": "Assigned", "priority": "High", "case_type": "Problem",
"account": "Whitmore & Castellanos", "contacts": ["Eleanor Whitmore"], "stage": "With planner",
"description": "Out of season by the wedding date. Two substitutes costed, waiting on the couple to choose." },
{ "name": "Guest count up by eighteen after the invitations went out", "status": "New", "priority": "High", "case_type": "Question",
"account": "The Ferris Family", "contacts": ["Margaret Ferris"], "stage": "New issue",
"description": "Pushes the room over its seated capacity. Needs a seating plan and a catering revision." },
{ "name": "Venue moved the load-in time to 6am", "status": "Pending", "priority": "Urgent", "case_type": "Incident",
"account": "Reyes & Nandakumar", "contacts": ["Camila Reyes"], "stage": "Awaiting vendor",
"description": "Clashes with the decor crew's call time. Waiting on the venue to confirm the earlier slot." },
{ "name": "AV quote came back over the approved budget", "status": "Closed", "priority": "Normal", "case_type": "Question",
"account": "Northwind Analytics", "contacts": ["Aditi Rao"], "stage": "Resolved",
"description": "Rescoped to two screens and one operator. Revised quote accepted." },
{ "name": "Request to arrange guest visas", "status": "Rejected", "priority": "Low", "case_type": "Question",
"account": "Reyes & Nandakumar", "contacts": ["Priya Nandakumar"], "stage": "Not our remit",
"description": "Outside the planning scope. Sent the couple a travel agent who handles this." }
],
"tasks": [
{ "title": "Confirm the peony substitute with the couple", "status": "In Progress", "priority": "High", "due_date": "2026-08-05",
"stage": "In progress", "ticket": "Florist cannot source peonies for the ceremony",
"description": "Two options costed. Eleanor prefers the garden roses; needs Diego to agree." },
{ "title": "Re-run the seating plan for eighteen more guests", "status": "New", "priority": "High", "due_date": "2026-08-07",
"stage": "To do", "ticket": "Guest count up by eighteen after the invitations went out",
"description": "Check the room's seated capacity before promising anything to the family." },
{ "title": "Send the revised AV quote to Aditi", "status": "Completed", "priority": "Medium", "due_date": "2026-07-28",
"stage": "Done", "ticket": "AV quote came back over the approved budget",
"description": "Two screens, one operator. Signed off on the call." },
{ "title": "Chase the signed contract and the deposit", "status": "In Progress", "priority": "High", "due_date": "2026-08-06",
"stage": "In progress", "deal": "Whitmore & Castellanos wedding — 12 September",
"description": "Date is a peak Saturday and is only held until the deposit clears." },
{ "title": "Send the anniversary proposal to the family", "status": "New", "priority": "High", "due_date": "2026-08-04",
"stage": "Scheduled", "deal": "Ferris golden anniversary dinner",
"description": "Private dining room, one long table, set menu with two choices." },
{ "title": "Walk the destination venue and photograph the ceremony space", "status": "Completed", "priority": "Medium",
"due_date": "2026-07-22", "stage": "Done", "deal": "Reyes & Nandakumar destination wedding",
"description": "Site visit done. Photos and the measured floor plan are in the shared folder." },
{ "title": "Quarterly check-in with Northwind", "status": "New", "priority": "Low", "due_date": "2026-09-01",
"stage": "To do", "account": "Northwind Analytics",
"description": "Review next year's calendar before the conference budget is set." },
{ "title": "Call back the New Year's Eve enquiry", "status": "New", "priority": "Medium", "due_date": "2026-08-03",
"stage": "To do", "lead": "New Year's Eve reception for 200",
"description": "First attempt went to voicemail. The date will go quickly." },
{ "title": "Send the destination package to the Goa enquiry", "status": "In Progress", "priority": "Medium",
"due_date": "2026-08-08", "stage": "In progress", "lead": "Destination wedding in Goa",
"description": "Include the travel supplement and the three-day run sheet." }
],
"leads": [
{ "title": "September wedding at Ashcombe Manor", "first_name": "Eleanor", "last_name": "Whitmore",
"email": "eleanor.whitmore@example.com", "phone": "+1 415 555 0132",
"source": "partner", "stage": "Contract sent",
"custom_fields": { "event_type": "wedding", "event_date": "2026-09-12", "guest_count": 140, "venue": "Ashcombe Manor", "budget": 48000, "venue_confirmed": true } },
{ "title": "Destination wedding in Goa", "first_name": "Camila", "last_name": "Reyes",
"email": "camila.reyes@example.com", "phone": "+1 305 555 0163",
"source": "existing customer", "stage": "Booked",
"custom_fields": { "event_type": "wedding", "event_date": "2027-02-06", "guest_count": 90, "venue": "Coral Sands Resort", "budget": 78000, "venue_confirmed": true } },
{ "title": "New Year's Eve reception for 200", "first_name": "Dominic", "last_name": "Vale",
"email": "dominic.vale@example.com", "phone": "+1 646 555 0416",
"source": "compaign", "stage": "New enquiry",
"custom_fields": { "event_type": "reception", "event_date": "2026-12-31", "guest_count": 200, "venue": "The Old Exchange", "budget": 55000, "venue_confirmed": false } },
{ "title": "Golden anniversary dinner for sixty", "first_name": "Margaret", "last_name": "Ferris",
"email": "margaret.ferris@example.com", "phone": "+1 617 555 0141",
"source": "existing customer", "stage": "Proposal sent",
"custom_fields": { "event_type": "milestone", "event_date": "2026-10-03", "guest_count": 60, "venue": "Harbour House", "budget": 12500, "venue_confirmed": true } },
{ "title": "Annual customer conference and awards night", "first_name": "Aditi", "last_name": "Rao",
"email": "aditi.rao@example.com", "phone": "+1 206 555 0152",
"source": "other", "stage": "Consultation booked",
"custom_fields": { "event_type": "conference", "event_date": "2027-03-18", "guest_count": 400, "venue": "Pier 66 Convention Centre", "budget": 96000, "venue_confirmed": false } },
{ "title": "Autumn fundraising gala with an auction", "first_name": "Jonah", "last_name": "Klein",
"email": "jonah.klein@example.com", "phone": "+1 312 555 0175",
"source": "public relations", "stage": "New enquiry",
"custom_fields": { "event_type": "gala", "event_date": "2026-11-14", "guest_count": 320, "venue": "Lakeside Ballroom", "budget": 34000, "venue_confirmed": false } },
{ "title": "Leadership retreat, two nights offsite", "first_name": "Rosalind", "last_name": "Beck",
"email": "rosalind.beck@example.com", "phone": "+1 212 555 0186",
"source": "call", "stage": "Date lost",
"custom_fields": { "event_type": "corporate", "event_date": "2026-09-24", "guest_count": 45, "venue": "Stonebridge Lodge", "budget": 22000, "venue_confirmed": true } },
{ "title": "Spring wedding in the vineyard", "first_name": "Isabelle", "last_name": "Moreau",
"email": "isabelle.moreau@example.com", "phone": "+1 707 555 0417",
"source": "partner", "stage": "Date held",
"custom_fields": { "event_type": "wedding", "event_date": "2027-05-08", "guest_count": 110, "venue": "Rivenwood Vineyard", "budget": 52000, "venue_confirmed": true } },
{ "title": "Engagement party, rooftop bar", "first_name": "Theo", "last_name": "Lindgren",
"email": "theo.lindgren@example.com", "phone": "+1 415 555 0418",
"source": "compaign", "stage": "Consultation booked",
"custom_fields": { "event_type": "engagement", "event_date": "2026-09-19", "guest_count": 70, "venue": "Skyline Terrace", "budget": 9500, "venue_confirmed": false } },
{ "title": "Product launch dinner for press", "first_name": "Naomi", "last_name": "Osei",
"email": "naomi.osei@example.com", "phone": "+1 206 555 0419",
"source": "public relations", "stage": "Proposal sent",
"custom_fields": { "event_type": "corporate", "event_date": "2026-10-22", "guest_count": 85, "venue": "The Foundry", "budget": 28000, "venue_confirmed": true } },
{ "title": "Beach wedding, barefoot ceremony", "first_name": "Marcus", "last_name": "Ilunga",
"email": "marcus.ilunga@example.com", "phone": "+1 786 555 0420",
"source": "partner", "stage": "New enquiry",
"custom_fields": { "event_type": "wedding", "event_date": "2027-04-17", "guest_count": 65, "venue": "Coral Bay", "budget": 31000, "venue_confirmed": false } },
{ "title": "Sixtieth birthday, black tie", "first_name": "Alan", "last_name": "Whitcombe",
"email": "alan.whitcombe@example.com", "phone": "+1 503 555 0421",
"source": "existing customer", "stage": "Contract sent",
"custom_fields": { "event_type": "milestone", "event_date": "2026-11-07", "guest_count": 120, "venue": "Grand Assembly Rooms", "budget": 26000, "venue_confirmed": true } },
{ "title": "Two-day summit with a gala dinner", "first_name": "Hassan", "last_name": "Qureshi",
"email": "hassan.qureshi@example.com", "phone": "+1 972 555 0422",
"source": "other", "stage": "Consultation booked",
"custom_fields": { "event_type": "conference", "event_date": "2027-06-11", "guest_count": 250, "venue": "Northgate Centre", "budget": 71000, "venue_confirmed": false } },
{ "title": "Winter wedding, candlelit ceremony", "first_name": "Rosa", "last_name": "Iglesias",
"email": "rosa.iglesias@example.com", "phone": "+1 619 555 0423",
"source": "compaign", "stage": "Date held",
"custom_fields": { "event_type": "wedding", "event_date": "2026-12-19", "guest_count": 95, "venue": "St Aubyn's Hall", "budget": 44000, "venue_confirmed": true } },
{ "title": "Charity lunch for two hundred", "first_name": "Grace", "last_name": "Mbeki",
"email": "grace.mbeki@example.com", "phone": "+1 470 555 0424",
"source": "public relations", "stage": "Proposal sent",
"custom_fields": { "event_type": "gala", "event_date": "2026-10-08", "guest_count": 200, "venue": "Civic Hall", "budget": 19000, "venue_confirmed": true } },
{ "title": "Wedding moved from another planner", "first_name": "Beatrice", "last_name": "Lindqvist",
"email": "beatrice.lindqvist@example.com", "phone": "+1 646 555 0425",
"source": "existing customer", "stage": "Date held",
"custom_fields": { "event_type": "wedding", "event_date": "2027-07-24", "guest_count": 130, "venue": "Marlowe Court", "budget": 61000, "venue_confirmed": true } },
{ "title": "Team offsite with an evening reception", "first_name": "Peter", "last_name": "Nakamura",
"email": "peter.nakamura@example.com", "phone": "+1 415 555 0426",
"source": "call", "stage": "New enquiry",
"custom_fields": { "event_type": "corporate", "event_date": "2026-11-20", "guest_count": 55, "venue": "Undecided", "budget": 17500, "venue_confirmed": false } },
{ "title": "Engagement dinner, family only", "first_name": "Amara", "last_name": "Diallo",
"email": "amara.diallo@example.com", "phone": "+1 404 555 0427",
"source": "compaign", "stage": "Booked",
"custom_fields": { "event_type": "engagement", "event_date": "2026-09-05", "guest_count": 30, "venue": "The Walled Garden", "budget": 6800, "venue_confirmed": true } },
{ "title": "Wedding cancelled, couple postponed", "first_name": "Owen", "last_name": "Griffiths",
"email": "owen.griffiths@example.com", "phone": "+1 720 555 0428",
"source": "partner", "stage": "Date lost",
"custom_fields": { "event_type": "wedding", "event_date": "2027-01-30", "guest_count": 100, "venue": "Fairhaven Barn", "budget": 39000, "venue_confirmed": false } },
{ "title": "Awards evening for a trade body", "first_name": "Victor", "last_name": "Nwosu",
"email": "victor.nwosu@example.com", "phone": "+1 312 555 0429",
"source": "other", "stage": "Consultation booked",
"custom_fields": { "event_type": "corporate", "event_date": "2027-02-27", "guest_count": 300, "venue": "Union Pavilion", "budget": 58000, "venue_confirmed": false } },
{ "title": "Renewal of vows on the anniversary", "first_name": "Claire", "last_name": "Dubois",
"email": "claire.dubois@example.com", "phone": "+1 646 555 0430",
"source": "existing customer", "stage": "Proposal sent",
"custom_fields": { "event_type": "milestone", "event_date": "2027-05-30", "guest_count": 50, "venue": "Rosecliff House", "budget": 15000, "venue_confirmed": true } },
{ "title": "Marquee wedding on a family farm", "first_name": "Nils", "last_name": "Andersen",
"email": "nils.andersen@example.com", "phone": "+1 214 555 0431",
"source": "call", "stage": "New enquiry",
"custom_fields": { "event_type": "wedding", "event_date": "2027-08-14", "guest_count": 180, "venue": "Private land, marquee hire", "budget": 67000, "venue_confirmed": false } },
{ "title": "Graduation reception for a school", "first_name": "Priya", "last_name": "Raman",
"email": "priya.raman@example.com", "phone": "+1 617 555 0432",
"source": "partner", "stage": "Contract sent",
"custom_fields": { "event_type": "reception", "event_date": "2027-06-26", "guest_count": 220, "venue": "Maple Heights Hall", "budget": 21000, "venue_confirmed": true } },
{ "title": "Wedding lost to a cheaper quote", "first_name": "Sinead", "last_name": "Murphy",
"email": "sinead.murphy@example.com", "phone": "+1 617 555 0433",
"source": "compaign", "stage": "Date lost",
"custom_fields": { "event_type": "wedding", "event_date": "2027-03-13", "guest_count": 85, "venue": "Ellesmere Hotel", "budget": 29000, "venue_confirmed": false } },
{ "title": "Summer party for a client's staff", "first_name": "Daniel", "last_name": "Ferreira",
"email": "daniel.ferreira@example.com", "phone": "+1 971 555 0434",
"source": "existing customer", "stage": "Booked",
"custom_fields": { "event_type": "corporate", "event_date": "2027-07-10", "guest_count": 160, "venue": "Riverside Gardens", "budget": 24000, "venue_confirmed": true } }
]
}
}

View file

@ -245,7 +245,7 @@
"custom_fields": { "service_line": "Brand Strategy", "engagement_type": "project", "estimated_start": "2026-10-20" } },
{ "title": "Payroll migration for a 200-person firm", "first_name": "Tobias", "last_name": "Werner",
"email": "tobias.werner@example.com", "phone": "+1 312 555 0220",
"source": "website", "stage": "Qualified",
"source": "other", "stage": "Qualified",
"opportunity_amount": 8400, "currency": "USD",
"custom_fields": { "service_line": "Accounting & Bookkeeping", "engagement_type": "project", "estimated_start": "2026-10-05" } },
{ "title": "Go-to-market strategy for a hardware launch", "first_name": "Elena", "last_name": "Vasquez",
@ -265,7 +265,7 @@
"custom_fields": { "service_line": "Technology Advisory", "engagement_type": "timeandmaterials", "estimated_start": "2026-09-25" } },
{ "title": "Employee handbook rewrite", "first_name": "Marcus", "last_name": "O'Rourke",
"email": "marcus.orourke@example.com", "phone": "+1 646 555 0224",
"source": "website", "stage": "Qualified",
"source": "other", "stage": "Qualified",
"opportunity_amount": 5600, "currency": "USD",
"custom_fields": { "service_line": "HR Consulting", "engagement_type": "project", "estimated_start": "2026-09-15" } },
{ "title": "Annual accounts and corporation tax", "first_name": "Fatima", "last_name": "Haddad",
@ -290,7 +290,7 @@
"custom_fields": { "service_line": "Interim CFO", "engagement_type": "retainer", "estimated_start": "2026-10-15" } },
{ "title": "Process mapping for a claims team", "first_name": "Hiroshi", "last_name": "Tanaka",
"email": "hiroshi.tanaka@example.com", "phone": "+1 206 555 0229",
"source": "website", "stage": "Qualified",
"source": "other", "stage": "Qualified",
"opportunity_amount": 9600, "currency": "USD",
"custom_fields": { "service_line": "Compliance Review", "engagement_type": "project", "estimated_start": "2026-09-22" } },
{ "title": "Pitch declined, went in-house", "first_name": "Bianca", "last_name": "Rossi",
@ -315,7 +315,7 @@
"custom_fields": { "service_line": "Interim CFO", "engagement_type": "project", "estimated_start": "2026-10-28" } },
{ "title": "Cyber compliance readiness review", "first_name": "Anneke", "last_name": "Visser",
"email": "anneke.visser@example.com", "phone": "+1 503 555 0234",
"source": "website", "stage": "Scoping",
"source": "other", "stage": "Scoping",
"opportunity_amount": 13500, "currency": "USD",
"custom_fields": { "service_line": "Compliance Review", "engagement_type": "timeandmaterials", "estimated_start": "2026-11-10" } }
]

View file

@ -235,7 +235,7 @@
"custom_fields": { "budget_max": 390000, "configuration": "2bed", "preferred_location": "Elm Terrace", "possession_by": "2027-01-15", "finance_required": false } },
{ "title": "Relocating for work, needs 3 bed", "first_name": "Daniel", "last_name": "Ferreira",
"email": "daniel.ferreira@example.com", "phone": "+1 971 555 0202",
"source": "website", "stage": "Contacted",
"source": "other", "stage": "Contacted",
"custom_fields": { "budget_max": 720000, "configuration": "3bed", "preferred_location": "Oakwood Park", "possession_by": "2026-12-01", "finance_required": true } },
{ "title": "Second home by the water", "first_name": "Claire", "last_name": "Dubois",
"email": "claire.dubois@example.com", "phone": "+1 646 555 0203",
@ -247,7 +247,7 @@
"custom_fields": { "budget_max": 900000, "configuration": "1bed", "preferred_location": "Riverside", "finance_required": false } },
{ "title": "First-time buyer, studio budget", "first_name": "Amara", "last_name": "Diallo",
"email": "amara.diallo@example.com", "phone": "+1 404 555 0205",
"source": "website", "stage": "New enquiry",
"source": "other", "stage": "New enquiry",
"custom_fields": { "budget_max": 185000, "configuration": "studio", "preferred_location": "Downtown Loop", "finance_required": true } },
{ "title": "Plot enquiry from a builder", "first_name": "Nils", "last_name": "Andersen",
"email": "nils.andersen@example.com", "phone": "+1 214 555 0206",
@ -263,7 +263,7 @@
"custom_fields": { "budget_max": 340000, "configuration": "1bed", "preferred_location": "Marina District", "possession_by": "2027-09-01", "finance_required": true } },
{ "title": "Withdrew after the survey", "first_name": "Sinead", "last_name": "Murphy",
"email": "sinead.murphy@example.com", "phone": "+1 617 555 0209",
"source": "website", "stage": "Fell through",
"source": "other", "stage": "Fell through",
"custom_fields": { "budget_max": 465000, "configuration": "2bed", "preferred_location": "Maple Heights", "finance_required": true } },
{ "title": "Commercial plot, roadside frontage", "first_name": "Hassan", "last_name": "Qureshi",
"email": "hassan.qureshi@example.com", "phone": "+1 972 555 0210",