Repository navigation
[Invoices]: Recurring frequency parity — interval-based schedules (every N days/weeks/months/years) #642
Description
v1 supports 28 recurrence intervals; v2's RecurringFrequency enum (Modules/Invoices/Enums/RecurringFrequency.php) supports 5 (daily, weekly, monthly, quarterly, yearly). v1 users with "every 2 weeks", "every 2 months", "every 15 days", or "every 2 years" billing cannot express their schedules in v2, and v1 data cannot be migrated losslessly.
v1 spec: application/modules/invoices/models/Mdl_invoices_recurring.php:23-52 defines the interval codes — days 1,2,3,4,5,6,15,30; weeks 1-4 (7D/14D/21D/28D); months 1-11; years 1-5 — and application/helpers/date_helper.php:194 (increment_date()) applies them as DateInterval('P' . $code) (e.g. 14D, 3M, 1Y). Epic #52 mentions "Support custom frequencies: every X days/weeks/months" as a bullet but no implementable issue exists.
Part of Epic #52.
Specific
Replace the single coarse frequency value with an interval pair on recurring_invoices:
frequency_valueunsignedTinyInt (>= 1)frequency_unitstring enum:day|week|month|year
Next-run advancement becomesnext_run_at->add(DateInterval("P{value}{D|W|M|Y}")), matching v1increment_date()exactly. KeepRecurringFrequencyas UI presets that map onto pairs (daily→1/day, weekly→1/week, monthly→1/month, quarterly→3/month, yearly→1/year) plus a "custom" option exposing the two fields; migration backfills existing rows via that map.
Measurable
- Every v1 code from
1Dto5Yis representable and round-trips through create/edit UI. - Engine advancement for
value=3, unit=monthfrom 2026-01-31 matches PHPDateIntervalmath (documented edge: lands on 2026-05-01? No — v1 does single-step: Jan 31 + P3M = May 1st per PHP overflow; assert actual PHP behavior in a pinned test). - Existing seeded/test rows keep working after migration (backfill map covers all 5 legacy enum values).
Achievable
One migration + form change in Modules/Invoices/Filament/Company/Resources/RecurringInvoices/Schemas/RecurringInvoiceForm.php (Select preset + conditional value/unit inputs) + table column formatter ("Every 2 weeks") + engine increment helper. RecurringInvoicesTest gains cases for custom intervals.
Relevant
Fortnightly and bi-monthly billing are common real-world cadences; this is a hard v1→v2 migration blocker for affected users.
Time-Bound
One sprint; should land before or with the generation engine so the engine's increment helper is written once against the pair.
Arrange
- Schedules: (14, day) a.k.a. fortnightly with
next_run_at = today; (3, month) withnext_run_at = today; legacy row created pre-migration with enumquarterly.
Act
- Run the migration; run the generation engine.
Assert
- Legacy
quarterlyrow backfilled tofrequency_value = 3, frequency_unit = month. - Fortnightly schedule's
next_run_atadvanced exactly 14 days; 3-month schedule advanced per PHPDateInterval('P3M')semantics (pinned test). - Form accepts value 1-99 and unit day/week/month/year; rejects 0 and negatives.
- Table renders "Every 14 days" / "Quarterly" style labels for pairs matching presets.
Activity