feat: 添加预算服务,包含预算的增删改查、进度获取及相关工具函数

This commit is contained in:
2026-02-02 15:30:21 +08:00
parent 9149f1515a
commit 6dd24864e8

View File

@@ -5,6 +5,47 @@
import api from './api'; import api from './api';
import type { Budget, ApiResponse, BudgetPeriodType } from '../types'; import type { Budget, ApiResponse, BudgetPeriodType } from '../types';
/**
* Backend Budget API response (snake_case)
*/
interface ApiBudget {
id: number;
name: string;
amount: number;
period_type: BudgetPeriodType;
category_id?: number;
account_id?: number;
is_rolling: boolean;
start_date: string;
end_date?: string;
spent?: number;
progress?: number;
created_at: string;
// Nested objects from Preload
category?: { id: number; name: string; icon?: string };
account?: { id: number; name: string };
}
/**
* Convert backend API response to frontend Budget model
*/
function mapApiBudgetToBudget(apiBudget: ApiBudget): Budget {
return {
id: apiBudget.id,
name: apiBudget.name,
amount: apiBudget.amount,
periodType: apiBudget.period_type,
categoryId: apiBudget.category_id,
accountId: apiBudget.account_id,
isRolling: apiBudget.is_rolling,
startDate: apiBudget.start_date,
endDate: apiBudget.end_date,
spent: apiBudget.spent ?? 0,
progress: apiBudget.progress ?? 0,
createdAt: apiBudget.created_at,
};
}
/** /**
* Budget form input interface * Budget form input interface
*/ */
@@ -35,19 +76,20 @@ export interface BudgetProgress {
* Get all budgets * Get all budgets
*/ */
export async function getBudgets(): Promise<Budget[]> { export async function getBudgets(): Promise<Budget[]> {
const response = await api.get<ApiResponse<Budget[]>>('/budgets'); const response = await api.get<ApiResponse<ApiBudget[]>>('/budgets');
return response.data || []; const apiBudgets = response.data || [];
return apiBudgets.map(mapApiBudgetToBudget);
} }
/** /**
* Get a single budget by ID * Get a single budget by ID
*/ */
export async function getBudget(id: number): Promise<Budget> { export async function getBudget(id: number): Promise<Budget> {
const response = await api.get<ApiResponse<Budget>>(`/budgets/${id}`); const response = await api.get<ApiResponse<ApiBudget>>(`/budgets/${id}`);
if (!response.data) { if (!response.data) {
throw new Error('Budget not found'); throw new Error('Budget not found');
} }
return response.data; return mapApiBudgetToBudget(response.data);
} }
/** /**
@@ -77,11 +119,11 @@ export async function createBudget(data: BudgetFormInput): Promise<Budget> {
start_date: data.startDate ? `${data.startDate}T00:00:00Z` : undefined, start_date: data.startDate ? `${data.startDate}T00:00:00Z` : undefined,
end_date: data.endDate ? `${data.endDate}T23:59:59Z` : undefined, end_date: data.endDate ? `${data.endDate}T23:59:59Z` : undefined,
}; };
const response = await api.post<ApiResponse<Budget>>('/budgets', payload); const response = await api.post<ApiResponse<ApiBudget>>('/budgets', payload);
if (!response.data) { if (!response.data) {
throw new Error(response.error || 'Failed to create budget'); throw new Error(response.error || 'Failed to create budget');
} }
return response.data; return mapApiBudgetToBudget(response.data);
} }
/** /**
@@ -100,11 +142,11 @@ export async function updateBudget(id: number, data: Partial<BudgetFormInput>):
if (data.startDate !== undefined) payload.start_date = `${data.startDate}T00:00:00Z`; if (data.startDate !== undefined) payload.start_date = `${data.startDate}T00:00:00Z`;
if (data.endDate !== undefined) payload.end_date = data.endDate ? `${data.endDate}T23:59:59Z` : null; if (data.endDate !== undefined) payload.end_date = data.endDate ? `${data.endDate}T23:59:59Z` : null;
const response = await api.put<ApiResponse<Budget>>(`/budgets/${id}`, payload); const response = await api.put<ApiResponse<ApiBudget>>(`/budgets/${id}`, payload);
if (!response.data) { if (!response.data) {
throw new Error(response.error || 'Failed to update budget'); throw new Error(response.error || 'Failed to update budget');
} }
return response.data; return mapApiBudgetToBudget(response.data);
} }
/** /**