115 lines
3.5 KiB
Go
115 lines
3.5 KiB
Go
package store
|
|
|
|
import (
|
|
"time"
|
|
|
|
"daili/internal/domain"
|
|
)
|
|
|
|
// Stats 大屏统计数据。
|
|
type Stats struct {
|
|
Customers int
|
|
ActiveSubs int
|
|
PausedSubs int
|
|
DueToday int
|
|
Due7 int
|
|
DueTodaySubs []Subscription
|
|
Heat map[string]int // 结束日 -> 该日到期数量
|
|
MonthDue int
|
|
Top []Subscription
|
|
TodayTasks int
|
|
TodayDone int
|
|
}
|
|
|
|
// Badges 顶栏常驻徽标。
|
|
func (db *DB) Badges(today time.Time) (dueToday, due3 int, err error) {
|
|
t := domain.Date(today)
|
|
if err = db.sql.QueryRow(`SELECT COUNT(*) FROM subscriptions WHERE status<>'closed' AND end_date=?`, t).Scan(&dueToday); err != nil {
|
|
return
|
|
}
|
|
err = db.sql.QueryRow(`SELECT COUNT(*) FROM subscriptions WHERE status<>'closed' AND end_date BETWEEN ? AND ?`,
|
|
t, domain.Date(domain.AddDays(today, 2))).Scan(&due3)
|
|
return
|
|
}
|
|
|
|
// ScreenStats 汇总大屏所需数据。
|
|
func (db *DB) ScreenStats(today time.Time, warnDays int) (*Stats, error) {
|
|
st := &Stats{Heat: map[string]int{}}
|
|
var err error
|
|
if st.Customers, err = db.CustomerCount(); err != nil {
|
|
return nil, err
|
|
}
|
|
if st.ActiveSubs, err = db.CountActiveSubs(domain.Date(today)); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = db.sql.QueryRow(`SELECT COUNT(*) FROM subscriptions WHERE status='paused'`).Scan(&st.PausedSubs); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = db.sql.QueryRow(`SELECT COUNT(*) FROM subscriptions WHERE status<>'closed' AND end_date=?`,
|
|
domain.Date(today)).Scan(&st.DueToday); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = db.sql.QueryRow(`SELECT COUNT(*) FROM subscriptions WHERE status<>'closed' AND end_date BETWEEN ? AND ?`,
|
|
domain.Date(today), domain.Date(domain.AddDays(today, 6))).Scan(&st.Due7); err != nil {
|
|
return nil, err
|
|
}
|
|
if st.DueTodaySubs, err = db.DueOn(domain.Date(today)); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 本月到期热力
|
|
ms := domain.MonthStart(today.Year(), today.Month())
|
|
me := domain.MonthEnd(today.Year(), today.Month())
|
|
rows, err := db.sql.Query(`SELECT end_date, COUNT(*) FROM subscriptions
|
|
WHERE status<>'closed' AND end_date BETWEEN ? AND ? GROUP BY end_date`,
|
|
domain.Date(ms), domain.Date(me))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for rows.Next() {
|
|
var d string
|
|
var n int
|
|
if err := rows.Scan(&d, &n); err != nil {
|
|
rows.Close()
|
|
return nil, err
|
|
}
|
|
st.Heat[d] = n
|
|
st.MonthDue += n
|
|
}
|
|
rows.Close()
|
|
|
|
// TOP:按日按剩余天数、按次按剩余次数,各自排序后交替取前 10
|
|
t := domain.Date(today)
|
|
daySubs, err := db.querySubs(subSelect+` WHERE s.status<>'closed' AND s.unit='day' AND s.end_date>=?
|
|
ORDER BY s.end_date ASC LIMIT 12`, t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cntSubs, err := db.querySubs(subSelect+` WHERE s.status<>'closed' AND s.unit='count' AND s.used_count < s.total_count
|
|
AND (IFNULL(s.end_date,'')='' OR s.end_date>=?)
|
|
ORDER BY (s.total_count - s.used_count) ASC LIMIT 12`, t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for i := 0; i < 10 && len(st.Top) < 10; i++ {
|
|
if i < len(daySubs) {
|
|
st.Top = append(st.Top, daySubs[i])
|
|
}
|
|
if len(st.Top) < 10 && i < len(cntSubs) {
|
|
st.Top = append(st.Top, cntSubs[i])
|
|
}
|
|
if i >= len(daySubs) && i >= len(cntSubs) {
|
|
break
|
|
}
|
|
}
|
|
_ = warnDays
|
|
return st, nil
|
|
}
|
|
|
|
// RecentDue 返回未来 n 天内到期的订阅(按到期日排序)。
|
|
func (db *DB) RecentDue(from string, days int) ([]Subscription, error) {
|
|
to := domain.Date(domain.AddDays(domain.ParseDateOr(from, domain.Today()), days-1))
|
|
return db.querySubs(subSelect+` WHERE s.status<>'closed' AND s.end_date BETWEEN ? AND ?
|
|
ORDER BY s.end_date, s.id`, from, to)
|
|
}
|