diff --git a/business/jxstore/promotion/jd_promotion.go b/business/jxstore/promotion/jd_promotion.go index 04abef783..84656216d 100644 --- a/business/jxstore/promotion/jd_promotion.go +++ b/business/jxstore/promotion/jd_promotion.go @@ -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, "") } diff --git a/business/jxstore/promotion/jd_promotion_test.go b/business/jxstore/promotion/jd_promotion_test.go new file mode 100644 index 000000000..ce42e7a5f --- /dev/null +++ b/business/jxstore/promotion/jd_promotion_test.go @@ -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)) +} diff --git a/business/jxutils/jxutils_cms.go b/business/jxutils/jxutils_cms.go index 028239321..2f037e977 100644 --- a/business/jxutils/jxutils_cms.go +++ b/business/jxutils/jxutils_cms.go @@ -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 +} diff --git a/business/model/promotion.go b/business/model/promotion.go index 5ff9109f8..4b70e1d32 100644 --- a/business/model/promotion.go +++ b/business/model/promotion.go @@ -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"` diff --git a/controllers/promotion.go b/controllers/promotion.go index 7beee27e6..afee0891c 100644 --- a/controllers/promotion.go +++ b/controllers/promotion.go @@ -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