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 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
*/
@@ -35,19 +76,20 @@ export interface BudgetProgress {
* Get all budgets
*/
export async function getBudgets(): Promise<Budget[]> {
const response = await api.get<ApiResponse<Budget[]>>('/budgets');
return response.data || [];
const response = await api.get<ApiResponse<ApiBudget[]>>('/budgets');
const apiBudgets = response.data || [];
return apiBudgets.map(mapApiBudgetToBudget);
}
/**
* Get a single budget by ID
*/
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) {
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,
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) {
throw new Error(response.error || 'Failed to create budget');
}
return response.data;
return mapApiBudgetToBudget(response.data);
}
/**
@@ -99,12 +141,12 @@ export async function updateBudget(id: number, data: Partial<BudgetFormInput>):
if (data.isRolling !== undefined) payload.is_rolling = data.isRolling;
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;
const response = await api.put<ApiResponse<Budget>>(`/budgets/${id}`, payload);
const response = await api.put<ApiResponse<ApiBudget>>(`/budgets/${id}`, payload);
if (!response.data) {
throw new Error(response.error || 'Failed to update budget');
}
return response.data;
return mapApiBudgetToBudget(response.data);
}
/**