147 lines
3.8 KiB
Go
147 lines
3.8 KiB
Go
package store
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"strings"
|
|
|
|
"daili/internal/domain"
|
|
)
|
|
|
|
// Package 服务套餐。
|
|
type Package struct {
|
|
ID int64
|
|
Name string
|
|
Unit string
|
|
DefaultAmount int
|
|
PriceCents int
|
|
HasPrice bool
|
|
Color string
|
|
Note string
|
|
Archived bool
|
|
SortOrder int
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
SubCount int
|
|
ActiveCount int
|
|
}
|
|
|
|
// UnitLabel 返回类型中文名。
|
|
func (p *Package) UnitLabel() string {
|
|
if p.Unit == domain.UnitCount {
|
|
return "按次"
|
|
}
|
|
return "按日"
|
|
}
|
|
|
|
func scanPackage(row interface{ Scan(dest ...any) error }) (*Package, error) {
|
|
var p Package
|
|
var price sql.NullInt64
|
|
var archived int
|
|
err := row.Scan(&p.ID, &p.Name, &p.Unit, &p.DefaultAmount, &price, &p.Color, &p.Note,
|
|
&archived, &p.SortOrder, &p.CreatedAt, &p.UpdatedAt)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, ErrNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
p.Archived = archived == 1
|
|
if price.Valid {
|
|
p.PriceCents = int(price.Int64)
|
|
p.HasPrice = true
|
|
}
|
|
return &p, nil
|
|
}
|
|
|
|
// ListPackages 列出套餐(含订阅计数)。
|
|
func (db *DB) ListPackages(includeArchived bool) ([]Package, error) {
|
|
q := `SELECT p.id,p.name,p.unit,p.default_amount,p.price_cents,p.color,p.note,p.archived,p.sort_order,p.created_at,p.updated_at,
|
|
(SELECT COUNT(*) FROM subscriptions s WHERE s.package_id=p.id),
|
|
(SELECT COUNT(*) FROM subscriptions s WHERE s.package_id=p.id AND s.status<>'closed')
|
|
FROM packages p`
|
|
if !includeArchived {
|
|
q += ` WHERE p.archived=0`
|
|
}
|
|
q += ` ORDER BY p.archived, p.sort_order, p.id`
|
|
rows, err := db.sql.Query(q)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
out := []Package{}
|
|
for rows.Next() {
|
|
var p Package
|
|
var price sql.NullInt64
|
|
var archived int
|
|
if err := rows.Scan(&p.ID, &p.Name, &p.Unit, &p.DefaultAmount, &price, &p.Color, &p.Note,
|
|
&archived, &p.SortOrder, &p.CreatedAt, &p.UpdatedAt, &p.SubCount, &p.ActiveCount); err != nil {
|
|
return nil, err
|
|
}
|
|
p.Archived = archived == 1
|
|
if price.Valid {
|
|
p.PriceCents = int(price.Int64)
|
|
p.HasPrice = true
|
|
}
|
|
out = append(out, p)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// Package 取单个套餐。
|
|
func (db *DB) Package(id int64) (*Package, error) {
|
|
return scanPackage(db.sql.QueryRow(`SELECT id,name,unit,default_amount,price_cents,color,note,archived,sort_order,created_at,updated_at
|
|
FROM packages WHERE id=?`, id))
|
|
}
|
|
|
|
// CreatePackage 新建套餐。
|
|
func (db *DB) CreatePackage(p *Package) (int64, error) {
|
|
now := nowStamp()
|
|
p.Unit = normalizeUnit(p.Unit)
|
|
if p.DefaultAmount <= 0 {
|
|
p.DefaultAmount = 30
|
|
}
|
|
if strings.TrimSpace(p.Color) == "" {
|
|
p.Color = "#2F5D50"
|
|
}
|
|
var price any
|
|
if p.HasPrice {
|
|
price = p.PriceCents
|
|
}
|
|
res, err := db.sql.Exec(`INSERT INTO packages(name,unit,default_amount,price_cents,color,note,archived,sort_order,created_at,updated_at)
|
|
VALUES(?,?,?,?,?,?,0,?,?,?)`,
|
|
p.Name, p.Unit, p.DefaultAmount, price, p.Color, p.Note, p.SortOrder, now, now)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return res.LastInsertId()
|
|
}
|
|
|
|
// UpdatePackage 更新套餐。
|
|
func (db *DB) UpdatePackage(p *Package) error {
|
|
p.Unit = normalizeUnit(p.Unit)
|
|
if p.DefaultAmount <= 0 {
|
|
p.DefaultAmount = 30
|
|
}
|
|
if strings.TrimSpace(p.Color) == "" {
|
|
p.Color = "#2F5D50"
|
|
}
|
|
var price any
|
|
if p.HasPrice {
|
|
price = p.PriceCents
|
|
}
|
|
_, err := db.sql.Exec(`UPDATE packages SET name=?,unit=?,default_amount=?,price_cents=?,color=?,note=?,sort_order=?,updated_at=?
|
|
WHERE id=?`, p.Name, p.Unit, p.DefaultAmount, price, p.Color, p.Note, p.SortOrder, nowStamp(), p.ID)
|
|
return err
|
|
}
|
|
|
|
// SetPackageArchived 归档/取消归档。
|
|
func (db *DB) SetPackageArchived(id int64, archived bool) error {
|
|
v := 0
|
|
if archived {
|
|
v = 1
|
|
}
|
|
_, err := db.sql.Exec(`UPDATE packages SET archived=?, updated_at=? WHERE id=?`, v, nowStamp(), id)
|
|
return err
|
|
}
|