Files
WorkBuddy/cnc-sales-backend/internal/api/aggregate.go
T
2026-08-16 16:29:06 +08:00

175 lines
5.0 KiB
Go

package api
import (
"database/sql"
"net/http"
"sort"
"time"
"cnc-sales-backend/internal/auth"
"cnc-sales-backend/internal/models"
)
// todayPanel 今日待处理聚合:逾期任务 / 今日到期任务 / 跟进到期客户
func (s *Server) todayPanel(w http.ResponseWriter, r *http.Request) {
uid := auth.UserID(r)
today := todayStr()
overdue := []models.Task{}
rows, err := s.db.Query(`SELECT `+taskCols+` FROM tasks t
LEFT JOIN customers c ON c.id=t.customer_id
WHERE t.owner_id=? AND t.deleted_at IS NULL AND t.status!='done' AND t.due_date!='' AND t.due_date<?`,
uid, today)
if err == nil {
defer rows.Close()
for rows.Next() {
if t, ok := scanTask(rows); ok {
overdue = append(overdue, *t)
}
}
}
dueToday := []models.Task{}
rows, err = s.db.Query(`SELECT `+taskCols+` FROM tasks t
LEFT JOIN customers c ON c.id=t.customer_id
WHERE t.owner_id=? AND t.deleted_at IS NULL AND t.status!='done' AND t.due_date=?`,
uid, today)
if err == nil {
defer rows.Close()
for rows.Next() {
if t, ok := scanTask(rows); ok {
dueToday = append(dueToday, *t)
}
}
}
dueFollowUps := []models.Customer{}
rows, err = s.db.Query(`SELECT `+customerCols+` FROM customers
WHERE owner_id=? AND deleted_at IS NULL AND next_follow_date!='' AND next_follow_date<=?`,
uid, today)
if err == nil {
defer rows.Close()
for rows.Next() {
c := models.Customer{}
if err := rows.Scan(&c.ID, &c.Company, &c.Industry, &c.Address, &c.Level, &c.Stage,
&c.MachineModel, &c.Budget, &c.WorkpieceNeeds, &c.PainPoints,
&c.NextFollowDate, &c.Source, &c.Remark, &c.CreatedAt, &c.UpdatedAt); err == nil {
dueFollowUps = append(dueFollowUps, c)
}
}
}
writeJSON(w, http.StatusOK, map[string]any{
"today": today,
"overdueTasks": overdue,
"dueTodayTasks": dueToday,
"dueFollowUps": dueFollowUps,
})
}
func scanTask(rows *sql.Rows) (*models.Task, bool) {
t := &models.Task{}
var customerID, repeat sql.NullString
if err := rows.Scan(&t.ID, &t.Title, &customerID, &t.DueDate, &t.Priority, &t.Status,
&repeat, &t.CreatedAt, &t.CustomerName); err != nil {
return nil, false
}
if customerID.Valid && customerID.String != "" {
t.CustomerID = &customerID.String
}
if repeat.Valid && repeat.String != "" {
t.Repeat = &repeat.String
}
return t, true
}
// pipelineStages 销售流程 8 阶段看板数据
func (s *Server) pipelineStages(w http.ResponseWriter, r *http.Request) {
uid := auth.UserID(r)
rows, err := s.db.Query("SELECT stage, company FROM customers WHERE owner_id=? AND deleted_at IS NULL", uid)
if err != nil {
writeErr(w, http.StatusInternalServerError, "查询失败")
return
}
defer rows.Close()
stageCompanies := map[string][]string{}
for rows.Next() {
var stage, company string
if err := rows.Scan(&stage, &company); err != nil {
continue
}
stageCompanies[stage] = append(stageCompanies[stage], company)
}
// 固定顺序输出 s1~s8
keys := make([]string, 0, len(models.StageNames))
for k := range models.StageNames {
keys = append(keys, k)
}
sort.Strings(keys)
out := []map[string]any{}
for _, k := range keys {
out = append(out, map[string]any{
"stage": k,
"name": models.StageNames[k],
"count": len(stageCompanies[k]),
"companies": stageCompanies[k],
})
}
writeJSON(w, http.StatusOK, out)
}
// statsOverview 数据看板统计
func (s *Server) statsOverview(w http.ResponseWriter, r *http.Request) {
uid := auth.UserID(r)
type kv struct {
Key string `json:"key"`
Count int `json:"count"`
}
groupBy := func(table, col string) []kv {
rows, err := s.db.Query("SELECT "+col+", COUNT(*) FROM "+table+" WHERE owner_id=? AND deleted_at IS NULL GROUP BY "+col, uid)
if err != nil {
return []kv{}
}
defer rows.Close()
out := []kv{}
for rows.Next() {
var k kv
if rows.Scan(&k.Key, &k.Count) == nil {
out = append(out, k)
}
}
return out
}
var totalCustomers, totalTasks, totalNotes, recentAdded int
_ = s.db.QueryRow("SELECT COUNT(*) FROM customers WHERE owner_id=? AND deleted_at IS NULL", uid).Scan(&totalCustomers)
_ = s.db.QueryRow("SELECT COUNT(*) FROM tasks WHERE owner_id=? AND deleted_at IS NULL", uid).Scan(&totalTasks)
_ = s.db.QueryRow("SELECT COUNT(*) FROM notes WHERE owner_id=? AND deleted_at IS NULL", uid).Scan(&totalNotes)
weekAgo := time.Now().AddDate(0, 0, -7).Format("2006-01-02")
_ = s.db.QueryRow("SELECT COUNT(*) FROM customers WHERE owner_id=? AND deleted_at IS NULL AND created_at>=?",
uid, weekAgo).Scan(&recentAdded)
// 阶段分布补全名称
stageDist := groupBy("customers", "stage")
for i := range stageDist {
if name, ok := models.StageNames[stageDist[i].Key]; ok {
stageDist[i].Key = name
}
}
writeJSON(w, http.StatusOK, map[string]any{
"totalCustomers": totalCustomers,
"totalTasks": totalTasks,
"totalNotes": totalNotes,
"recentAdded7d": recentAdded,
"levelDistribution": groupBy("customers", "level"),
"stageDistribution": stageDist,
"taskStatus": groupBy("tasks", "status"),
})
}