feat: conditional & match funding pledges — deeply integrated across entire product

- Schema: isConditional, conditionType, conditionText, conditionThreshold, conditionMet, conditionMetAt on Pledge
- Pledge form: 'This is a match pledge' toggle after amount selection
  - Two modes: threshold (if target is reached) and match (match funding)
  - Goal amount passed through from event
- Auto-trigger: when total raised hits threshold, conditional pledges unlock automatically
  - WhatsApp notification sent to donor when unlocked
  - Threshold check runs after every pledge creation AND every status change
- Cron: skips conditional pledges until conditionMet=true (no premature reminders)
- Dashboard Home: progress bar shows conditional segment (amber), stats grid adds Conditional column
- Dashboard Money: conditional/unlocked badge on pledge rows
- Dashboard Collect: hero shows conditional total in amber
- Dashboard Reports: financial summary shows conditional breakdown
- Donor 'My Pledges': conditional card with condition text + activation status
- Confirmation step: specialized messaging for match pledges
- CRM export: includes is_conditional, condition_type, condition_text, condition_met columns
- Status guide: conditional status explained in human language
This commit is contained in:
2026-03-05 04:19:23 +08:00
parent c11bf4bea7
commit 50d449e2b7
23 changed files with 607 additions and 140 deletions

View File

@@ -135,6 +135,16 @@ model Pledge {
iPaidClickedAt DateTime?
notes String?
// --- Conditional / Match Funding ---
// A conditional pledge activates only when a condition is met.
// e.g. "I'll give £5,000 if you raise £20,000" or "I'll match up to £5,000"
isConditional Boolean @default(false)
conditionType String? // "threshold" | "match" | "custom"
conditionText String? // Human-readable: "If £20,000 is raised"
conditionThreshold Int? // pence — when event total reaches this, condition is met
conditionMet Boolean @default(false)
conditionMetAt DateTime?
// Payment scheduling — the core of "pledge now, pay later"
dueDate DateTime? // null = pay now, set = promise to pay on this date
planId String? // groups installments together