fix(reports): bucket dashboard ticket trend in the user's timezone #274

Merged
David merged 3 commits from fix/pms-360-user-timezone-date-bucketing into main 2026-06-17 12:24:11 +02:00
Owner

Date-bucketed renders disagreed about which day an instant belongs to: records timestamp UTC, but Calendar/Dispatch/"today" surfaces use browser-local time, so a ticket logged at 23:30 Pacific showed on the next UTC day in some views and the local day in others. The fix is a single source of truth: the active user's users.timezone (PMS-253).

This repo is mokosh-server plus the shared mokosh-types crate (consumed by the mokosh-apps WASM frontend per PMS-129). The enumerated UI surfaces (Time Entries list, weekly Timesheet, Calendar/Dispatch "today", dashboard "today" widget) render in mokosh-apps and are out of this repo's reach; the server contribution is the canonical bucketing primitive plus the one server-computed per-day bucket.

Adds mokosh_types::datetime with user_local_date(dt, tz) -> NaiveDate, user_today(now, tz), canonical_tz_name(tz) (falls back to UTC on an invalid/empty preference). Living in the shared crate means mokosh-apps reuses the exact same primitive for every frontend date bucket (proposed-approach items 1-4).

Rewrites the reports dashboard ticket_trend_30d to bucket via (created_at AT TIME ZONE $tz)::date with the 30-day window anchored to the user-local today, threading CurrentUser.timezone from the handler and the CSV export path (proposed-approach item 5).

Audit of date-bucket call sites in this repo (AC #4):

  • reports::dashboard ticket_trend_30d: per-day GROUP BY on a timestamptz, previously created_at::date (UTC). FIXED to bucket in the user's timezone.
  • reports::tickets / reports::time: created_at::date / closed_at::date BETWEEN $from AND $to, and time_entries.date BETWEEN: date-RANGE filters bounded by client-supplied dates, not per-day "today" buckets. time_entries.date is a stored DATE column (no tz). Left as-is; can adopt the same AT TIME ZONE wrapping once the frontend sends user-local ranges.
  • reports::billing aging, reports::clients warranty/renewal, reports::projects overdue: compare DATE columns (due_date, end_date, warranty_expiry, target_end_date) against CURRENT_DATE: DATE vs DATE, no timestamp/timezone skew.
  • time_tracking weekly timesheet DATE_TRUNC('week', date): groups the stored DATE column, not a timestamptz; no tz skew.
  • billing/contracts today = now.date_naive(): server-internal billing-period math, not a user-facing day-bucket render.
  • Frontend (mokosh-apps, separate repo): Time Entries list, weekly Timesheet, Calendar/Dispatch "today", dashboard "today" widget. These should call the new mokosh_types::datetime helpers; the AC checkboxes naming those UI surfaces require the mokosh-apps change as follow-up.

Tests: unit tests on the helper (Pacific 23:30 stays on the local day; LA vs London differ for the same instant; invalid/empty tz falls back to UTC) and an integration test proving the dashboard trend buckets the same UTC instant onto different days for a UTC vs an America/Los_Angeles viewer.

#PMS-360

Date-bucketed renders disagreed about which day an instant belongs to: records timestamp UTC, but Calendar/Dispatch/"today" surfaces use browser-local time, so a ticket logged at 23:30 Pacific showed on the next UTC day in some views and the local day in others. The fix is a single source of truth: the active user's users.timezone (PMS-253). This repo is mokosh-server plus the shared mokosh-types crate (consumed by the mokosh-apps WASM frontend per PMS-129). The enumerated UI surfaces (Time Entries list, weekly Timesheet, Calendar/Dispatch "today", dashboard "today" widget) render in mokosh-apps and are out of this repo's reach; the server contribution is the canonical bucketing primitive plus the one server-computed per-day bucket. Adds mokosh_types::datetime with user_local_date(dt, tz) -> NaiveDate, user_today(now, tz), canonical_tz_name(tz) (falls back to UTC on an invalid/empty preference). Living in the shared crate means mokosh-apps reuses the exact same primitive for every frontend date bucket (proposed-approach items 1-4). Rewrites the reports dashboard ticket_trend_30d to bucket via (created_at AT TIME ZONE $tz)::date with the 30-day window anchored to the user-local today, threading CurrentUser.timezone from the handler and the CSV export path (proposed-approach item 5). Audit of date-bucket call sites in this repo (AC #4): - reports::dashboard ticket_trend_30d: per-day GROUP BY on a timestamptz, previously created_at::date (UTC). FIXED to bucket in the user's timezone. - reports::tickets / reports::time: created_at::date / closed_at::date BETWEEN $from AND $to, and time_entries.date BETWEEN: date-RANGE filters bounded by client-supplied dates, not per-day "today" buckets. time_entries.date is a stored DATE column (no tz). Left as-is; can adopt the same AT TIME ZONE wrapping once the frontend sends user-local ranges. - reports::billing aging, reports::clients warranty/renewal, reports::projects overdue: compare DATE columns (due_date, end_date, warranty_expiry, target_end_date) against CURRENT_DATE: DATE vs DATE, no timestamp/timezone skew. - time_tracking weekly timesheet DATE_TRUNC('week', date): groups the stored DATE column, not a timestamptz; no tz skew. - billing/contracts today = now.date_naive(): server-internal billing-period math, not a user-facing day-bucket render. - Frontend (mokosh-apps, separate repo): Time Entries list, weekly Timesheet, Calendar/Dispatch "today", dashboard "today" widget. These should call the new mokosh_types::datetime helpers; the AC checkboxes naming those UI surfaces require the mokosh-apps change as follow-up. Tests: unit tests on the helper (Pacific 23:30 stays on the local day; LA vs London differ for the same instant; invalid/empty tz falls back to UTC) and an integration test proving the dashboard trend buckets the same UTC instant onto different days for a UTC vs an America/Los_Angeles viewer. #PMS-360
fix(reports): bucket dashboard ticket trend in the user's timezone
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 1m7s
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 2m41s
Integration / integration tests (pull_request) Successful in 6m31s
e56be9027f
Date-bucketed renders disagreed about which day an instant belongs to: records timestamp UTC, but Calendar/Dispatch/"today" surfaces use browser-local time, so a ticket logged at 23:30 Pacific showed on the next UTC day in some views and the local day in others. The fix is a single source of truth: the active user's users.timezone (PMS-253).

This repo is mokosh-server plus the shared mokosh-types crate (consumed by the mokosh-apps WASM frontend per PMS-129). The enumerated UI surfaces (Time Entries list, weekly Timesheet, Calendar/Dispatch "today", dashboard "today" widget) render in mokosh-apps and are out of this repo's reach; the server contribution is the canonical bucketing primitive plus the one server-computed per-day bucket.

Adds mokosh_types::datetime with user_local_date(dt, tz) -> NaiveDate, user_today(now, tz), canonical_tz_name(tz) (falls back to UTC on an invalid/empty preference). Living in the shared crate means mokosh-apps reuses the exact same primitive for every frontend date bucket (proposed-approach items 1-4).

Rewrites the reports dashboard ticket_trend_30d to bucket via (created_at AT TIME ZONE $tz)::date with the 30-day window anchored to the user-local today, threading CurrentUser.timezone from the handler and the CSV export path (proposed-approach item 5).

Audit of date-bucket call sites in this repo (AC #4):
- reports::dashboard ticket_trend_30d: per-day GROUP BY on a timestamptz, previously created_at::date (UTC). FIXED to bucket in the user's timezone.
- reports::tickets / reports::time: created_at::date / closed_at::date BETWEEN $from AND $to, and time_entries.date BETWEEN: date-RANGE filters bounded by client-supplied dates, not per-day "today" buckets. time_entries.date is a stored DATE column (no tz). Left as-is; can adopt the same AT TIME ZONE wrapping once the frontend sends user-local ranges.
- reports::billing aging, reports::clients warranty/renewal, reports::projects overdue: compare DATE columns (due_date, end_date, warranty_expiry, target_end_date) against CURRENT_DATE: DATE vs DATE, no timestamp/timezone skew.
- time_tracking weekly timesheet DATE_TRUNC('week', date): groups the stored DATE column, not a timestamptz; no tz skew.
- billing/contracts today = now.date_naive(): server-internal billing-period math, not a user-facing day-bucket render.
- Frontend (mokosh-apps, separate repo): Time Entries list, weekly Timesheet, Calendar/Dispatch "today", dashboard "today" widget. These should call the new mokosh_types::datetime helpers; the AC checkboxes naming those UI surfaces require the mokosh-apps change as follow-up.

Tests: unit tests on the helper (Pacific 23:30 stays on the local day; LA vs London differ for the same instant; invalid/empty tz falls back to UTC) and an integration test proving the dashboard trend buckets the same UTC instant onto different days for a UTC vs an America/Los_Angeles viewer.

#PMS-360
Merge main into fix/pms-360-user-timezone-date-bucketing
Some checks failed
Check / fmt + clippy + compile + unit/doc tests (pull_request) Failing after 10s
Integration / integration tests (pull_request) Failing after 40s
E2E / Playwright against staging (pull_request) Successful in 2m1s
0b3dd22d34
Resolve crates/mokosh-types/src/lib.rs: keep both module decls in alphabetical order - datetime (PMS-360) and mileage_tracking (landed on main). The reports routes/service/tests auto-merged.

#PMS-360

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge branch 'main' into fix/pms-360-user-timezone-date-bucketing
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
E2E / Playwright against staging (pull_request) Successful in 1m21s
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 3m29s
Integration / integration tests (pull_request) Successful in 8m27s
c3814e3bcc
David merged commit d08de8fa24 into main 2026-06-17 12:24:11 +02:00
David deleted branch fix/pms-360-user-timezone-date-bucketing 2026-06-17 12:24:11 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!274
No description provided.