package handler import ( "fmt" "net/http" "strings" "time" "github.com/gin-gonic/gin" "billai-server/model" "billai-server/repository" ) // UpdateBillRequest 账单更新请求(字段均为可选) type UpdateBillRequest struct { Time *string `json:"time,omitempty"` Category *string `json:"category,omitempty"` Merchant *string `json:"merchant,omitempty"` Description *string `json:"description,omitempty"` IncomeExpense *string `json:"income_expense,omitempty"` Amount *float64 `json:"amount,omitempty"` PayMethod *string `json:"pay_method,omitempty"` Status *string `json:"status,omitempty"` Remark *string `json:"remark,omitempty"` ReviewLevel *string `json:"review_level,omitempty"` } type UpdateBillResponse struct { Result bool `json:"result"` Message string `json:"message,omitempty"` Data *model.CleanedBill `json:"data,omitempty"` } func parseBillTime(s string) (time.Time, error) { s = strings.TrimSpace(s) formats := []string{ "2006-01-02 15:04:05", "2006-01-02T15:04:05Z07:00", "2006-01-02T15:04:05Z", "2006-01-02", } for _, f := range formats { if t, err := time.ParseInLocation(f, s, time.Local); err == nil { return t, nil } } return time.Time{}, fmt.Errorf("unsupported time format") } // UpdateBill POST /api/bills/:id 更新清洗后的账单记录 func UpdateBill(c *gin.Context) { id := strings.TrimSpace(c.Param("id")) if id == "" { c.JSON(http.StatusBadRequest, UpdateBillResponse{Result: false, Message: "缺少账单 ID"}) return } var req UpdateBillRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, UpdateBillResponse{Result: false, Message: "参数解析失败: " + err.Error()}) return } updates := map[string]interface{}{} if req.Time != nil { t, err := parseBillTime(*req.Time) if err != nil { c.JSON(http.StatusBadRequest, UpdateBillResponse{Result: false, Message: "时间格式错误"}) return } updates["time"] = t } if req.Category != nil { v := strings.TrimSpace(*req.Category) if v == "" { c.JSON(http.StatusBadRequest, UpdateBillResponse{Result: false, Message: "分类不能为空"}) return } updates["category"] = v } if req.Merchant != nil { v := strings.TrimSpace(*req.Merchant) if v == "" { c.JSON(http.StatusBadRequest, UpdateBillResponse{Result: false, Message: "商家不能为空"}) return } updates["merchant"] = v } if req.Description != nil { updates["description"] = strings.TrimSpace(*req.Description) } if req.IncomeExpense != nil { v := strings.TrimSpace(*req.IncomeExpense) if v != "" && v != "收入" && v != "支出" { c.JSON(http.StatusBadRequest, UpdateBillResponse{Result: false, Message: "income_expense 只能是 收入 或 支出"}) return } updates["income_expense"] = v } if req.Amount != nil { updates["amount"] = *req.Amount } if req.PayMethod != nil { updates["pay_method"] = strings.TrimSpace(*req.PayMethod) } if req.Status != nil { updates["status"] = strings.TrimSpace(*req.Status) } if req.Remark != nil { updates["remark"] = strings.TrimSpace(*req.Remark) } if req.ReviewLevel != nil { // 允许设置为空字符串(清除复核等级)或 HIGH/LOW v := strings.TrimSpace(*req.ReviewLevel) if v != "" && v != "HIGH" && v != "LOW" { c.JSON(http.StatusBadRequest, UpdateBillResponse{Result: false, Message: "review_level 只能是空、HIGH 或 LOW"}) return } updates["review_level"] = v } if len(updates) == 0 { c.JSON(http.StatusBadRequest, UpdateBillResponse{Result: false, Message: "没有可更新的字段"}) return } updates["updated_at"] = time.Now() repo := repository.GetRepository() if repo == nil { c.JSON(http.StatusInternalServerError, UpdateBillResponse{Result: false, Message: "数据库未连接"}) return } updated, err := repo.UpdateCleanedBillByID(id, updates) if err != nil { if err == repository.ErrNotFound { c.JSON(http.StatusNotFound, UpdateBillResponse{Result: false, Message: "账单不存在"}) return } c.JSON(http.StatusInternalServerError, UpdateBillResponse{Result: false, Message: "更新失败: " + err.Error()}) return } c.JSON(http.StatusOK, UpdateBillResponse{Result: true, Message: "更新成功", Data: updated}) }