From b158fd09ea6f27dfb2dcb4acb0066bef92968137 Mon Sep 17 00:00:00 2001 From: gazebo Date: Sat, 13 Oct 2018 20:28:20 +0800 Subject: [PATCH] - CreatePromotion --- business/jxstore/promotion/jd_promotion.go | 236 +++++++++++++++++++++ business/jxutils/dtask/dtask.go | 26 +-- business/jxutils/jxutils_reflect.go | 26 ++- business/model/promotion.go | 25 +++ controllers/promotion.go | 51 +++++ globals/beegodb/beegodb.go | 2 + routers/commentsRouter_controllers.go | 8 + routers/router.go | 5 + 8 files changed, 355 insertions(+), 24 deletions(-) create mode 100644 business/jxstore/promotion/jd_promotion.go create mode 100644 business/model/promotion.go create mode 100644 controllers/promotion.go diff --git a/business/jxstore/promotion/jd_promotion.go b/business/jxstore/promotion/jd_promotion.go new file mode 100644 index 000000000..1986344a0 --- /dev/null +++ b/business/jxstore/promotion/jd_promotion.go @@ -0,0 +1,236 @@ +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/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/api" +) + +const ( + PromotionTypeNormal = 1 + PromotionTypeDirectDown = 3 + PromotionTypeLimitedTime = 4 +) + +const ( + PriceTypePrice = 1 // 绝对价格 + PriceTypePercentage = 2 // 百分比 +) + +const ( + PromotionLimitedTimeMinPercentage = 80 +) + +const ( + PromotionStatusCreated = 1 + PromotionStatusEnded = 2 +) + +type SkuPrice struct { + SkuID int `json:"skuID"` + PriceType int `json:"priceType"` + Price int `json:"price"` // 分,这个不是单价 +} + +type PromotionParams struct { + Name string + Advertising string + Type int + BeginAt time.Time + EndAt time.Time + StoreIDs []int + SkuPrices []*SkuPrice +} + +var ( + ErrEmptySkus = errors.New("空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) +} + +func init() { + gob.Register(&PromotionParams{}) + gob.Register([]*SkuPrice{}) +} + +func CreateJdPromotion(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[int]*SkuPrice) + for k, v := range params.SkuPrices { + skuIDs[k] = v.SkuID + skuPriceMap[v.SkuID] = v + } + sql := ` + SELECT t1.* + FROM store_sku_bind t1 + WHERE t1.jd_sync_status = 0 AND t1.deleted_at = ? AND t1.store_id = ? AND t1.sku_id IN ( + ` + dao.GenQuestionMarks(len(skuIDs)) + ")" + + errMsg := "" + modifyPricesList := make(map[int][]*jdapi.SkuPriceInfo) + promotionPrices := make([]map[string]interface{}, len(params.StoreIDs)*len(params.SkuPrices)) + index := 0 + for _, storeID := range params.StoreIDs { + var skuBinds []*model.StoreSkuBind + if err = dao.GetRows(db, &skuBinds, sql, utils.DefaultTimeValue, storeID, skuIDs); err != nil { + return "", err + } + for _, skuBind := range skuBinds { + promotionSkuPrice := skuPriceMap[skuBind.SkuID] + if promotionSkuPrice.PriceType == PriceTypePercentage { + promotionSkuPrice.Price = skuBind.Price * promotionSkuPrice.Price / 100 + } + if promotionSkuPrice.Price >= skuBind.Price { + errMsg += fmt.Sprintf("促销价大于等于原价,storeID:%d, skuID:%d\n", storeID, skuBind.SkuID) + } + if errMsg == "" { + if params.Type == PromotionTypeLimitedTime { + if skuBind.Price*PromotionLimitedTimeMinPercentage/100 < promotionSkuPrice.Price { + modifyPricesList[storeID] = append(modifyPricesList[storeID], &jdapi.SkuPriceInfo{ + OutSkuId: utils.Int2Str(skuBind.SkuID), + Price: promotionSkuPrice.Price*100/PromotionLimitedTimeMinPercentage + 1, + }) + } + } + promotionPrices[index] = map[string]interface{}{ + jdapi.KeyOutStationNo: utils.Int2Str(storeID), + jdapi.KeyOutSkuId: utils.Int2Str(skuBind.SkuID), + jdapi.KeyPromotionPrice: promotionSkuPrice.Price, + } + index++ + } + } + } + promotionPrices = promotionPrices[:index] + if len(promotionPrices) == 0 { + return "", ErrEmptySkus + } + if errMsg != "" { + return "", errors.New(errMsg) + } + + 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)) + } + 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) + + task := tasksch.RunTask("CreateJdPromotion update sku price", false, nil, 0, 1, userName, func(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 + }, params.StoreIDs) + if _, err = task.GetResult(0); err != nil { + return "", err + } + if err = promotionHandler.CreatePromotionRules(infoId, "", 1, 1, 1, 1); err != nil { + return "", err + } + task = tasksch.RunManagedTask("CreateJdPromotion CreatePromotionSku", false, nil, 0, jdapi.MaxPromotionSkuCount, userName, func(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) + if _, err = task.GetResult(0); err != nil { + return "", err + } + err = promotionHandler.ConfirmPromotion(infoId, "") + + return "", err +} diff --git a/business/jxutils/dtask/dtask.go b/business/jxutils/dtask/dtask.go index 608a35b69..3b9aa9903 100644 --- a/business/jxutils/dtask/dtask.go +++ b/business/jxutils/dtask/dtask.go @@ -1,8 +1,6 @@ package dtask import ( - "bytes" - "encoding/base64" "encoding/gob" "errors" "reflect" @@ -10,6 +8,7 @@ import ( "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxcallback/scheduler/basesch" + "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model/dao" ) @@ -93,7 +92,7 @@ func (m *DurableTaskMan) AddItem(taskID, objHint string, funcName string, params TaskIndex: d.data.TotalItem, FinishedAt: utils.DefaultTimeValue, } - if item.Params, err = SerializeData(params); err == nil { + if item.Params, err = jxutils.SerializeData(params); err == nil { db := dao.GetDB() if err = dao.CreateEntity(db, item); err == nil { _, err = dao.UpdateEntity(db, d.data, "TotalItem") @@ -116,7 +115,7 @@ func (m *DurableTaskMan) StartTask(taskID string) error { objValue := reflect.ValueOf(obj) func2Call := objValue.MethodByName(taskItem.FuncName) params := []interface{}{} - DeSerializeData(taskItem.Params, ¶ms) + jxutils.DeSerializeData(taskItem.Params, ¶ms) valueParams := make([]reflect.Value, len(params)) for k, v := range params { @@ -147,22 +146,3 @@ func (m *DurableTaskMan) StartTask(taskID string) error { func defObjCreator(objHint string) interface{} { return basesch.FixedBaseScheduler.GetPurchasePlatformFromVendorID(0) } - -func SerializeData(data interface{}) (strValue string, err error) { - var buf bytes.Buffer - enc := gob.NewEncoder(&buf) - if err = enc.Encode(data); err == nil { - strValue = base64.StdEncoding.EncodeToString(buf.Bytes()) - return strValue, nil - } - return "", err -} - -func DeSerializeData(strValue string, dataPtr interface{}) (err error) { - byteData, err := base64.StdEncoding.DecodeString(strValue) - if err == nil { - dec := gob.NewDecoder(bytes.NewReader(byteData)) - return dec.Decode(dataPtr) - } - return err -} diff --git a/business/jxutils/jxutils_reflect.go b/business/jxutils/jxutils_reflect.go index 1f4f610c2..4f5c151cd 100644 --- a/business/jxutils/jxutils_reflect.go +++ b/business/jxutils/jxutils_reflect.go @@ -1,6 +1,11 @@ package jxutils -import "reflect" +import ( + "bytes" + "encoding/base64" + "encoding/gob" + "reflect" +) func CheckAndGetStructValue(item interface{}) *reflect.Value { value := reflect.ValueOf(item) @@ -20,3 +25,22 @@ func SetObjFieldByName(obj interface{}, fieldName string, value interface{}) { refValue := CheckAndGetStructValue(obj) refValue.FieldByName(fieldName).Set(reflect.ValueOf(value)) } + +func SerializeData(data interface{}) (strValue string, err error) { + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + if err = enc.Encode(data); err == nil { + strValue = base64.StdEncoding.EncodeToString(buf.Bytes()) + return strValue, nil + } + return "", err +} + +func DeSerializeData(strValue string, dataPtr interface{}) (err error) { + byteData, err := base64.StdEncoding.DecodeString(strValue) + if err == nil { + dec := gob.NewDecoder(bytes.NewReader(byteData)) + return dec.Decode(dataPtr) + } + return err +} diff --git a/business/model/promotion.go b/business/model/promotion.go new file mode 100644 index 000000000..cd4557300 --- /dev/null +++ b/business/model/promotion.go @@ -0,0 +1,25 @@ +package model + +import ( + "time" +) + +type Promotion struct { + ModelIDCULD + + VendorID int + Name string `orm:"size(64)" json:"name"` + Type int + Status int + SyncStatus int + VendorPromotionID string `orm:"size(64)" json:"vendorPromotionID"` + BeginAt time.Time `orm:"type(datetime);index" json:"beginAt"` + EndAt time.Time `orm:"type(datetime);index" json:"endAt"` + Params string `orm:"type(text)" json:"params"` +} + +func (*Promotion) TableUnique() [][]string { + return [][]string{ + []string{"Name", "VendorID", "Type", "DeletedAt"}, + } +} diff --git a/controllers/promotion.go b/controllers/promotion.go new file mode 100644 index 000000000..e23bcfb2b --- /dev/null +++ b/controllers/promotion.go @@ -0,0 +1,51 @@ +package controllers + +import ( + "errors" + + "github.com/astaxie/beego" + + "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-callback/business/jxstore/promotion" + "git.rosy.net.cn/jx-callback/business/model" +) + +type PromotionController struct { + beego.Controller +} + +// @Title 创建促销 +// @Description 创建促销 +// @Param token header string true "认证token" +// @Param vendorID formData int true "厂商ID,当前只支持京东:0 " +// @Param name formData string true "促销名,必须唯一(所以名子上最好带上日期)" +// @Param beginAt formData string true "开始时间" +// @Param endAt formData string true "结束时间" +// @Param type formData int true "促销类型,3:直降,4:限时抢购" +// @Param storeIDs formData string true "json数据,storeID列表[1,2,3]" +// @Param skuPrices formData string true "json数据,价格信息列表" +// @Param isAsync formData bool false "是否异常,缺省否(暂时只支持同步)" +// @Param advertising formData string false "广告语" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /CreatePromotion [post] +func (c *PromotionController) CreatePromotion() { + c.callCreatePromotion(func(params *tPromotionCreatePromotionParams) (retVal interface{}, errCode string, err error) { + if params.VendorID != model.VendorIDJD { + return nil, "", errors.New("当前只支持创建京东促销") + } + promotionParams := &promotion.PromotionParams{ + Name: params.Name, + BeginAt: utils.Str2Time(params.BeginAt), + EndAt: utils.Str2Time(params.EndAt), + Type: params.Type, + Advertising: params.Advertising, + } + 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.IsAsync, promotionParams, GetUserNameFromToken(params.Token)) + } + } + return retVal, "", err + }) +} diff --git a/globals/beegodb/beegodb.go b/globals/beegodb/beegodb.go index c98574be8..8c1b4e70e 100644 --- a/globals/beegodb/beegodb.go +++ b/globals/beegodb/beegodb.go @@ -38,6 +38,8 @@ func Init() { orm.RegisterModel(&model.SkuCategory{}) orm.RegisterModel(&model.WeiXins{}, &model.JxBackendUser{}) // orm.RegisterModel(&model.DurableTask{}, &model.DurableTaskItem{}) + + orm.RegisterModel(&model.Promotion{}) } // create table orm.RunSyncdb("default", false, true) diff --git a/routers/commentsRouter_controllers.go b/routers/commentsRouter_controllers.go index 9faa824c7..696b48289 100644 --- a/routers/commentsRouter_controllers.go +++ b/routers/commentsRouter_controllers.go @@ -199,6 +199,14 @@ func init() { MethodParams: param.Make(), Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:PromotionController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:PromotionController"], + beego.ControllerComments{ + Method: "CreatePromotion", + Router: `/CreatePromotion`, + AllowHTTPMethods: []string{"post"}, + MethodParams: param.Make(), + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:SkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:SkuController"], beego.ControllerComments{ Method: "AddCategory", diff --git a/routers/router.go b/routers/router.go index 0296fb6bb..e1940ad3a 100644 --- a/routers/router.go +++ b/routers/router.go @@ -61,6 +61,11 @@ func init() { &controllers.FinancialController{}, ), ), + beego.NSNamespace("/promotion", + beego.NSInclude( + &controllers.PromotionController{}, + ), + ), ) beego.AddNamespace(ns)