241 lines
6.0 KiB
Go
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, // 统计接口不返回具体记录
|
|
},
|
|
})
|
|
}
|