feat(budget): make a settle-up payment's date editable

The settle-up form only exposed from/to/amount and silently dated the
payment by created_at, unlike a regular expense which has an editable
date. Add a settled_at column (independent of created_at, same split as
expense_date) through the schema, migration, service, REST and MCP
surfaces, and the desktop + mobile forms; the ledger groups a payment by
settled_at, falling back to created_at for rows recorded before the
column existed.
This commit is contained in:
RemEx
2026-09-11 15:04:51 +00:00
committed by Maurice
parent 76e182f44e
commit bd46089450
16 changed files with 200 additions and 36 deletions
@@ -3,6 +3,7 @@ import { http, HttpResponse } from 'msw'
import MCostsTab from '../../../../src/mobile/screens/trip/tabs/MCostsTab'
import type { MTripShellApi, TripPlanner } from '../../../../src/mobile/screens/trip/MTripShell'
import { budgetApi } from '../../../../src/api/client'
import { localToday } from '../../../../src/components/Planner/today'
import { clearExchangeRateCache } from '../../../../src/hooks/useExchangeRates'
import { useAuthStore } from '../../../../src/store/authStore'
import { useSettingsStore } from '../../../../src/store/settingsStore'
@@ -463,7 +464,7 @@ describe('MCostsTab', () => {
fireEvent.change(within(dialog).getByPlaceholderText('0.00'), { target: { value: '12,5' } })
expect(submit).toBeEnabled()
fireEvent.click(submit)
expect(create).toHaveBeenCalledWith(7, { from_user_id: 1, to_user_id: 2, amount: 12.5, currency: 'USD' })
expect(create).toHaveBeenCalledWith(7, { from_user_id: 1, to_user_id: 2, amount: 12.5, currency: 'USD', settled_at: localToday() })
await waitFor(() => expect(settlementBases).toHaveLength(2))
})
@@ -298,6 +298,12 @@ describe('costsModel — list filters and grouping', () => {
const b = settlement({ id: 2, created_at: '2026-07-02T09:00:00Z' });
expect(filterSettlements([a, b], filters({ dayKey: '2026-07-02' }), 1).map(s => s.id)).toEqual([2]);
});
it('FE-MOB-CMOD-036: filterSettlements narrows by settled_at when set, over the day it was recorded', () => {
const a = settlement({ id: 1, settled_at: '2026-07-05', created_at: '2026-07-01T09:00:00Z' });
expect(filterSettlements([a], filters({ dayKey: '2026-07-05' }), 1).map(s => s.id)).toEqual([1]);
expect(filterSettlements([a], filters({ dayKey: '2026-07-01' }), 1)).toEqual([]);
});
});
describe('costsModel — ledger grouping (expenses + settlement payments)', () => {
@@ -332,6 +338,11 @@ describe('costsModel — ledger grouping (expenses + settlement payments)', () =
expect(groupLedgerByDay([], [s])).toEqual([{ dateKey: '2026-07-09', entries: [{ kind: 'payment', date: '2026-07-09', settlement: s }] }]);
});
it('FE-MOB-CMOD-037: a payment groups by its own settled_at, not the day it was recorded', () => {
const s = settlement({ id: 4, settled_at: '2026-07-11', created_at: '2026-07-09T08:00:00Z' });
expect(groupLedgerByDay([], [s])).toEqual([{ dateKey: '2026-07-11', entries: [{ kind: 'payment', date: '2026-07-11', settlement: s }] }]);
});
it('FE-MOB-CMOD-035: empty inputs produce no groups', () => {
expect(groupLedgerByDay([], [])).toEqual([]);
});