Dentolize · Daily Notifications Cron Walkthrough
On this pageSettings toggles (web + mobile)Daily Summary message contentDaily Appointments Summary message contentScheduling and edge cases

For Quality

There is no automated test coverage for this feature (no tests exist anywhere under packages/server/src/cronJobs/, and none were added here). Everything below is manual-verification territory, primarily via the internal admin "Execute Cron Job" action (ignoreHour: true), which bypasses the local-time gate so a run can be forced immediately.

Settings toggles (web + mobile)

  • Toggle Daily Summary off, save, reload the page — confirm it stays

off (i.e. it actually persisted, not just visually unchecked).

  • Do the same for Daily Appointments Summary.
  • Regression check specific to this PR: toggle Treasury

notifications off and back on, on the web app, and confirm it persists across a reload. This PR incidentally fixed a pre-existing bug where the Treasury checkbox rendered but was never included in the saved payload — worth explicit regression coverage since it wasn't the PR's stated goal (see Notification Settings Toggles).

  • Confirm both new toggles show up correctly in all 9 languages — this is

translation-file text (app.summaryCron, app.summaryCronNotifications, app.appointmentsCron, app.appointmentsCronNotifications), separate from the cron's own message-body translations below.

Daily Summary message content

  • A user with full access (or all three of invoice/payment analytics +

view-all-branches) and the toggle on should receive it; a user missing any one of those permissions should not, even with the toggle on.

  • A user with the right permissions but the toggle off should not receive

it.

  • Verify the numbers against the source screens for a day with: some

invoices, some payments, a mix of completed/canceled/no-show appointments. Specifically check:

  • Balance top-up invoices are excluded from the invoice total.
  • Payments of type BALANCE and refunded payments are excluded from the

payments total.

  • "Remaining" never goes negative even if a test invoice has paid > total.
  • The completed/total appointment count includes canceled and no-show

appointments in the "total" but not in "completed."

  • Verify currency displayed matches the company's configured currency.

Daily Appointments Summary message content

  • A doctor with completed appointments today and none tomorrow → "no

appointments scheduled for tomorrow" variant.

  • A doctor with nothing completed today but appointments tomorrow → "no

completed appointments" variant.

  • A doctor with both → the full variant, including a correctly formatted

first-appointment time for tomorrow.

  • A doctor with neither → confirm they receive no message (not an

empty one).

  • Total time formatting: verify 0h collapses to just minutes (e.g.

"45m", not "0h 45m") and that duration reads correctly for a multi-hour appointment.

  • Cancel or no-show tomorrow's only appointment for a doctor and confirm

they drop out of "upcoming" — but a canceled/no-show appointment today still counts toward the company-wide "total appointments" figure in the Daily Summary.

Scheduling and edge cases

  • Time zone edge case: create/find a company with timeZone unset

(null) and force-run the cron. Per the code, this should throw inside the per-company loop rather than falling back to UTC as the code's own comment claims — worth confirming whether this actually happens in the current environment, and if so, whether it takes down the entire run (every company not yet processed that tick) rather than just skipping the one bad company. See The Cron for the exact mechanism.

  • Two clinics, two time zones: confirm each clinic's summary fires

independently at its own local 23:30, not at a shared UTC time.

  • Overlap protection: while a run is in progress (running: true in

Redis), confirm a second invocation (natural hourly tick, or a manual Execute click) is a no-op rather than double-sending.

  • Disabled company: confirm a disabled company (disabled: true) is

skipped entirely, even via manual execute.

  • Disabled user: confirm a disabled user never receives either

message, even if they'd otherwise qualify.

  • Stale/invalid push tokens: a user with no valid Expo push tokens

(Expo.isExpoPushToken fails, or pushNotifications is empty) should be silently skipped, not error the run.

  • Notification tap behavior: confirm tapping either notification just

opens the app without navigating anywhere specific (there's no id in the payload, so the app's generic navigation-on-tap handler doesn't fire) — this is expected per the current code, not a bug to file, but worth knowing so it isn't mistaken for one during a test pass.