更新oa的数据统计
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -1149,3 +1150,150 @@ func nullableReimburseString(value string) *string {
|
||||
func uint64Ptr(value uint64) *uint64 {
|
||||
return &value
|
||||
}
|
||||
|
||||
// Dashboard GET /backend/reimbursements/dashboard — 报销统计(周/月/年 + 近6个月趋势 + 状态分布)
|
||||
func (c *BackendReimburseController) Dashboard() {
|
||||
claims, ok := c.claims()
|
||||
if !ok {
|
||||
c.reply(401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
y, m, _ := now.Date()
|
||||
today := time.Date(y, m, now.Day(), 0, 0, 0, 0, now.Location())
|
||||
tomorrow := today.AddDate(0, 0, 1)
|
||||
weekday := int(today.Weekday())
|
||||
if weekday == 0 {
|
||||
weekday = 7
|
||||
}
|
||||
weekStart := today.AddDate(0, 0, -(weekday - 1))
|
||||
monthStart := time.Date(y, m, 1, 0, 0, 0, 0, now.Location())
|
||||
yearStart := time.Date(y, 1, 1, 0, 0, 0, 0, now.Location())
|
||||
|
||||
var rows []models.BackendReimbursement
|
||||
_, err := models.Orm.QueryTable(new(models.BackendReimbursement)).
|
||||
Filter("tid", claims.TenantId).
|
||||
Filter("user_id", claims.UserID).
|
||||
Filter("is_deleted", 0).
|
||||
All(&rows)
|
||||
if err != nil {
|
||||
c.reply(500, "查询失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// ponytail: 个人报销单量小,一次全量查询内存聚合;量大时改 SQL GROUP BY
|
||||
newStat := func() map[string]interface{} {
|
||||
return map[string]interface{}{"total": 0.0, "paid": 0.0, "unpaid": 0.0, "count": 0}
|
||||
}
|
||||
addTo := func(stat map[string]interface{}, row *models.BackendReimbursement) {
|
||||
stat["total"] = stat["total"].(float64) + row.TotalAmount
|
||||
stat["count"] = stat["count"].(int) + 1
|
||||
if row.Status == 5 { // 已打款视为已报销
|
||||
stat["paid"] = stat["paid"].(float64) + row.TotalAmount
|
||||
} else {
|
||||
stat["unpaid"] = stat["unpaid"].(float64) + row.TotalAmount
|
||||
}
|
||||
}
|
||||
inRange := func(t, start, end time.Time) bool {
|
||||
return !t.Before(start) && t.Before(end)
|
||||
}
|
||||
|
||||
weekStat, monthStat, yearStat := newStat(), newStat(), newStat()
|
||||
monthKeys := make([]string, 0, 6)
|
||||
monthStats := map[string]map[string]interface{}{}
|
||||
for i := 5; i >= 0; i-- {
|
||||
key := time.Date(y, m-time.Month(i), 1, 0, 0, 0, 0, now.Location()).Format("2006-01")
|
||||
monthKeys = append(monthKeys, key)
|
||||
monthStats[key] = newStat()
|
||||
}
|
||||
statusNames := map[int8]string{0: "草稿", 1: "审批中", 2: "已通过", 3: "已驳回", 4: "已撤回", 5: "已打款"}
|
||||
statusDist := map[string]interface{}{}
|
||||
|
||||
for i := range rows {
|
||||
row := &rows[i]
|
||||
if inRange(row.ApplyDate, weekStart, tomorrow) {
|
||||
addTo(weekStat, row)
|
||||
}
|
||||
if inRange(row.ApplyDate, monthStart, tomorrow) {
|
||||
addTo(monthStat, row)
|
||||
}
|
||||
if !row.ApplyDate.Before(yearStart) {
|
||||
addTo(yearStat, row)
|
||||
}
|
||||
if st, ok := monthStats[row.ApplyDate.Format("2006-01")]; ok {
|
||||
addTo(st, row)
|
||||
}
|
||||
sk := strconv.Itoa(int(row.Status))
|
||||
dist, ok := statusDist[sk].(map[string]interface{})
|
||||
if !ok {
|
||||
dist = map[string]interface{}{"name": statusNames[row.Status], "count": 0, "amount": 0.0}
|
||||
statusDist[sk] = dist
|
||||
}
|
||||
dist["count"] = dist["count"].(int) + 1
|
||||
dist["amount"] = dist["amount"].(float64) + row.TotalAmount
|
||||
}
|
||||
|
||||
trend := make([]map[string]interface{}, 0, len(monthKeys))
|
||||
for _, key := range monthKeys {
|
||||
st := monthStats[key]
|
||||
trend = append(trend, map[string]interface{}{
|
||||
"month": key, "total": st["total"], "paid": st["paid"],
|
||||
"unpaid": st["unpaid"], "count": st["count"],
|
||||
})
|
||||
}
|
||||
|
||||
// 报销类型分析:仅统计已打款(status=5)的报销单费用明细
|
||||
paidIDs := map[uint64]bool{}
|
||||
for i := range rows {
|
||||
if rows[i].Status == 5 {
|
||||
paidIDs[rows[i].ID] = true
|
||||
}
|
||||
}
|
||||
var items []models.BackendReimbursementItem
|
||||
_, err = models.Orm.QueryTable(new(models.BackendReimbursementItem)).
|
||||
Filter("tid", claims.TenantId).
|
||||
Filter("uid", claims.UserID).
|
||||
Filter("is_deleted", 0).
|
||||
All(&items)
|
||||
if err != nil {
|
||||
c.reply(500, "查询失败", nil)
|
||||
return
|
||||
}
|
||||
typeAgg := map[string]*struct {
|
||||
count int
|
||||
amount float64
|
||||
}{}
|
||||
for i := range items {
|
||||
if !paidIDs[items[i].ReimbursementID] {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSpace(items[i].ExpenseType)
|
||||
if name == "" {
|
||||
name = "未分类"
|
||||
}
|
||||
agg, ok := typeAgg[name]
|
||||
if !ok {
|
||||
agg = &struct {
|
||||
count int
|
||||
amount float64
|
||||
}{}
|
||||
typeAgg[name] = agg
|
||||
}
|
||||
agg.count++
|
||||
agg.amount += items[i].Amount
|
||||
}
|
||||
expenseTypes := make([]map[string]interface{}, 0, len(typeAgg))
|
||||
for name, agg := range typeAgg {
|
||||
expenseTypes = append(expenseTypes, map[string]interface{}{
|
||||
"name": name, "count": agg.count, "amount": agg.amount,
|
||||
})
|
||||
}
|
||||
sort.Slice(expenseTypes, func(i, j int) bool {
|
||||
return expenseTypes[i]["amount"].(float64) > expenseTypes[j]["amount"].(float64)
|
||||
})
|
||||
|
||||
c.reply(200, "success", map[string]interface{}{
|
||||
"week": weekStat, "month": monthStat, "year": yearStat,
|
||||
"trend": trend, "status_dist": statusDist, "expense_types": expenseTypes,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user