Files
billai/server/handler/update_bill.go
clz bacbabc0a5 feat: 添加账单软删除功能
- 新增删除按钮(带二次确认)到账单详情抽屉
- 后端实现软删除(设置 is_deleted 标记)
- 所有查询方法自动过滤已删除记录
- 账单列表和复核页面都支持删除
- 版本更新至 1.2.0
2026-01-25 18:49:07 +08:00

157 lines
4.3 KiB
Go

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})
}