bilibili-backup/app/admin/main/growup/service/trade.go
2019-04-22 02:59:20 +00:00

302 lines
8.0 KiB
Go

package service
import (
"context"
"fmt"
"strings"
"time"
"go-common/app/admin/main/growup/dao"
"go-common/app/admin/main/growup/dao/resource"
"go-common/app/admin/main/growup/model"
"go-common/library/log"
)
const (
_datetimeLayout = "2006-01-02 15:04:05"
_dateLayout = "2006-01-02"
)
func (s *Service) tradeDao() dao.TradeDao {
return s.dao
}
// SyncGoods . secret portal...
func (s *Service) SyncGoods(c context.Context, gt int) (eff int64, err error) {
if gt != int(model.GoodsVIP) {
err = fmt.Errorf("unsupported goodsType(%v)", gt)
return
}
// query
vips, err := resource.VipProducts(c)
if err != nil {
return
}
existing := make(map[string]*model.GoodsInfo)
allGoods, err := s.tradeDao().GoodsList(c, fmt.Sprintf("is_deleted=0 AND goods_type=%d", gt), 0, 200)
if err != nil {
return
}
for _, v := range allGoods {
existing[v.ProductID] = v
}
// hardcoded <vip_product_duration_in_month, external_resource_id> k-v pairs
m := map[int32]int64{
1: 16,
3: 17,
12: 18,
}
// add incrementally
newGoods := make([]string, 0)
for k, v := range vips {
if _, ok := existing[k]; ok {
continue
}
rid, ok := m[v.Month]
if !ok {
err = fmt.Errorf("unkonwn vip goods, month(%d)", v.Month)
}
newGoods = append(newGoods, fmt.Sprintf("('%s', %d, %d, %d, %d)", v.ProductID, rid, gt, model.DisplayOff, 100))
}
if len(newGoods) == 0 {
return
}
fields := "ex_product_id, ex_resource_id, goods_type, is_display, discount"
return s.tradeDao().AddGoods(c, fields, strings.Join(newGoods, ","))
}
// GoodsList .
func (s *Service) GoodsList(c context.Context, from, limit int) (total int64, res []*model.GoodsInfo, err error) {
if total, err = s.tradeDao().GoodsCount(c, "is_deleted=0"); err != nil {
return
}
if total == 0 {
res = make([]*model.GoodsInfo, 0)
return
}
if res, err = s.tradeDao().GoodsList(c, "is_deleted=0", from, limit); err != nil || len(res) == 0 {
return
}
// external information of vip goods
var vips map[string]*model.GoodsInfo
if vips, err = resource.VipProducts(c); err != nil {
return
}
for _, target := range res {
if src, ok := vips[target.ProductID]; ok {
model.MergeExternal(target, src)
}
target.GoodsTypeDesc = target.GoodsType.Desc()
}
return
}
// UpdateGoodsInfo by ID
func (s *Service) UpdateGoodsInfo(c context.Context, discount int, ID int64) (int64, error) {
// select and diff before update?
set := fmt.Sprintf("discount=%d", discount)
return s.tradeDao().UpdateGoods(c, set, "", []int64{ID})
}
// UpdateGoodsDisplay by IDs
func (s *Service) UpdateGoodsDisplay(c context.Context, status model.DisplayStatus, IDs []int64) (eff int64, err error) {
switch status {
case model.DisplayOn:
eff, err = s.onlineGoods(c, IDs)
case model.DisplayOff:
eff, err = s.offlineGoods(c, IDs)
default:
err = fmt.Errorf("illegal display status(%v)", status)
}
return
}
// onlineGoods by IDs
func (s *Service) onlineGoods(c context.Context, IDs []int64) (int64, error) {
now := time.Now().Format(_datetimeLayout)
set := fmt.Sprintf("is_display=%d, display_on_time='%s'", model.DisplayOn, now)
return s.tradeDao().UpdateGoods(c, set, "is_deleted=0", IDs)
}
// offlineGoods by IDs
func (s *Service) offlineGoods(c context.Context, IDs []int64) (int64, error) {
now := time.Now().Format(_datetimeLayout)
set := fmt.Sprintf("is_display=%d, display_off_time='%s'", model.DisplayOff, now)
return s.tradeDao().UpdateGoods(c, set, "is_deleted=0", IDs)
}
// OrderStatistics .
func (s *Service) OrderStatistics(c context.Context, arg *model.OrderQueryArg) (data interface{}, err error) {
if pass := preprocess(c, arg); !pass {
return
}
where := orderQueryStr(arg)
var orders []*model.OrderInfo
if orders, err = s.orderAll(c, where); err != nil {
return
}
for _, v := range orders {
v.GenDerived()
}
data = orderStatistics(orders, arg.StartTime, arg.EndTime, arg.TimeType)
return
}
// orderAll . be careful using this
func (s *Service) orderAll(c context.Context, where string) (orders []*model.OrderInfo, err error) {
offset, size := 0, 2000
for {
list, err := s.tradeDao().OrderList(c, where, offset, size)
if err != nil {
return nil, err
}
orders = append(orders, list...)
if len(list) < size {
break
}
offset += len(list)
}
return
}
// orderStatistics . ugly...
func orderStatistics(orders []*model.OrderInfo, start, end time.Time, timeType model.TimeType) interface{} {
type orderStatUnit struct {
orderNum int64
totalPrice int64
totalCost int64
}
m := make(map[time.Time]*orderStatUnit)
for _, v := range orders {
date := timeType.RangeStart(v.OrderTime)
if _, ok := m[date]; !ok {
m[date] = &orderStatUnit{}
}
m[date].orderNum += v.GoodsNum
m[date].totalCost += v.TotalCost
m[date].totalPrice += v.TotalPrice
}
dates, orderNums, totalCost, totalPrice := make([]string, 0), make([]int64, 0), make([]int64, 0), make([]int64, 0)
for start.Before(end) {
next := timeType.Next()(start)
dates = append(dates, timeType.RangeDesc(start, next))
if v, ok := m[start]; ok {
orderNums = append(orderNums, v.orderNum)
totalCost = append(totalCost, v.totalCost)
totalPrice = append(totalPrice, v.totalPrice)
} else {
orderNums = append(orderNums, 0)
totalCost = append(totalCost, 0)
totalPrice = append(totalPrice, 0)
}
start = next
}
// result
data := map[string]interface{}{
"xAxis": dates,
"order_num": orderNums,
"total_cost": totalCost,
"total_income": totalPrice,
}
return data
}
// OrderExport .
func (s *Service) OrderExport(c context.Context, arg *model.OrderQueryArg, from, limit int) (res []byte, err error) {
_, orders, err := s.OrderList(c, arg, from, limit)
if err != nil {
return
}
records := make([][]string, 0, len(orders)+1)
records = append(records, model.OrderExportFields())
for _, v := range orders {
records = append(records, v.ExportStrings())
}
if res, err = FormatCSV(records); err != nil {
log.Error("FormatCSV error(%v)", err)
}
return
}
// OrderList .
func (s *Service) OrderList(c context.Context, arg *model.OrderQueryArg, from, limit int) (total int64, list []*model.OrderInfo, err error) {
list = make([]*model.OrderInfo, 0)
if pass := preprocess(c, arg); !pass {
return
}
where := orderQueryStr(arg)
if total, err = s.tradeDao().OrderCount(c, where); err != nil || total == 0 {
return
}
if list, err = s.tradeDao().OrderList(c, where, from, limit); err != nil || len(list) == 0 {
return
}
// fetch names
mids := make([]int64, 0)
for _, v := range list {
mids = append(mids, v.MID)
}
m, err := resource.NamesByMIDs(c, mids)
if err != nil {
return
}
// generate & merge
for _, v := range list {
v.GenDerived().GenDesc()
if name, ok := m[v.MID]; ok {
v.Nickname = name
}
}
return
}
func preprocess(c context.Context, arg *model.OrderQueryArg) bool {
if arg.Nickname != "" {
mid, err := resource.MidByNickname(c, arg.Nickname)
if err != nil || mid == 0 {
return false
}
log.Info("resource.MidByNickname name(%s) mid(%d)", arg.Nickname, arg.MID)
if arg.MID == 0 {
arg.MID = mid
}
if arg.MID != mid {
log.Error("illegal mid(%d) and nickname(%s) pair", arg.MID, arg.Nickname)
return false
}
}
arg.StartTime = arg.TimeType.RangeStart(time.Unix(arg.FromTime, 0))
arg.EndTime = arg.TimeType.RangeEnd(time.Unix(arg.ToTime, 0))
return true
}
func orderQueryStr(arg *model.OrderQueryArg) string {
var where []string
where = append(where, "is_deleted=0")
{
// 左开右闭
where = append(where, fmt.Sprintf("order_time >= '%s'", arg.StartTime.Format(_dateLayout)))
where = append(where, fmt.Sprintf("order_time < '%s'", arg.EndTime.Format(_dateLayout)))
}
if arg.GoodsType > 0 {
where = append(where, fmt.Sprintf("goods_type=%d", arg.GoodsType))
}
if arg.GoodsID != "" {
where = append(where, fmt.Sprintf("goods_id='%s'", arg.GoodsID))
}
if arg.GoodsName != "" {
where = append(where, fmt.Sprintf("goods_name='%s'", arg.GoodsName))
}
if arg.OrderNO != "" {
where = append(where, fmt.Sprintf("order_no='%s'", arg.OrderNO))
}
if arg.MID > 0 {
where = append(where, fmt.Sprintf("mid=%d", arg.MID))
}
return strings.Join(where, " AND ")
}