feat: 添加预算管理功能,包括其handler、service和repository层。

This commit is contained in:
2026-02-02 13:55:41 +08:00
parent d08f7902c9
commit 4b2355deee
3 changed files with 75 additions and 3 deletions

View File

@@ -98,6 +98,7 @@ func (h *BudgetHandler) GetBudget(c *gin.Context) {
}
// GetAllBudgets handles GET /api/v1/budgets
// Returns budgets with calculated spent and progress fields
func (h *BudgetHandler) GetAllBudgets(c *gin.Context) {
// Get user ID from context
userID, exists := c.Get("user_id")
@@ -106,7 +107,8 @@ func (h *BudgetHandler) GetAllBudgets(c *gin.Context) {
return
}
budgets, err := h.service.GetAllBudgets(userID.(uint))
// Use GetAllBudgetsWithProgress to return budgets with spent and progress calculated
budgets, err := h.service.GetAllBudgetsWithProgress(userID.(uint))
if err != nil {
api.InternalError(c, "Failed to get budgets")
return

View File

@@ -66,8 +66,19 @@ func (r *BudgetRepository) Update(budget *models.Budget) error {
return fmt.Errorf("failed to check budget existence: %w", err)
}
// Update the budget
if err := r.db.Save(budget).Error; err != nil {
// Update the budget using Select to explicitly specify fields to update
// This ensures that nil/NULL values for CategoryID and AccountID are properly saved
if err := r.db.Model(budget).Select(
"name",
"amount",
"period_type",
"category_id", // Explicitly include to allow setting to NULL
"account_id", // Explicitly include to allow setting to NULL
"is_rolling",
"start_date",
"end_date",
"updated_at",
).Updates(budget).Error; err != nil {
return fmt.Errorf("failed to update budget: %w", err)
}
return nil

View File

@@ -126,6 +126,65 @@ func (s *BudgetService) GetAllBudgets(userID uint) ([]models.Budget, error) {
return budgets, nil
}
// BudgetWithProgress represents a budget with calculated progress fields
type BudgetWithProgress struct {
models.Budget
Spent float64 `json:"spent"`
Progress float64 `json:"progress"`
}
// GetAllBudgetsWithProgress retrieves all budgets with calculated spent and progress for a user
func (s *BudgetService) GetAllBudgetsWithProgress(userID uint) ([]BudgetWithProgress, error) {
budgets, err := s.repo.GetAll(userID)
if err != nil {
return nil, fmt.Errorf("failed to get budgets: %w", err)
}
result := make([]BudgetWithProgress, len(budgets))
now := time.Now()
for i, budget := range budgets {
// Calculate current period
startDate, endDate := s.calculateCurrentPeriod(&budget, now)
// Get spent amount for current period
spent, err := s.repo.GetSpentAmount(&budget, startDate, endDate)
if err != nil {
// Log error but continue with 0 spent
spent = 0
}
// Calculate effective amount and progress
effectiveAmount := budget.Amount
if budget.IsRolling {
// For rolling budgets, calculate effective amount
periodsElapsed := s.calculatePeriodsElapsed(&budget, startDate)
totalBudget := budget.Amount * float64(periodsElapsed+1)
historyEnd := startDate.Add(-time.Second)
historySpent := 0.0
if historyEnd.After(budget.StartDate) {
historySpent, _ = s.repo.GetSpentAmount(&budget, budget.StartDate, historyEnd)
}
effectiveAmount = totalBudget - historySpent
}
progress := 0.0
if effectiveAmount > 0 {
progress = (spent / effectiveAmount) * 100
}
result[i] = BudgetWithProgress{
Budget: budget,
Spent: spent,
Progress: progress,
}
}
return result, nil
}
// UpdateBudget updates an existing budget after verifying ownership
func (s *BudgetService) UpdateBudget(userID, id uint, input BudgetInput) (*models.Budget, error) {
// Get existing budget