On this page
The one-paragraph versionWhat actually changed in the codeWhy this mattersScope and honest caveatsWhere to go nextOverview
PR #242 — "Move leads to renewal even if they are churned" Branch mo/update_lead_cron → main. One file changed, four lines added, four removed.
This is the smallest possible diff attached to a meaningful change in how Dentolize manages its own customer base. Read the whole page — the diff is four lines, but the behaviour it unlocks is not obvious from reading them.
The one-paragraph version
Dentolize runs its own sales pipeline inside Dentolize. Every clinic that buys the product is mirrored as a lead in Dentolize's own tenant, and a scheduled job (companyLeadCron) walks that list every three days: it measures how much each clinic actually used the product, tags the lead accordingly, writes a scorecard note, and — when the subscription is about to lapse — drags the lead into the Renewal column so a human picks it up.
Until this PR, any lead sitting in the Churned column was skipped entirely. This PR deletes that exclusion. Churned customers are now scored, tagged, annotated, and — if their subscription is coming up for renewal — moved into Renewal like anybody else.
A second, smaller change moves the "is this company disabled?" test from a per-row if inside the loop into the database query itself.
What actually changed in the code
Both changes are in packages/server/src/cronJobs/companies/companyLeadCron.js.
Change 1 — the churn exclusion is gone.
// before
if (patient && patient.lead && patient.lead.stageId !== CHURNED_STAGE_ID) {
// after
if (patient && patient.lead) {
The constant CHURNED_STAGE_ID = '2397d5d6-0ecd-4796-9f8e-aa78db0a1a79' was deleted along with it. See Change 1 — Churned Leads Rejoin Renewal.
Change 2 — disabled companies are excluded by the query, not the loop.
// before
const companies = await prisma.company.findMany({ orderBy: { tierExpiry: 'desc' } })
...
if (company.referenceId && !company.disabled) {
// after
const companies = await prisma.company.findMany({
orderBy: { tierExpiry: 'desc' },
where: { disabled: false }
})
...
if (company.referenceId) {
Which companies get processed is unchanged. What does change is the run statistic the job reports. See Change 2 — Disabled Companies Filtered at the Query.
Why this matters
A churned customer is not a dead customer. In this system "churned" is a column somebody dragged a card into — often months ago, often while the subscription was still paid up and running. Because the job skipped that column wholesale, those accounts went dark:
- no usage score refresh,
- no
WORKING/WORKING_LOW/NOT_WORKINGtag update, - no
unusedFeaturesrefresh on the company record, - no activity scorecard note,
- and critically, no renewal prompt when their subscription came due.
A customer who was written off but kept using the product, and whose renewal date arrived, produced no signal at all. This PR closes that hole.
Scope and honest caveats
This is a backend-only, unreleased change. There is no new screen, no new API, no new permission, and nothing a clinic user will see. The only human-visible surface is the internal admin panel.
Three things are worth knowing up front, because the PR description does not mention them:
- Whether churned leads actually move depends on data, not code. The renewal guard
compares lead views (stage.viewId), not stage IDs. If the Churned stage lives in the same view as the Renewal stage, those leads still will not move. Nothing in the repository pins this down. See For Quality.
CHURNED_STAGE_IDis now dead everywhere. A full-repository grep finds zero
remaining references to that UUID, and zero references to the word "churned" in any .js, .graphql or .prisma file. Churn is no longer a concept the code knows about.
- A pre-existing ordering defect now fires more often. The move path decrements the
order of every lead in the source stage rather than just those below the moved card, because order is never selected onto the in-memory lead. Churned is typically the largest column, so this PR increases how often that bug is hit. It is not introduced here, but it is amplified here. See Stage Move Mechanics and Known Defects.
Where to go next
| If you are… | Read |
|---|---|
| Writing copy or a changelog | For Marketing |
| Talking to customers about renewals | For Sales |
| Answering "why did this account move?" | For Support |
| Teaching the pipeline to a new CSM | For Training |
| Deciding whether to ship it | For Stakeholders |
| Testing it | For Quality |
| Looking at real screens | Feature Tour |
| Reading the code line by line | The Company Lead Cron, End to End |