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() _, 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(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() _, err = task2.GetResult(0) } 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) }