Files
billai/server/handler/bills.go
2026-01-26 13:44:22 +08:00

241 lines
6.0 KiB
Go

package handler
import (
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"billai-server/model"
"billai-server/repository"
)
// ListBillsRequest 账单列表请求参数
type ListBillsRequest struct {
Page int `form:"page"` // 页码,从 1 开始
PageSize int `form:"page_size"` // 每页数量,默认 20
StartDate string `form:"start_date"` // 开始日期 YYYY-MM-DD
EndDate string `form:"end_date"` // 结束日期 YYYY-MM-DD
Category string `form:"category"` // 分类筛选
Type string `form:"type"` // 账单类型 alipay/wechat/jd
IncomeExpense string `form:"income_expense"` // 收支类型 收入/支出
}
// ListBillsResponse 账单列表响应
type ListBillsResponse struct {
Result bool `json:"result"`
Message string `json:"message,omitempty"`
Data *ListBillsData `json:"data,omitempty"`
}
// ListBillsData 账单列表数据
type ListBillsData struct {
Total int64 `json:"total"` // 总记录数
TotalExpense float64 `json:"total_expense"` // 筛选条件下的总支出
TotalIncome float64 `json:"total_income"` // 筛选条件下的总收入
Page int `json:"page"` // 当前页码
PageSize int `json:"page_size"` // 每页数量
Pages int `json:"pages"` // 总页数
Bills []model.CleanedBill `json:"bills"` // 账单列表
}
// ListBills 获取清洗后的账单列表
func ListBills(c *gin.Context) {
var req ListBillsRequest
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, ListBillsResponse{
Result: false,
Message: "参数解析失败: " + err.Error(),
})
return
}
// 设置默认值
if req.Page < 1 {
req.Page = 1
}
if req.PageSize < 1 {
req.PageSize = 20
}
// if req.PageSize > 100 {
// req.PageSize = 100 // 限制最大每页数量
// }
// 构建筛选条件
filter := make(map[string]interface{})
// 时间范围筛选
if req.StartDate != "" || req.EndDate != "" {
timeFilter := make(map[string]interface{})
if req.StartDate != "" {
// 使用本地时区解析日期,避免 UTC 时区问题
startTime, err := time.ParseInLocation("2006-01-02", req.StartDate, time.Local)
if err == nil {
timeFilter["$gte"] = startTime
}
}
if req.EndDate != "" {
// 使用本地时区解析日期,避免 UTC 时区问题
endTime, err := time.ParseInLocation("2006-01-02", req.EndDate, time.Local)
if err == nil {
// 结束日期包含当天,所以加一天
endTime = endTime.Add(24 * time.Hour)
timeFilter["$lt"] = endTime
}
}
if len(timeFilter) > 0 {
filter["time"] = timeFilter
}
}
// 分类筛选
if req.Category != "" {
filter["category"] = req.Category
}
// 账单类型筛选
if req.Type != "" {
filter["bill_type"] = req.Type
}
// 收支类型筛选
if req.IncomeExpense != "" {
filter["income_expense"] = req.IncomeExpense
}
// 获取数据
repo := repository.GetRepository()
if repo == nil {
c.JSON(http.StatusInternalServerError, ListBillsResponse{
Result: false,
Message: "数据库未连接",
})
return
}
// 获取账单列表(带分页)
bills, total, err := repo.GetCleanedBillsPaged(filter, req.Page, req.PageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, ListBillsResponse{
Result: false,
Message: "查询失败: " + err.Error(),
})
return
}
// 获取聚合统计
totalExpense, totalIncome, err := repo.GetBillsAggregate(filter)
if err != nil {
c.JSON(http.StatusInternalServerError, ListBillsResponse{
Result: false,
Message: "统计失败: " + err.Error(),
})
return
}
// 计算总页数
pages := int(total) / req.PageSize
if int(total)%req.PageSize > 0 {
pages++
}
c.JSON(http.StatusOK, ListBillsResponse{
Result: true,
Data: &ListBillsData{
Total: total,
TotalExpense: totalExpense,
TotalIncome: totalIncome,
Page: req.Page,
PageSize: req.PageSize,
Pages: pages,
Bills: bills,
},
})
}
// parsePageParam 解析分页参数
func parsePageParam(s string, defaultVal int) int {
if s == "" {
return defaultVal
}
val, err := strconv.Atoi(s)
if err != nil || val < 1 {
return defaultVal
}
return val
}
// MonthlyStatsResponse 月度统计响应
type MonthlyStatsResponse struct {
Result bool `json:"result"`
Message string `json:"message,omitempty"`
Data []model.MonthlyStat `json:"data,omitempty"`
}
// MonthlyStats 获取月度统计数据(全部数据,不受筛选条件影响)
func MonthlyStats(c *gin.Context) {
repo := repository.GetRepository()
if repo == nil {
c.JSON(http.StatusInternalServerError, MonthlyStatsResponse{
Result: false,
Message: "数据库未连接",
})
return
}
stats, err := repo.GetMonthlyStats()
if err != nil {
c.JSON(http.StatusInternalServerError, MonthlyStatsResponse{
Result: false,
Message: "查询失败: " + err.Error(),
})
return
}
c.JSON(http.StatusOK, MonthlyStatsResponse{
Result: true,
Data: stats,
})
}
// ReviewStats 获取待复核数据统计
func ReviewStats(c *gin.Context) {
repo := repository.GetRepository()
// 从MongoDB查询所有需要复核的账单
bills, err := repo.GetBillsNeedReview()
if err != nil {
c.JSON(http.StatusInternalServerError, model.ReviewResponse{
Result: false,
Message: "查询失败: " + err.Error(),
})
return
}
highCount := 0
lowCount := 0
totalCount := 0
// 统计各等级的复核记录
for _, bill := range bills {
totalCount++
if bill.ReviewLevel == "HIGH" {
highCount++
} else if bill.ReviewLevel == "LOW" {
lowCount++
}
}
c.JSON(http.StatusOK, model.ReviewResponse{
Result: true,
Message: "获取成功",
Data: &model.ReviewData{
Total: totalCount,
High: highCount,
Low: lowCount,
Records: nil, // 统计接口不返回具体记录
},
})
}