Repository navigation
Recurring: every N days (daily frequency) - #44
Merged
Merged
Conversation
Adds a Daily frequency: occurrences on start_on + k x interval days (1-365), counted with DateOnly so DST never moves one. day_of_month is refused for daily items and ignored if stored. - Next due is the first occurrence on or after today (list and get_recurring). - Editing the schedule drops pending proposals dated today or later and proposes again from the new anchor; confirmed, skipped and overdue ones stay. - Monthly equivalent: amount x 365 / (days x 12) for daily; weekly aligned to the same 365-day basis (was x 52 / 12). - upsert_recurring accepts frequency daily, interval 1-365 for daily. - UI: 'Every N days' option with a days field; list shows 'Every 15 days' and, on narrow screens, schedule and next date under the name.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Some payments repeat every N days from a date (gym every 15 days) rather than on fixed days of the month. "Weekly × 2" drifts from 15 days, and two monthly items stay tied to the calendar. This PR adds a
Dailyfrequency that uses the existinginterval: "every 15 days" isfrequency: Daily, interval: 15.Rules implemented
start_on, thenstart_on + k × intervaldays (k = 1, 2, …). This is computed in closed form withDateOnly, with noDateTimeor offsets, so a DST change never moves an occurrence.day_of_month: the validator rejects it for daily items (API andupsert_recurring). The domain ignores and clears any stored value, and editing an item into daily drops the old day.end_on: no occurrence after it.nextDueOninGET /api/recurringandget_recurringis now the first scheduled occurrence on or after today, even when that occurrence is already awaiting confirmation. This applies to every frequency. Before, it was the scheduler's internal pointer, which sits past the 7-day look-ahead.start_on).monthlyEquivalentEur,monthlyFixedCostsEurand the income/savings totals inget_recurring):amount × 365 / (interval × 12)for daily items. The formula moved into the domain asRecurringTransaction.MonthlyEquivalent.upsert_recurringacceptsfrequency: "daily", and the interval andday_of_monthdescriptions are updated. Argument validation stays strict.get_recurringreturnsfrequency: "Daily"(same casing as the existing values) plusinterval.docs/ai.mdis updated.interval. Before, saving a "weekly × 2" item from the UI silently reset it to 1.HaveConversion<string>, varchar(32), no check constraint), so no migration was needed.Daily = 3is appended at the end anyway.Weekly normalisation finding
Weekly items were normalised as
amount × 52 / 12 / interval(4.333 weeks per month). The intent is the same as the 365-day formula (average occurrences per month), so weekly now usesamount × 365 / (7 × interval × 12)(4.345 weeks per month). That makes weekly × 1 identical to every 7 days, and weekly × 2 identical to every 14 days. Weekly monthly-equivalents rise by about 0.27%. Monthly (amount / interval) and yearly (amount / (12 × interval)) are unchanged.Test plan
dotnet build(0 warnings)end_onmid-cyclestart_onday_of_monthignored for daily itemsday_of_monthrejected for dailynextDueOn, 400 ondayOfMonth/366, interval 15 → 14 regenerates pending items while the confirmed one staysupsert_recurringdaily: pending item proposed,get_recurringfrequency/interval/monthly equivalent/upcoming, 400 onday_of_month/366/monthly 30, interval editng build,ng test(90, including the newschedule.spec.tsfor labels, request body and form rules)playwright testpassed, stack torn downKnown limitation: "today" is still the UTC calendar day, the same as the rest of the app (proposer, AI tools). With Europe/Lisbon at UTC+0/+1, a schedule can switch to the next day up to one hour late around midnight. Occurrence dates themselves don't depend on time zone.