374 lines
12 KiB
Go
374 lines
12 KiB
Go
package promotion
|
||
|
||
import (
|
||
"encoding/gob"
|
||
"errors"
|
||
"fmt"
|
||
"mime/multipart"
|
||
"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/excel"
|
||
"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 (
|
||
PromotionStatusCreated = 1
|
||
PromotionStatusEnded = 2
|
||
)
|
||
|
||
const (
|
||
colSkuIDIndex = 0
|
||
colSkuPriceIndex = 3
|
||
colNameIndex = 5
|
||
colStoreIDIndex = 6
|
||
colBeginAtIndex = 9
|
||
colEndAtIndex = 10
|
||
)
|
||
|
||
const (
|
||
DefaultLimitSkuCount = 100
|
||
)
|
||
|
||
type SkuPrice struct {
|
||
SkuID int `json:"skuID"`
|
||
PriceType int `json:"priceType"`
|
||
Price int `json:"price"` // 分,这个不是单价
|
||
LimitSkuCount int `json:"limitSkuCount"`
|
||
}
|
||
|
||
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)"`
|
||
}
|
||
|
||
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)
|
||
}
|
||
|
||
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)
|
||
}
|
||
|
||
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)
|
||
}
|
||
|
||
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 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,
|
||
VendorID: model.VendorIDJD,
|
||
Type: params.Type,
|
||
Status: PromotionStatusCreated,
|
||
SyncStatus: model.SyncFlagNewMask,
|
||
BeginAt: params.BeginAt,
|
||
EndAt: params.EndAt,
|
||
}
|
||
dao.WrapAddIDCULDEntity(promotion, userName)
|
||
if promotion.Params, err = jxutils.SerializeData(params); err != nil {
|
||
return "", err
|
||
}
|
||
dao.Begin(db)
|
||
defer func() {
|
||
dao.Rollback(db)
|
||
}()
|
||
|
||
if err = dao.CreateEntity(db, promotion); err != nil {
|
||
return "", err
|
||
}
|
||
|
||
var promotionHandler JdPromotionHandler
|
||
if params.Type == PromotionTypeDirectDown {
|
||
promotionHandler = &JdDirectDownHandler{}
|
||
} else if params.Type == PromotionTypeLimitedTime {
|
||
promotionHandler = &JdLimitedTimeHandler{}
|
||
} else {
|
||
panic(fmt.Sprintf("unknown promotion type:%d", params.Type))
|
||
}
|
||
if !globals.EnableStoreWrite {
|
||
promotionHandler = &JdNullHandler{}
|
||
}
|
||
|
||
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()
|
||
if _, err = task1.GetResult(0); err != nil {
|
||
return "", err
|
||
}
|
||
} else if step == 1 {
|
||
if err = promotionHandler.CreatePromotionRules(infoId, "", 1, 1, 1, 1); err != nil {
|
||
return "", err
|
||
}
|
||
} else if step == 2 {
|
||
task2 := tasksch.NewParallelTask("CreateJdPromotion CreatePromotionSku", tasksch.NewParallelConfig().SetBatchSize(jdapi.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()
|
||
if _, err = task2.GetResult(0); err != nil {
|
||
return "", err
|
||
}
|
||
} else if step == 3 {
|
||
err = promotionHandler.ConfirmPromotion(infoId, "")
|
||
}
|
||
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 excelStr2Time(timeStr string) (tm time.Time, err error) {
|
||
return time.ParseInLocation("2006年1月2日15点4分5秒", timeStr, time.Local)
|
||
}
|