- Shipped
- September 3, 2026 at 8:02 PM UTC
- Author
- Kamo
- Commit
- be5d937
Three related changes to the lead-assignment core. **The accept gate now asks two questions, over every assigned lead.** LeadAcceptEligibilityService replaces the comment gate that lived inside CreditLeadAssignmentService. A lead blocks its holder from taking another when either it has no note from them written SINCE it was assigned to them, or it is still sitting at Fresh > New Lead / Farm > Farm Fresh. Both halves close a hole. The old rule counted any note ever written, so a lead that came back to a member passed on a note from their previous stint with it. And it looked only at leads acquired through the auto-assignment system, which left every other route — a manager's hand-assignment, an import, a self-claim — as a way to accumulate untouched leads with the pool still open. The rule is two set-based queries whatever the size of the member's book: the current assignments, then one grouped MediaObj aggregate over their stream guids. Widening the scope to every assignment made the old query-per-lead loop unaffordable — it is the shape that made the /leads grid slow, and this runs on every render of the leads nav for every signed-in member. The blocking statuses are named explicitly in LeadStatusType rather than derived from isAllowSwitchBack. Those two happen to be the only statuses carrying that flag, but it answers a different question, and a future status marked the same way must not silently start blocking accepts. Matching is on the stored system descriptions, so an org's custom status labels never affect the rule. **Masked contact info is obfuscated rather than blanked.** ContactObfuscator turns (555) 555-5555 into (555) 555-**55, snadjafinia@gmail.com into s**********@gmail.com, and 123 Main St into *** Main St. A blank field is indistinguishable from one nobody filled in, so a restricted viewer could not tell "there is a number here you may not read" from "this lead has no number" — and staff chased the second reading. City, postal code and country now survive on the same basis the state always did: coarse enough to identify a duplicate, useless for reaching anybody once the street number is gone. Date of birth stays absent; no partial form of it is both recognisable and safe. The browser mirrors this exactly **************** because LeadRealtimePublisher fans a full unmasked DTO out to every subscriber in the org with no per-recipient projection possible. The Java and vitest suites assert the same strings so the two cannot drift apart unnoticed. **Unassigned free-for-all leads are visible and claimable.** **************** claims one specific unassigned lead. Free-for-all is the open door and carries the accept gate with it — otherwise it would be the way around the discipline it exists alongside — and mints/spends its own credit exactly as acceptNextLead does, so Max Daily Spend still caps it. ASSIGN_LEADS is the elevated door: any unassigned lead, no gate, no credit. Licensing is a legal constraint and neither door waives it. LeadGridQuery gains **************** turning the grid's permission floor from "leads assigned to me" into "leads assigned to me, plus anything genuinely up for grabs". Hiding unclaimed free-for-all leads from everyone without VIEW_UNASSIGNED_LEADS left them claimable in theory and unreachable in practice.