Files
subtrack/internal/web/handlers_subs.go
T
chun_qiu f047662129
build / build-and-test (push) Failing after 1m24s
首次推送
2026-10-18 19:44:38 +08:00

186 lines
5.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package web
import (
"fmt"
"net/http"
"strings"
"daili/internal/domain"
"daili/internal/store"
)
type subsData struct {
List []SubView
Pager Pager
State string
PackageID int64
Q string
Packages []store.Package
Total int
}
func (s *Server) handleSubscriptions(w http.ResponseWriter, r *http.Request) {
today := domain.Today()
warn := s.warnDays()
state := r.URL.Query().Get("state")
if state == "" {
state = "active"
}
packageID := int64(s.queryInt(r, "package_id", 0))
q := r.URL.Query().Get("q")
pageNo := s.queryInt(r, "page", 1)
if pageNo < 1 {
pageNo = 1
}
f := store.SubFilter{Q: q, State: state, PackageID: packageID, Limit: pageSize, Offset: (pageNo - 1) * pageSize, Today: today}
list, total, err := s.db.ListSubs(f)
if err != nil {
http.Error(w, "读取订阅失败", http.StatusInternalServerError)
return
}
pager := newPager(r, pageNo, pageSize, total)
if pager.Page != pageNo {
f.Offset = (pager.Page - 1) * pageSize
list, total, err = s.db.ListSubs(f)
if err != nil {
http.Error(w, "读取订阅失败", http.StatusInternalServerError)
return
}
}
pkgs, _ := s.db.ListPackages(true)
data := &subsData{
List: subViews(list, today, warn), Pager: pager, State: state,
PackageID: packageID, Q: q, Packages: pkgs, Total: total,
}
p := s.newPage(r, "订阅", "subscriptions")
p.D = data
s.render(w, r, "subscriptions", p)
}
func (s *Server) handleSubExtend(w http.ResponseWriter, r *http.Request) {
id := pathID(r)
sub, err := s.db.Sub(id)
if err != nil {
http.NotFound(w, r)
return
}
u := s.currentUser(r)
var opID int64
if u != nil {
opID = u.ID
}
note := strings.TrimSpace(r.PostFormValue("note"))
back := backURL(r, "/customers/"+itoa(sub.CustomerID))
if sub.Unit == domain.UnitDay {
newEnd := strings.TrimSpace(r.PostFormValue("new_end_date"))
plan, err := s.db.ExtendDays(id, newEnd, note, opID)
if err != nil {
s.redirect(w, r, back+"#extend", "追加失败:"+err.Error())
return
}
s.logOp(r, "追加时长", "subscription", id, fmt.Sprintf("%s 追加 %d 天:%s → %s(起点 %s)",
sub.SubTitle(), plan.Days, sub.EndDate, domain.Date(plan.NewEnd), domain.Date(plan.Base)))
s.redirect(w, r, back, fmt.Sprintf("已追加 %d 天,新结束日 %s", plan.Days, domain.Date(plan.NewEnd)))
return
}
add := s.formInt(r, "add_count", 0)
optEnd := strings.TrimSpace(r.PostFormValue("new_end_opt"))
if err := s.db.AddCount(id, add, optEnd, note, opID); err != nil {
s.redirect(w, r, back+"#extend", "追加失败:"+err.Error())
return
}
s.logOp(r, "追加次数", "subscription", id, fmt.Sprintf("%s 追加 %d 次", sub.SubTitle(), add))
s.redirect(w, r, back, fmt.Sprintf("已追加 %d 次", add))
}
func (s *Server) handleSubAction(action string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := pathID(r)
sub, err := s.db.Sub(id)
if err != nil {
http.NotFound(w, r)
return
}
back := backURL(r, "/customers/"+itoa(sub.CustomerID))
switch action {
case "pause":
if err := s.db.PauseSub(id, 0); err != nil {
s.redirect(w, r, back, "暂停失败:"+err.Error())
return
}
s.logOp(r, "暂停订阅", "subscription", id, sub.SubTitle()+" 暂停")
s.redirect(w, r, back, "已暂停,剩余时长将冻结")
case "resume":
shift, err := s.db.ResumeSub(id, 0)
if err != nil {
s.redirect(w, r, back, "恢复失败:"+err.Error())
return
}
msg := "已恢复"
if shift > 0 {
msg = fmt.Sprintf("已恢复,结束日顺延 %d 天", shift)
}
s.logOp(r, "恢复订阅", "subscription", id, fmt.Sprintf("%s 恢复,顺延 %d 天", sub.SubTitle(), shift))
s.redirect(w, r, back, msg)
case "close":
if err := s.db.CloseSub(id); err != nil {
s.redirect(w, r, back, "结束失败:"+err.Error())
return
}
s.logOp(r, "结束订阅", "subscription", id, sub.SubTitle()+" 结束")
s.redirect(w, r, back, "订阅已结束")
case "reopen":
if err := s.db.ReopenSub(id); err != nil {
s.redirect(w, r, back, "启用失败:"+err.Error())
return
}
s.logOp(r, "重新启用", "subscription", id, sub.SubTitle()+" 重新启用")
s.redirect(w, r, back, "订阅已重新启用")
default:
http.NotFound(w, r)
}
}
}
func (s *Server) handleCheckinSave(w http.ResponseWriter, r *http.Request) {
id := pathID(r)
sub, err := s.db.Sub(id)
if err != nil {
http.NotFound(w, r)
return
}
back := backURL(r, "/calendar?d="+r.PostFormValue("date"))
date := strings.TrimSpace(r.PostFormValue("date"))
status := strings.TrimSpace(r.PostFormValue("status"))
note := strings.TrimSpace(r.PostFormValue("note"))
u := s.currentUser(r)
var opID int64
if u != nil {
opID = u.ID
}
if err := s.db.SaveCheckin(id, date, status, note, opID); err != nil {
s.redirect(w, r, back, "打卡失败:"+err.Error())
return
}
label := "已完成"
if status == "skipped" {
label = "已跳过"
}
s.logOp(r, "打卡", "subscription", id, fmt.Sprintf("%s %s 打卡:%s", sub.SubTitle(), date, label))
s.redirect(w, r, back, "已记录:"+sub.CustomerTail+" "+sub.CustomerName+" "+date)
}
func (s *Server) handleCheckinDelete(w http.ResponseWriter, r *http.Request) {
id := pathID(r)
back := backURL(r, "/calendar")
subID, err := s.db.DeleteCheckin(id, 0)
if err != nil {
s.redirect(w, r, back, "撤销失败:"+err.Error())
return
}
s.logOp(r, "撤销打卡", "subscription", subID, "撤销打卡记录 #"+itoa(id))
s.redirect(w, r, back, "已撤销打卡")
}