Change 2 — Disabled Companies Filtered at the Query
The quieter half of the PR. It looks like pure tidying, and it almost is — but it changes one number that a human looks at.
Business view
Some clinic accounts are disabled: switched off in the admin panel, no longer active tenants. The renewal automation has always ignored them, and it still does. That part is genuinely unchanged.
What changed is where the skipping happens. Previously the job loaded every company that had ever existed, then walked the list and stepped over the disabled ones. Now it asks the database for only the non-disabled companies in the first place.
Two practical consequences:
It is faster and lighter. The job no longer pulls every disabled tenant's full record into memory just to discard it. On a large customer base that is a real saving on every run.
The "Total" number on the admin Jobs page now means something different. After each run the job reports how many companies it looked at. That figure used to include disabled accounts; now it counts only live ones. If you have been watching that number, expect it to drop — permanently, and with no failure behind it. It is a definition change, not a regression.
Technical view
The diff
packages/server/src/cronJobs/companies/companyLeadCron.js
const companies = await prisma.company.findMany({
- orderBy: { tierExpiry: 'desc' }
+ orderBy: { tierExpiry: 'desc' },
+ where: { disabled: false }
})
for await (const company of companies) {
- if (company.referenceId && !company.disabled) {
+ if (company.referenceId) {
Company.disabled is Boolean @default(false) at packages/prisma/schema.prisma:125. Because it is non-nullable with a default, where: { disabled: false } and !company.disabled select exactly the same rows. There is no three-valued-logic gap where a null would be included by one form and excluded by the other.
What is genuinely identical
The set of companies that reaches the body of the loop. Both before and after, a company is processed if and only if disabled === false and referenceId is truthy. The referenceId guard was left in the loop because it is a Float? (schema.prisma:110) and the intent is "truthy", which is not expressible as a simple Prisma where — referenceId: 0 would need excluding as well as null, so hoisting it would have been a behaviour change. Leaving it in the loop is the correct call.
What is not identical: total
At companyLeadCron.js:285-294 the job writes its run statistics to redis:
await redisClient.set(
name,
JSON.stringify({
running: false,
total: companies.length,
started,
ended: new Date(),
error: null
})
)
companies.length is the length of the fetched array. Before this PR that was every company in the database; now it is every non-disabled company.
That value is read back by the admin API:
- Query
getCronJobs: [String!]!—packages/server/src/schema.graphql:272 - Resolver —
packages/server/src/resolvers/queries/adminQueries.js:461-469, which does
redisClient.get(job.name) for each registered job and returns the merged JSON as strings
- Permission
isAdmin—packages/server/src/permissions/permissions.js:2076
and rendered as the Total column of the cron dashboard at packages/clinic-web/src/components/admin/cronJobs/CronJobsStats.js:49-53, reachable at /ZROYKuKEVCvQykPlS4kP/jobs (packages/clinic-web/src/containers/AdminDashboardRouter.js:143).
So this is the one externally observable effect of Change 2: a number on an internal dashboard gets smaller. It was never a count of work done — a company can be fetched, counted, and then skipped by the referenceId guard or by having no linked lead — so its meaning was already loose. It is now "live companies scanned" rather than "all companies scanned".
We confirmed this on the branch sandbox: a manual run reported Total = 2, matching the two non-disabled seeded companies. See the Feature Tour.
Ordering
orderBy: { tierExpiry: 'desc' } is unchanged, so companies are still processed furthest-expiry-first. Nothing in the job depends on the order — there is no batching, no early exit, no cursor — so this is cosmetic. It does mean that if the job throws partway through (the whole loop is inside one try/catch at :11/:295), the companies that got processed are the ones with the latest expiry dates, i.e. the least urgent ones. The accounts most in need of a renewal nudge are processed last and are the first casualties of a mid-run failure. That is pre-existing behaviour, unchanged by this PR, but worth knowing.