diff --git a/business/jxstore/cms/cms.go b/business/jxstore/cms/cms.go index 654e76d99..7e332ff51 100644 --- a/business/jxstore/cms/cms.go +++ b/business/jxstore/cms/cms.go @@ -46,6 +46,8 @@ func InitServiceInfo(version, buildDate, gitCommit string) { "orderTypeName": model.OrderTypeName, "storeDeliveryTypeName": scheduler.StoreDeliveryTypeName, "taskStatusName": tasksch.TaskStatusName, + "opRequestType": model.RequestTypeName, + "opRequestStatusName": model.RequestStatusName, }, } Init() diff --git a/business/jxstore/cms/cms_test.go b/business/jxstore/cms/cms_test.go index c295f981b..b5a8371be 100644 --- a/business/jxstore/cms/cms_test.go +++ b/business/jxstore/cms/cms_test.go @@ -21,7 +21,7 @@ func init() { } func TestGetQiniuUploadToken(t *testing.T) { - token, err := GetQiniuUploadToken(jxcontext.AdminCtx, "") + token, err := GetQiniuUploadToken(jxcontext.AdminCtx, "", "") if err != nil { t.Fatal(err) } diff --git a/business/jxstore/cms/store_sku.go b/business/jxstore/cms/store_sku.go index 53e448a80..4a37a1505 100644 --- a/business/jxstore/cms/store_sku.go +++ b/business/jxstore/cms/store_sku.go @@ -8,6 +8,7 @@ import ( "time" "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-callback/business/jxcallback/auth/weixin" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" "git.rosy.net.cn/jx-callback/business/model" @@ -71,6 +72,13 @@ type SkuSaleInfo struct { Count int // 销售的总份数 } +type StoreOpRequestInfo struct { + model.StoreOpRequest + StoreName string + SkuNamePrefix string + SkuNameName string +} + // 商品不可售,直接排除 // 如果门店商品是可售状态,那么会忽略区域限制。否则有区域限制 func GetStoreSkus(ctx *jxcontext.Context, storeID int, isFocus bool, keyword string, params map[string]interface{}, offset, pageSize int) (skuNamesInfo *StoreSkuNamesInfo, err error) { @@ -426,10 +434,15 @@ func checkStoresSkusSaleCity(ctx *jxcontext.Context, db *dao.DaoDB, storeIDs []i } func updateStoresSkusWithoutSync(ctx *jxcontext.Context, storeIDs []int, skuBindInfos []*StoreSkuBindInfo) (needSyncSkus []int, err error) { + globals.SugarLogger.Debugf("updateStoresSkusWithoutSync, storeIDs:%v, skuBindInfos:%s", storeIDs, utils.Format4Output(skuBindInfos, false)) db := dao.GetDB() if err = checkStoresSkusSaleCity(ctx, db, storeIDs, skuBindInfos); err != nil { return nil, err } + if storeIDs, skuBindInfos, err = filterStorePriceChange(ctx, storeIDs, skuBindInfos); err != nil { + return nil, err + } + userName := ctx.GetUserName() needSyncIDMap := make(map[int]int) dao.Begin(db) @@ -450,7 +463,7 @@ func updateStoresSkusWithoutSync(ctx *jxcontext.Context, storeIDs []int, skuBind JOIN sku_name t3 ON t1.name_id = t3.id AND t3.deleted_at = ? WHERE t1.name_id = ? AND t1.deleted_at = ? `, storeID, utils.DefaultTimeValue, utils.DefaultTimeValue, skuBindInfo.NameID, utils.DefaultTimeValue); err == nil { - globals.SugarLogger.Debug(utils.Format4Output(allBinds, false)) + // globals.SugarLogger.Debug(utils.Format4Output(allBinds, false)) inSkuBinsMap := make(map[int]*StoreSkuBindSkuInfo, len(inSkuBinds)) for _, v := range inSkuBinds { inSkuBinsMap[v.SkuID] = v @@ -832,6 +845,214 @@ func CopyStoreSkus(ctx *jxcontext.Context, fromStoreID, toStoreID int, copyMode return num, err } +func shouldPendingStorePriceChange(ctx *jxcontext.Context, storeID int, skuBindInfo *StoreSkuBindInfo) bool { + return globals.EnablePendingChange && ctx.GetLoginType() == weixin.LoginType +} + +func filterStorePriceChange(ctx *jxcontext.Context, storeIDs []int, skuBindInfos []*StoreSkuBindInfo) (filteredStoreIDs []int, filteredSkuBindInfos []*StoreSkuBindInfo, err error) { + globals.SugarLogger.Debug("filterStorePriceChange") + if globals.EnablePendingChange { + db := dao.GetDB() + dao.Begin(db) + defer dao.Rollback(db) + for _, storeID := range storeIDs { + for _, skuBindInfo := range skuBindInfos { + shouldPending := shouldPendingStorePriceChange(ctx, storeID, skuBindInfo) + if shouldPending { + changeReq := &model.StoreOpRequest{ + Type: model.RequestTypeChangePrice, + StoreID: storeID, + ItemID: skuBindInfo.NameID, + Status: model.RequestStatusNew, + UserID: ctx.GetUserName(), + IntParam1: skuBindInfo.UnitPrice, + IntParam2: skuBindInfo.IsSale, + } + if skuBindInfo.IsFocus == 1 { + changeReq.Type = model.RequestTypeFocusSkuName + } + if len(skuBindInfo.Skus) > 0 { + changeReq.JsonParam = string(utils.MustMarshal(skuBindInfo.Skus)) + } + + dao.WrapAddIDCULDEntity(changeReq, ctx.GetUserName()) + if err = dao.CreateOrUpdate(db, changeReq); err != nil { + return nil, nil, err + } + + // 去除价格相关的部分 + if skuBindInfo.IsFocus == 1 { + skuBindInfo.IsFocus = 0 + } + skuBindInfo.UnitPrice = 0 + } + } + } + dao.Commit(db) + } + return storeIDs, skuBindInfos, nil +} + +func AcceptStoreOpRequests(ctx *jxcontext.Context, reqIDs []int) (err error) { + if globals.EnablePendingChange { + if len(reqIDs) > 0 { + subErrors := make(map[int]error) + db := dao.GetDB() + for _, reqID := range reqIDs { + op := &model.StoreOpRequest{} + op.ID = reqID + if err2 := dao.GetEntity(db, op); err2 != nil { + subErrors[reqID] = err2 + } else { + if op.Status == model.RequestStatusNew { + skuBindInfo := &StoreSkuBindInfo{ + NameID: op.ItemID, + UnitPrice: op.IntParam1, + IsSale: op.IntParam2, + } + if op.Type == model.RequestTypeFocusSkuName { + skuBindInfo.IsFocus = 1 + } + if op.JsonParam != "" { + if err2 = utils.UnmarshalUseNumber([]byte(op.JsonParam), &skuBindInfo.Skus); err2 != nil { + subErrors[reqID] = err2 + } + } + if err2 == nil { + _, err2 := UpdateStoresSkus(ctx, []int{op.StoreID}, []*StoreSkuBindInfo{skuBindInfo}) + isLocalSucess := true + if err2 != nil { + subErrors[reqID] = err2 + if !isSyncError(err2) { + isLocalSucess = false + } + } + if isLocalSucess { + if err2 := changeStoreOpStatus(ctx, []int{reqID}, model.RequestStatusAccepted, ""); err2 != nil { + subErrors[reqID] = err2 + } + } + } + } + } + } + if len(subErrors) > 0 { + errMsg := "" + for k, v := range subErrors { + errMsg += fmt.Sprintf("req:%d, error:%s\n", k, v.Error()) + } + err = errors.New(errMsg) + } + } + } + return err +} + +func RejectStoreOpRequests(ctx *jxcontext.Context, reqIDs []int, rejectReason string) (err error) { + return changeStoreOpStatus(ctx, reqIDs, model.RequestStatusRejected, rejectReason) +} + +// 当前些函数只针对type为: RequestTypeChangePrice与RequestTypeFocusSkuName的查询才有效 +func GetStoreOpRequests(ctx *jxcontext.Context, fromTime, toTime time.Time, keyword string, params map[string]interface{}, offset, pageSize int) (requestList []*StoreOpRequestInfo, err error) { + if globals.EnablePendingChange { + sql := ` + SELECT t1.id, t1.created_at, t1.updated_at, t1.last_operator, t1.deleted_at, + t1.type, t1.store_id, t1.item_id, t1.status, t1.user_id, t1.int_param1, t1.int_param2, + t2.name store_name, t3.prefix sku_name_prefix, t3.name sku_name_name, AVG(t5.unit_price) unit_price + FROM store_op_request t1 + JOIN store t2 ON t1.store_id = t2.id + JOIN sku_name t3 ON t1.item_id = t3.id + JOIN sku t4 ON t3.id = t4.name_id + LEFT JOIN store_sku_bind t5 ON t1.store_id = t5.store_id AND t4.id = t5.sku_id AND t5.deleted_at = ? + WHERE t1.created_at >= ? AND t1.created_at <= ? + ` + sqlParams := []interface{}{ + utils.DefaultTimeValue, + fromTime, + toTime, + } + if keyword != "" { + keywordLike := "%" + keyword + "%" + sql += " AND ( t2.name LIKE ? OR t3.name LIKE ?" + sqlParams = append(sqlParams, keywordLike, keywordLike) + if keywordInt64, err2 := strconv.ParseInt(keyword, 10, 64); err2 == nil { + sql += " OR t1.store_id = ? OR t1.item_id = ?" + sqlParams = append(sqlParams, keywordInt64, keywordInt64) + } + sql += ")" + } + if params["storeIDs"] != nil { + var storeIDs []int + if err = utils.UnmarshalUseNumber([]byte(params["storeIDs"].(string)), &storeIDs); err != nil { + return nil, err + } + if len(storeIDs) > 0 { + sql += " AND t1.store_id IN (" + dao.GenQuestionMarks(len(storeIDs)) + ")" + sqlParams = append(sqlParams, storeIDs) + } + } + if params["types"] != nil { + var typeList []int + if err = utils.UnmarshalUseNumber([]byte(params["types"].(string)), &typeList); err != nil { + return nil, err + } + if len(typeList) > 0 { + sql += " AND t1.type IN (" + dao.GenQuestionMarks(len(typeList)) + ")" + sqlParams = append(sqlParams, typeList) + } + } + if params["statuss"] != nil { + var statusList []int + if err = utils.UnmarshalUseNumber([]byte(params["statuss"].(string)), &statusList); err != nil { + return nil, err + } + if len(statusList) > 0 { + sql += " AND t1.status IN (" + dao.GenQuestionMarks(len(statusList)) + ")" + sqlParams = append(sqlParams, statusList) + } + } + sql += ` + GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 + LIMIT ? OFFSET ?` + pageSize = jxutils.FormalizePageSize(pageSize) + sqlOffset := offset + sqlPageSize := pageSize + sqlParams = append(sqlParams, sqlPageSize, sqlOffset) + db := dao.GetDB() + // globals.SugarLogger.Debug(sql) + // globals.SugarLogger.Debug(utils.Format4Output(sqlParams, false)) + if err = dao.GetRows(db, &requestList, sql, sqlParams...); err == nil { + return requestList, nil + } + } + return nil, err +} + +func changeStoreOpStatus(ctx *jxcontext.Context, reqIDs []int, status int8, rejectReason string) (err error) { + globals.SugarLogger.Debugf("changeStoreOpStatus, reqIDs:%v", reqIDs) + if globals.EnablePendingChange { + if len(reqIDs) > 0 { + db := dao.GetDB() + dao.Begin(db) + defer dao.Rollback(db) + for _, reqID := range reqIDs { + op := &model.StoreOpRequest{} + op.Remark = rejectReason + op.Status = status + dao.WrapUpdateULEntity(op, ctx.GetUserName()) + op.DeletedAt = time.Now() + op.ID = reqID + // globals.SugarLogger.Debug(utils.Format4Output(op, false)) + if _, err = dao.UpdateEntity(db, op, "Remark", "Status", "DeletedAt", "LastOperator", "UpdatedAt"); err != nil { + return err + } + } + dao.Commit(db) + } + } + return err +} + func setStoreSkuBindStatus(skuBind *model.StoreSkuBind, status int8) { skuBind.JdSyncStatus |= status skuBind.ElmSyncStatus |= status diff --git a/business/jxstore/cms/sync.go b/business/jxstore/cms/sync.go index 0a179e31c..e88c59ef5 100644 --- a/business/jxstore/cms/sync.go +++ b/business/jxstore/cms/sync.go @@ -477,3 +477,8 @@ func makeSyncError(err error) (newErr error) { func (e *SyncError) Error() string { return fmt.Sprintf("本地数据修改成功,但同步失败,请根据错误提示处理!,同步错误信息:%s", e.Original.Error()) } + +func isSyncError(err error) bool { + _, ok := err.(*SyncError) + return ok +} diff --git a/business/jxutils/jxcontext/jxcontext.go b/business/jxutils/jxcontext/jxcontext.go index e5a8f6f88..05fd4389b 100644 --- a/business/jxutils/jxcontext/jxcontext.go +++ b/business/jxutils/jxcontext/jxcontext.go @@ -71,6 +71,13 @@ func (ctx *Context) GetUserName() string { return userName } +func (ctx *Context) GetLoginType() string { + if ctx.userInfo != nil { + return ctx.userInfo.LoginType + } + return "" +} + func (ctx *Context) GetUserID() string { return ctx.token } diff --git a/business/model/dao/dao.go b/business/model/dao/dao.go index 31964af0d..b1a34bf43 100644 --- a/business/model/dao/dao.go +++ b/business/model/dao/dao.go @@ -137,6 +137,14 @@ func CreateEntity(db *DaoDB, item interface{}) (err error) { return err } +func CreateOrUpdate(db *DaoDB, item interface{}, colConflitAndArgs ...string) (err error) { + if db == nil { + db = GetDB() + } + _, err = db.db.InsertOrUpdate(item, colConflitAndArgs...) + return err +} + func DeleteEntity(db *DaoDB, item interface{}, cols ...string) (num int64, err error) { if db == nil { db = GetDB() diff --git a/business/model/store_sku.go b/business/model/store_sku.go index 9443bf168..03f986967 100644 --- a/business/model/store_sku.go +++ b/business/model/store_sku.go @@ -11,6 +11,29 @@ const ( MaxStoreSkuStockQty = 99999 ) +const ( + RequestTypeChangePrice = 1 + RequestTypeFocusSkuName = 2 +) + +const ( + RequestStatusNew = 0 + RequestStatusRejected = 1 + RequestStatusAccepted = 2 +) + +var ( + RequestTypeName = map[int]string{ + RequestTypeChangePrice: "更改价格", + RequestTypeFocusSkuName: "关注商品", + } + RequestStatusName = map[int]string{ + RequestStatusNew: "待审核", + RequestStatusRejected: "拒绝", + RequestStatusAccepted: "已批准", + } +) + type StoreSkuCategoryMap struct { ModelIDCULD @@ -57,3 +80,23 @@ func (*StoreSkuBind) TableUnique() [][]string { []string{"StoreID", "SkuID", "DeletedAt"}, } } + +type StoreOpRequest struct { + ModelIDCULD // DeletedAt用于表示请求操作结束,而并不一定是删除 + + Type int8 + StoreID int `orm:"column(store_id)"` + ItemID int `orm:"column(item_id)"` // 这个根据type不同,可能是SKUNAME ID或SKU ID + Status int8 + UserID string `orm:"size(48);column(user_id)"` + IntParam1 int + IntParam2 int + JsonParam string `orm:"size(3000)"` + Remark string `orm:"size(255)"` +} + +func (*StoreOpRequest) TableUnique() [][]string { + return [][]string{ + []string{"StoreID", "Type", "ItemID", "DeletedAt"}, + } +} diff --git a/conf/app.conf b/conf/app.conf index 58e68a6c4..24f3f1bb1 100644 --- a/conf/app.conf +++ b/conf/app.conf @@ -25,6 +25,7 @@ enableElmStoreWrite = true enableMtwmStoreWrite = false orderUseNewTable = true +enablePendingChange = true aliKey = "LTAI6xJUGaP6WdMQ" aliSecret = "CLmx5T93Bgi89EGAxWM4RTAXUsiHbM" @@ -89,6 +90,7 @@ enableStoreWrite = true enableEbaiStoreWrite = true enableMtwmStoreWrite = true +enablePendingChange = false [prod2] httpport = 8082 diff --git a/controllers/cms_store_sku.go b/controllers/cms_store_sku.go index cf3d5ef5c..9b8454940 100644 --- a/controllers/cms_store_sku.go +++ b/controllers/cms_store_sku.go @@ -1,6 +1,7 @@ package controllers import ( + "fmt" "math" "time" @@ -213,3 +214,57 @@ func (c *StoreSkuController) GetStoresSkusSaleInfo() { return retVal, "", err }) } + +// @Title 得到商家商品修改价格请求信息 +// @Description 得到商家商品修改价格请求信息 +// @Param token header string true "认证token" +// @Param fromTime query string true "申请开始时间" +// @Param toTime query string false "申请结束时间" +// @Param keyword query string false "查询关键字(可以为空,为空表示不限制)" +// @Param storeIDs query string false "门店ID列表" +// @Param itemIDs query string false "id列表对象" +// @Param types query string false "类型列表对象" +// @Param statuss query string false "状态列表对象" +// @Param offset query int false "门店列表起始序号(以0开始,缺省为0)" +// @Param pageSize query int false "门店列表页大小(缺省为50,-1表示全部)" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /GetStoreOpRequests [get] +func (c *StoreSkuController) GetStoreOpRequests() { + c.callGetStoreOpRequests(func(params *tStoreSkuGetStoreOpRequestsParams) (retVal interface{}, errCode string, err error) { + var ( + timeList []time.Time + ) + if timeList, err = jxutils.BatchStr2Time(params.FromTime, params.ToTime); err != nil { + return retVal, "", err + } + retVal, err = cms.GetStoreOpRequests(params.Ctx, timeList[0], timeList[1], params.Keyword, params.MapData, params.Offset, params.PageSize) + return retVal, "", err + }) +} + +// @Title 处理商家商品价格申请 +// @Description 处理商家商品价格申请 +// @Param token header string true "认证token" +// @Param reqIDs formData string true "请求ID列表对象" +// @Param handleType formData int true "-1拒绝,1批准" +// @Param rejectReason formData string false "拒绝理由,拒绝时要求" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /HandleStoreOpRequest [put] +func (c *StoreSkuController) HandleStoreOpRequest() { + c.callHandleStoreOpRequest(func(params *tStoreSkuHandleStoreOpRequestParams) (retVal interface{}, errCode string, err error) { + var reqIDs []int + if err = utils.UnmarshalUseNumber([]byte(params.ReqIDs), &reqIDs); err != nil { + return retVal, "", err + } + if params.HandleType == 1 { + err = cms.AcceptStoreOpRequests(params.Ctx, reqIDs) + } else if params.HandleType == -1 { + err = cms.RejectStoreOpRequests(params.Ctx, reqIDs, params.RejectReason) + } else { + err = fmt.Errorf("handleType=%d是非法值", params.HandleType) + } + return retVal, "", err + }) +} diff --git a/globals/beegodb/beegodb.go b/globals/beegodb/beegodb.go index 1e7cafa99..2601db738 100644 --- a/globals/beegodb/beegodb.go +++ b/globals/beegodb/beegodb.go @@ -37,6 +37,10 @@ func Init() { orm.RegisterModel(&model.Promotion{}, &model.PromotionStore{}, &model.PromotionSku{}) } + + if globals.EnablePendingChange { + orm.RegisterModel(&model.StoreOpRequest{}) + } // create table orm.RunSyncdb("default", false, true) } diff --git a/globals/globals.go b/globals/globals.go index 756edbc21..4ec63597c 100644 --- a/globals/globals.go +++ b/globals/globals.go @@ -28,6 +28,7 @@ var ( EnableEbaiStoreWrite bool EnableElmStoreWrite bool EnableMtwmStoreWrite bool + EnablePendingChange bool OrderUseNewTable bool @@ -59,6 +60,7 @@ func Init() { EnableEbaiStoreWrite = beego.AppConfig.DefaultBool("enableEbaiStoreWrite", false) EnableElmStoreWrite = beego.AppConfig.DefaultBool("enableElmStoreWrite", false) EnableMtwmStoreWrite = beego.AppConfig.DefaultBool("enableMtwmStoreWrite", false) + EnablePendingChange = beego.AppConfig.DefaultBool("enablePendingChange", false) if EnableStore { OrderUseNewTable = beego.AppConfig.DefaultBool("orderUseNewTable", false) diff --git a/routers/commentsRouter_controllers.go b/routers/commentsRouter_controllers.go index e9429833d..d9cded737 100644 --- a/routers/commentsRouter_controllers.go +++ b/routers/commentsRouter_controllers.go @@ -599,6 +599,14 @@ func init() { MethodParams: param.Make(), Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], + beego.ControllerComments{ + Method: "GetStoreOpRequests", + Router: `/GetStoreOpRequests`, + AllowHTTPMethods: []string{"get"}, + MethodParams: param.Make(), + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], beego.ControllerComments{ Method: "GetStoreSkus", @@ -615,6 +623,14 @@ func init() { MethodParams: param.Make(), Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], + beego.ControllerComments{ + Method: "HandleStoreOpRequest", + Router: `/HandleStoreOpRequest`, + AllowHTTPMethods: []string{"put"}, + MethodParams: param.Make(), + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], beego.ControllerComments{ Method: "SyncStoresSkus",