You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(opportunity): derive days_in_stage from a stored stage_entry_date
`crm_opportunity.days_in_stage` was a plain number column that nothing ever
incremented. The lifecycle hook reset it to 0 on a stage change and no sweep
or hook anywhere raised it, so `opportunity_stagnation`'s
`days_in_stage > 14` filter matched only the rows the seed had hardcoded —
real deals could sit in a stage indefinitely without ever being nudged.
Add `stage_entry_date`, a stored, indexed date stamped by the lifecycle hook
on insert and re-stamped on every stage change, and turn `days_in_stage` into
a formula counting from it. Correct on every read, with no nightly
full-table pass.
A formula is evaluated after the query (`applyFormulaPlan` walks the returned
rows), so it is not a real column and cannot be a filter or sort key. Every
consumer that selects rather than displays moves to `stage_entry_date`:
- `opportunity_stagnation` filters `stage_entry_date < {TODAY() - 14}` — the
same test, resolved by the flow template engine. Verified against a booted
server: the sweep loops over exactly the 30 open deals with
`days_in_stage > 14`, boundary inclusive-exact, matching the pre-change
selection on main.
- The `stale_opportunities` view drops the threshold filter it could no
longer express (the list data path resolves no date macros either) and
becomes an open-deals list ordered longest-in-stage first, with
`days_in_stage` on show.
Seeds carry `stage_entry_date` on every opportunity — open deals straddling
the 14-day threshold, settled deals entering their closed stage on the close
date — replacing the hardcoded `days_in_stage` numbers.
Two guards in `test/metadata-references.test.ts` pin the class: no list view
may filter or sort on a formula field, and no flow data node may filter on
one. The second instance they caught is fixed here too — the quarterly
forecast view's `attainment_pct` tiebreaker was a dead sort key, now
`closed_amount`.
Refs #489
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJygXUafjc7ckGMmE2AaGz
|**Notes & Next Steps**| Description, next steps |
@@ -54,7 +54,7 @@ You don't have to remember any of this — the system does it for you:
54
54
55
55
-**Probability** is set from the stage whenever you move the deal (you *can* override it manually if you have a special situation, but it'll reset next time the stage changes).
56
56
-**Expected revenue** = amount × probability ÷ 100, recalculated whenever amount or probability changes.
57
-
-**Days in current stage** is recomputed daily — it surfaces stagnant deals.
57
+
-**Stage entry date** is stamped the moment a deal is created and re-stamped every time you move it to a new stage. **Days in current stage**counts from it, so it is always up to date — that is what surfaces stagnant deals.
58
58
- When you mark a deal **Closed Won and the amount is over $100K**, sales management gets an email.
59
59
- When the amount exceeds **$500K**, the deal enters the [Discount Approval](/docs/revenue/approvals) process automatically.
0 commit comments