Files
jx-callback/business/jxstore/promotion/jd_promotion.go

885 lines
30 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/storeskulock"
"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 = 79
)
const (
colSkuIDIndex = 0
colSkuPriceIndex = 3
colNameIndex = 5
colStoreIDIndex = 6
colBeginAtIndex = 9
colEndAtIndex = 10
)
const (
DefaultLimitSkuCount = 100
MaxPromotionSkuCount = jdapi.MaxPromotionSkuCount
)
const (
defSearchDays = 7
stockRefreshGap = 5 * time.Minute
userName = "jdpromotion"
)
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 tPromotionItemInfo struct {
SkuID int `orm:"column(sku_id)"`
IsLock int8 // 是否锁定门店商品信息
EndAt time.Time
JdStoreID string `orm:"column(vendor_store_id)"`
JdSkuID int64 `orm:"column(jd_id)"`
}
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 scheduleDailyRoutine() {
executeTime := utils.GetCurDate().Add(24*time.Hour + 5*time.Minute) // 凌晨00:05执行
duration := executeTime.Sub(time.Now())
if duration > 1*time.Hour {
UpdateJdPromotionStatus()
}
// globals.SugarLogger.Debug(duration)
time.AfterFunc(duration, func() {
UpdateJdPromotionStatus()
scheduleDailyRoutine()
})
}
func scheduleRoutine() {
RefreshJdLockStoreSku()
RefreshJdStoreSkuStock()
time.AfterFunc(stockRefreshGap, func() {
scheduleRoutine()
})
}
func Init() {
scheduleDailyRoutine()
scheduleRoutine()
}
func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueWhenError bool, vendorPromotionID string, params *PromotionParams, userName string) (hint string, err error) {
if len(params.SkuPrices) == 0 {
return "", ErrEmptySkus
}
db := dao.GetDB()
modifyPricesList := make(map[int][]*jdapi.SkuPriceInfo)
promotionPrices := make([]map[string]interface{}, len(params.StoreIDs)*len(params.SkuPrices))
var jxStoreIDs []int
if vendorPromotionID == "" {
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 := ""
index := 0
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 + 5,
})
}
}
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,
CreateType: model.PromotionCreateTypeByJX,
}
if vendorPromotionID != "" {
promotion.VendorPromotionID = vendorPromotionID
promotion.CreateType = model.PromotionCreateTypeByVendor
}
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,
IsLock: skuPrice.IsLock,
}
dao.WrapAddIDCULDEntity(promotionSku, ctx.GetUserName())
if err = dao.CreateEntity(db, promotionSku); err != nil {
return "", err
}
}
if vendorPromotionID == "" {
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, params2 ...interface{}) (result interface{}, err error) {
if step == 0 {
task1 := tasksch.NewParallelTask("CreateJdPromotion update sku price", nil, userName, func(t *tasksch.ParallelTask, batchItemList []interface{}, params2 ...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 globals.EnableStoreWrite {
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).SetIsContinueWhenError(isContinueWhenError), userName, func(task *tasksch.ParallelTask, batchItemList []interface{}, params2 ...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)
if isContinueWhenError && err != nil { // todo isContinueWhenError为true时强制忽略此步的错误
err = nil
}
} else if step == 3 {
err = promotionHandler.ConfirmPromotion(infoId, "")
if err == nil {
promotion.Status = model.PromotionStatusRemoteCreated
} else {
promotion.Status = model.PromotionStatusRemoteFailed
}
db := dao.GetDB()
if _, err = dao.UpdateEntityLogically(db, promotion, map[string]interface{}{
"Status": promotion.Status,
}, ctx.GetUserName(), nil); err == nil {
RefreshJdPromotionLockStatus(promotion.ID)
}
}
return nil, err
}, 4)
ctx.SetTaskOrAddChild(rootTask, nil)
tasksch.ManageTask(rootTask).Run()
if !isAsync {
_, err = rootTask.GetResult(0)
}
hint = rootTask.ID
} else {
dao.Commit(db)
}
return hint, 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"].(string))
}
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) {
globals.SugarLogger.Debug("CancelJdPromotion promotionID:%d", promotionID)
db := dao.GetDB()
promotion := &model.Promotion{}
promotion.ID = promotionID
if err = dao.GetEntity(db, promotion); err != nil {
return err
}
if promotion.Status != model.PromotionStatusRemoteCreated {
return errors.New("当前状态不能进行取消操作")
}
promotionHandler := getPromotionHander(promotion.Type)
if promotionHandler == nil {
return errors.New("非法的活动类型")
}
if err = promotionHandler.CancelPromotion(utils.Str2Int64(promotion.VendorPromotionID), ""); err == nil {
if _, err = dao.UpdateEntityLogically(db, promotion, map[string]interface{}{
"Status": model.PromotionStatusCanceled,
}, ctx.GetUserName(), nil); err == nil {
RefreshJdPromotionLockStatus(promotionID)
}
}
return err
}
// 每一段时间运行一次
func RefreshJdLockStoreSku() (err error) {
globals.SugarLogger.Debug("RefreshJdLockStoreSku")
sql := `
SELECT t22.vendor_store_id, t3.sku_id, t32.jd_id, MAX(t3.is_lock) is_lock, MAX(t1.end_at) end_at
FROM promotion t1
JOIN promotion_store t2 ON t1.id = t2.promotion_id
JOIN store_map t22 ON t2.store_id = t22.store_id AND t22.vendor_id = ? AND t22.deleted_at = ?
JOIN promotion_sku t3 ON t1.id = t3.promotion_id
JOIN sku t32 ON t3.sku_id = t32.id
WHERE t1.deleted_at = ? AND t1.vendor_id = ? AND t1.status = ? AND (t1.begin_at <= ? AND t1.end_at >= ?)
GROUP BY 1,2,3
`
nowDate := utils.GetCurDate()
sqlParams := []interface{}{
model.VendorIDJD,
utils.DefaultTimeValue,
utils.DefaultTimeValue,
model.VendorIDJD,
model.PromotionStatusRemoteCreated,
nowDate,
nowDate,
}
var promotionItemList []*tPromotionItemInfo
db := dao.GetDB()
if err = dao.GetRows(db, &promotionItemList, sql, sqlParams...); err != nil {
if !dao.IsNoRowsError(err) {
globals.SugarLogger.Warnf("RefreshJdLockStoreSku GetRows failed with error:%v", err)
}
return err
}
storeskulock.ClearJdStoreSkuLock()
return RefreshJdPromotionItemListLockStatus(promotionItemList)
}
func RefreshJdStoreSkuStock() (err error) {
globals.SugarLogger.Debug("RefreshJdStoreSkuStock")
sql := `
SELECT t22.vendor_store_id, t3.sku_id, t32.jd_id, MAX(t3.is_lock) is_lock, MAX(t1.end_at) end_at
FROM promotion t1
JOIN promotion_store t2 ON t1.id = t2.promotion_id
JOIN store_map t22 ON t2.store_id = t22.store_id AND t22.vendor_id = ? AND t22.deleted_at = ?
JOIN promotion_sku t3 ON t1.id = t3.promotion_id AND t3.is_lock = 1
JOIN sku t32 ON t3.sku_id = t32.id
JOIN store_sku_bind t4 ON t2.store_id = t4.store_id AND t3.sku_id = t4.sku_id AND t4.deleted_at = ?
WHERE t1.deleted_at = ? AND t1.vendor_id = ? AND t1.status = ? AND (t1.begin_at <= ? AND t1.end_at >= ?)
GROUP BY 1,2,3
ORDER BY 1,2,3
`
nowDate := utils.GetCurDate()
sqlParams := []interface{}{
model.VendorIDJD,
utils.DefaultTimeValue,
utils.DefaultTimeValue,
utils.DefaultTimeValue,
model.VendorIDJD,
model.PromotionStatusRemoteCreated,
nowDate,
nowDate,
}
var promotionItemList []*tPromotionItemInfo
db := dao.GetDB()
if err = dao.GetRows(db, &promotionItemList, sql, sqlParams...); err != nil {
if !dao.IsNoRowsError(err) {
globals.SugarLogger.Warnf("RefreshJdStoreSkuStock GetRows failed with error:%v", err)
}
return err
}
if len(promotionItemList) > 0 {
task := tasksch.NewParallelTask("RefreshJdStoreSkuStock", tasksch.NewParallelConfig().SetBatchSize(jdapi.MaxStoreSkuBatchSize).SetIsContinueWhenError(true), "schedule", func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) {
stockList := make([]*jdapi.SkuStock, 0)
stationNo := batchItemList[0].(*tPromotionItemInfo).JdStoreID
for _, v := range batchItemList {
promotionItem := v.(*tPromotionItemInfo)
if promotionItem.JdStoreID != stationNo {
globals.SugarLogger.Debugf("RefreshJdStoreSkuStock BatchUpdateCurrentQtys stationNo:%s, stockList:%s", stationNo, utils.Format4Output(stockList, false))
_, err = api.JdAPI.BatchUpdateCurrentQtys("", stationNo, stockList, userName)
if err != nil {
globals.SugarLogger.Warnf("RefreshJdStoreSkuStock BatchUpdateCurrentQtys failed with error:%v", err)
}
stockList = make([]*jdapi.SkuStock, 0)
stationNo = promotionItem.JdStoreID
}
stockList = append(stockList, &jdapi.SkuStock{
OutSkuId: utils.Int2Str(promotionItem.SkuID),
StockQty: model.MaxStoreSkuStockQty,
})
}
globals.SugarLogger.Debugf("RefreshJdStoreSkuStock BatchUpdateCurrentQtys stationNo:%s, stockList:%s", stationNo, utils.Format4Output(stockList, false))
_, err = api.JdAPI.BatchUpdateCurrentQtys("", stationNo, stockList, userName)
if err != nil {
globals.SugarLogger.Warnf("RefreshJdStoreSkuStock BatchUpdateCurrentQtys failed with error:%v", err)
}
return nil, err
}, promotionItemList)
task.Run()
_, err = task.GetResult(0)
}
return err
}
// 每晚凌晨运行一次
func UpdateJdPromotionStatus() (num int64, err error) {
sql := `
UPDATE promotion t1
SET t1.status = ?
WHERE t1.deleted_at = ? AND t1.vendor_id = ? AND t1.status = ? AND t1.end_at < ?
`
nowDate := utils.GetCurDate()
sqlParams := []interface{}{
model.PromotionStatusEnded,
utils.DefaultTimeValue,
model.VendorIDJD,
model.PromotionStatusRemoteCreated,
nowDate,
}
db := dao.GetDB()
return dao.ExecuteSQL(db, sql, sqlParams...)
}
func RefreshJdPromotionItemListLockStatus(promotionItemList []*tPromotionItemInfo) (err error) {
globals.SugarLogger.Debugf("RefreshJdPromotionItemListLockStatus promotionItemList:%s", utils.Format4Output(promotionItemList, false))
if len(promotionItemList) > 0 {
expire := promotionItemList[0].EndAt.Add(24 * time.Hour)
for _, item := range promotionItemList {
if item.IsLock != 0 {
storeskulock.LockJdStoreSku(item.JdStoreID, item.JdSkuID, expire)
} else {
storeskulock.UnlockJdStoreSku(item.JdStoreID, item.JdSkuID)
}
}
}
return err
}
func RefreshJdPromotionLockStatus(promotionID int) (err error) {
globals.SugarLogger.Debugf("RefreshJdPromotionLockStatus promotionID:%d", promotionID)
sql := `
SELECT t22.vendor_store_id, t3.sku_id, t32.jd_id, IF(t1.begin_at <= ? AND t1.end_at >= ? AND t1.status = ?, t3.is_lock, 0) is_lock, t1.end_at
FROM promotion t1
JOIN promotion_store t2 ON t1.id = t2.promotion_id
JOIN store_map t22 ON t2.store_id = t22.store_id AND t22.vendor_id = ? AND t22.deleted_at = ?
JOIN promotion_sku t3 ON t1.id = t3.promotion_id
JOIN sku t32 ON t3.sku_id = t32.id
WHERE t1.id = ?
`
nowDate := utils.GetCurDate()
sqlParams := []interface{}{
nowDate,
nowDate,
model.PromotionStatusRemoteCreated,
model.VendorIDJD,
utils.DefaultTimeValue,
promotionID,
}
var promotionItemList []*tPromotionItemInfo
db := dao.GetDB()
// globals.SugarLogger.Debug(sql)
// globals.SugarLogger.Debug(utils.Format4Output(sqlParams, false))
if err = dao.GetRows(db, &promotionItemList, sql, sqlParams...); err != nil {
return err
}
return RefreshJdPromotionItemListLockStatus(promotionItemList)
}
func LockPromotionSkus(ctx *jxcontext.Context, promotionID int, isLock int, skuIDs []int) (num int64, err error) {
globals.SugarLogger.Debugf("LockPromotionSkus promotionID:%d, isLock:%t, skuIDs:%v", promotionID, isLock, skuIDs)
if isLock != 0 {
isLock = 1
}
sql := `
UPDATE promotion_sku t1
SET t1.is_lock = ?
WHERE t1.promotion_id = ?
`
sqlParams := []interface{}{
isLock,
promotionID,
}
if len(skuIDs) > 0 {
sql += " AND t1.sku_id IN (" + dao.GenQuestionMarks(len(skuIDs)) + ")"
sqlParams = append(sqlParams, skuIDs)
}
db := dao.GetDB()
num, err = dao.ExecuteSQL(db, sql, sqlParams...)
if err == nil {
RefreshJdPromotionLockStatus(promotionID)
if isLock != 0 {
RefreshJdStoreSkuStock()
}
}
return num, err
}
func OnStoreStockMsg(msg *jdapi.CallbackStoreStockMsg) (retVal *jdapi.CallbackResponse) {
var err error
globals.SugarLogger.Debugf("OnStoreStockMsg msg:%s", utils.Format4Output(msg, false))
// globals.SugarLogger.Debugf("OnStoreStockMsg IsJdStoreSkuLocked:%t", storeskulock.IsJdStoreSkuLocked(msg.StationNo, msg.SkuId))
if (msg.Vendibility == 1 || !msg.Have) && storeskulock.IsJdStoreSkuLocked(msg.StationNo, msg.SkuId) {
db := dao.GetDB()
sku := &model.Sku{}
sku.JdID = msg.SkuId
if err = dao.GetEntity(db, sku, model.FieldJdID); err == nil {
if msg.Vendibility == 1 {
vendibility := &jdapi.StockVendibility{
OutSkuId: utils.Int2Str(sku.ID),
DoSale: true,
}
_, err = api.JdAPI.BatchUpdateVendibility("", msg.StationNo, []*jdapi.StockVendibility{
vendibility,
}, userName)
}
if !msg.Have {
stock := &jdapi.SkuStock{
OutSkuId: utils.Int2Str(sku.ID),
StockQty: model.MaxStoreSkuStockQty,
}
_, err = api.JdAPI.BatchUpdateCurrentQtys("", msg.StationNo, []*jdapi.SkuStock{
stock,
}, userName)
}
}
}
return jdapi.Err2CallbackResponse(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
}