Files
jx-callback/business/jxstore/promotion/jd_promotion.go
2018-11-05 22:04:28 +08:00

589 lines
20 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 promotion
import (
"encoding/gob"
"errors"
"fmt"
"time"
"git.rosy.net.cn/baseapi/platformapi/jdapi"
"git.rosy.net.cn/baseapi/utils"
"git.rosy.net.cn/jx-callback/business/jxutils"
"git.rosy.net.cn/jx-callback/business/jxutils/jxcontext"
"git.rosy.net.cn/jx-callback/business/jxutils/tasksch"
"git.rosy.net.cn/jx-callback/business/model"
"git.rosy.net.cn/jx-callback/business/model/dao"
"git.rosy.net.cn/jx-callback/globals"
"git.rosy.net.cn/jx-callback/globals/api"
)
const (
PromotionTypeNormal = 1
PromotionTypeDirectDown = 3
PromotionTypeLimitedTime = 4
)
const (
PriceTypePrice = 1 // 绝对价格
PriceTypePercentage = 2 // 百分比
)
const (
PromotionLimitedTimeMinPercentage = 80
)
const (
colSkuIDIndex = 0
colSkuPriceIndex = 3
colNameIndex = 5
colStoreIDIndex = 6
colBeginAtIndex = 9
colEndAtIndex = 10
)
const (
DefaultLimitSkuCount = 100
MaxPromotionSkuCount = jdapi.MaxPromotionSkuCount
)
const (
defSearchDays = 7
)
type SkuPrice struct {
SkuID int `json:"skuID"`
PriceType int `json:"priceType"`
Price int `json:"price"` // 分这个不是单价是这个sku的活动价
LimitSkuCount int `json:"limitSkuCount"`
IsLock int8 `json:"isLock"`
}
type tSimpleStore struct {
StoreID int `orm:"column(store_id)" json:"storeID"`
Name string `orm:"column(name)" json:"name"`
}
type PromotionParams struct {
Name string
Advertising string
Type int
BeginAt time.Time
EndAt time.Time
StoreIDs []int
SkuPrices []*SkuPrice
}
type tStoreSkuBindExt struct {
model.StoreSkuBind
JdID int64 `orm:"column(jd_id)"`
}
type tPromotionInfo struct {
model.Promotion
StoreStr string `orm:"column(store_str)" json:"-"`
SkuPriceStr string `orm:"column(sku_price_str)" json:"-"`
Stores []*tSimpleStore `orm:"-" json:"stores"`
SkuPrices []*SkuPrice `orm:"-" json:"skuPrices"`
}
var (
ErrEmptySkus = errors.New("空sku或指定的SKU没有被门店认领请检查")
)
type JdPromotionHandler interface {
CreatePromotionInfos(name string, beginDate, endDate time.Time, outInfoId, advertising string) (infoId int64, err error)
CreatePromotionRules(infoId int64, outInfoId string, limitDevice, limitPin, limitCount, limitDaily int) (err error)
CreatePromotionSku(infoId int64, outInfoId string, skus []map[string]interface{}) (skusResult []map[string]interface{}, err error)
ConfirmPromotion(infoId int64, outInfoId string) (err error)
CancelPromotion(infoId int64, outInfoId string) (err error)
}
type JdDirectDownHandler struct {
}
func (p *JdDirectDownHandler) CreatePromotionInfos(name string, beginDate, endDate time.Time, outInfoId, advertising string) (infoId int64, err error) {
return api.JdAPI.CreatePromotionInfosSingle(name, beginDate, endDate, outInfoId, advertising)
}
func (p *JdDirectDownHandler) CreatePromotionRules(infoId int64, outInfoId string, limitDevice, limitPin, limitCount, limitDaily int) (err error) {
return api.JdAPI.CreatePromotionRules(infoId, outInfoId, limitDevice, limitPin, limitCount, limitDaily)
}
func (p *JdDirectDownHandler) CreatePromotionSku(infoId int64, outInfoId string, skus []map[string]interface{}) (skusResult []map[string]interface{}, err error) {
return api.JdAPI.CreatePromotionSkuSingle(infoId, outInfoId, skus)
}
func (p *JdDirectDownHandler) ConfirmPromotion(infoId int64, outInfoId string) (err error) {
return api.JdAPI.ConfirmPromotionSingle(infoId, outInfoId)
}
func (p *JdDirectDownHandler) CancelPromotion(infoId int64, outInfoId string) (err error) {
return api.JdAPI.CancelPromotionSingle(infoId, outInfoId)
}
type JdLimitedTimeHandler struct {
}
func (p *JdLimitedTimeHandler) CreatePromotionInfos(name string, beginDate, endDate time.Time, outInfoId, advertising string) (infoId int64, err error) {
return api.JdAPI.CreatePromotionInfosLimitTime(name, beginDate, endDate, outInfoId, advertising)
}
func (p *JdLimitedTimeHandler) CreatePromotionRules(infoId int64, outInfoId string, limitDevice, limitPin, limitCount, limitDaily int) (err error) {
return api.JdAPI.CreatePromotionRules(infoId, outInfoId, limitDevice, limitPin, limitCount, limitDaily)
}
func (p *JdLimitedTimeHandler) CreatePromotionSku(infoId int64, outInfoId string, skus []map[string]interface{}) (skusResult []map[string]interface{}, err error) {
return api.JdAPI.CreatePromotionSkuLimitTime(infoId, outInfoId, skus)
}
func (p *JdLimitedTimeHandler) ConfirmPromotion(infoId int64, outInfoId string) (err error) {
return api.JdAPI.ConfirmPromotionLimitTime(infoId, outInfoId)
}
func (p *JdLimitedTimeHandler) CancelPromotion(infoId int64, outInfoId string) (err error) {
return api.JdAPI.CancelPromotionLimitTime(infoId, outInfoId)
}
type JdNullHandler struct {
}
func (p *JdNullHandler) CreatePromotionInfos(name string, beginDate, endDate time.Time, outInfoId, advertising string) (infoId int64, err error) {
return jxutils.GenFakeID(), nil
}
func (p *JdNullHandler) CreatePromotionRules(infoId int64, outInfoId string, limitDevice, limitPin, limitCount, limitDaily int) (err error) {
return nil
}
func (p *JdNullHandler) CreatePromotionSku(infoId int64, outInfoId string, skus []map[string]interface{}) (skusResult []map[string]interface{}, err error) {
return nil, nil
}
func (p *JdNullHandler) ConfirmPromotion(infoId int64, outInfoId string) (err error) {
return nil
}
func (p *JdNullHandler) CancelPromotion(infoId int64, outInfoId string) (err error) {
return nil
}
func init() {
gob.Register(&PromotionParams{})
gob.Register([]*SkuPrice{})
}
func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync bool, params *PromotionParams, userName string) (hint string, err error) {
if len(params.SkuPrices) == 0 {
return "", ErrEmptySkus
}
db := dao.GetDB()
skuIDs := make([]int, len(params.SkuPrices))
skuPriceMap := make(map[int64]*SkuPrice)
for k, v := range params.SkuPrices {
skuIDs[k] = v.SkuID
skuPriceMap[int64(v.SkuID)] = v
}
sql := ""
var sqlParam []interface{}
if isIDJd {
sql = `
SELECT t1.*, t2.jd_id
FROM store_sku_bind t1
JOIN sku t2 ON t1.sku_id = t2.id AND t2.deleted_at = ?
JOIN store_map t3 ON t1.store_id = t3.store_id AND t3.vendor_id = ? AND t3.deleted_at = ?
WHERE t1.jd_sync_status = 0 AND t1.deleted_at = ? AND t2.jd_id IN (
` + dao.GenQuestionMarks(len(skuIDs)) + ") AND t3.vendor_store_id = ?"
sqlParam = append(sqlParam, utils.DefaultTimeValue, model.VendorIDJD, utils.DefaultTimeValue)
} else {
sql = `
SELECT t1.*, 0 jd_id
FROM store_sku_bind t1
WHERE t1.jd_sync_status = 0 AND t1.deleted_at = ? AND t1.sku_id IN (
` + dao.GenQuestionMarks(len(skuIDs)) + ") AND t1.store_id = ?"
}
sqlParam = append(sqlParam, utils.DefaultTimeValue, skuIDs)
errMsg := ""
modifyPricesList := make(map[int][]*jdapi.SkuPriceInfo)
promotionPrices := make([]map[string]interface{}, len(params.StoreIDs)*len(params.SkuPrices))
index := 0
var jxStoreIDs []int
for _, storeID := range params.StoreIDs {
var skuBinds []*tStoreSkuBindExt
if err = dao.GetRows(db, &skuBinds, sql, append(sqlParam, storeID)...); err != nil {
return "", err
}
for k, skuBind := range skuBinds {
if k == 0 {
jxStoreIDs = append(jxStoreIDs, skuBind.StoreID)
}
mapSkuID := int64(skuBind.SkuID)
if isIDJd {
mapSkuID = skuBind.JdID
}
promotionSkuPrice := skuPriceMap[mapSkuID]
if promotionSkuPrice.PriceType == PriceTypePercentage {
promotionSkuPrice.Price = skuBind.Price * promotionSkuPrice.Price / 100
}
if promotionSkuPrice.Price >= skuBind.Price {
errMsg += fmt.Sprintf("促销价大于等于原价storeID:%d, skuID:%d\n", skuBind.StoreID, skuBind.SkuID)
}
if promotionSkuPrice.LimitSkuCount <= 0 {
promotionSkuPrice.LimitSkuCount = DefaultLimitSkuCount
}
if errMsg == "" {
if params.Type == PromotionTypeLimitedTime {
if skuBind.Price*PromotionLimitedTimeMinPercentage/100 < promotionSkuPrice.Price {
modifyPricesList[skuBind.StoreID] = append(modifyPricesList[skuBind.StoreID], &jdapi.SkuPriceInfo{
OutSkuId: utils.Int2Str(skuBind.SkuID),
Price: promotionSkuPrice.Price*100/PromotionLimitedTimeMinPercentage + 1,
})
}
}
promotionPrices[index] = map[string]interface{}{
jdapi.KeyOutStationNo: utils.Int2Str(skuBind.StoreID),
jdapi.KeyOutSkuId: utils.Int2Str(skuBind.SkuID),
jdapi.KeyPromotionPrice: promotionSkuPrice.Price,
jdapi.KeyLimitSkuCount: promotionSkuPrice.LimitSkuCount,
}
index++
}
}
}
if errMsg != "" {
return "", errors.New(errMsg)
}
promotionPrices = promotionPrices[:index]
if len(promotionPrices) == 0 {
return "", ErrEmptySkus
}
promotion := &model.Promotion{
Name: params.Name,
Advertising: params.Advertising,
VendorID: model.VendorIDJD,
Type: params.Type,
Status: model.PromotionStatusLocalCreated,
BeginAt: params.BeginAt,
EndAt: params.EndAt,
}
dao.WrapAddIDCULDEntity(promotion, userName)
// if promotion.Params, err = jxutils.SerializeData(params); err != nil {
// return "", err
// }
// promotion.Params = string(utils.MustMarshal(params))
dao.Begin(db)
defer func() {
dao.Rollback(db)
}()
if err = dao.CreateEntity(db, promotion); err != nil {
return "", err
}
for _, storeID := range params.StoreIDs {
promotionStore := &model.PromotionStore{
PromotionID: promotion.ID,
StoreID: storeID,
}
dao.WrapAddIDCULDEntity(promotionStore, ctx.GetUserName())
if err = dao.CreateEntity(db, promotionStore); err != nil {
return "", err
}
}
for _, skuPrice := range params.SkuPrices {
promotionSku := &model.PromotionSku{
PromotionID: promotion.ID,
SkuID: skuPrice.SkuID,
PriceType: skuPrice.PriceType,
Price: skuPrice.Price,
LimitSkuCount: skuPrice.LimitSkuCount,
}
dao.WrapAddIDCULDEntity(promotionSku, ctx.GetUserName())
if err = dao.CreateEntity(db, promotionSku); err != nil {
return "", err
}
}
promotionHandler := getPromotionHander(params.Type)
if promotionHandler == nil {
return "", errors.New("非法的促销类型")
}
infoId, err2 := promotionHandler.CreatePromotionInfos(params.Name, params.BeginAt, params.EndAt, utils.Int2Str(promotion.ID), params.Advertising)
if err = err2; err != nil {
return "", err
}
promotion.VendorPromotionID = utils.Int64ToStr(infoId)
if _, err = dao.UpdateEntity(db, promotion); err != nil {
return "", err
}
dao.Commit(db)
rootTask := tasksch.NewSeqTask("CreateJdPromotion", userName, func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) {
if step == 0 {
task1 := tasksch.NewParallelTask("CreateJdPromotion update sku price", nil, userName, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) {
storeID := batchItemList[0].(int)
modifyPricesList := jxutils.SplitSlice(modifyPricesList[storeID], jdapi.MaxStoreSkuBatchSize)
for _, modifyPrices := range modifyPricesList {
modifyPrices2 := make([]*jdapi.SkuPriceInfo, len(modifyPrices))
for k, v := range modifyPrices {
modifyPrices2[k] = v.(*jdapi.SkuPriceInfo)
}
if _, err = api.JdAPI.UpdateVendorStationPrice(utils.Int2Str(storeID), "", modifyPrices2); err != nil {
return nil, err
}
}
return nil, nil
}, jxStoreIDs)
task.AddChild(task1).Run()
_, err = task1.GetResult(0)
} else if step == 1 {
err = promotionHandler.CreatePromotionRules(infoId, "", 1, 1, 1, 1)
} else if step == 2 {
task2 := tasksch.NewParallelTask("CreateJdPromotion CreatePromotionSku", tasksch.NewParallelConfig().SetBatchSize(MaxPromotionSkuCount), userName, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) {
skus := make([]map[string]interface{}, len(batchItemList))
for k, v := range batchItemList {
skus[k] = v.(map[string]interface{})
}
_, err = promotionHandler.CreatePromotionSku(infoId, "", skus)
return nil, err
}, promotionPrices)
task.AddChild(task2).Run()
_, err = task2.GetResult(0)
} else if step == 3 {
err = promotionHandler.ConfirmPromotion(infoId, "")
if err == nil {
promotion.Status = model.PromotionStatusRemoteCreated
} else {
promotion.Status = model.PromotionStatusRemoteFailed
}
db := dao.GetDB()
_, err = dao.UpdateEntityLogically(db, promotion, map[string]interface{}{
"Status": promotion.Status,
}, ctx.GetUserName(), nil)
}
return nil, err
}, 4)
ctx.SetTaskOrAddChild(rootTask, nil)
tasksch.ManageTask(rootTask).Run()
if !isAsync {
_, err = rootTask.GetResult(0)
}
return rootTask.ID, err
}
// func CreatePromotionByExcel(ctx *jxcontext.Context, isAsync bool, promotionType int, fileHeader *multipart.FileHeader, userName string) (hint string, err error) {
// file, err := fileHeader.Open()
// if err != nil {
// return "", err
// }
// contents := excel.Excel2Slice(file)
// file.Close()
// var promotionParams *PromotionParams
// for _, v := range contents {
// promotionParams = &PromotionParams{
// Name: v[1][colNameIndex],
// Type: promotionType,
// }
// if promotionParams.BeginAt, err = excelStr2Time(v[1][colBeginAtIndex]); err != nil {
// return "", err
// }
// if promotionParams.EndAt, err = excelStr2Time(v[1][colEndAtIndex]); err != nil {
// return "", err
// }
// for rowIndex, row := range v {
// if rowIndex > 0 {
// isBreak := true
// if row[colSkuIDIndex] != "" {
// isBreak = false
// jdSkuID := int(utils.Str2Int64(row[colSkuIDIndex]))
// promotionParams.SkuPrices = append(promotionParams.SkuPrices, &SkuPrice{
// SkuID: jdSkuID,
// PriceType: PriceTypePrice,
// Price: int(jxutils.StandardPrice2Int(utils.Str2Float64(row[colSkuPriceIndex]))),
// })
// }
// if row[colStoreIDIndex] != "" {
// isBreak = false
// jdStoreID := int(utils.Str2Int64(row[colStoreIDIndex]))
// promotionParams.StoreIDs = append(promotionParams.StoreIDs, jdStoreID)
// }
// if isBreak {
// break
// }
// }
// }
// break
// }
// // globals.SugarLogger.Debug(utils.Format4Output(promotionParams, false))
// // globals.SugarLogger.Debug(isAsync)
// return CreateJdPromotion(ctx, true, isAsync, promotionParams, userName)
// }
func GetJdPromotions(ctx *jxcontext.Context, keyword string, params map[string]interface{}, offset, pageSize int) (pagedInfo *model.PagedInfo, err error) {
sql := `
SELECT SQL_CALC_FOUND_ROWS
t1.id,
t1.created_at,
t1.updated_at,
t1.last_operator,
t1.vendor_id,
t1.name,
t1.type,
t1.status,
t1.vendor_promotion_id,
t1.begin_at,
t1.end_at,
t1.advertising,
CONCAT("[", GROUP_CONCAT(DISTINCT CONCAT('{"storeID":', t2.store_id, ', "name":"', t22.name, '"}')), "]") store_str,
CONCAT("[", GROUP_CONCAT(DISTINCT CONCAT('{"skuID":', t3.sku_id, ', "priceType":', t3.price_type, ', "price":', t3.price, ', "limitSkuCount":', t3.limit_sku_count, ', "isLock":', t3.is_lock, '}')), "]") sku_price_str
FROM promotion t1
JOIN promotion_store t2 ON t1.id = t2.promotion_id
JOIN store t22 ON t2.store_id = t22.id
JOIN promotion_sku t3 ON t1.id = t3.promotion_id
WHERE t1.deleted_at = ?
`
sqlParams := []interface{}{
utils.DefaultTimeValue,
}
if keyword != "" {
keywordLike := "%" + keyword + "%"
sql += " AND ( t1.name LIKE ?"
sqlParams = append(sqlParams, keywordLike)
keywordInt := utils.Str2Int64WithDefault(keyword, 0)
if keywordInt > 0 {
sql += `
OR t1.id = ? OR t1.vendor_promotion_id = ?
OR (SELECT COUNT(*) FROM promotion_store tt1 WHERE tt1.promotion_id = t1.id AND tt1.store_id = ?) > 0
OR (SELECT COUNT(*) FROM promotion_sku tt1 WHERE tt1.promotion_id = t1.id AND tt1.sku_id = ?) > 0
`
sqlParams = append(sqlParams, keywordInt, keywordInt, keywordInt, keywordInt)
}
sql += ")"
}
if params["vendorID"] != nil {
sql += " AND t1.vendor_id = ?"
sqlParams = append(sqlParams, params["vendorID"].(int))
}
if params["promotionID"] != nil {
sql += " AND t1.id = ?"
sqlParams = append(sqlParams, params["promotionID"].(int))
}
if params["vendorPromotionID"] != nil {
sql += " AND t1.vendor_promotion_id = ?"
sqlParams = append(sqlParams, params["vendorPromotionID"].(int))
}
if params["name"] != nil {
sql += " AND t1.name LIKE ?"
sqlParams = append(sqlParams, "%"+params["name"].(string)+"%")
}
if params["beginAt"] != nil {
sql += " AND t1.begin_at <= ?"
beginAt, err2 := utils.TryStr2Time(params["beginAt"].(string))
if err = err2; err != nil {
return nil, err
}
sqlParams = append(sqlParams, beginAt)
}
if params["endAt"] != nil {
sql += " AND t1.end_at >= ?"
endAt, err2 := utils.TryStr2Time(params["endAt"].(string))
if err = err2; err != nil {
return nil, err
}
sqlParams = append(sqlParams, endAt)
}
days := defSearchDays
if params["days"] != nil {
days = params["days"].(int)
}
sql += " AND t1.created_at >= ?"
sqlParams = append(sqlParams, time.Now().Add(-time.Duration(days)*24*time.Hour))
if params["type"] != nil {
sql += " AND t1.type = ?"
sqlParams = append(sqlParams, params["type"].(int))
}
if params["storeID"] != nil {
sql += " AND (SELECT COUNT(*) FROM promotion_store tt1 WHERE tt1.promotion_id = t1.id AND tt1.store_id = ?) > 0"
sqlParams = append(sqlParams, params["storeID"].(int))
}
if params["skuID"] != nil {
sql += " AND (SELECT COUNT(*) FROM promotion_sku tt1 WHERE tt1.promotion_id = t1.id AND tt1.sku_id = ?) > 0"
sqlParams = append(sqlParams, params["skuID"].(int))
}
sql += `
GROUP BY
1,2,3,4,5,6,7,8,9,10,11,12
ORDER BY t1.id DESC
LIMIT ? OFFSET ?
`
pageSize = jxutils.FormalizePageSize(pageSize)
if offset < 0 {
offset = 0
}
sqlParams = append(sqlParams, pageSize, offset)
db := dao.GetDB()
dao.Begin(db)
defer func() {
dao.Rollback(db)
if r := recover(); r != nil {
panic(r)
}
}()
// globals.SugarLogger.Debug(utils.Format4Output(sqlParams, false))
// globals.SugarLogger.Debug(sql)
var promotionList []*tPromotionInfo
if err = dao.GetRows(db, &promotionList, sql, sqlParams...); err == nil {
for _, v := range promotionList {
if v.StoreStr != "" {
if err = utils.UnmarshalUseNumber([]byte(v.StoreStr), &v.Stores); err != nil {
return nil, err
}
}
if v.SkuPriceStr != "" {
if err = utils.UnmarshalUseNumber([]byte(v.SkuPriceStr), &v.SkuPrices); err != nil {
return nil, err
}
}
}
pagedInfo = &model.PagedInfo{}
countInfo := &struct{ Ct int }{}
if err = dao.GetRow(db, countInfo, "SELECT FOUND_ROWS() ct"); err == nil {
pagedInfo.TotalCount = countInfo.Ct
}
pagedInfo.Data = promotionList
}
dao.Commit(db)
return pagedInfo, err
}
func CancelJdPromotion(ctx *jxcontext.Context, promotionID int) (err error) {
db := dao.GetDB()
promotion := &model.Promotion{}
promotion.ID = promotionID
if err = dao.GetEntity(db, promotion); err != nil {
return err
}
promotionHandler := getPromotionHander(promotion.Type)
if promotionHandler == nil {
return errors.New("非法的促销类型")
}
if err = promotionHandler.CancelPromotion(utils.Str2Int64(promotion.VendorPromotionID), ""); err == nil {
_, err = dao.UpdateEntityLogically(db, promotion, map[string]interface{}{
"Status": model.PromotionStatusCanceled,
}, ctx.GetUserName(), nil)
}
return err
}
func excelStr2Time(timeStr string) (tm time.Time, err error) {
return time.ParseInLocation("2006年1月2日15点4分5秒", timeStr, time.Local)
}
func getPromotionHander(promotionType int) JdPromotionHandler {
var promotionHandler JdPromotionHandler
if promotionType == PromotionTypeDirectDown {
promotionHandler = &JdDirectDownHandler{}
} else if promotionType == PromotionTypeLimitedTime {
promotionHandler = &JdLimitedTimeHandler{}
} else {
// panic(fmt.Sprintf("unknown promotion type:%d", promotionType))
return nil
}
if !globals.EnableStoreWrite {
promotionHandler = &JdNullHandler{}
}
return promotionHandler
}