Skip to content
Docs

Command palette

Jump to a page or search the docs.

Changelog

Recurring expense

How a scheduled cost (insurance, a loan payment, parking, a tax) becomes an expected entry the owner confirms into the ledger or skips, shown in M-Gastos and in the alerts.

Markdown

What it does

A Recurring Expense (RF30, US14) schedules a cost on a car: insurance, a loan payment, parking, a tax or other, with an amount in the car's currency and a cadence. The cadence is everyMonths from an anchor date (anchorOn): 1 is monthly on its day ("every 10th"), 12 yearly on its date, anything up to 120 every N months. Every occurrence is counted from the anchor, so the 31st clamps per month and never drifts (nextOccurrence in @autofolio/domain/reminder).

Nothing reaches the ledger by itself. The cron tick turns each due occurrence into an expected entry: a reminder row with recurring_expense_id, due that day and titled with the payee or the kind (in the author's language). So the alert list, push and email of the reminder job show it as they show any reminder. The owner then confirms it with the actual amount and date, which writes the ★ ledger entry, or skips it.

Confirming books:

  • insurance, parking, tax, other: an Expense (category insurance, other, tax, other) whose ★ ledger entry shares the expected entry's id, so M-Gastos and the TCO count it;
  • a loan payment: loan_interest (a cost) and loan_principal (never an expense, RN05) ★ ledger entries, and the Loan's new ★ balance. The interest is a month at the Loan's latest balance and rate (an annual rate divided by 12), at most the payment; the owner can change it to match the statement. Without a rate the owner enters it.

Procedures

ProcedureWhoFailures
recurringExpense.list { vehicleId? }Members of the Organization—
recurringExpense.create { id, vehicleId, kind, label?, amountMinor, everyMonths, firstDueOn }Members of the OrganizationINPUT_VALIDATION, NOT_FOUND (car), BAD_REQUEST (not in the garage, a loan payment without a Loan), CONFLICT (id)
recurringExpense.update { id, kind?, label?, amountMinor?, everyMonths?, firstDueOn? }Members of the OrganizationINPUT_VALIDATION, NOT_FOUND, BAD_REQUEST (a loan payment without a Loan)
recurringExpense.delete { id }Members of the OrganizationNOT_FOUND
recurringExpense.expected { id }Members of the OrganizationNOT_FOUND (not open)
recurringExpense.confirm { id, amountMinor, effectiveDate, interestMinor? }Members of the OrganizationINPUT_VALIDATION, NOT_FOUND, BAD_REQUEST (car no longer owned), CONFLICT (skipped)
recurringExpense.skip { id }Members of the OrganizationNOT_FOUND, CONFLICT (confirmed)

list answers the schedules by next due and the open expected entries, oldest first, each with a loan payment's split. A first date in the past starts at its next occurrence from today; a new date or interval restarts the schedule the same way. Deleting a schedule deletes its expected entries; confirmed costs stay in the ledger.

Tick and idempotency

pendingRecurringExpenses (recurring-expense.jobs.ts) runs every minute: it reads at most 100 schedules of current cars due by today, inserts their expected entries (one per schedule and date, a unique index) and moves each schedule from the date it read to the next occurrence. A schedule far behind catches up an occurrence a tick. A sold car's schedule is no longer read.

The client generates a schedule's id; replaying it answers what was saved. Confirming or skipping again changes nothing.

Data and isolation

recurring_expense has row-level security by Active Organization; tenant traffic can update only its kind, label, amount, interval and dates. Expected entries are reminder rows. No audit_log event: a schedule is not money of record, and a confirmed occurrence is a ★ ledger entry with its author (RNF12).

App

M-Gastos shows "Upcoming recurring" (recorrentes) under the categories: the schedule due first ("Insurance · every 10th", its amount), or how many payments wait to be confirmed. It opens Recurring expenses (/recurring): To confirm, Scheduled, and Add (add-recorrente). The form (/recurring-form) picks the type, payee, amount, Monthly / Yearly / Every N months and the first due date; "Loan payment" is offered only for a car with a Loan. An expected entry (/expected-expense, also from its alert on the Service tab) confirms the amount and date, with the interest of a loan payment, or skips it. autofolio://dev/M-Gastos opens the seeded M3 with its insurance on the 10th; the Porsche's parking waits to be confirmed under All cars.

Key files

Edit on GitHubDocumentation is in English.

We use essential cookies to make our app work. With your consent, we also use optional cookies to improve your experience. Read our Cookie Policy and Privacy Policy.