* feat(invoices): choose the first invoice date on recurring schedules
A yearly or quarterly recurring schedule had no way to say which month it
bills in: the dialog exposed interval and day of month only, so a yearly
schedule created in September always fired in September. The phase of a
schedule is fully defined by its first run date, which the table already
stores as next_run_date and the create API already accepted as start_date
but nothing exposed.
- Dialog: new date field (first invoice date on create, next invoice date on
edit), prefilled with the next natural occurrence so the default is "no
offset"; kept in step with day of month both ways; shows the following
three run dates so the phase is visible. Sent as start_date on create and
as next_run_date on edit only when the user actually re-phased.
- API: create validates start_date (on the day_of_month grid, not in the
past); update accepts next_run_date (on the grid for the effective day,
strictly after today in Stockholm) and lets it win over the automatic
recompute a day change or reactivation does.
- Staged operations / MCP: start_date documented as the phase; update tool
gains next_run_date. Commit executor rejects off-grid dates and rolls a
date that went stale before approval forward on its own grid.
- lib/invoices/recurring-run-date.ts: pure, client-safe grid helpers shared
by the dialog, the routes, the executors and the cron service.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqkDCm4nhPZda2ft5WpRNC
* fix(invoices): validate schedule dates at MCP staging, use Stockholm's calendar in the dialog
Resolves the skeptic and CI findings on #2338 in one pass:
- MCP staging tools now apply the same grid and past/future rules as the
routes to start_date and next_run_date, so the preview a human approves
is exactly what the commit executor writes (previously an off-grid date
staged fine and failed at approval, and a past next_run_date was rolled
to another date silently).
- The dialog computes today and the default first invoice date in
Europe/Stockholm instead of the browser's zone, matching the server;
getStockholmDateHour moved to the client-safe module and is re-exported
from the service.
- gnubok_update_recurring_schedule description trimmed under the 280-char
limit while keeping the clamping and Stockholm phrases the registration
test requires.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqkDCm4nhPZda2ft5WpRNC
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>