feat(ts): type Bill + Payment API responses with branded Dollars (A2-A3)
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 <noreply@anthropic.com>
This commit is contained in:
parent
3c7a55c3a1
commit
337ad95a3e
|
|
@ -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.
|
// Fetch CSRF token from the server once and cache in memory.
|
||||||
// The cookie is httpOnly so document.cookie cannot access it directly.
|
// The cookie is httpOnly so document.cookie cannot access it directly.
|
||||||
|
|
@ -31,12 +31,6 @@ export interface ApiError extends Error {
|
||||||
code?: string;
|
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. */
|
/** Result of toggling a tracker row paid/unpaid. */
|
||||||
export interface TogglePaidResult {
|
export interface TogglePaidResult {
|
||||||
paymentId?: number;
|
paymentId?: number;
|
||||||
|
|
@ -239,13 +233,13 @@ export const api = {
|
||||||
updateMonthlyStartingAmounts: (data: Body) => put('/monthly-starting-amounts', data),
|
updateMonthlyStartingAmounts: (data: Body) => put('/monthly-starting-amounts', data),
|
||||||
|
|
||||||
// Bills
|
// Bills
|
||||||
bills: (params: QueryParams = {}) => get(`/bills${queryString(params)}`),
|
bills: (params: QueryParams = {}) => get<Bill[]>(`/bills${queryString(params)}`),
|
||||||
allBills: (params: QueryParams = {}) => get(`/bills${queryString({ inactive: true, ...params })}`),
|
allBills: (params: QueryParams = {}) => get<Bill[]>(`/bills${queryString({ inactive: true, ...params })}`),
|
||||||
deletedBills: () => get('/bills/deleted'),
|
deletedBills: () => get<Bill[]>('/bills/deleted'),
|
||||||
billAudit: (includeInactive = false) => get(`/bills/audit${includeInactive ? '?inactive=true' : ''}`),
|
billAudit: (includeInactive = false) => get(`/bills/audit${includeInactive ? '?inactive=true' : ''}`),
|
||||||
bill: (id: Id) => get(`/bills/${id}`),
|
bill: (id: Id) => get<Bill>(`/bills/${id}`),
|
||||||
createBill: (data: Body) => post('/bills', data),
|
createBill: (data: Body) => post<Bill>('/bills', data),
|
||||||
updateBill: (id: Id, data: Body) => put(`/bills/${id}`, data),
|
updateBill: (id: Id, data: Body) => put<Bill>(`/bills/${id}`, data),
|
||||||
reorderBills: (order: Body) => put('/bills/reorder', order),
|
reorderBills: (order: Body) => put('/bills/reorder', order),
|
||||||
archiveBill: (id: Id, archived = true) => put(`/bills/${id}/archived`, { archived }),
|
archiveBill: (id: Id, archived = true) => put(`/bills/${id}/archived`, { archived }),
|
||||||
updateBillBalance: (id: Id, bal: unknown) => _fetch('PATCH', `/bills/${id}/balance`, { current_balance: bal }),
|
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}`),
|
deleteCatalogDescriptor: (id: Id) => _fetch('DELETE', `/subscriptions/catalog/descriptors/${id}`),
|
||||||
|
|
||||||
// Payments
|
// Payments
|
||||||
quickPay: (data: Body) => post<PaymentRecord>('/payments/quick', data),
|
quickPay: (data: Body) => post<Payment>('/payments/quick', data),
|
||||||
confirmAutopaySuggestion: (billId: Id, data: Body) => post(`/payments/autopay-suggestions/${billId}/confirm`, 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),
|
dismissAutopaySuggestion: (billId: Id, data: Body) => post(`/payments/autopay-suggestions/${billId}/dismiss`, data),
|
||||||
bulkPay: (items: Body) => post('/payments/bulk', items),
|
bulkPay: (items: Body) => post('/payments/bulk', items),
|
||||||
createPayment: (data: Body) => post('/payments', data),
|
createPayment: (data: Body) => post<Payment>('/payments', data),
|
||||||
updatePayment: (id: Id, data: Body) => put(`/payments/${id}`, data),
|
updatePayment: (id: Id, data: Body) => put<Payment>(`/payments/${id}`, data),
|
||||||
deletePayment: (id: Id) => del(`/payments/${id}`),
|
deletePayment: (id: Id) => del(`/payments/${id}`),
|
||||||
restorePayment: (id: Id) => post(`/payments/${id}/restore`),
|
restorePayment: (id: Id) => post<Payment>(`/payments/${id}/restore`),
|
||||||
recentAutoMatched: () => get('/payments/recent-auto'),
|
recentAutoMatched: () => get('/payments/recent-auto'),
|
||||||
undoAutoMatch: (id: Id) => post(`/payments/${id}/undo-auto`),
|
undoAutoMatch: (id: Id) => post(`/payments/${id}/undo-auto`),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,50 @@ export interface Payment {
|
||||||
[key: string]: unknown;
|
[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
|
// One tracker row: a bill's state for a given month. Built by
|
||||||
// statusService.buildTrackerRow and augmented by trackerService.getTracker.
|
// statusService.buildTrackerRow and augmented by trackerService.getTracker.
|
||||||
export interface TrackerRow {
|
export interface TrackerRow {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue