package act import ( "fmt" "math" "time" "git.rosy.net.cn/jx-callback/business/authz" "git.rosy.net.cn/jx-callback/business/jxutils/ddmsg" // "github.com/astaxie/beego/adapter/plugins/authz" "git.rosy.net.cn/jx-callback/business/authz/autils" "git.rosy.net.cn/jx-callback/business/partner/purchase/ebai" "git.rosy.net.cn/jx-callback/globals/api" "git.rosy.net.cn/baseapi/platformapi/dingdingapi" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/baseapi/utils/errlist" "git.rosy.net.cn/jx-callback/business/jxstore/cms" "git.rosy.net.cn/jx-callback/business/jxstore/permission" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/partner" "git.rosy.net.cn/jx-callback/globals" "github.com/360EntSecGroup-Skylar/excelize" "git.rosy.net.cn/jx-callback/business/jxutils/jsonerr" "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" ) const ( DefActSkuStock = 200 // 缺省活动库存 ) type ActOrderRuleParam struct { SalePrice int64 `orm:"" json:"salePrice"` // 满的价格 DeductPrice int64 `orm:"" json:"deductPrice"` // 减的价格 } type ActStoreSkuParam struct { model.ActStoreSku ActualActPrice int64 `json:"actualActPrice,omitempty"` // 单品级活动用,创建活动时商品的活动价格 VendorPrice int64 `json:"vendorPrice,omitempty"` // 创建活动时的平台价格 ErrMsg string `json:"errMsg,omitempty"` } type ActDetail struct { model.Act2 } type tPreCreateActVendorInfo struct { VendorID int VendorPrice int64 `orm:"" json:"vendorPrice"` // 单品级活动用,创建活动时商品的原始平台价 ActualActPrice int64 `orm:"" json:"actualActPrice"` // 单品级活动用,创建活动时商品的活动价格 } type tPreCreateActStoreSku struct { model.ActStoreSku VendorInfoList []*tPreCreateActVendorInfo `json:"vendorInfoList"` } type tPreCreateActInfo struct { model.Act ValidVendorIDs []int ActStoreSku []*tPreCreateActStoreSku `json:"actStoreSku"` } type tActRuleInfo struct { MinDiscount int MaxDiscount int } type ActManager struct { } type SheetParamAct struct { StoreIDCol int SkuIDCol int SkuPricePercentageCol int ActPriceCol int EarningPriceCol int StockCol int ActTypeCol int ActPricePercentageCol int ActNameCol int BeginTimeCol int EndTimeCol int } var ( FixedActManager *ActManager actRuleMap = map[int]map[int]*tActRuleInfo{ model.VendorIDJD: map[int]*tActRuleInfo{ model.ActSkuFake: &tActRuleInfo{ MinDiscount: 0, MaxDiscount: 500, }, model.ActSkuDirectDown: &tActRuleInfo{ MinDiscount: 0, MaxDiscount: 99, }, model.ActSkuSecKill: &tActRuleInfo{ MinDiscount: 0, MaxDiscount: 80, }, }, model.VendorIDMTWM: map[int]*tActRuleInfo{ model.ActSkuFake: &tActRuleInfo{ MinDiscount: 0, MaxDiscount: 500, }, model.ActSkuDirectDown: &tActRuleInfo{ MinDiscount: 30, MaxDiscount: 99, }, model.ActSkuSecKill: &tActRuleInfo{ MinDiscount: 0, MaxDiscount: 30, }, }, model.VendorIDEBAI: map[int]*tActRuleInfo{ model.ActSkuFake: &tActRuleInfo{ MinDiscount: 0, MaxDiscount: 500, }, model.ActSkuDirectDown: &tActRuleInfo{ MinDiscount: 0, MaxDiscount: 99, }, }, model.VendorIDJX: map[int]*tActRuleInfo{ model.ActSkuFake: &tActRuleInfo{ MinDiscount: 0, MaxDiscount: 500, }, model.ActSkuDirectDown: &tActRuleInfo{ MinDiscount: 0, MaxDiscount: 99, }, model.ActSkuSecKill: &tActRuleInfo{ MinDiscount: 0, MaxDiscount: 80, }, model.ActSkuDiscount: &tActRuleInfo{ MinDiscount: 100, MaxDiscount: 100, }, }, } ) func init() { FixedActManager = &ActManager{} partner.InitActManager(FixedActManager) } func getVendorPriceFromStoreSkuBind(bind *model.StoreSkuBind, vendorID int) (vendorPrice int) { switch vendorID { case model.VendorIDJD: vendorPrice = bind.JdPrice case model.VendorIDMTWM: vendorPrice = bind.MtwmPrice case model.VendorIDEBAI: vendorPrice = bind.EbaiPrice case model.VendorIDJX: vendorPrice = bind.JxPrice } return vendorPrice } func ActStoreSkuParam2Model(ctx *jxcontext.Context, db *dao.DaoDB, act *model.Act, vendorIDs []int, actStoreSku []*ActStoreSkuParam) ( validVendorIDs []int, actStoreSkuList []*model.ActStoreSku, actStoreSkuMapList []*model.ActStoreSkuMap, conflictActStoreSku []*model.ActStoreSku2, err error) { wholeValidVendorMap := make(map[int]int) if len(actStoreSku) > 0 { storeIDMap := make(map[int]int) skuIDMap := make(map[int]int) storeSkuParamMap := make(map[int][]*ActStoreSkuParam) actStoreSkuMap := make(map[int64]bool) var wrongSkuList []*ActStoreSkuParam for _, v := range actStoreSku { if act.Type == model.ActSkuFake && v.EarningPrice == 0 { wrongSkuList = append(wrongSkuList, v) } else { storeIDMap[v.StoreID] = 1 skuIDMap[v.SkuID] = 1 storeSkuParamMap[v.StoreID] = append(storeSkuParamMap[v.StoreID], v) actStoreSkuMap[jxutils.Combine2Int(v.StoreID, v.SkuID)] = true } } if len(wrongSkuList) > 0 { return nil, nil, nil, nil, jsonerr.New(wrongSkuList, model.ErrCodeJsonActEarningPriceIsZero) } storeIDs := jxutils.IntMap2List(storeIDMap) skuIDs := jxutils.IntMap2List(skuIDMap) // 判断活动是否重叠的检查,当前忽略京东平台及所有结算信息 if act.Type != model.ActSkuFake { effectActStoreSkuList, err := dao.GetEffectiveActStoreSkuInfo(db, 0, vendorIDs, act.Type, storeIDs, skuIDs, act.BeginAt, act.EndAt) if err != nil { globals.SugarLogger.Errorf("GetEffectiveActStoreSkuInfo can not get sku promotion info for error:%v", err) return nil, nil, nil, nil, err } if len(effectActStoreSkuList) > 0 { for _, v := range effectActStoreSkuList { if actStoreSkuMap[jxutils.Combine2Int(v.StoreID, v.SkuID)] { conflictActStoreSku = append(conflictActStoreSku, v) } } } } storeSkuList, err2 := dao.GetStoresSkusInfo(db, storeIDs, skuIDs) if err = err2; err != nil { return nil, nil, nil, nil, err } storeSkuMap := make(map[int64]*model.StoreSkuBind) for _, v := range storeSkuList { storeSkuMap[jxutils.Combine2Int(v.StoreID, v.SkuID)] = v } for storeID, oneStoreSkuParam := range storeSkuParamMap { validVendorMap := make(map[int]int) validSkuMap := make(map[int]int) for _, vendorID := range vendorIDs { storeDetail, err2 := dao.GetStoreDetail(db, storeID, vendorID, "") if err = err2; err == nil { if storeDetail.IsSync != 0 { if act.Type == model.ActSkuFake || storeDetail.Status != model.StoreStatusDisabled && storeDetail.VendorStatus != model.StoreStatusDisabled { for _, v := range oneStoreSkuParam { validVendorMap[vendorID] = 1 validSkuMap[v.SkuID] = 1 v.ActID = act.ID actSkuMap := &model.ActStoreSkuMap{ ActID: act.ID, StoreID: storeID, SkuID: v.SkuID, VendorID: vendorID, } storeSkuInfo := storeSkuMap[jxutils.Combine2Int(v.StoreID, v.SkuID)] if storeSkuInfo != nil { jxPrice := storeSkuInfo.Price actSkuMap.VendorPrice = int64(getVendorPriceFromStoreSkuBind(storeSkuInfo, vendorID)) v.OriginalPrice = int64(jxPrice) } var err2 error if act.Type != model.ActSkuFake { // 非结算,要计算实际活动价格 if storeSkuInfo == nil { v.ErrMsg = fmt.Sprintf("门店:%d没有关注商品:%d", v.StoreID, v.SkuID) wrongSkuList = append(wrongSkuList, v) continue } if !(vendorID == model.VendorIDJX || act.Type == model.ActSkuFake) { actSkuMap.SyncStatus = model.SyncFlagNewMask } if v.ActPrice != 0 { actSkuMap.ActualActPrice = v.ActPrice } else { percentage := act.PricePercentage if v.PricePercentage != 0 { percentage = v.PricePercentage } actSkuMap.ActualActPrice = int64(jxutils.CaculateSkuActVendorPrice(int(actSkuMap.VendorPrice), percentage, 0)) if actSkuMap.ActualActPrice > 10 { actSkuMap.ActualActPrice = int64(math.Floor(float64(actSkuMap.ActualActPrice)/10) * 10) } } if actSkuMap.ActualActPrice <= 0 { actSkuMap.ActualActPrice = 1 } if err2 = checkDiscountValidation(vendorIDs, act.Type, float64(actSkuMap.ActualActPrice)*100/float64(actSkuMap.VendorPrice)); err2 != nil { v.ErrMsg = err2.Error() v.ActualActPrice = actSkuMap.ActualActPrice v.VendorPrice = actSkuMap.VendorPrice wrongSkuList = append(wrongSkuList, v) } } if err2 == nil { actSkuMap.EarningPrice = v.EarningPrice dao.WrapAddIDCULDEntity(actSkuMap, ctx.GetUserName()) actStoreSkuMapList = append(actStoreSkuMapList, actSkuMap) } } wholeValidVendorMap[vendorID] = 1 } } } else if !dao.IsNoRowsError(err) { return nil, nil, nil, nil, err } else { err = nil } } if len(wrongSkuList) == 0 { for _, v := range oneStoreSkuParam { if validSkuMap[v.SkuID] == 1 { // todo 这里是否需要判断 storeSku := &v.ActStoreSku dao.WrapAddIDCULDEntity(storeSku, ctx.GetUserName()) actStoreSkuList = append(actStoreSkuList, storeSku) } } } } if len(wrongSkuList) > 0 { return nil, nil, nil, nil, jsonerr.New(wrongSkuList, model.ErrCodeJsonActPriceTooLarger) } } return jxutils.IntMap2List(wholeValidVendorMap), actStoreSkuList, actStoreSkuMapList, conflictActStoreSku, err } func addActStoreSkuBind(ctx *jxcontext.Context, db *dao.DaoDB, actStoreSkuList []*model.ActStoreSku, actStoreSkuMapList []*model.ActStoreSkuMap) (err error) { dao.Begin(db) defer func() { if r := recover(); r != nil { dao.Rollback(db) panic(r) } }() storeSkuMap := make(map[int64]int) for _, v := range actStoreSkuList { if v.Stock == 0 { v.Stock = DefActSkuStock } err = dao.CreateEntity(db, v) if err != nil { dao.Rollback(db) return err } storeSkuMap[jxutils.Combine2Int(v.StoreID, v.SkuID)] = v.ID } for _, v := range actStoreSkuMapList { v.BindID = storeSkuMap[jxutils.Combine2Int(v.StoreID, v.SkuID)] } if len(actStoreSkuMapList) > 0 { err = dao.CreateMultiEntities(db, actStoreSkuMapList) if err != nil { dao.Rollback(db) return err } } dao.Commit(db) return err } func checkActUpdate(actID int, actMap map[int]*model.Act2) (err error) { if len(actMap) == 0 { return fmt.Errorf("活动%d不存在或已被取消", actID) } errList := errlist.New() for vendorID, act := range actMap { if vendorID == model.VendorIDEBAI && act.CreateType != model.ActCreateTypeAPI { errList.AddErr(fmt.Errorf("饿百平台不支持修改或取消网页活动")) } } return errList.GetErrListAsOne() } func AddActStoreSkuBind(ctx *jxcontext.Context, db *dao.DaoDB, actID int, actStoreSku []*ActStoreSkuParam) (err error) { actMap, err := dao.GetActVendorInfo(db, actID, nil) if err != nil { return err } if err = checkActUpdate(actID, actMap); err != nil { return err } vendorIDs := partner.GetVendorIDsFromActMap(actMap) var act *model.Act if len(vendorIDs) > 0 { act = &actMap[vendorIDs[0]].Act } else { act = &model.Act{} act.ID = actID if err = dao.GetEntity(db, act); err != nil { return err } } if act.Status != model.ActStatusCreated || time.Now().Sub(act.EndAt) > 0 { return fmt.Errorf("当前活动状态:%s不能进行此操作,或已过期", model.ActStatusName[act.Status]) } // TODO conflictActStoreSku的处理 _, actStoreSkuList, actStoreSkuMapList, _, err := ActStoreSkuParam2Model(ctx, db, act, vendorIDs, actStoreSku) if err != nil { return err } dao.Begin(db) defer func() { if r := recover(); r != nil || err != nil { dao.Rollback(db) if r != nil { panic(r) } } }() if err = addActStoreSkuBind(ctx, db, actStoreSkuList, actStoreSkuMapList); err != nil { return err } if act.Type != model.ActSkuFake { for _, act := range actMap { if act.VendorID != model.VendorIDJX { if _, err = dao.UpdateEntityLogically(db, partner.Act2ActMap(act), map[string]interface{}{ model.FieldSyncStatus: act.SyncStatus | model.SyncFlagModifiedMask, }, ctx.GetUserName(), nil); err != nil { return err } } } } dao.Commit(db) return err } func getActRule(vendorID, actType int) (actRule *tActRuleInfo, err error) { if actRuleMap[vendorID] != nil { actRule = actRuleMap[vendorID][actType] } if actRule == nil { err = fmt.Errorf("%s不支持%s活动", model.VendorChineseNames[vendorID], model.ActTypeName[actType]) } return actRule, err } func checkDiscountValidation(vendorIDs []int, actType int, pricePercentage float64) (err error) { pricePercentageMin := int(math.Floor(pricePercentage)) pricePercentageMax := int(math.Ceil(pricePercentage)) errList := errlist.New() for _, vendorID := range vendorIDs { actRule, err2 := getActRule(vendorID, actType) if err2 == nil { if pricePercentageMin < actRule.MinDiscount { errList.AddErr(fmt.Errorf("%s%s活动折扣必须大于:%d", model.VendorChineseNames[vendorID], model.ActTypeName[actType], actRule.MinDiscount)) } else if pricePercentageMax > actRule.MaxDiscount { errList.AddErr(fmt.Errorf("%s%s活动折扣必须小于:%d", model.VendorChineseNames[vendorID], model.ActTypeName[actType], actRule.MaxDiscount)) } } else { errList.AddErr(err2) } } return errList.GetErrListAsOne() } func checkActValidation(act *model.Act, vendorIDs []int) (err error) { errList := errlist.New() if utils.IsTimeZero(act.BeginAt) || utils.IsTimeZero(act.EndAt) { errList.AddErr(fmt.Errorf("活动开始与结束时间必须指定")) } else if act.EndAt.Sub(act.BeginAt) < 0 { errList.AddErr(fmt.Errorf("活动开始时间必须小于活动结束时间")) } vendorIDMap := make(map[int]int) for _, vendorID := range vendorIDs { vendorIDMap[vendorID] = 1 } if act.Type == model.ActSkuDirectDown || act.Type == model.ActSkuSecKill { if act.PricePercentage == 0 { errList.AddErr(fmt.Errorf("必须指定缺省活动折扣")) } else if err = checkDiscountValidation(vendorIDs, act.Type, float64(act.PricePercentage)); err != nil { errList.AddErr(err) } } else if act.Type == model.ActSkuFake { } else if act.Type == model.ActSkuDiscount { if act.DiscountType == 0 { errList.AddErr(fmt.Errorf("折扣活动必须选择折扣类型")) } if act.DiscountValue1 == 0 || act.DiscountValue2 == 0 { errList.AddErr(fmt.Errorf("折扣活动必须填入两档")) } } else { errList.AddErr(fmt.Errorf("当前只支持%s与%s活动", model.ActTypeName[model.ActSkuDirectDown], model.ActTypeName[model.ActSkuSecKill])) } err = errList.GetErrListAsOne() return err } func setActDefault(act *model.Act) { if act.LimitCount == 0 { act.LimitCount = 1 // 缺省限购一份,如果确定不限,明确给一个很大的值 } if act.LimitUser == 0 { act.LimitUser = 1 } act.Status = model.ActStatusCreated } func PreCreateAct(ctx *jxcontext.Context, act *model.Act, vendorIDs []int, actRules []*ActOrderRuleParam, actStoreSku []*ActStoreSkuParam) (preCreateActInfo *tPreCreateActInfo, err error) { if err = checkActValidation(act, vendorIDs); err != nil { return nil, err } setActDefault(act) db := dao.GetDB() validVendorIDs, actStoreSkuList, actStoreSkuMapList, conflictActStoreSku, err := ActStoreSkuParam2Model(ctx, db, act, vendorIDs, actStoreSku) if err != nil { return nil, err } if len(validVendorIDs) == 0 { return nil, fmt.Errorf("没有一个合法平台可以创建活动") } if act.OverlapRule == model.OverlapRuleNormal && len(conflictActStoreSku) > 0 { return nil, jsonerr.New(conflictActStoreSku, model.ErrCodeJsonActSkuConflict) } preCreateActInfo = &tPreCreateActInfo{ ValidVendorIDs: validVendorIDs, } storeSkuMap := make(map[int64]*tPreCreateActStoreSku) for _, v := range actStoreSkuList { tmp := &tPreCreateActStoreSku{ ActStoreSku: *v, } storeSkuMap[jxutils.Combine2Int(v.StoreID, v.SkuID)] = tmp preCreateActInfo.ActStoreSku = append(preCreateActInfo.ActStoreSku, tmp) } for _, v := range actStoreSkuMapList { index := jxutils.Combine2Int(v.StoreID, v.SkuID) storeSkuMap[index].VendorInfoList = append(storeSkuMap[index].VendorInfoList, &tPreCreateActVendorInfo{ VendorID: v.VendorID, VendorPrice: v.VendorPrice, ActualActPrice: v.ActualActPrice, }) } return preCreateActInfo, nil } func CreateAct(ctx *jxcontext.Context, act *model.Act, vendorIDs []int, vendorOrgCode string, actRules []*ActOrderRuleParam, actStoreSku []*ActStoreSkuParam, isAsync bool) (hint string, err error) { //权限 if permission.IsRoled(ctx) { if storeIDsMap, err := permission.GetUserStoresResultMap(ctx.GetUserID()); err == nil { var actStoreSku2 []*ActStoreSkuParam for _, v := range actStoreSku { if storeIDsMap[v.StoreID] != 0 { actStoreSku2 = append(actStoreSku2, v) } } actStoreSku = nil actStoreSku = actStoreSku2 } } if err = checkActValidation(act, vendorIDs); err != nil { return "", err } act.VendorMask = model.GetVendorMask(vendorIDs...) if act.VendorMask&(^model.GetVendorMask(model.VendorIDJX)) != 0 && vendorOrgCode == "" { return "", fmt.Errorf("必须指定平台分账号信息") } setActDefault(act) db := dao.GetDB() // TODO // 事务应该从ActStoreSkuParam2Model之后开始 // 创建Act也应该往后放 dao.Begin(db) defer func() { if r := recover(); r != nil { dao.Rollback(db) panic(r) } }() dao.WrapAddIDCULDEntity(act, ctx.GetUserName()) err = dao.CreateEntity(db, act) if err != nil { dao.Rollback(db) return "", err } validVendorIDs, actStoreSkuList, actStoreSkuMapList, conflictActStoreSku, err := ActStoreSkuParam2Model(ctx, db, act, vendorIDs, actStoreSku) if err != nil { dao.Rollback(db) return "", err } if len(validVendorIDs) == 0 { dao.Rollback(db) return "", fmt.Errorf("没有一个合法平台可以创建活动") } var neeSyncActIDs []int if len(conflictActStoreSku) > 0 { if act.OverlapRule == model.OverlapRuleNormal { dao.Rollback(db) return "", jsonerr.New(conflictActStoreSku, model.ErrCodeJsonActSkuConflict) } if neeSyncActIDs, err = DeleteActStoreSkuList(ctx, db, conflictActStoreSku); err != nil { dao.Rollback(db) return "", err } } var actMapList []*model.ActMap for _, vendorID := range validVendorIDs { actMap := &model.ActMap{ ActID: act.ID, VendorID: vendorID, VendorOrgCode: vendorOrgCode, } if !(vendorID == model.VendorIDJX || act.Type == model.ActSkuFake) { actMap.SyncStatus = model.SyncFlagNewMask } dao.WrapAddIDCULDEntity(actMap, ctx.GetUserName()) actMapList = append(actMapList, actMap) } err = dao.CreateMultiEntities(db, actMapList) if err != nil { dao.Rollback(db) return "", err } if err = addActStoreSkuBind(ctx, db, actStoreSkuList, actStoreSkuMapList); err != nil { dao.Rollback(db) return "", err } dao.Commit(db) neeSyncActIDs = append(neeSyncActIDs, act.ID) task := tasksch.NewParallelTask(fmt.Sprintf("处理活动创建%d", act.ID), tasksch.NewParallelConfig().SetParallelCount(1).SetIsContinueWhenError(true), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { actID := batchItemList[0].(int) _, err = SyncAct(ctx, task, actID, nil, false) return retVal, err }, neeSyncActIDs) tasksch.HandleTask(task, nil, true).Run() if !isAsync { hint = utils.Int2Str(act.ID) } else { _, err = task.GetResult(0) hint = utils.Int2Str(len(neeSyncActIDs)) } return hint, err } func vendorActInfo2Model(ctx *jxcontext.Context, db *dao.DaoDB, act2 *model.Act2, actStoreSku []*model.ActStoreSku2) (actStoreSkuList []*model.ActStoreSku, actStoreSkuMapList []*model.ActStoreSkuMap, err error) { vendorStoreIDMap := make(map[string]int) vendorSkuIDMap := make(map[string]int) for _, v := range actStoreSku { vendorStoreIDMap[v.VendorStoreID] = 1 vendorSkuIDMap[v.VendorSkuID] = 1 } // globals.SugarLogger.Debug(utils.Format4Output(vendorStoreIDMap, false)) // globals.SugarLogger.Debug(utils.Format4Output(vendorSkuIDMap, false)) vendorID := act2.VendorID storeSkuList, err2 := dao.GetStoresSkusInfoByVendorInfo(db, vendorID, jxutils.StringMap2List(vendorStoreIDMap), jxutils.StringMap2List(vendorSkuIDMap)) if err = err2; err != nil { return nil, nil, err } storeSkuMap := make(map[string]*dao.StoreSkuBindWithVendorInfo) for _, v := range storeSkuList { storeSkuMap[v.VendorStoreID+"/"+v.VendorSkuID] = v } storeSkuMap2 := make(map[int64]int) for _, v := range actStoreSku { if storeSkuInfo := storeSkuMap[v.VendorStoreID+"/"+v.VendorSkuID]; storeSkuInfo != nil { index := jxutils.Combine2Int(storeSkuInfo.StoreID, storeSkuInfo.SkuID) if storeSkuMap2[index] == 0 { storeSkuMap2[index] = 1 actSku := &model.ActStoreSku{ ActID: act2.ID, StoreID: storeSkuInfo.StoreID, SkuID: storeSkuInfo.SkuID, Stock: v.Stock, ActPrice: v.ActualActPrice, OriginalPrice: int64(storeSkuInfo.Price), } dao.WrapAddIDCULDEntity(actSku, ctx.GetUserName()) actStoreSkuList = append(actStoreSkuList, actSku) actSkuMap := &model.ActStoreSkuMap{ ActID: act2.ID, VendorActID: act2.VendorActID, StoreID: storeSkuInfo.StoreID, SkuID: storeSkuInfo.SkuID, VendorID: vendorID, SyncStatus: 0, VendorPrice: 0, ActualActPrice: v.ActualActPrice, } dao.WrapAddIDCULDEntity(actSkuMap, ctx.GetUserName()) actStoreSkuMapList = append(actStoreSkuMapList, actSkuMap) } } } return actStoreSkuList, actStoreSkuMapList, err } func (a *ActManager) CreateActFromVendor(ctx *jxcontext.Context, act2 *model.Act2, actStoreSku []*model.ActStoreSku2) (actID int, err error) { globals.SugarLogger.Debugf("CreateActFromVendor vendorID:%d, vendorActID:%s", act2.VendorID, act2.VendorActID) db := dao.GetDB() return createActFromVendor(ctx, db, act2, actStoreSku) } func createActFromVendor(ctx *jxcontext.Context, db *dao.DaoDB, act2 *model.Act2, actStoreSku []*model.ActStoreSku2) (actID int, err error) { actMap := &model.ActMap{ VendorID: act2.VendorID, VendorOrgCode: act2.VendorOrgCode, VendorActID: act2.VendorActID, SyncStatus: 0, } dao.WrapAddIDCULDEntity(actMap, ctx.GetUserName()) if actMap.VendorActID != "" { if err = dao.GetEntity(db, actMap, model.FieldVendorActID, model.FieldVendorID, model.FieldDeletedAt); err == nil { return actMap.ActID, nil } else if !dao.IsNoRowsError(err) { return 0, err } err = nil } dao.Begin(db) defer func() { if r := recover(); r != nil { dao.Rollback(db) panic(r) } }() act := &act2.Act act.VendorMask = model.GetVendorMask(act2.VendorID) dao.WrapAddIDCULDEntity(act, ctx.GetUserName()) err = dao.CreateEntity(db, act) if err != nil { dao.Rollback(db) return 0, err } actMap.ActID = act.ID err = dao.CreateEntity(db, actMap) if err != nil { dao.Rollback(db) return 0, err } actStoreSkuList, actStoreSkuMapList, err := vendorActInfo2Model(ctx, db, act2, actStoreSku) if err != nil { dao.Rollback(db) return 0, err } if err = addActStoreSkuBind(ctx, db, actStoreSkuList, actStoreSkuMapList); err != nil { dao.Rollback(db) return 0, err } dao.Commit(db) return act.ID, nil } func (a *ActManager) IsVendorActExist(ctx *jxcontext.Context, vendorActID string, vendorID int) (isExist bool) { db := dao.GetDB() actMap := &model.ActMap{ VendorActID: vendorActID, VendorID: vendorID, } actMap.DeletedAt = utils.DefaultTimeValue if err := dao.GetEntity(db, actMap, "VendorActID", "VendorID", "DeletedAt"); err == nil { isExist = true } return isExist } func QueryActs(ctx *jxcontext.Context, actID int, offset, pageSize int, syncStatus int, keyword string, vendorID int, statusList, actTypeList, createTypeList []int, storeID, skuID, cityCode int, beginAt, endAt, createdAtFrom, createdAtTo time.Time) (pagedInfo *dao.PagedActListInfo, err error) { var skuIDs []int if skuID > 0 { skuIDs = []int{skuID} } return dao.QueryActs(dao.GetDB(), actID, offset, pageSize, syncStatus, keyword, vendorID, statusList, actTypeList, createTypeList, storeID, skuIDs, cityCode, beginAt, endAt, createdAtFrom, createdAtTo) } func GetActStoreSkuInfo(ctx *jxcontext.Context, actID int, vendorIDs []int, keyword string, offset, pageSize int) (retVal interface{}, err error) { db := dao.GetDB() totalCount, actStoreSkuList, err := dao.GetActStoreSkuVendorList(db, actID, vendorIDs, nil, nil, keyword, offset, pageSize) if err != nil { return nil, err } for _, v := range actStoreSkuList { v.SkuName = jxutils.ComposeSkuName(v.Prefix, v.SkuNameName, v.Comment, v.Unit, v.SpecQuality, v.SpecUnit, 0, v.ExPrefix, v.ExPrefixBegin, v.ExPrefixEnd) } if pageSize > 0 && pageSize != model.UnlimitedPageSize { pagedInfo := &model.PagedInfo{ TotalCount: totalCount, Data: actStoreSkuList, } retVal = pagedInfo } else { retVal = actStoreSkuList } return retVal, err } func CancelAct(ctx *jxcontext.Context, actID int) (err error) { db := dao.GetDB() if _, err = DeleteActStoreSkuBind(ctx, db, actID, nil); err != nil { return err } _, err = SyncAct(ctx, nil, actID, nil, false) act := &model.Act{} act.ID = actID if err == nil { if _, err = dao.UpdateEntityLogically(db, act, map[string]interface{}{ model.FieldStatus: model.ActStatusCanceled, }, ctx.GetUserName(), nil); err != nil { return err } } return err } // actStoreSkuParam为空,不会删除act_store_sku,但会删除act_store_sku_map func DeleteActStoreSkuBind(ctx *jxcontext.Context, db *dao.DaoDB, actID int, actStoreSkuParam []*ActStoreSkuParam) (originSyncStatus int8, err error) { actMap, err := dao.GetActVendorInfo(db, actID, nil) if err != nil { return 0, err } if err = checkActUpdate(actID, actMap); err != nil { return 0, err } actStoreSkuMap, err := dao.GetActStoreSkuVendorInfo(db, actID, nil, nil, nil) if err != nil { return 0, err } act := actMap[partner.GetVendorIDsFromActMap(actMap)[0]] if act.Status != model.ActStatusCreated || time.Now().Sub(act.EndAt) > 0 { return 0, fmt.Errorf("当前活动状态:%s,不能进行此操作,或已过期", model.ActStatusName[act.Status]) } dao.Begin(db) defer func() { if r := recover(); r != nil || err != nil { dao.Rollback(db) if r != nil { panic(r) } } }() actStoreSkuParamMap := make(map[int64]*ActStoreSkuParam) for _, v := range actStoreSkuParam { actStoreSkuParamMap[jxutils.Combine2Int(v.StoreID, v.SkuID)] = v if _, err = dao.DeleteEntityLogically(db, &model.ActStoreSku{}, nil, ctx.GetUserName(), map[string]interface{}{ model.FieldActID: actID, model.FieldStoreID: v.StoreID, model.FieldSkuID: v.SkuID, }); err != nil { return 0, err } } isNeedCancelAct := true for vendorID, act := range actMap { originSyncStatus |= act.SyncStatus isDeleteAll := true isDeleteAtLeastOne := false if true { //actStoreSkuParam != nil { actStoreSkuMap := partner.SplitActStoreSku(actStoreSkuMap[vendorID]) for storeID := range actStoreSkuMap { for _, actStoreSku := range actStoreSkuMap[storeID] { if actStoreSkuParam == nil || actStoreSkuParamMap[jxutils.Combine2Int(actStoreSku.StoreID, actStoreSku.SkuID)] != nil { if act.Type == model.ActSkuFake { _, err = dao.DeleteEntityLogically(db, &model.ActStoreSkuMap{}, nil, ctx.GetUserName(), map[string]interface{}{ model.FieldActID: actID, model.FieldStoreID: actStoreSku.StoreID, model.FieldSkuID: actStoreSku.SkuID, }) } else { _, err = dao.UpdateEntityLogically(db, partner.ActStoreSku2ActStoreSkuMap(actStoreSku), map[string]interface{}{ model.FieldSyncStatus: actStoreSku.SyncStatus | model.SyncFlagDeletedMask, }, ctx.GetUserName(), nil) } if err != nil { return 0, err } isDeleteAtLeastOne = true } else { isNeedCancelAct = false isDeleteAll = false } } } } else { isDeleteAll = true isDeleteAtLeastOne = true } if isDeleteAll || isDeleteAtLeastOne { syncStatus := int8(model.SyncFlagModifiedMask) if isDeleteAll { syncStatus = model.SyncFlagDeletedMask } syncStatus |= act.SyncStatus if act.Type != model.ActSkuFake && vendorID != model.VendorIDJX { if _, err = dao.UpdateEntityLogically(db, partner.Act2ActMap(act), map[string]interface{}{ model.FieldSyncStatus: syncStatus, }, ctx.GetUserName(), nil); err != nil { return 0, err } } } if isDeleteAll != isNeedCancelAct { globals.SugarLogger.Warnf("deleteActStoreBind, actID:%d isDeleteAll:%t != isNeedCancelAct:%t", act.ID, isDeleteAll, isNeedCancelAct) } } if isNeedCancelAct { // act := &model.Act{} // act.ID = actID // if err == nil { // if _, err = dao.UpdateEntityLogically(db, act, // map[string]interface{}{ // model.FieldStatus: model.ActStatusCanceled, // }, ctx.GetUserName(), nil); err != nil { // return 0, err // } // } } dao.Commit(db) return originSyncStatus, err } func DeleteActStoreSkuList(ctx *jxcontext.Context, db *dao.DaoDB, actStoreSkuList []*model.ActStoreSku2) (actIDs []int, err error) { if globals.IsStoreSkuAct { actStoreSkuParamMap := make(map[int][]*ActStoreSkuParam) for _, v := range actStoreSkuList { param := &ActStoreSkuParam{ ActStoreSku: model.ActStoreSku{ ActID: v.ActID, StoreID: v.StoreID, SkuID: v.SkuID, }, } actStoreSkuParamMap[v.ActID] = append(actStoreSkuParamMap[v.ActID], param) } for actID := range actStoreSkuParamMap { if _, err = DeleteActStoreSkuBind(ctx, db, actID, actStoreSkuParamMap[actID]); err != nil { return nil, err } } for actID := range actStoreSkuParamMap { actIDs = append(actIDs, actID) } } return actIDs, nil } // todo 当前逻辑要求传入活动的全部SKU信息(以便低层做一些判断,比如全部删除时要取消,所以暂时删除storeIDs与skuIDs这两个参数 func SyncAct(ctx *jxcontext.Context, parentTask tasksch.ITask, actID int, vendorIDs /*, storeIDs, skuIDs */ []int, isAsync bool) (hint string, err error) { db := dao.GetDB() actMap, err := dao.GetActVendorInfo(db, actID, vendorIDs) if err != nil { return "", err } actStoreSkuMap, err := dao.GetActStoreSkuVendorInfo(db, actID, nil, nil, nil) if err != nil { return "", err } if vendorIDs == nil { vendorIDs = partner.GetVendorIDsFromActMap(actMap) } if len(vendorIDs) == 0 || vendorIDs[0] == model.VendorIDJX || actMap[vendorIDs[0]].Type == model.ActSkuFake { return "", nil } task := tasksch.NewParallelTask("SyncAct", tasksch.NewParallelConfig().SetIsContinueWhenError(true), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { vendorID := batchItemList[0].(int) if handler, _ := partner.GetPurchasePlatformFromVendorID(vendorID).(partner.IPurchasePlatformActHandler); handler != nil { tmpActMap := &model.ActMap{} tmpActMap.ID = actMap[vendorID].MapID if !globals.IsStoreSkuAct || actMap[vendorID].IsSpecial == 0 { if err = handler.SyncAct(ctx, task, actMap[vendorID], nil, actStoreSkuMap[vendorID]); err == nil { retVal = []int{1} } else { tmpActMap.Remark = utils.LimitUTF8StringLen(err.Error(), 1024) } // 保存最后一次同步错误信息 dao.UpdateEntity(db, tmpActMap, "Remark") } else { err = SyncSpecialAct(ctx, task, actMap[vendorID], nil, actStoreSkuMap[vendorID]) } } else { globals.SugarLogger.Warnf("SyncAct strange actID:%d, vendorID:%d", actID, vendorID) } return retVal, err }, vendorIDs) tasksch.HandleTask(task, parentTask, true).Run() if !isAsync { result, err2 := task.GetResult(0) if err = err2; err == nil { hint = utils.Int2Str(len(result)) } } else { hint = task.GetID() } return hint, err } func RefreshPageActs(ctx *jxcontext.Context, vendorIDs []int, createdFrom time.Time, isAsync bool) (hint string, err error) { task := tasksch.NewParallelTask("RefreshPageActs", nil, ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { vendorID := batchItemList[0].(int) if handler, _ := partner.GetPurchasePlatformFromVendorID(vendorID).(partner.IPurchasePlatformPageActHandler); handler != nil { db := dao.GetDB() actList, err2 := handler.GetPageActList(ctx, createdFrom) globals.SugarLogger.Debug(utils.Format4Output(actList, false)) if err = err2; err != nil { return nil, err } // actInfo, err2 := dao.QueryActs(db, 0, 0, -1, -1, "", vendorID, nil, []int{model.ActCreateTypeSpider}, // nil, 0, nil, 0, utils.DefaultTimeValue, utils.DefaultTimeValue, createdFrom, utils.DefaultTimeValue) vendorActIDs, err2 := dao.GetExistVendorActIDs(db, vendorID) if err = err2; err != nil { return nil, err } localActMap := jxutils.StringList2Map(vendorActIDs) // localActMap := make(map[string]*dao.ActVendorInfo) // for _, v := range vendorActIDs { // if v.VendorList[0].VendorActID != "" { // localActMap[v.VendorList[0].VendorActID] = v // } // } var needAddActList []*model.Act2 // var needUpdateActList []*model.Act for _, v := range actList { localAct := localActMap[v.VendorActID] if localAct == 0 { if v.Status == model.ActStatusCreated { needAddActList = append(needAddActList, v) } } // else if v.Status != localAct.Status { // localAct.Status = v.Status // needUpdateActList = append(needUpdateActList, &localAct.Act) // } } subTask := tasksch.NewParallelTask("RefreshPageAct Sub", nil, ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { act2 := batchItemList[0].(*model.Act2) actStoreSkuList, err := handler.GetPageActSkuList(ctx, act2.VendorActID) if err == nil { retVal = actStoreSkuList } return retVal, err }, needAddActList) tasksch.AddChild(task, subTask).Run() skuList, err2 := subTask.GetResult(0) if err = err2; err != nil { return nil, err } needAddActSkuMap := make(map[string][]*model.ActStoreSku2) for _, v := range skuList { actStoreSku := v.(*model.ActStoreSku2) needAddActSkuMap[actStoreSku.VendorActID] = append(needAddActSkuMap[actStoreSku.VendorActID], actStoreSku) } dao.Begin(db) defer func() { if r := recover(); r != nil || err != nil { dao.Rollback(db) if r != nil { panic(r) } } }() for _, v := range needAddActList { if len(needAddActSkuMap[v.VendorActID]) > 0 { if _, err = createActFromVendor(ctx, db, v, needAddActSkuMap[v.VendorActID]); err != nil { return nil, err } } } // for _, v := range needUpdateActList { // if _, err = dao.UpdateEntity(db, v, model.FieldStatus); err != nil { // return nil, err // } // } dao.Commit(db) } return retVal, err }, vendorIDs) tasksch.HandleTask(task, nil, true).Run() if !isAsync { hint = "1" _, err = task.GetResult(0) } else { hint = task.GetID() } return hint, err } func DeleteStoresFromAct(ctx *jxcontext.Context, vendorID int, actTypes, storeIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) { db := dao.GetDB() task := tasksch.NewParallelTask("将门店从所有活动中删除", tasksch.NewParallelConfig().SetParallelCount(1).SetIsContinueWhenError(isContinueWhenError), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { storeID := batchItemList[0].(int) acts, _ := dao.QueryActs(db, 0, 0, 50, -1, "", vendorID, []int{1}, actTypes, nil, storeID, nil, 0, utils.ZeroTimeValue, utils.ZeroTimeValue, time.Now().AddDate(0, -3, 0), time.Now()) for _, v := range acts.Data { var actStoreSkuParam []*ActStoreSkuParam _, actStoreSkus, _ := dao.GetActStoreSkuVendorList(db, v.ID, []int{vendorID}, nil, nil, "", 0, 99999) for _, actStoreSku := range actStoreSkus { if actStoreSku.StoreID == storeID { aa := &ActStoreSkuParam{ ActStoreSku: model.ActStoreSku{ StoreID: storeID, SkuID: actStoreSku.SkuID, ActID: v.ID, }, } actStoreSkuParam = append(actStoreSkuParam, aa) } } _, err = DeleteActStoreSkuBind(ctx, db, v.ID, actStoreSkuParam) if err == nil { _, err = SyncAct(ctx, nil, v.ID, nil, true) } } return retVal, err }, storeIDs) tasksch.HandleTask(task, nil, true).Run() if isAsync { hint = task.GetID() } else { resultList, err2 := task.GetResult(0) if err = err2; err == nil { hint = utils.Int2Str(len(resultList)) } } return hint, err } func DeleteSkusFromAct(ctx *jxcontext.Context, vendorID int, actTypes, skuIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) { db := dao.GetDB() actMap := make(map[int]*model.Act) pagedInfo, err2 := dao.QueryActs(db, 0, 0, -1, -1, "", vendorID, []int{model.ActStatusCreated}, actTypes, nil, 0, skuIDs, 0, utils.DefaultTimeValue, utils.DefaultTimeValue, time.Now().Add(-24*30*3*time.Hour), utils.DefaultTimeValue) if err = err2; err != nil { return "", err } for _, v := range pagedInfo.Data { actMap[v.Act.ID] = &v.Act } if len(actMap) == 0 { return "", nil } skuIDMap := jxutils.IntList2Map(skuIDs) var actIDList []int for k := range actMap { actIDList = append(actIDList, k) } task := tasksch.NewParallelTask("将SKU从所有活动中删除", tasksch.NewParallelConfig().SetParallelCount(1).SetIsContinueWhenError(isContinueWhenError), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { actID := batchItemList[0].(int) _, actStoreSkuList, err := dao.GetActStoreSkuVendorList(db, actID, []int{-1}, nil, skuIDs, "", 0, -1) if err == nil { // db := dao.GetDB() var deleteList []*ActStoreSkuParam for _, v := range actStoreSkuList { if skuIDMap[v.SkuID] == 1 { deleteList = append(deleteList, &ActStoreSkuParam{ ActStoreSku: model.ActStoreSku{ StoreID: v.StoreID, SkuID: v.SkuID, }, }) } } if len(deleteList) > 0 { // globals.SugarLogger.Debug(utils.Format4Output(deleteList, false)) originSyncStatus, err2 := DeleteActStoreSkuBind(ctx, db, actID, deleteList) if err = err2; err == nil && originSyncStatus == 0 { if _, err = SyncAct(ctx, task, actID, nil, false); err == nil { retVal = deleteList } } } } return retVal, err }, actIDList) tasksch.HandleTask(task, nil, true).Run() if isAsync { hint = task.GetID() } else { resultList, err2 := task.GetResult(0) if err = err2; err == nil { hint = utils.Int2Str(len(resultList)) } } return hint, err } func ForceUpdateVendorPrice(ctx *jxcontext.Context, vendorID int, actType int, storeSkuList []*ActStoreSkuParam, lockTime time.Time, isAsync bool) (hint string, err error) { var wrongSkuList []*ActStoreSkuParam var storeSkuBindList []*model.StoreSkuBind actRule, _ := getActRule(vendorID, actType) db := dao.GetDB() errList := errlist.New() for _, v := range storeSkuList { storeSkuBind := &model.StoreSkuBind{ StoreID: v.StoreID, SkuID: v.SkuID, } storeSkuBind.DeletedAt = utils.DefaultTimeValue if err = dao.GetEntity(db, storeSkuBind, model.FieldStoreID, model.FieldSkuID, model.FieldDeletedAt); err == nil { vendorPrice := int(v.VendorPrice) if vendorPrice != 0 { if err2 := checkDiscountValidation([]int{vendorID}, actType, float64(v.ActPrice)*100/float64(v.VendorPrice)); err2 != nil { v.ErrMsg = err2.Error() v.ActualActPrice = v.ActPrice wrongSkuList = append(wrongSkuList, v) storeSkuBind = nil } } else { vendorPrice = dao.GetStoreSkuBindVendorPrice(storeSkuBind, vendorID) if checkDiscountValidation([]int{vendorID}, actType, float64(v.ActPrice)*100/float64(vendorPrice)) != nil { vendorPrice = int(v.ActPrice)*100/actRule.MaxDiscount + 10 } else { storeSkuBind = nil } } if storeSkuBind != nil { dao.SetStoreSkuBindVendorPrice(storeSkuBind, vendorID, vendorPrice, lockTime) if vendorID != model.VendorIDJX { dao.SetStoreSkuBindSyncStatus(storeSkuBind, vendorID, dao.GetStoreSkuBindSyncStatus(storeSkuBind, vendorID)|model.SyncFlagPriceMask) } storeSkuBind.LastOperator = ctx.GetUserName() storeSkuBindList = append(storeSkuBindList, storeSkuBind) } } else if dao.IsNoRowsError(err) { // 忽略不存在错 err = nil } else { errList.AddErr(fmt.Errorf("获取门店:%d商品:%d出错:%s", v.StoreID, v.SkuID, err)) } } if err = errList.GetErrListAsOne(); err != nil { return "", err } if len(wrongSkuList) > 0 { return "", jsonerr.New(wrongSkuList, model.ErrCodeJsonActPriceTooLarger) } storeVendorIDMap := make(map[int]string) storeSkuIDMap := make(map[int][]int) task := tasksch.NewSeqTask2(fmt.Sprintf("强制刷新门店商品%s平台价", model.VendorChineseNames[vendorID]), ctx, false, func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { switch step { case 0: errList := errlist.New() for _, storeSkuBind := range storeSkuBindList { if _, err2 := dao.UpdateEntity(db, storeSkuBind); err2 == nil { if storeVendorIDMap[storeSkuBind.StoreID] == "" { if storeDetail, err2 := dao.GetStoreDetail(db, storeSkuBind.StoreID, vendorID, ""); err2 == nil { storeVendorIDMap[storeSkuBind.StoreID] = storeDetail.VendorStoreID } } storeSkuIDMap[storeSkuBind.StoreID] = append(storeSkuIDMap[storeSkuBind.StoreID], storeSkuBind.SkuID) errList.AddErr(err2) } } err = errList.GetErrListAsOne() case 1: if vendorID != model.VendorIDJX && len(storeVendorIDMap) > 0 { var storeIDs []int for storeID := range storeVendorIDMap { storeIDs = append(storeIDs, storeID) } subTask := tasksch.NewParallelTask("同步平台价格", tasksch.NewParallelConfig().SetIsContinueWhenError(true), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { storeID := batchItemList[0].(int) _, err = cms.SyncStoreSkuNew2(ctx, task, 0 /*model.SyncFlagPriceMask*/, vendorID, storeID, storeVendorIDMap[storeID], "", nil, storeSkuIDMap[storeID], nil, true, false, true) return retVal, err }, storeIDs) tasksch.HandleTask(subTask, task, true).Run() _, err = subTask.GetResult(0) } } return result, err }, 2) tasksch.HandleTask(task, nil, true).Run() if isAsync { hint = task.GetID() } else { _, err2 := task.GetResult(0) if err = err2; err == nil { hint = utils.Int2Str(1) } } return hint, err } func SyncActStoreSku2StoreSkuAct(ctx *jxcontext.Context, db *dao.DaoDB, parentTask tasksch.ITask, act *model.Act2) (err error) { dao.Begin(db) defer func() { if r := recover(); r != nil || err != nil { dao.Rollback(db) if r != nil { panic(r) } } }() // 创建时,会直接覆盖,后续修改时,会判断是否是相同的act_id // MySql的SET语句,顺序是有关系的,如果当前SET语句中有判断,且依赖于前一条SET过的字段,是用的是SET后的值的 sql := ` UPDATE store_sku_act t1 JOIN act_store_sku_map t2 ON t2.store_id = t1.store_id AND t2.sku_id = t1.sku_id AND t2.vendor_id = t1.vendor_id AND t2.act_id = ? AND t2.sync_status <> 0 JOIN act_store_sku t3 ON t3.store_id = t1.store_id AND t3.sku_id = t1.sku_id AND t3.act_id = t2.act_id JOIN act_map t4 ON t4.act_id = t2.act_id AND t4.vendor_id = t2.vendor_id SET t1.sync_status = IF(t1.act_percentage = IF(t2.sync_status & ? = 0 AND t3.deleted_at = ?, t3.price_percentage, 0), 0, ?), t1.act_percentage = IF(t2.sync_status & ? = 0 AND t3.deleted_at = ?, t3.price_percentage, 0), t1.updated_at = NOW(), t1.last_operator = ?, t1.hint_act_id = t2.act_id WHERE t1.vendor_id = ? AND (t4.sync_status & ? <> 0 OR t1.hint_act_id = t2.act_id) ` sqlParams := []interface{}{ act.ID, model.SyncFlagDeletedMask, utils.DefaultTimeValue, model.SyncFlagModifiedMask, model.SyncFlagDeletedMask, utils.DefaultTimeValue, ctx.GetUserName(), act.VendorID, model.SyncFlagNewMask, } _, err = dao.ExecuteSQL(dao.GetDB(), sql, sqlParams...) if err != nil { return err } sql = ` INSERT INTO store_sku_act(id, created_at, updated_at, last_operator, store_id, sku_id, vendor_id, act_percentage, sync_status, hint_act_id) SELECT 0, NOW(), NOW(), ?, t2.store_id, t2.sku_id, t2.vendor_id, t3.price_percentage, ?, t2.act_id FROM act_store_sku_map t2 JOIN act_store_sku t3 ON t3.store_id = t2.store_id AND t3.sku_id = t2.sku_id AND t3.act_id = t2.act_id LEFT JOIN store_sku_act t1 ON t2.store_id = t1.store_id AND t2.sku_id = t1.sku_id AND t2.vendor_id = t1.vendor_id WHERE t2.vendor_id = ? AND t2.act_id = ? AND t2.sync_status <> 0 AND t1.id IS NULL ` sqlParams = []interface{}{ ctx.GetUserName(), model.SyncFlagModifiedMask, act.VendorID, act.ID, } _, err = dao.ExecuteSQL(dao.GetDB(), sql, sqlParams...) if err != nil { return err } sql = ` UPDATE act_store_sku_map t2 SET t2.deleted_at = IF(t2.sync_status & ? = 0, t2.deleted_at, NOW()), t2.sync_status = 0 WHERE t2.vendor_id = ? AND t2.act_id = ? ` sqlParams = []interface{}{ model.SyncFlagDeletedMask, act.VendorID, act.ID, } _, err = dao.ExecuteSQL(dao.GetDB(), sql, sqlParams...) if err != nil { return err } sql = ` UPDATE act_map t4 SET t4.sync_status = 0 WHERE t4.vendor_id = ? AND t4.act_id = ? ` sqlParams = []interface{}{ act.VendorID, act.ID, } _, err = dao.ExecuteSQL(dao.GetDB(), sql, sqlParams...) if err != nil { return err } dao.Commit(db) return err } func SyncSpecialAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actOrderRules []*model.ActOrderRule, actStoreSkuList []*model.ActStoreSku2) (err error) { db := dao.GetDB() if err = SyncActStoreSku2StoreSkuAct(ctx, db, parentTask, act); err == nil { err = cms.FullSyncStoreSkuBindAct(ctx, parentTask, act.ID, nil, nil) } return err } func CreateActByExcel(ctx *jxcontext.Context, files string, vendorID int, vendorOrgCode string, mixType int, isFocus, isSync, isAsync, isContinueWhenError bool) (hint string, err error) { // if len(files) == 0 { // return "", errors.New("没有文件上传!") // } // fileHeader := files[0] // file, err := fileHeader.Open() hint, err = CreateActByExcelBin(ctx, files, vendorID, vendorOrgCode, mixType, isFocus, isSync, isAsync, isContinueWhenError) // file.Close() return hint, err } func CreateActByExcelBin(ctx *jxcontext.Context, reader string, vendorID int, vendorOrgCode string, mixType int, isFocus, isSync, isAsync, isContinueWhenError bool) (hint string, err error) { sheetParam := &SheetParamAct{ StoreIDCol: 0, SkuIDCol: 2, SkuPricePercentageCol: 4, ActPriceCol: 5, EarningPriceCol: 6, StockCol: 7, ActTypeCol: 8, ActPricePercentageCol: 9, ActNameCol: 10, BeginTimeCol: 11, EndTimeCol: 12, } taskSeqFunc := func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { var ( actObj = &model.Act{} actStoreSkuList []*ActStoreSkuParam actStoreSkusResult []*ActStoreSkuParam storeIDs []int ) switch step { case 0: //读取excel文件 xlsx, err := excelize.OpenFile("111.xlsx") // xlsx, err := excelize.OpenReader(reader) if err != nil { return result, err } rows, _ := xlsx.GetRows(xlsx.GetSheetName(1)) for rowNum, row := range rows { if rowNum < 1 { continue } loadExcelForCreateAct(rowNum, mixType, row, sheetParam, actObj, actStoreSkuList, storeIDs) } case 1: //叉乘 if mixType == 2 { for _, v := range storeIDs { for _, vv := range actStoreSkuList { actStoreSkus := &ActStoreSkuParam{} actStoreSkus = vv actStoreSkus.StoreID = v actStoreSkusResult = append(actStoreSkusResult, actStoreSkus) } } _, err = CreateAct(ctx, actObj, []int{vendorID}, vendorOrgCode, nil, actStoreSkusResult, isAsync) } else if mixType == 1 { _, err = CreateAct(ctx, actObj, []int{vendorID}, vendorOrgCode, nil, actStoreSkuList, isAsync) } } return result, err } taskSeq := tasksch.NewSeqTask2("根据Excel创建活动-序列任务", ctx, isContinueWhenError, taskSeqFunc, 5) tasksch.HandleTask(taskSeq, nil, true).Run() if !isAsync { _, err = taskSeq.GetResult(0) hint = "1" } else { hint = taskSeq.GetID() } return hint, err } func loadExcelForCreateAct(rowNum, mixType int, row []string, sheetParam *SheetParamAct, actObj *model.Act, actStoreSkuList []*ActStoreSkuParam, storeIDs []int) { actStoreSku := &ActStoreSkuParam{} for k, cell := range row { if rowNum == 1 { if k == sheetParam.ActTypeCol { var actType int for k, v := range model.ActTypeName { if cell == v { actType = k break } } actObj.Type = actType } if k == sheetParam.ActPricePercentageCol { actObj.PricePercentage = int(utils.Str2Float64(cell) * 10) } if k == sheetParam.ActNameCol { actObj.Name = cell } if k == sheetParam.BeginTimeCol { begin, err := time.Parse("2006年1月2日 15:04:05", cell) if err != nil { begin = utils.ZeroTimeValue } actObj.BeginAt = begin } if k == sheetParam.EndTimeCol { end, err := time.Parse("2006年1月2日 15:04:05", cell) if err != nil { end = utils.ZeroTimeValue } actObj.EndAt = end } } if k == sheetParam.StoreIDCol { //一行一行 if mixType == 1 { actStoreSku.StoreID = int(utils.Str2Int64(cell)) } else if mixType == 2 { storeIDs = append(storeIDs, int(utils.Str2Int64(cell))) } } if k == sheetParam.SkuIDCol { actStoreSku.SkuID = int(utils.Str2Int64(cell)) } if k == sheetParam.SkuPricePercentageCol { actStoreSku.PricePercentage = int(utils.Str2Float64WithDefault(cell, 0) * 10) } if k == sheetParam.ActPriceCol { actStoreSku.ActPrice = int64(utils.Str2Float64(cell) * 100) } if k == sheetParam.EarningPriceCol { actStoreSku.EarningPrice = int64(utils.Str2Float64(cell) * 100) } if k == sheetParam.StockCol { actStoreSku.Stock = int(utils.Str2Int64(cell)) } } actStoreSkuList = append(actStoreSkuList, actStoreSku) } func ChangeJxPriceByDiscountAct(ctx *jxcontext.Context) { var ( db = dao.GetDB() pageSize = 9999 ) globals.SugarLogger.Debug("ChangeJxPriceByDiscountAct begin:") page, err := dao.QueryActs(db, 0, 0, pageSize, -1, "", model.VendorIDJX, []int{model.YES}, []int{model.ActSkuDiscount}, nil, 0, nil, 0, utils.ZeroTimeValue, utils.ZeroTimeValue, time.Now().AddDate(0, -3, 0), time.Now()) if err != nil { return } if len(page.Data) == 0 { return } for _, act := range page.Data { _, actStoreSkus, _ := dao.GetActStoreSkuVendorList(db, act.ID, nil, nil, nil, "", 0, pageSize) if len(actStoreSkus) == 0 { continue } for _, actStoreSku2 := range actStoreSkus { actStoreSkuMap := &model.ActStoreSkuMap{ ModelIDCULD: model.ModelIDCULD{ ID: actStoreSku2.MapID, }, ActID: actStoreSku2.ActID, StoreID: actStoreSku2.StoreID, SkuID: actStoreSku2.SkuID, TrendPrice: actStoreSku2.TrendPrice, TrendType: actStoreSku2.TrendType, ActualActPrice: actStoreSku2.ActualActPrice, VendorPrice: actStoreSku2.VendorPrice, } storeSkus, _ := dao.GetStoresSkusInfo(db, []int{actStoreSkuMap.StoreID}, []int{actStoreSkuMap.SkuID}) if len(storeSkus) == 0 { continue } var ( storeSku = storeSkus[0] shouldStockOut = utils.Float64TwoInt64(math.Ceil(float64(storeSku.Stock) / float64(60))) //每个时间点应出货 = 总库存/60 = N actualStockOut int64 //每个时间点实际出货 = C pricePercentage float64 //每次涨跌值为 原价的 2%,即原价100,每个时间点降价为2,涨价也为2 actualPrice int64 minJxPrice int64 ) if storeSku.Stock == 0 { continue } actualStockOut, err = dao.GetOrderStoreSkusCount(db, actStoreSkuMap.StoreID, actStoreSkuMap.SkuID, time.Now().Add(-time.Minute*10), time.Now()) dao.Begin(db) defer func() { if r := recover(); r != nil || err != nil { dao.Rollback(db) if r != nil { panic(r) } } }() //第一档时间内 if (time.Now().Hour() >= 10 && time.Now().Hour() < 20) || (time.Now().Hour() == 20 && time.Now().Minute() < 1) { pricePercentage = 0.02 actualPrice = utils.Float64TwoInt64(float64(actStoreSkuMap.VendorPrice) * pricePercentage) if actStoreSkuMap.TrendType == model.TrendTypeUp { actStoreSkuMap.ActualActPrice = actStoreSkuMap.ActualActPrice + actualPrice // storeSku.JxPrice = storeSku.JxPrice + int(actualPrice) } else if actStoreSkuMap.TrendType == model.TrendTypeDown { actStoreSkuMap.ActualActPrice = actStoreSkuMap.ActualActPrice - actualPrice //判断活动的折扣类型是最低价还是最低折扣(第一档) if act.DiscountType == model.ActDiscountTypePrice { minJxPrice = int64(act.DiscountValue1) } else if act.DiscountType == model.ActDiscountTypePercentage { minJxPrice = actStoreSkuMap.VendorPrice * int64(act.DiscountValue1) / 100 } if actStoreSkuMap.ActualActPrice <= minJxPrice { actStoreSkuMap.ActualActPrice = minJxPrice } // storeSku.JxPrice = storeSku.JxPrice - int(actualPrice) stock := checkPriceDefendOrderByPrice(db, storeSku.StoreID, storeSku.SkuID, storeSku.Stock, int(actStoreSkuMap.ActualActPrice)) if stock != -1 { storeSku.Stock = stock if _, err = dao.UpdateEntity(db, storeSku, "Stock"); err != nil { dao.Rollback(db) } } } if actStoreSkuMap.ActualActPrice >= actStoreSkuMap.VendorPrice { actStoreSkuMap.ActualActPrice = actStoreSkuMap.VendorPrice } // if _, err = dao.UpdateEntity(db, actStoreSkuMap, "ActualActPrice"); err != nil { // dao.Rollback(db) // } //C >= 2N 涨价趋势,最高涨价到无折扣 if actualStockOut >= 2*shouldStockOut { actStoreSkuMap.TrendType = model.TrendTypeUp if actStoreSkuMap.ActualActPrice == actStoreSkuMap.VendorPrice { actStoreSkuMap.TrendType = model.TrendTypeNothing } } else if actualStockOut < utils.Float64TwoInt64(math.Ceil(float64(shouldStockOut)/float64(2))) { //C <= N/2 降价趋势,最低降价到设置到最低折扣 actStoreSkuMap.TrendType = model.TrendTypeDown if actStoreSkuMap.ActualActPrice == minJxPrice { actStoreSkuMap.TrendType = model.TrendTypeNothing } } else { actStoreSkuMap.TrendType = model.TrendTypeNothing } actStoreSkuMap.TrendPrice = int(actualPrice) if _, err = dao.UpdateEntity(db, actStoreSkuMap, "ActualActPrice", "TrendType", "TrendPrice"); err != nil { dao.Rollback(db) } } else { //第二档时间内 pricePercentage = 0.1 actualPrice = utils.Float64TwoInt64(float64(actStoreSkuMap.VendorPrice) * pricePercentage) //判断活动的折扣类型是最低价还是最低折扣(第二档) if act.DiscountType == model.ActDiscountTypePrice { minJxPrice = int64(act.DiscountValue2) } else if act.DiscountType == model.ActDiscountTypePercentage { minJxPrice = actStoreSkuMap.VendorPrice * int64(act.DiscountValue2) / 100 } if actStoreSkuMap.ActualActPrice == minJxPrice { actStoreSkuMap.TrendType = model.TrendTypeNothing } else { actStoreSkuMap.TrendType = model.TrendTypeDown } actStoreSkuMap.TrendPrice = int(actualPrice) // if _, err = dao.UpdateEntity(db, actStoreSkuMap, "TrendType", "TrendPrcie"); err != nil { // dao.Rollback(db) // } actStoreSkuMap.ActualActPrice = actStoreSkuMap.ActualActPrice - actualPrice if actStoreSkuMap.ActualActPrice <= minJxPrice { actStoreSkuMap.ActualActPrice = minJxPrice } stock := checkPriceDefendOrderByPrice(db, storeSku.StoreID, storeSku.SkuID, storeSku.Stock, int(actStoreSkuMap.ActualActPrice)) if stock != -1 { storeSku.Stock = stock if _, err = dao.UpdateEntity(db, storeSku, "Stock"); err != nil { dao.Rollback(db) } } //22:00 恢复库存为100,价格恢复原价 if time.Now().Hour() == 22 && time.Now().Minute() < 1 { storeSku.Stock = 100 if _, err = dao.UpdateEntity(db, storeSku, "Stock"); err != nil { dao.Rollback(db) } actStoreSkuMap.ActualActPrice = actStoreSkuMap.VendorPrice actStoreSkuMap.TrendType = model.TrendTypeNothing actStoreSkuMap.TrendPrice = 0 } if _, err = dao.UpdateEntity(db, actStoreSkuMap, "ActualActPrice", "TrendType", "TrendPrice"); err != nil { dao.Rollback(db) } } dao.Commit(db) } } } func checkPriceDefendOrderByPrice(db *dao.DaoDB, storeID, skuID, stock, jxPrice int) (realStock int) { priceDefends, _ := dao.GetPriceDefendOrder(db, "", []int{storeID}, []int{skuID}, []int{jxutils.GetDefendPriceIssue()}, 0, 0, 0, 1, "", utils.ZeroTimeValue, utils.ZeroTimeValue, false) if len(priceDefends) == 0 { return -1 } for _, v := range priceDefends { //如果刚好守的价和降的价一样,再判断库存够不够 if v.DefendPrice > int64(jxPrice) { if v.Count <= stock { stock -= v.Count v.IsSuccess = model.YES v.RealPrice = int64(jxPrice) dao.UpdateEntity(db, v, "IsSuccess", "RealPrice") } else { continue } } else { continue } } return stock } func GetVendorPopActs(ctx *jxcontext.Context, vendorID, storeID int) (result interface{}, err error) { if vendorID == model.VendorIDEBAI { result, err = api.EbaiAPI.GetMainActivityList(utils.Str2Int(ebai.EbaiSupplierIDhc)) } else if vendorID == model.VendorIDMTWM { if storeID == 0 { return nil, fmt.Errorf("要选门店才能查美团的活动") } store, _ := dao.GetStoreDetail(dao.GetDB(), storeID, vendorID, "") result, err = api.MtwmAPI.GetCenterList(store.VendorStoreID) } else { return nil, fmt.Errorf("暂不支持此平台") } return result, err } func GetVendorPopActDetail(ctx *jxcontext.Context, vendorID, storeID, actID int) (result interface{}, err error) { if vendorID == model.VendorIDEBAI { results, err2 := api.EbaiAPI.GetMainActivityDetail(actID) err = err2 for _, v := range results { results2, _ := api.EbaiAPI.GetSubActivityDetail(int(v.ID)) v.GetSubActivityDetailResult = results2 } result = results } else if vendorID == model.VendorIDMTWM { if storeID == 0 { return nil, fmt.Errorf("要选门店才能查美团的活动") } store, _ := dao.GetStoreDetail(dao.GetDB(), storeID, vendorID, "") result, err = api.MtwmAPI.GetInviteDetail(actID, store.VendorStoreID) } else { return nil, fmt.Errorf("暂不支持此平台") } return result, err } func GetNewVendorPopActs(ctx *jxcontext.Context) (err error) { actList, err := api.EbaiAPI.GetMainActivityList(utils.Str2Int(ebai.EbaiSupplierIDhc)) if err != nil { return err } send := func(vendorID int, name1, name2 string) { var ( roleList []*authz.RoleInfo opName = "" userIDs []string ) if vendorID == model.VendorIDEBAI { opName = "lirongwei" } else { opName = "xiaoqiu" } roleList = append(roleList, autils.NewRole(opName, 0)) if userIDMap, err := cms.GetRolesUserList(jxcontext.AdminCtx, roleList); err == nil { for _, v := range userIDMap { for _, vv := range v { userIDs = append(userIDs, vv) } } } for _, v := range userIDs { ddmsg.SendUserMessage(dingdingapi.MsgTyeText, v, model.VendorNames[vendorID]+"有新平台流量活动了!", model.VendorNames[vendorID]+"有未报名的平台流量活动,请查看!活动名:"+name1+"->"+name2) } } for _, v := range actList { results, _ := api.EbaiAPI.GetMainActivityDetail(int(v.ActivityID)) for _, vv := range results { local, _ := time.LoadLocation("Local") t, _ := time.ParseInLocation("2006-01-02 15:04:05", vv.SigninEndTime, local) if time.Now().Sub(t) < 0 { result, _ := api.EbaiAPI.GetSubActivityDetail(int(vv.ID)) if result.Status.Value == "NO_SIGN_UP" { send(model.VendorIDEBAI, vv.Name, result.Activity.Name) } } } } return err }