In a multi-tenant system, one invoices table holds two different things with the same
organization_id: the platform's charges to the business, and the business's invoices to its own customers.
A daily dunning cron forgot the one column that tells them apart — and suspended businesses because their customers paid late.
Run it yourself, in both modes.
Meet Dlamini Plumbing: their Sebenza subscription is paid, and they've issued two invoices to their own customers — one of which is 6 days overdue, because Mrs Naidoo is slow with EFTs. The cron scans for unpaid invoices older than the 5-day grace period. Watch which rows it matches.
Platform invoices and the tenant's customer invoices are rows in one table, joined by the same
organization_id. Only is_platform separates “what you owe us” from “what your customers owe you”.
Platform invoices are inserted paid or pending — never sent. So the cron's
status IN ('sent','overdue') could only ever match customer invoices. The wrong behaviour was the only behaviour.
The first fix scoped the join to is_platform = true — correct, but since platform rows are never
sent, the cron now matched nothing: no dunning at all. Adding pending to the status set brought it back.
The only email on this path fired at the moment of suspension. Now the owner gets a dated warning every day of the grace period — amount, days overdue, the exact freeze date, and “your data is not deleted”.
invoices states its
is_platform stance explicitly, in the SQL, with a comment. In a multi-tenant table, forgetting the discriminator
doesn't fail loudly — it targets someone else's data and succeeds.