mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-05-19 08:48:48 +02:00
All checks were successful
Deploy to VPS / deploy (push) Successful in 23s
- Add paste-and-preview modal for entering pension fund balances from bank website - Backend upsert logic so n8n PDF uploads overwrite manual entries - Chart now shows actual snapshot data with dynamic month labels - New POST /pensions/manual endpoint for JSON-based fund entry Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
307 lines
7.7 KiB
TypeScript
307 lines
7.7 KiB
TypeScript
import axios from 'axios';
|
|
|
|
const api = axios.create({
|
|
baseURL: import.meta.env.VITE_API_URL || '/api/v1',
|
|
});
|
|
|
|
api.interceptors.request.use((config) => {
|
|
const token = localStorage.getItem('token');
|
|
if (token) config.headers.Authorization = `Bearer ${token}`;
|
|
return config;
|
|
});
|
|
|
|
api.interceptors.response.use(
|
|
(res) => res,
|
|
(err) => {
|
|
if (err.response?.status === 401) {
|
|
localStorage.removeItem('token');
|
|
window.location.href = '/login';
|
|
}
|
|
return Promise.reject(err);
|
|
},
|
|
);
|
|
|
|
export default api;
|
|
|
|
export async function login(username: string, password: string) {
|
|
const form = new URLSearchParams();
|
|
form.append('username', username);
|
|
form.append('password', password);
|
|
const { data } = await api.post('/auth/login', form);
|
|
localStorage.setItem('token', data.access_token);
|
|
return data;
|
|
}
|
|
|
|
export interface Account {
|
|
id: number;
|
|
bank: string;
|
|
currency: string;
|
|
label: string;
|
|
balance: number;
|
|
account_type: string;
|
|
next_payment: number | null;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface Category {
|
|
id: number;
|
|
name: string;
|
|
icon: string;
|
|
auto_match_patterns: string | null;
|
|
}
|
|
|
|
export interface ImportResult {
|
|
imported: number;
|
|
duplicates: number;
|
|
errors: string[];
|
|
}
|
|
|
|
// --- User Settings ---
|
|
|
|
export interface SectionSettings {
|
|
label: string;
|
|
color: string;
|
|
cardColor: string;
|
|
visible: boolean;
|
|
order: number;
|
|
expanded: boolean;
|
|
}
|
|
|
|
export interface DashboardSettings {
|
|
sections: Record<string, SectionSettings>;
|
|
}
|
|
|
|
export interface UserSettingsData {
|
|
dashboard: DashboardSettings;
|
|
}
|
|
|
|
export interface UserSettingsResponse {
|
|
key: string;
|
|
data: UserSettingsData;
|
|
updated_at: string;
|
|
}
|
|
|
|
export const getSettings = () => api.get<UserSettingsResponse>('/settings/');
|
|
export const updateSettings = (data: UserSettingsData) => api.patch<UserSettingsResponse>('/settings/', { data });
|
|
|
|
export interface Transaction {
|
|
id: number;
|
|
amount: number;
|
|
currency: string;
|
|
merchant: string;
|
|
city: string | null;
|
|
date: string;
|
|
card_type: string | null;
|
|
card_last4: string | null;
|
|
authorization_code: string | null;
|
|
reference: string | null;
|
|
transaction_type: string;
|
|
source: string;
|
|
bank: string;
|
|
notes: string | null;
|
|
category_id: number | null;
|
|
category: Category | null;
|
|
created_at: string;
|
|
}
|
|
|
|
// --- Budget / Recurring Items ---
|
|
|
|
export type RecurringItemType = 'INCOME' | 'EXPENSE' | 'SAVINGS';
|
|
export type RecurringFrequency = 'WEEKLY' | 'MONTHLY' | 'QUARTERLY' | 'BIANNUAL' | 'YEARLY';
|
|
|
|
export interface RecurringItem {
|
|
id: number;
|
|
name: string;
|
|
amount: number;
|
|
currency: string;
|
|
item_type: RecurringItemType;
|
|
frequency: RecurringFrequency;
|
|
day_of_month: number | null;
|
|
month_of_year: number | null;
|
|
override_amounts: Record<string, number> | null;
|
|
category_id: number | null;
|
|
is_active: boolean;
|
|
notes: string | null;
|
|
created_at: string;
|
|
category: Category | null;
|
|
}
|
|
|
|
export interface RecurringItemCreate {
|
|
name: string;
|
|
amount: number;
|
|
currency?: string;
|
|
item_type: RecurringItemType;
|
|
frequency?: RecurringFrequency;
|
|
day_of_month?: number | null;
|
|
month_of_year?: number | null;
|
|
override_amounts?: Record<string, number> | null;
|
|
category_id?: number | null;
|
|
is_active?: boolean;
|
|
notes?: string | null;
|
|
}
|
|
|
|
export interface RecurringItemUpdate {
|
|
name?: string;
|
|
amount?: number;
|
|
currency?: string;
|
|
item_type?: RecurringItemType;
|
|
frequency?: RecurringFrequency;
|
|
day_of_month?: number | null;
|
|
month_of_year?: number | null;
|
|
override_amounts?: Record<string, number> | null;
|
|
category_id?: number | null;
|
|
is_active?: boolean;
|
|
notes?: string | null;
|
|
}
|
|
|
|
export interface RecurringItemDetail {
|
|
id: number;
|
|
name: string;
|
|
amount: number;
|
|
projected_amount: number | null;
|
|
used_actual: boolean;
|
|
item_type: string;
|
|
frequency: string;
|
|
category_name: string | null;
|
|
category_id: number | null;
|
|
}
|
|
|
|
export interface ActualsBySource {
|
|
source: string;
|
|
total_compra: number;
|
|
total_devolucion: number;
|
|
net: number;
|
|
count: number;
|
|
}
|
|
|
|
export interface MonthlyProjection {
|
|
month: number;
|
|
year: number;
|
|
projected_income: number;
|
|
projected_fixed_expenses: number;
|
|
projected_savings: number;
|
|
actual_credit_card: number;
|
|
actual_cash: number;
|
|
actual_transfers: number;
|
|
uncovered_actual: number;
|
|
gran_total_egresos: number;
|
|
net_balance: number;
|
|
carryover_balance: number;
|
|
cumulative_balance: number;
|
|
balance_overridden: boolean;
|
|
}
|
|
|
|
export interface YearlyProjection {
|
|
year: number;
|
|
months: MonthlyProjection[];
|
|
annual_income: number;
|
|
annual_expenses: number;
|
|
annual_savings: number;
|
|
annual_net: number;
|
|
}
|
|
|
|
export interface MonthlyDetail {
|
|
year: number;
|
|
month: number;
|
|
income_items: RecurringItemDetail[];
|
|
expense_items: RecurringItemDetail[];
|
|
savings_items: RecurringItemDetail[];
|
|
actuals_by_source: ActualsBySource[];
|
|
total_projected_income: number;
|
|
total_projected_expenses: number;
|
|
total_projected_savings: number;
|
|
uncovered_actual: number;
|
|
gran_total_egresos: number;
|
|
net_balance: number;
|
|
}
|
|
|
|
// Budget API functions
|
|
export const getRecurringItems = (params?: { item_type?: string; is_active?: boolean }) =>
|
|
api.get<RecurringItem[]>('/budget/recurring', { params });
|
|
export const createRecurringItem = (data: RecurringItemCreate) =>
|
|
api.post<RecurringItem>('/budget/recurring', data);
|
|
export const updateRecurringItem = (id: number, data: RecurringItemUpdate) =>
|
|
api.patch<RecurringItem>(`/budget/recurring/${id}`, data);
|
|
export const deleteRecurringItem = (id: number) =>
|
|
api.delete(`/budget/recurring/${id}`);
|
|
export const getYearlyProjection = (year: number) =>
|
|
api.get<YearlyProjection>(`/budget/projection/${year}`);
|
|
export const getMonthlyDetail = (year: number, month: number) =>
|
|
api.get<MonthlyDetail>(`/budget/month/${year}/${month}`);
|
|
export const upsertBalanceOverride = (year: number, month: number, override_balance: number) =>
|
|
api.put(`/budget/balance-override/${year}/${month}`, { override_balance });
|
|
export const deleteBalanceOverride = (year: number, month: number) =>
|
|
api.delete(`/budget/balance-override/${year}/${month}`);
|
|
|
|
// --- Salarios ---
|
|
|
|
export interface SalariosSummary {
|
|
count: number;
|
|
total_amount: number;
|
|
latest_date: string | null;
|
|
}
|
|
|
|
export const getSalarios = (params?: { limit?: number; offset?: number }) =>
|
|
api.get<Transaction[]>('/salarios/', { params });
|
|
export const getSalariosSummary = () =>
|
|
api.get<SalariosSummary>('/salarios/summary');
|
|
|
|
// --- Pensions ---
|
|
|
|
export interface PensionSnapshot {
|
|
id: number;
|
|
fund: string;
|
|
contract_number: string;
|
|
period_start: string;
|
|
period_end: string;
|
|
saldo_anterior: number;
|
|
aportes: number;
|
|
rendimientos: number;
|
|
retiros: number;
|
|
traslados: number;
|
|
comision: number;
|
|
correccion: number;
|
|
bonificacion: number;
|
|
saldo_final: number;
|
|
source_filename: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface PensionUploadResult {
|
|
imported: number;
|
|
updated: number;
|
|
duplicates: number;
|
|
errors: string[];
|
|
snapshots: PensionSnapshot[];
|
|
}
|
|
|
|
export interface PensionManualEntry {
|
|
fund: string;
|
|
period_start: string;
|
|
period_end: string;
|
|
saldo_anterior: number;
|
|
aportes: number;
|
|
rendimientos: number;
|
|
retiros: number;
|
|
traslados: number;
|
|
comision: number;
|
|
correccion: number;
|
|
bonificacion: number;
|
|
saldo_final: number;
|
|
}
|
|
|
|
export const uploadPensionPDFs = (files: File[]) => {
|
|
const form = new FormData();
|
|
files.forEach((f) => form.append('files', f));
|
|
return api.post<PensionUploadResult>('/pensions/upload', form);
|
|
};
|
|
|
|
export const getPensionSnapshots = () =>
|
|
api.get<PensionSnapshot[]>('/pensions/snapshots');
|
|
|
|
export const getPensionFundSummary = () =>
|
|
api.get<PensionSnapshot[]>('/pensions/fund-summary');
|
|
|
|
export const submitPensionManualEntries = (entries: PensionManualEntry[]) =>
|
|
api.post<PensionUploadResult>('/pensions/manual', { entries });
|