From 337ad95a3e1e4044a0ef6d4ef7efcceffd982525 Mon Sep 17 00:00:00 2001 From: null Date: Fri, 3 Jul 2026 22:24:35 -0500 Subject: [PATCH] feat(ts): type Bill + Payment API responses with branded Dollars (A2-A3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Bill and reuses Payment domain types (money → Dollars, mirroring serializeBill/serializePayment which spread the DB row and convert the money columns). Wired: bills/allBills/deletedBills → Bill[], bill/createBill/updateBill → Bill; quickPay/createPayment/updatePayment/restorePayment → Payment. Replaced api.ts's minimal PaymentRecord with the richer @/types Payment (usePaymentActions still resolves payment.id). typecheck 0, build green, 48 client tests. Co-Authored-By: Claude Opus 4.8 --- client/api.ts | 28 +++++++++++----------------- client/types.ts | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/client/api.ts b/client/api.ts index 607b948..fbd82de 100644 --- a/client/api.ts +++ b/client/api.ts @@ -1,4 +1,4 @@ -import type { TrackerResponse } from '@/types'; +import type { Bill, Payment, TrackerResponse } from '@/types'; // Fetch CSRF token from the server once and cache in memory. // The cookie is httpOnly so document.cookie cannot access it directly. @@ -31,12 +31,6 @@ export interface ApiError extends Error { code?: string; } -/** Minimal shape of a payment record returned by the pay endpoints. */ -export interface PaymentRecord { - id: number; - [key: string]: unknown; -} - /** Result of toggling a tracker row paid/unpaid. */ export interface TogglePaidResult { paymentId?: number; @@ -239,13 +233,13 @@ export const api = { updateMonthlyStartingAmounts: (data: Body) => put('/monthly-starting-amounts', data), // Bills - bills: (params: QueryParams = {}) => get(`/bills${queryString(params)}`), - allBills: (params: QueryParams = {}) => get(`/bills${queryString({ inactive: true, ...params })}`), - deletedBills: () => get('/bills/deleted'), + bills: (params: QueryParams = {}) => get(`/bills${queryString(params)}`), + allBills: (params: QueryParams = {}) => get(`/bills${queryString({ inactive: true, ...params })}`), + deletedBills: () => get('/bills/deleted'), billAudit: (includeInactive = false) => get(`/bills/audit${includeInactive ? '?inactive=true' : ''}`), - bill: (id: Id) => get(`/bills/${id}`), - createBill: (data: Body) => post('/bills', data), - updateBill: (id: Id, data: Body) => put(`/bills/${id}`, data), + bill: (id: Id) => get(`/bills/${id}`), + createBill: (data: Body) => post('/bills', data), + updateBill: (id: Id, data: Body) => put(`/bills/${id}`, data), reorderBills: (order: Body) => put('/bills/reorder', order), archiveBill: (id: Id, archived = true) => put(`/bills/${id}/archived`, { archived }), updateBillBalance: (id: Id, bal: unknown) => _fetch('PATCH', `/bills/${id}/balance`, { current_balance: bal }), @@ -311,14 +305,14 @@ export const api = { deleteCatalogDescriptor: (id: Id) => _fetch('DELETE', `/subscriptions/catalog/descriptors/${id}`), // Payments - quickPay: (data: Body) => post('/payments/quick', data), + quickPay: (data: Body) => post('/payments/quick', data), confirmAutopaySuggestion: (billId: Id, data: Body) => post(`/payments/autopay-suggestions/${billId}/confirm`, data), dismissAutopaySuggestion: (billId: Id, data: Body) => post(`/payments/autopay-suggestions/${billId}/dismiss`, data), bulkPay: (items: Body) => post('/payments/bulk', items), - createPayment: (data: Body) => post('/payments', data), - updatePayment: (id: Id, data: Body) => put(`/payments/${id}`, data), + createPayment: (data: Body) => post('/payments', data), + updatePayment: (id: Id, data: Body) => put(`/payments/${id}`, data), deletePayment: (id: Id) => del(`/payments/${id}`), - restorePayment: (id: Id) => post(`/payments/${id}/restore`), + restorePayment: (id: Id) => post(`/payments/${id}/restore`), recentAutoMatched: () => get('/payments/recent-auto'), undoAutoMatch: (id: Id) => post(`/payments/${id}/undo-auto`), diff --git a/client/types.ts b/client/types.ts index bb8ef50..8a035e4 100644 --- a/client/types.ts +++ b/client/types.ts @@ -30,6 +30,50 @@ export interface Payment { [key: string]: unknown; } +// A bill as serialized by the server (billsService.serializeBill spreads the DB +// row and converts the three money columns to dollars). SQLite booleans come back +// as 0/1 integers. Extra joined columns (sparkline, has_merchant_rule, …) pass +// through as `unknown` via the index signature. +export interface Bill { + id: number; + user_id?: number; + name: string; + category_id: number | null; + category_name?: string | null; + due_day: number | null; + override_due_date: string | null; + bucket: string; + expected_amount: Dollars; + interest_rate: number | null; + billing_cycle: string | null; + autopay_enabled: number; + autodraft_status: string | null; + auto_mark_paid: number; + website: string | null; + username: string | null; + account_info: string | null; + has_2fa: number; + notes: string | null; + history_visibility: string | null; + active: number; + cycle_type: string | null; + cycle_day: string | number | null; + current_balance: Dollars | null; + minimum_payment: Dollars | null; + snowball_order: number | null; + snowball_include: number; + snowball_exempt: number; + is_subscription: number; + subscription_type: string | null; + reminder_days_before: number | null; + subscription_source: string | null; + subscription_detected_at: string | null; + created_at?: string; + updated_at?: string; + deleted_at?: string | null; + [key: string]: unknown; +} + // One tracker row: a bill's state for a given month. Built by // statusService.buildTrackerRow and augmented by trackerService.getTracker. export interface TrackerRow {