- auto create jd promotion on jd new promotion event

This commit is contained in:
gazebo
2018-11-08 16:08:35 +08:00
parent bd6cf367a0
commit 69aad912cd
5 changed files with 227 additions and 40 deletions

View File

@@ -53,6 +53,18 @@ const (
userName = "jdpromotion"
)
const (
keyPromotionSource = "promotionSource"
keyPromotionStatus = "promotionStatus"
keyLimitDevice = "limitDevice"
keyLimitPin = "limitPin"
keyLimitCount = "limitCount"
keyLimitDaily = "limitDaily"
)
const (
PromotionSourceOpenPlatform = "开放平台"
)
type SkuPrice struct {
SkuID int `json:"skuID"`
PriceType int `json:"priceType"`
@@ -86,7 +98,8 @@ type PromotionParams struct {
type tStoreSkuBindExt struct {
model.StoreSkuBind
JdID int64 `orm:"column(jd_id)"`
JdSkuID int64 `orm:"column(jd_id)"`
VendorStoreID string `orm:"column(vendor_store_id)"`
}
type tPromotionInfo struct {
@@ -101,6 +114,19 @@ var (
ErrEmptySkus = errors.New("空sku或指定的SKU没有被门店认领请检查")
)
var (
jd2jxPromotionStatusMap = map[int]int{
jdapi.PromotionStateNotConfirm: model.PromotionStatusRemoteCreated,
jdapi.PromotionStateConfirmed: model.PromotionStatusRemoteCreated,
jdapi.PromotionStateCanceled: model.PromotionStatusCanceled,
jdapi.PromotionStateEnded: model.PromotionStatusEnded,
}
)
var (
ErrLimitDeviceIsInvalid = errors.New("必须指定一个limitCount当limitPin或limitDevice不都为0时")
)
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)
@@ -198,17 +224,49 @@ func Init() {
scheduleRoutine()
}
func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueWhenError bool, vendorPromotionID string, params *PromotionParams, userName string) (hint string, err error) {
func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueWhenError bool, vendorPromotionID string, params *PromotionParams, mapData map[string]interface{}) (hint string, err error) {
if vendorPromotionID != "" && len(vendorPromotionID) != len("14863853") {
return "", fmt.Errorf("%s看起来不像是一个有效的京东活动ID请仔细检查一下", vendorPromotionID)
}
if len(params.SkuPrices) == 0 {
return "", ErrEmptySkus
}
limitDaily := 1
if limitDaily2, ok := mapData[keyLimitDaily]; ok {
limitDaily = jxutils.Int2OneZero(limitDaily2.(int))
}
limitPin := 1
if limitPin2, ok := mapData[keyLimitPin]; ok {
limitPin = jxutils.Int2OneZero(limitPin2.(int))
}
limitDevice := 1
if limitDevice2, ok := mapData[keyLimitDevice]; ok {
limitDevice = jxutils.Int2OneZero(limitDevice2.(int))
}
limitCount := 1
if limitCount2, ok := mapData[keyLimitCount]; ok {
limitCount = limitCount2.(int)
}
if (limitDevice == 1 || limitPin == 1) && limitCount == 0 {
return "", ErrLimitDeviceIsInvalid
}
userName := ctx.GetUserName()
db := dao.GetDB()
modifyPricesList := make(map[int][]*jdapi.SkuPriceInfo)
promotionPrices := make([]map[string]interface{}, len(params.StoreIDs)*len(params.SkuPrices))
var jxStoreIDs []int
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,
Source: PromotionSourceOpenPlatform,
}
if vendorPromotionID == "" {
skuIDs := make([]int, len(params.SkuPrices))
@@ -217,25 +275,24 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueW
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 = ?"
sql := `
SELECT t1.*, t2.jd_id, t3.vendor_store_id
FROM store_sku_bind t1
JOIN sku t2 ON t1.sku_id = t2.id
JOIN store_map t3 ON t1.store_id = t3.store_id AND t3.vendor_id = ? AND t3.deleted_at = ?
WHERE t1.deleted_at = ?
`
sqlParam := []interface{}{
model.VendorIDJD,
utils.DefaultTimeValue,
utils.DefaultTimeValue,
skuIDs,
}
if isIDJd {
sql += " AND t2.jd_id IN (" + dao.GenQuestionMarks(len(skuIDs)) + ") AND t3.vendor_store_id = ?"
} else {
sql += " AND t1.sku_id IN (" + dao.GenQuestionMarks(len(skuIDs)) + ") AND t1.store_id = ?"
}
sqlParam = append(sqlParam, utils.DefaultTimeValue, skuIDs)
errMsg := ""
index := 0
@@ -250,7 +307,7 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueW
}
mapSkuID := int64(skuBind.SkuID)
if isIDJd {
mapSkuID = skuBind.JdID
mapSkuID = skuBind.JdSkuID
}
promotionSkuPrice := skuPriceMap[mapSkuID]
if promotionSkuPrice.PriceType == PriceTypePercentage {
@@ -272,8 +329,10 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueW
}
}
promotionPrices[index] = map[string]interface{}{
jdapi.KeyOutStationNo: utils.Int2Str(skuBind.StoreID),
jdapi.KeyOutSkuId: utils.Int2Str(skuBind.SkuID),
jdapi.KeyStationNo: utils.Str2Int64(skuBind.VendorStoreID),
jdapi.KeySkuId: skuBind.JdSkuID,
// jdapi.KeyOutStationNo: utils.Int2Str(skuBind.StoreID),
// jdapi.KeyOutSkuId: utils.Int2Str(skuBind.SkuID),
jdapi.KeyPromotionPrice: promotionSkuPrice.Price,
jdapi.KeyLimitSkuCount: promotionSkuPrice.LimitSkuCount,
}
@@ -288,22 +347,17 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueW
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 != "" {
} else {
promotion.VendorPromotionID = vendorPromotionID
promotion.CreateType = model.PromotionCreateTypeByVendor
if status, ok := mapData[keyPromotionStatus]; ok {
promotion.Status = status.(int)
}
if source, ok := mapData[keyPromotionSource]; ok {
promotion.Source = source.(string)
}
}
dao.WrapAddIDCULDEntity(promotion, userName)
// if promotion.Params, err = jxutils.SerializeData(params); err != nil {
// return "", err
@@ -379,7 +433,7 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueW
task.AddChild(task1).Run()
_, err = task1.GetResult(0)
} else if step == 1 {
err = promotionHandler.CreatePromotionRules(infoId, "", 1, 1, 1, 1)
err = promotionHandler.CreatePromotionRules(infoId, "", limitDevice, limitPin, limitCount, limitDaily)
} 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))
@@ -873,9 +927,93 @@ func OnStoreStockMsg(msg *jdapi.CallbackStoreStockMsg) (retVal *jdapi.CallbackRe
}
func OnNewPromotionMsg(msg *jdapi.CallbackOrderMsg) (retVal *jdapi.CallbackResponse) {
result, err := api.JdAPI.QueryPromotionInfo(utils.Str2Int64(msg.BillID))
return createLocalPromotionFromRemote(utils.Str2Int64(msg.BillID))
}
func createLocalPromotionFromRemote(promotionInfoId int64) (retVal *jdapi.CallbackResponse) {
result, err := api.JdAPI.QueryPromotionInfo(promotionInfoId)
if err == nil {
globals.SugarLogger.Debug(utils.Format4Output(result, false))
db := dao.GetDB()
promotion := &model.Promotion{
VendorPromotionID: utils.Int64ToStr(promotionInfoId),
}
if err = dao.GetEntity(db, promotion, "VendorPromotionID"); dao.IsNoRowsError(err) {
storeIDMap := make(map[int64]int)
skuIDMap := make(map[int64]int)
skuMap := make(map[int64]*jdapi.PromotionSkuResult)
// 注意这样处理可能是有问题我们假定的是门店信息与SKU信息的叉乘
for _, v := range result.SkuResultList {
storeIDMap[v.StationNo] = 1
skuIDMap[v.SkuId] = 1
skuMap[v.SkuId] = v
}
jdStoreIDs := make([]string, len(storeIDMap))
index := 0
for k := range storeIDMap {
jdStoreIDs[index] = utils.Int64ToStr(k)
index++
}
jdSkuIDs := jxutils.Int64Map2List(skuIDMap)
var skuList []*model.Sku
var storeMapList []*model.StoreMap
if err = dao.GetRows(db, &storeMapList, `
SELECT *
FROM store_map
WHERE vendor_id = ? AND deleted_at = ? AND vendor_store_id IN (`+
dao.GenQuestionMarks(len(jdStoreIDs))+")",
model.VendorIDJD, utils.DefaultTimeValue, jdStoreIDs); err != nil {
return jdapi.Err2CallbackResponse(err, "")
}
if err = dao.GetRows(db, &skuList, `
SELECT *
FROM sku
WHERE jd_id IN (`+
dao.GenQuestionMarks(len(jdSkuIDs))+")",
jdSkuIDs); err != nil {
return jdapi.Err2CallbackResponse(err, "")
}
jxStoreIDs := make([]int, len(storeMapList))
for k, v := range storeMapList {
jxStoreIDs[k] = v.StoreID
}
priceList := make([]*SkuPrice, len(skuList))
var skuResult *jdapi.PromotionSkuResult
for k, v := range skuList {
skuResult = skuMap[v.JdID]
priceList[k] = &SkuPrice{
SkuID: v.ID,
PriceType: PriceTypePrice,
Price: skuResult.PromotionPrice,
LimitSkuCount: 0,
IsLock: 0,
}
}
// globals.SugarLogger.Debugf("jxStoreIDs:%s", utils.Format4Output(jxStoreIDs, false))
// globals.SugarLogger.Debugf("priceList:%s", utils.Format4Output(priceList, false))
promotionParams := &PromotionParams{
Name: result.Source + "-" + utils.Int64ToStr(result.PromotionInfoId),
Advertising: "",
Type: result.PromotionType,
BeginAt: result.BeginTime,
EndAt: result.EndTime,
StoreIDs: jxStoreIDs,
SkuPrices: priceList,
}
mapData := map[string]interface{}{
keyPromotionStatus: jd2jxPromotionStatusMap[result.PromotionState],
keyPromotionSource: result.Source,
}
if skuResult != nil {
mapData[keyLimitDaily] = skuResult.LimitDaily
mapData[keyLimitDevice] = skuResult.LimitDevice
mapData[keyLimitPin] = skuResult.LimitPin
}
_, err = CreateJdPromotion(jxcontext.AdminCtx, false, true, false, utils.Int64ToStr(promotionInfoId), promotionParams, mapData)
if dao.IsDuplicateError(err) || err == ErrLimitDeviceIsInvalid {
err = nil
}
}
}
return jdapi.Err2CallbackResponse(err, "")
}

View File

@@ -0,0 +1,23 @@
package promotion
import (
"testing"
"git.rosy.net.cn/jx-callback/globals"
"git.rosy.net.cn/jx-callback/globals/api"
"git.rosy.net.cn/jx-callback/globals/beegodb"
"github.com/astaxie/beego"
)
func init() {
beego.InitBeegoBeforeTest("/Users/xujianhua/go/src/git.rosy.net.cn/jx-callback/conf/app.conf")
beego.BConfig.RunMode = "alpha" // InitBeegoBeforeTest会将runmode设置为test
globals.Init()
beegodb.Init()
api.Init()
}
func TestCreateLocalPromotionFromRemote(t *testing.T) {
t.Log(createLocalPromotionFromRemote(14510904))
}

View File

@@ -115,6 +115,16 @@ func IntMap2List(intMap map[int]int) []int {
return retVal
}
func Int64Map2List(int64Map map[int64]int) []int64 {
retVal := make([]int64, len(int64Map))
index := 0
for k := range int64Map {
retVal[index] = k
index++
}
return retVal
}
// 计算SKU价格unitPrice为一斤的单价specQuality为质量单位为克
func CaculateSkuPrice(unitPrice int, specQuality float32, specUnit string, skuNameUnit string) int {
if skuNameUnit != "份" {
@@ -170,3 +180,10 @@ func FormalizePageSize(pageSize int) int {
func FormalizeName(name string) string {
return utils.TrimBlankChar(strings.Replace(strings.Replace(name, "\t", "", -1), "\"", "", -1))
}
func Int2OneZero(value int) int {
if value != 0 {
return 1
}
return 0
}

View File

@@ -29,6 +29,11 @@ type Promotion struct {
Advertising string `orm:"size(255)" json:"advertising"`
Type int `json:"type"`
Status int `json:"status"`
LimitDevice int8 `json:"limitDevice"`
LimitPint int8 `json:"limitPint"`
LimitDaily int8 `json:"limitDaily"`
LimitCount int `json:"limitCount"`
Source string `orm:"size(255)" json:"source"`
CreateType int8 `json:"createType"`
VendorPromotionID string `orm:"size(64);column(vendor_promotion_id);index" json:"vendorPromotionID"`
BeginAt time.Time `orm:"type(datetime);index" json:"beginAt"`

View File

@@ -24,6 +24,10 @@ type PromotionController struct {
// @Param type formData int true "活动类型3直降4限时抢购"
// @Param storeIDs formData string true "json数据storeID列表[1,2,3]"
// @Param skuPrices formData string true "json数据价格信息列表"
// @Param limitDaily formData int false "是否按日0-不限1-限购 (限时抢需填)"
// @Param limitDevice formData int false "是否设备限购0-不限1-限购"
// @Param limitPin formData int false "是否账号限购0-不限1-限购"
// @Param limitCount formData int false "限购件数 0-不限如账号限购、设备限购有一个为1则限购件数必须大于0的整数"
// @Param isAsync formData bool false "是否异步,缺省否(暂时只支持同步)"
// @Param isContinueWhenError formData bool false "单个同步失败是否继续缺省false"
// @Param advertising formData string false "广告语"
@@ -53,7 +57,7 @@ func (c *PromotionController) CreatePromotion() {
}
if err = utils.UnmarshalUseNumber([]byte(params.StoreIDs), &promotionParams.StoreIDs); err == nil {
if err = utils.UnmarshalUseNumber([]byte(params.SkuPrices), &promotionParams.SkuPrices); err == nil {
retVal, err = promotion.CreateJdPromotion(params.Ctx, false, params.IsAsync, params.IsContinueWhenError, params.VendorPromotionID, promotionParams, params.Ctx.GetUserName())
retVal, err = promotion.CreateJdPromotion(params.Ctx, false, params.IsAsync, params.IsContinueWhenError, params.VendorPromotionID, promotionParams, params.MapData)
}
}
return retVal, "", err