package cms import ( "bytes" "errors" "fmt" "io" "math" "mime/multipart" "sort" "strconv" "strings" "time" "unicode/utf8" "git.rosy.net.cn/jx-callback/globals/refutil" "github.com/360EntSecGroup-Skylar/excelize" "git.rosy.net.cn/baseapi" "git.rosy.net.cn/jx-callback/business/authz/autils" "git.rosy.net.cn/jx-callback/business/jxcallback/orderman" "git.rosy.net.cn/baseapi/platformapi/autonavi" "git.rosy.net.cn/baseapi/platformapi/baidunavi" "git.rosy.net.cn/baseapi/platformapi/dingdingapi" "git.rosy.net.cn/baseapi/platformapi/jdapi" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/baseapi/utils/errlist" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/jxutils/ddmsg" "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/msg" "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/business/model/legacymodel" "git.rosy.net.cn/jx-callback/business/partner" "git.rosy.net.cn/jx-callback/business/partner/purchase/ebai" "git.rosy.net.cn/jx-callback/business/partner/purchase/jd" "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/globals/api" ) const ( GET_BAD_COMMENTS_TYPE = 0 //获取差评的标志 GET_ALL_COMMENTS_TYPE = 1 //获取所有评论的标志 GET_FIXED_COMMENTS_TYPE = 2 //获取已解决评论的标志 ) type StoreExt struct { model.Store MarketManName string `orm:"size(8)" json:"marketManName"` // 市场负责人姓名 OperatorName string `orm:"size(8)" json:"operatorName"` // 运营人姓名 OperatorName2 string `orm:"size(8)" json:"operatorName2"` // 非京东运营人姓名 FloatLng float64 `json:"lng"` FloatLat float64 `json:"lat"` ProvinceCode int `json:"provinceCode"` ProvinceName string `json:"provinceName"` CityName string `json:"cityName"` DistrictName string `json:"districtName"` StoreMapStr string `json:"-"` CourierMapStr string `json:"-"` PayeeBankName string `json:"payeeBankName"` // 开户行名称 // StoreMaps []map[string]interface{} `orm:"-"` // CourierMaps []map[string]interface{} `orm:"-"` StoreMaps []*model.StoreMap `json:"StoreMaps"` CourierMaps []*model.StoreCourierMap `json:"CourierMaps"` OrderCount int `json:"orderCount"` } type Store4User struct { model.ModelIDCULD OriginalName string `orm:"-" json:"originalName"` Name string `orm:"size(255)" json:"name"` OpenTime1 int16 `json:"openTime1"` // 930就表示9点半,用两个的原因是为了支持中午休息,1与2的时间段不能交叉,为0表示没有 CloseTime1 int16 `json:"closeTime1"` // 格式同上 OpenTime2 int16 `json:"openTime2"` // 格式同上 CloseTime2 int16 `json:"closeTime2"` // 格式同上 Status int `json:"status"` CityCode int `orm:"default(0);null" json:"cityCode"` // todo ? DistrictCode int `orm:"default(0);null" json:"districtCode"` // todo ? Address string `orm:"size(255)" json:"address"` Tel1 string `orm:"size(32);index" json:"tel1"` Tel2 string `orm:"size(32);index" json:"tel2"` Lng int `json:"-"` // 乘了10的6次方 Lat int `json:"-"` // 乘了10的6次方 DeliveryRangeType int8 `json:"deliveryRangeType"` // 参见相关常量定义 DeliveryRange string `orm:"type(text)" json:"deliveryRange"` // 如果DeliveryRangeType为DeliveryRangeTypePolygon,则为逗号分隔坐标,分号分隔的坐标点(坐标与Lng和Lat一样,都是整数),比如 121361504,31189308;121420555,31150238。否则为半径,单位为米 FloatLng float64 `json:"lng"` FloatLat float64 `json:"lat"` CityName string `json:"cityName"` DistrictName string `json:"districtName"` Distance int `json:"distance"` WalkDistance int `json:"walkDistance"` } type Store4UserList []*Store4User func (x Store4UserList) Len() int { return len(x) } func (x Store4UserList) Less(i, j int) bool { if x[i].Status != x[j].Status { return x[i].Status > x[j].Status } if x[i].WalkDistance != x[j].WalkDistance { return x[i].WalkDistance < x[j].WalkDistance } return x[i].Distance < x[j].Distance } func (x Store4UserList) Swap(i, j int) { x[i], x[j] = x[j], x[i] } type StoresInfo struct { TotalCount int `json:"totalCount"` MapCenterLng float64 `json:"mapCenterLng"` MapCenterLat float64 `json:"mapCenterLat"` Stores []*StoreExt `json:"stores"` } type JxBadCommentsExt struct { legacymodel.JxBadComments StoreName string `json:"storeName"` CityName string `json:"cityName"` VendorOrderID2 string `orm:"column(vendor_order_id2);size(48);index" json:"vendorOrderID2"` } type VendorStoreExcel struct { StoreID int `json:"门店ID"` VendorStoreName string `json:"门店名"` Status string `json:"营业状态"` Tel1 string `json:"电话1"` Tel2 string `json:"电话2"` CityName string `json:"城市名"` Address string `json:"地址"` MarketManName string `json:"市场负责人"` OperatorName string `json:"运营负责人"` OperatorName2 string `json:"运营负责人2"` } type JdStoreLevelExt struct { Level string `json:"level` PageNo int `json:"pageNo"` } var ( ErrMissingInput = errors.New("没有有效的输入参数") ErrCanNotFindVendor = errors.New("vendorID参数不合法") ) var ( storeKeyPropertyMap = map[string]int{ "name": 1, "cityCode": 1, "districtCode": 1, "address": 1, "tel1": 1, "tel2": 1, "openTime1": 1, "closeTime1": 1, "openTime2": 1, "closeTime2": 1, "lng": 1, "lat": 1, "deliveryRangeType": 1, "deliveryRange": 1, "status": 1, "promoteInfo": 1, } storeMapKeyPropertyMap = map[string]int{ "status": 1, "freightDeductionPack": 1, "vendorStoreName": 1, "boxFee": 1, } WatchVendorStoreTimeList = []string{ "8:00:00", "10:00:00", "11:00:00", "15:00:00", "20:00:00", } titleListStore = []string{ "门店ID", "门店名", "营业状态", "电话1", "电话2", "城市名", "地址", "市场负责人", "运营负责人", "运营负责人2", } roleMap = map[string]string{ "marketManPhone": "市场负责人电话", "marketManRole": "市场负责人组(角色,单人)", "jxBrandFeeFactor": "京西品牌费因子", "marketAddFeeFactor": "市场附加费因子", "payeeName": "收款人姓名", "payeeAccountNo": "收款账号", "payeeBankBranchName": "开户支行", "payeeBankCode": "开户行代码", "payPercentage": "支付比例", } roleMoblieMap = map[string]string{ "17380734342": "17380734342", //漆云的手机 ,用于判断updatestore的权限 "18328080405": "18328080405", //肖娜娜的手机 "13350726500": "13350726500", //谭翔心 "15928865396": "15928865396", //何佳梦 "18048531223": "18048531223", //石老板 } ) func getStoresSql(ctx *jxcontext.Context, keyword string, params map[string]interface{}, orderTimeFrom, orderTimeTo time.Time) (sql string, sqlParams []interface{}, sqlFrom string, sqlFromParams []interface{}, err error) { sqlFrom = ` FROM store t1 LEFT JOIN new_config bank ON bank.deleted_at = ? AND bank.type = ? AND bank.key = t1.payee_bank_code LEFT JOIN place city ON t1.city_code = city.code AND city.level = 2 LEFT JOIN place province ON province.code = city.parent_code AND province.level = 1 LEFT JOIN place district ON t1.district_code = district.code AND district.level = 3 /* LEFT JOIN store_map m1 ON t1.id = m1.store_id AND m1.deleted_at = ? LEFT JOIN store_courier_map m2 ON t1.id = m2.store_id AND m2.deleted_at = ? */ LEFT JOIN user mm ON mm.mobile <> '' AND mm.mobile = t1.market_man_phone AND mm.deleted_at = ? LEFT JOIN user om ON om.mobile <> '' AND om.mobile = t1.operator_phone AND om.deleted_at = ? LEFT JOIN user om2 ON om2.mobile <> '' AND om2.mobile = t1.operator_phone2 AND om2.deleted_at = ? ` sqlFromParams = []interface{}{ utils.DefaultTimeValue, model.ConfigTypeBank, // utils.DefaultTimeValue, // utils.DefaultTimeValue, utils.DefaultTimeValue, utils.DefaultTimeValue, utils.DefaultTimeValue, } sqlWhere := ` WHERE t1.deleted_at = ? ` sqlWhereParams := []interface{}{ utils.DefaultTimeValue, } for mapCondKey, tableName := range map[string]string{ "vendorStoreCond": "store_map", "courierStoreCond": "store_courier_map", } { if mapCond := strings.ToUpper(utils.Interface2String(params[mapCondKey])); mapCond == "AND" || mapCond == "OR" { mapCondsStr := utils.Interface2String(params[mapCondKey+"s"]) if mapCondsStr != "" { var vendorStoreConds map[string]interface{} if err = utils.UnmarshalUseNumber([]byte(mapCondsStr), &vendorStoreConds); err != nil { return "", nil, "", nil, err } sqlVendorStoreCond := "" for vendor, cond2 := range vendorStoreConds { var cond string if condStr, ok := cond2.(string); !ok { cond = utils.Int64ToStr(utils.ForceInterface2Int64(cond2)) } else { cond = condStr } tableAlias := tableName + vendor if cond != "0" && cond != "" { if sqlVendorStoreCond == "" { if mapCond == "AND" { sqlVendorStoreCond += " AND ( 1 = 1" } else { sqlVendorStoreCond += " AND ( 1 = 0" } } sqlFrom += "\nLEFT JOIN " + tableName + " " + tableAlias + " ON " + tableAlias + ".vendor_id = ? AND " + tableAlias + ".store_id = t1.id AND " + tableAlias + ".deleted_at = ? AND " + tableAlias + ".is_sync <> 0 " sqlFromParams = append(sqlFromParams, vendor, utils.DefaultTimeValue) if cond == "1" { sqlVendorStoreCond += " " + mapCond + " " + tableAlias + ".id IS NOT NULL" } else if cond == "-1" { sqlVendorStoreCond += " " + mapCond + " " + tableAlias + ".id IS NULL" } else { sqlFrom += " AND " + tableAlias + ".vendor_org_code = ?" sqlFromParams = append(sqlFromParams, cond) sqlVendorStoreCond += " " + mapCond + " " + tableAlias + ".id IS NOT NULL" } } } if sqlVendorStoreCond != "" { sqlWhere += sqlVendorStoreCond + ")" } } } } if keyword != "" { keywordLike := "%" + keyword + "%" sqlWhere += ` AND (t1.name LIKE ? OR t1.tel1 LIKE ? OR t1.tel2 LIKE ? OR t1.operator_phone LIKE ? OR t1.operator_phone2 LIKE ? OR t1.market_man_phone LIKE ? OR t1.last_operator LIKE ? OR city.name LIKE ? OR t1.address LIKE ? OR t1.printer_sn LIKE ? OR t1.licence_code LIKE ? OR t1.id_code LIKE ?` sqlWhereParams = append(sqlWhereParams, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike) if keywordInt64, err2 := strconv.ParseInt(keyword, 10, 64); err2 == nil { if true { // jxutils.IsLegalMobileNumber(keywordInt64) { sqlWhere += ` OR ( SELECT COUNT(*) FROM casbin_rule t11 JOIN user t12 ON t12.user_id = t11.v0 AND t12.mobile LIKE ? WHERE t11.v1 <> '' AND (t11.v1 = CONCAT(?, t1.id) OR t11.v1 = CONCAT(?, t1.market_man_role) OR t11.v1 = CONCAT(?, t1.operator_role) OR t11.v1 = CONCAT(?, t1.operator_role2)) ) > 0` prefix := autils.NewRole("", 0).GetFullName() sqlWhereParams = append(sqlWhereParams, keyword+"%", autils.NewStoreBossRole(-1).GetFullName(), prefix, prefix, prefix) // 必须要前缀,不然不能用过些会很慢 } sqlWhere += " OR t1.id = ? OR t1.city_code = ? OR t1.district_code = ?" sqlWhereParams = append(sqlWhereParams, keywordInt64, keywordInt64, keywordInt64) if jxutils.GuessVendorIDFromVendorStoreID(keywordInt64) != model.VendorIDUnknown { sqlWhere += ` OR (SELECT COUNT(*) FROM store_map tsm WHERE t1.id = tsm.store_id AND tsm.deleted_at = ? AND tsm.vendor_store_id = ?) > 0 OR (SELECT COUNT(*) FROM store_courier_map tsm WHERE t1.id = tsm.store_id AND tsm.deleted_at = ? AND tsm.vendor_store_id = ?) > 0 ` sqlWhereParams = append(sqlWhereParams, utils.DefaultTimeValue, keyword, utils.DefaultTimeValue, keyword) } } sqlWhere += ")" } if params["storeID"] != nil || params["storeIDs"] != nil { var storeIDs []int if params["storeIDs"] != nil { if err = jxutils.Strings2Objs(utils.Interface2String(params["storeIDs"]), &storeIDs); err != nil { return "", nil, "", nil, err } } if params["storeID"] != nil { storeIDs = append(storeIDs, params["storeID"].(int)) } if len(storeIDs) > 0 { sqlWhere += " AND t1.id IN (" + dao.GenQuestionMarks(len(storeIDs)) + ")" sqlWhereParams = append(sqlWhereParams, storeIDs) } } if params["startStoreID"] != nil { sqlWhere += " AND t1.id >= ?" sqlWhereParams = append(sqlWhereParams, params["startStoreID"]) } if params["name"] != nil { sqlWhere += " AND t1.name LIKE ?" sqlWhereParams = append(sqlWhereParams, "%"+params["name"].(string)+"%") } if params["placeID"] != nil { level := 2 if params["placeLevel"] != nil { level = params["placeLevel"].(int) } if level == 2 { sqlWhere += " AND t1.city_code = ?" } else { sqlWhere += " AND t1.district_code = ?" } sqlWhereParams = append(sqlWhereParams, params["placeID"].(int)) } if params["address"] != nil { sqlWhere += " AND t1.address LIKE ?" sqlWhereParams = append(sqlWhereParams, "%"+params["address"].(string)+"%") } if params["tel"] != nil { sqlWhere += " AND (t1.tel1 LIKE ? OR t1.tel2 LIKE ?)" sqlWhereParams = append(sqlWhereParams, "%"+params["tel"].(string)+"%") sqlWhereParams = append(sqlWhereParams, "%"+params["tel"].(string)+"%") } if params["statuss"] != nil { var statuss []int if err = utils.UnmarshalUseNumber([]byte(params["statuss"].(string)), &statuss); err != nil { return "", nil, "", nil, err } if len(statuss) > 0 { sqlWhere += " AND t1.status IN (" + dao.GenQuestionMarks(len(statuss)) + ")" sqlWhereParams = append(sqlWhereParams, statuss) } } sql = sqlFrom + sqlWhere sqlParams = append(sqlParams, sqlFromParams...) sqlParams = append(sqlParams, sqlWhereParams...) return sql, sqlParams, sqlFrom, sqlFromParams, nil } func setStoreMapInfo(ctx *jxcontext.Context, db *dao.DaoDB, storesInfo *StoresInfo, storeIDs []int, briefLevel int) (err error) { storeMapList, err := dao.GetStoresMapList(db, nil, storeIDs, nil, model.StoreStatusAll, model.StoreIsSyncAll, "") if err != nil { return err } storeCourierList, err := dao.GetStoreCourierList(db, storeIDs, model.StoreStatusAll, model.StoreAuditStatusAll) if err != nil { return err } storeMapMap := dao.StoreMapList2Map(storeMapList) storeCourierMap := dao.StoreCourierList2Map(storeCourierList) for _, v := range storesInfo.Stores { if briefLevel > 0 { v.DeliveryRange = "" v.IDCardFront = "" v.IDCardBack = "" v.IDCardHand = "" v.Licence = "" v.Licence2Image = "" } for _, v2 := range storeMapMap[v.ID] { v.StoreMaps = append(v.StoreMaps, v2) } for _, v2 := range storeCourierMap[v.ID] { v.CourierMaps = append(v.CourierMaps, v2) } } return nil } // todo 门店绑定信息可以考虑以数组形式返回,而不是现在这样 func GetStores(ctx *jxcontext.Context, keyword string, params map[string]interface{}, offset, pageSize int, orderTimeFrom, orderTimeTo time.Time, orderCountFrom, orderCountTo int) (retVal *StoresInfo, err error) { briefLevel := int(utils.ForceInterface2Int64(params["briefLevel"])) sql, sqlParams, _, _, err := getStoresSql(ctx, keyword, params, orderTimeFrom, orderTimeTo) if err != nil { return nil, err } sql = ` SELECT DISTINCT t1.*, CAST(t1.lng AS DECIMAL(15,6))/1000000 float_lng, CAST(t1.lat AS DECIMAL(15,6))/1000000 float_lat, IF(mm.name <> '', mm.name, mm.user_id2) market_man_name, bank.value payee_bank_name, IF(om.name <> '', om.name, om.user_id2) operator_name, IF(om2.name <> '', om2.name, om2.user_id2) operator_name2, province.code province_code, province.name province_name, city.name city_name, district.name district_name ` + sql + ` ORDER BY t1.id DESC ` db := dao.GetDB() retVal = &StoresInfo{} var storeIDs []int var storeList []*StoreExt offset = jxutils.FormalizePageOffset(offset) pageSize = jxutils.FormalizePageSize(pageSize) mapLimit := false // globals.SugarLogger.Debug(sql) // globals.SugarLogger.Debug(utils.Format4Output(sqlParams, false)) if err = dao.GetRows(db, &storeList, sql, sqlParams...); err == nil { // 地图区域限制过滤 if mapLongitude2, ok := params["mapLongitude"].(string); ok { var ( mapLatitude, mapLongitude float64 mapRadius int ) mapLimit = true mapLongitude = utils.Str2Float64(mapLongitude2) mapLatitude = utils.Str2Float64(params["mapLatitude"].(string)) mapRadius = params["mapRadius"].(int) for _, v := range storeList { valid := !mapLimit if mapLimit { valid = jxutils.EarthDistance(mapLongitude, mapLatitude, v.FloatLng, v.FloatLat)*1000 <= float64(mapRadius) } if valid { retVal.Stores = append(retVal.Stores, v) } } } else { retVal.Stores = storeList } // 订单情况过滤 storeList, err = filterStoreByOrderInfo(db, retVal.Stores, orderTimeFrom, orderTimeTo, orderCountFrom, orderCountTo) if err != nil { return nil, err } // 分页 retVal.TotalCount = len(storeList) retVal.Stores = nil for i := offset; i < offset+pageSize && i < len(storeList); i++ { storeIDs = append(storeIDs, storeList[i].ID) retVal.Stores = append(retVal.Stores, storeList[i]) } if len(storeIDs) == 0 { return retVal, nil } // 导出门店地图标信息时,可能会需要转换门店坐标 needConver2Baidu := int(utils.Interface2Int64WithDefault(params["coordinateType"], 0)) == model.CoordinateTypeBaiDu if needConver2Baidu { task := tasksch.NewParallelTask("坐标转换", tasksch.NewParallelConfig().SetParallelCount(4).SetBatchSize(autonavi.MaxConvertCount), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { var coords []*baidunavi.Coordinate for _, v := range batchItemList { store := v.(*StoreExt) coords = append(coords, &baidunavi.Coordinate{ Lng: store.FloatLng, Lat: store.FloatLat, }) } coords, err = api.BaiDuNaviAPI.BatchCoordinateConvert(coords, baidunavi.CoordSysGCJ02, baidunavi.CoordSysBaiDu) if err == nil { for k, v := range batchItemList { store := v.(*StoreExt) coord := coords[k] store.FloatLng = coord.Lng store.FloatLat = coord.Lat } } return retVal, err }, retVal.Stores) task.Run() task.GetResult(0) } } else { return nil, err } if len(retVal.Stores) > 0 { setStoreMapInfo(ctx, db, retVal, storeIDs, briefLevel) retVal.MapCenterLng, retVal.MapCenterLat = getMapCenter(retVal.Stores) } return retVal, err } func filterStoreByOrderInfo(db *dao.DaoDB, inStores []*StoreExt, orderTimeFrom, orderTimeTo time.Time, orderCountFrom, orderCountTo int) (outStores []*StoreExt, err error) { if len(inStores) > 0 && !utils.IsTimeZero(orderTimeFrom) { storeIDs := make([]int, len(inStores)) for k, v := range inStores { storeIDs[k] = v.ID } orderSaleList, err2 := dao.GetStoresOrderSaleInfo(dao.GetDB(), storeIDs, orderTimeFrom, orderTimeTo, []int{model.OrderStatusFinished}) if err = err2; err != nil { return nil, err } storeOrderCountMap := make(map[int]int) for _, v := range orderSaleList { storeOrderCountMap[v.StoreID] += v.Count } for _, v := range inStores { orderCount := storeOrderCountMap[v.ID] if orderCount >= orderCountFrom && orderCount <= orderCountTo { v.OrderCount = orderCount outStores = append(outStores, v) } } } else { outStores = inStores } return outStores, err } func getMapCenter(storeList []*StoreExt) (lng, lat float64) { globals.SugarLogger.Debugf("getMapCenter len(storeList):%d", len(storeList)) if len(storeList) == 0 { return 0, 0 } lngAvg := float64(0) latAvg := float64(0) storeListLenFloat := float64(len(storeList)) for _, store := range storeList { lngAvg += store.FloatLng latAvg += store.FloatLat } lngAvg = lngAvg / storeListLenFloat latAvg = latAvg / storeListLenFloat distAvg := float64(0) for _, store := range storeList { distAvg += math.Sqrt((store.FloatLng-lngAvg)*(store.FloatLng-lngAvg) + (store.FloatLat-latAvg)*(store.FloatLat-latAvg)) } distAvg = (distAvg / storeListLenFloat) maxDist := distAvg * 5 newStoreList := []*StoreExt{} for _, store := range storeList { dist := math.Sqrt((store.FloatLng-lngAvg)*(store.FloatLng-lngAvg) + (store.FloatLat-latAvg)*(store.FloatLat-latAvg)) if dist <= maxDist { lng += store.FloatLng lat += store.FloatLat newStoreList = append(newStoreList, store) } } if len(newStoreList) == len(storeList) { lng = lng / float64(len(newStoreList)) lat = lat / float64(len(newStoreList)) // for _, store := range storeList { // globals.SugarLogger.Debugf("store:%s, lng:%f, lat:%f", store.Name, store.FloatLng, store.FloatLat) // } // globals.SugarLogger.Debugf("lng:%f, lat:%f", lng, lat) return lng, lat } return getMapCenter(newStoreList) } func GetVendorStore(ctx *jxcontext.Context, vendorID int, vendorOrgCode, vendorStoreID string) (retVal *StoreExt, err error) { if handler := CurVendorSync.GetStoreHandler(vendorID); handler != nil { result, err2 := handler.ReadStore(ctx, vendorOrgCode, vendorStoreID) if err = err2; err == nil { retVal = &StoreExt{ Store: result.Store, FloatLng: jxutils.IntCoordinate2Standard(result.Lng), FloatLat: jxutils.IntCoordinate2Standard(result.Lat), } db := dao.GetDB() if city, err2 := dao.GetPlaceByCode(db, result.CityCode); err2 == nil { retVal.CityName = city.Name } if district, err2 := dao.GetPlaceByCode(db, result.DistrictCode); err2 == nil { retVal.DistrictName = district.Name } if !jxutils.IsLegalStoreID(retVal.ID) { retVal.ID = 0 } return retVal, nil } return nil, err } return nil, ErrCanNotFindVendor } func isUpdateStoreNeedSync(valid map[string]interface{}) bool { for k := range valid { if storeKeyPropertyMap[k] == 1 { return true } } return false } func checkBankBranch(payeeBankBranchName string) (err error) { if payeeBankBranchName != "" { if strings.Index(payeeBankBranchName, "支行") == -1 && strings.Index(payeeBankBranchName, "分行") == -1 { err = fmt.Errorf("支行信息异常,你可能没有在使用最新的版本,请强制刷新浏览器或联系开发") } } return err } func checkCreateStore(store *model.Store) (err error) { if store.Lng == 0 || store.Lat == 0 || store.Address == "" { err = fmt.Errorf("必须设置地址信息") } return err } func checkStoreDeliveryRange(deliveryRange string) (err error) { if deliveryRange != "" { points := jxutils.CoordinateStr2Points(deliveryRange) for _, point := range points { for _, coord := range point { if coord == 0 || math.IsNaN(coord) { return fmt.Errorf("坐标点数据非法:%s", deliveryRange) } } } } return err } func UpdateStore(ctx *jxcontext.Context, storeID int, payload map[string]interface{}, userName string) (num int64, err error) { globals.SugarLogger.Debugf("UpdateStore storeID:%d, payload:%s", storeID, utils.Format4Output(payload, true)) if err = checkBankBranch(utils.Interface2String(payload["payeeBankBranchName"])); err != nil { return 0, err } db := dao.GetDB() store := &model.Store{} store.ID = storeID if err = dao.GetEntity(db, store); err != nil { return 0, err } var outStore *model.Store var beforStore = *store if payload["autoEnableAt"] != nil { payload["autoEnableAt"] = utils.Time2Date(utils.Str2Time(utils.Interface2String(payload["autoEnableAt"]))) } valid := dao.StrictMakeMapByStructObject2(payload, store, &outStore, userName) if err = checkStoreDeliveryRange(utils.Interface2String(valid["deliveryRange"])); err != nil { return 0, err } if globals.EnableWXAuth2 { if err = dao.ValidateRoles(db, utils.Interface2String(valid["marketManRole"]), utils.Interface2String(valid["operatorRole"]), utils.Interface2String(valid["operatorRole2"])); err != nil { return 0, err } } if payload["lng"] != nil || payload["lat"] != nil { intLng := jxutils.StandardCoordinate2Int(utils.Interface2Float64WithDefault(payload["lng"], 0.0)) intLat := jxutils.StandardCoordinate2Int(utils.Interface2Float64WithDefault(payload["lat"], 0.0)) if intLng != 0 && intLng != store.Lng { valid["lng"] = intLng } if intLat != 0 && intLat != store.Lng { valid["lat"] = intLat } } if valid["originalName"] != nil { delete(valid, "originalName") } if valid["printerBindInfo"] != nil { delete(valid, "printerBindInfo") } syncStatus := model.SyncFlagModifiedMask if valid["name"] != nil { valid["name"] = jxutils.FormalizeName(valid["name"].(string)) store.Name = valid["name"].(string) syncStatus |= model.SyncFlagStoreName } // globals.SugarLogger.Debug(utils.Format4Output(valid, false)) printerVendorID := int(utils.Interface2Int64WithDefault(valid["printerVendorID"], 0)) if printerVendorID == 0 { printerVendorID = store.PrinterVendorID } if printerVendorID == model.VendorIDXiaoWM { delete(valid, "printerKey") } // 网络打印机处理 if valid["printerVendorID"] != nil || valid["printerSN"] != nil || valid["printerKey"] != nil { if valid["printerVendorID"] == nil { valid["printerVendorID"] = store.PrinterVendorID } if printerVendorID := int(utils.Interface2Int64WithDefault(valid["printerVendorID"], 0)); printerVendorID > 0 { handler := partner.GetPrinterPlatformFromVendorID(printerVendorID) if handler == nil { return 0, fmt.Errorf("不支持的打印机厂商ID:%d", printerVendorID) } if valid["printerSN"] == nil { valid["printerSN"] = store.PrinterSN } if valid["printerKey"] == nil { valid["printerKey"] = store.PrinterKey } newID1, newID2, err2 := handler.RegisterPrinter(ctx, valid["printerSN"].(string), valid["printerKey"].(string), store.Name) if err = err2; err != nil { return 0, err } handler.EmptyPrintList(ctx, newID1, newID2) if newID1 != "" { valid["printerSN"] = newID1 } if newID2 != "" { valid["printerKey"] = newID2 } } else { valid["printerVendorID"] = 0 valid["printerSN"] = "" valid["printerKey"] = "" } valid["printerBindInfo"] = "" if handler := partner.GetPrinterPlatformFromVendorID(store.PrinterVendorID); handler != nil { handler.UnregisterPrinter(ctx, store.PrinterSN, store.PrinterKey) } } if linkStoreID, ok := valid["linkStoreID"].(int); ok { linkStoreID, err = getRealLinkStoreID(linkStoreID) if err != nil { return 0, err } valid["linkStoreID"] = linkStoreID } for _, v := range []string{ "lng", "lat", "cityCode", "address", "deliveryRange", } { if valid[v] != nil { syncStatus |= model.SyncFlagStoreAddress break } } status := 0 if valid["status"] != nil || valid["autoEnableAt"] != nil { if valid["status"] != nil { syncStatus |= model.SyncFlagStoreStatus status = int(utils.Interface2Int64WithDefault(valid["status"], 0)) if status == model.StoreStatusDisabled { valid["deliveryRangeType"] = model.DeliveryRangeTypeRadius valid["deliveryRange"] = "1" } } else { status = store.Status } if status != model.StoreStatusClosed && status != model.StoreStatusHaveRest { valid["autoEnableAt"] = nil } else { if valid["autoEnableAt"] != nil { status = model.StoreStatusHaveRest } else { status = model.StoreStatusClosed valid["autoEnableAt"] = nil } valid["status"] = status } } if valid["deliveryRange"] != nil { valid["deliveryRange"] = strings.Trim(valid["deliveryRange"].(string), ";") } //时间校验 if outStore != nil { if outStore.OpenTime1 != 0 && outStore.CloseTime1 != 0 { if err := ValidateStructPartial(outStore, "OpenTime1", "CloseTime1"); err != nil { return 0, errors.New(fmt.Sprintf("门店营业时间1设置不合法!时间范围1 :[%v] 至 [%v]", outStore.OpenTime1, outStore.CloseTime1)) } } if (outStore.OpenTime1 == 0 && outStore.CloseTime1 != 0) || (outStore.OpenTime1 != 0 && outStore.CloseTime1 == 0) { return 0, errors.New(fmt.Sprintf("门店营业时间1设置不合法!时间范围1 :[%v] 至 [%v]", outStore.OpenTime1, outStore.CloseTime1)) } if outStore.OpenTime2 != 0 && outStore.CloseTime2 != 0 { if err := ValidateStructPartial(outStore, "OpenTime2", "CloseTime2"); err != nil { return 0, errors.New(fmt.Sprintf("门店营业时间2设置不合法!时间范围2 :[%v] 至 [%v]", outStore.OpenTime2, outStore.CloseTime2)) } } if (outStore.OpenTime2 == 0 && outStore.CloseTime2 != 0) || (outStore.OpenTime2 != 0 && outStore.CloseTime2 == 0) { return 0, errors.New(fmt.Sprintf("门店营业时间2设置不合法!时间范围2 :[%v] 至 [%v]", outStore.OpenTime1, outStore.CloseTime1)) } if outStore.OpenTime1 != 0 && outStore.CloseTime1 != 0 && outStore.OpenTime2 != 0 && outStore.CloseTime2 != 0 { if outStore.OpenTime2 < outStore.CloseTime1 { return 0, errors.New(fmt.Sprintf("门店营业时间设置不合法!第二段营业时间应该在第一段营业时间之后!")) } } if beginAt, endAt := GetTimeMixByInt(outStore.OpenTime1, outStore.CloseTime1, outStore.OpenTime2, outStore.CloseTime2); beginAt != 0 && endAt != 0 { return 0, errors.New(fmt.Sprintf("两段门店营业时间不可交叉!时间范围1 :[%v] 至 [%v], 时间范围2 :[%v] 至 [%v]", outStore.OpenTime1, outStore.CloseTime1, outStore.OpenTime2, outStore.CloseTime2)) } } if valid["promoteInfo"] != nil { valid["promoteInfo"] = strings.ReplaceAll(valid["promoteInfo"].(string), "{phone}", store.Tel1) } for k, _ := range valid { if roleMap[k] != "" { if authInfo, err := ctx.GetV2AuthInfo(); err == nil { if roleMoblieMap[authInfo.Mobile] == "" { return 0, errors.New(fmt.Sprintf("当前用户 [%v] 无权限修改 [%v] 字段!", authInfo.Name, roleMap[k])) } } } } // districtCode := 0 // if valid["districtCode"] != nil { // districtCode = int(utils.MustInterface2Int64(valid["districtCode"])) // } // if districtCode == 0 && store.DistrictCode == 0 { // if lng == 0 { // lng = jxutils.IntCoordinate2Standard(store.Lng) // lat = jxutils.IntCoordinate2Standard(store.Lat) // } // valid["districtCode"] = api.AutonaviAPI.GetCoordinateDistrictCode(lng, lat) // } globals.SugarLogger.Debugf("UpdateStore track:%s, storeID:%d, valid:%s", ctx.GetTrackInfo(), storeID, utils.Format4Output(valid, true)) if len(valid) > 0 { if globals.IsAddEvent { mapBefore := refutil.FindMapAndStructMixed(valid, beforStore) err = AddEventDetail(db, ctx, model.OperateUpdate, storeID, model.ThingTypeStore, storeID, BuildDiffData(mapBefore), BuildDiffData(valid)) } dao.Begin(db) defer func() { dao.Rollback(db) }() if num, err = dao.UpdateEntityLogically(db, store, valid, userName, nil); err == nil && num == 1 { if isUpdateStoreNeedSync(valid) { dummy := &model.StoreMap{} kv := make(map[string]interface{}) if valid["status"] != nil { if syncStatus&model.SyncFlagStoreStatus != 0 && status == model.StoreStatusOpened { kv[model.FieldStatus] = status } } _, err2 := dao.UpdateEntityLogicallyAndUpdateSyncStatus(db, dummy, kv, userName, map[string]interface{}{ model.FieldStoreID: store.ID, }, model.FieldSyncStatus, syncStatus) if err = err2; err == nil { dao.Commit(db) globals.SugarLogger.Debugf("UpdateStore track:%s, before call SyncStore", ctx.GetTrackInfo()) _, err = CurVendorSync.SyncStore(ctx, db, -1, store.ID, false, userName) if valid["tel1"] != nil || valid["tel2"] != nil { TryAddStoreBossRole4StoreByMobile(ctx, store.ID, []string{utils.Interface2String(valid["tel1"]), utils.Interface2String(valid["tel2"])}) } if syncStatus&model.SyncFlagStoreAddress != 0 || valid["tel1"] != nil || valid["payeeName"] != nil { updateCourierStores(ctx, storeID) } } } else { dao.Commit(db) } notifyStoreOperatorChanged(store, valid["operatorPhone"]) notifyStoreMarketChanged(store, valid["marketManPhone"]) if err == nil { if valid["openTime1"] != 0 || valid["closeTime1"] != 0 || valid["openTime2"] != 0 || valid["closeTime2"] != 0 { err = CurVendorSync.ChangeStoreSkuSaleStatus(ctx, storeID, true, true) } } } } else { globals.SugarLogger.Debugf("UpdateStore track:%s, store:%s", ctx.GetTrackInfo(), utils.Format4Output(store, true)) } return num, err } func notifyStoreOperatorChanged(store *model.Store, newOperator2 interface{}) { if store.OperatorPhone != "" && newOperator2 != nil { db := dao.GetDB() if user, err := dao.GetUserByID(db, "mobile", store.OperatorPhone); err == nil { curUserName := "" if newOperator := utils.Interface2String(newOperator2); newOperator != "" { if curUser, err := dao.GetUserByID(db, "mobile", newOperator); err == nil { curUserName = curUser.GetName() } } ddmsg.SendUserMessage(dingdingapi.MsgTyeText, user.GetID(), "门店运营变更", fmt.Sprintf("门店:%d-%s,原运营:%s,变更为:%s", store.ID, store.Name, user.GetName(), curUserName)) } } } func notifyStoreMarketChanged(store *model.Store, newMarketManPhone2 interface{}) { if store.MarketManPhone != "" && newMarketManPhone2 != nil { db := dao.GetDB() if user, err := dao.GetUserByID(db, "mobile", store.MarketManPhone); err == nil { curUserName := "" if newOperator := utils.Interface2String(newMarketManPhone2); newOperator != "" { if curUser, err := dao.GetUserByID(db, "mobile", newOperator); err == nil { curUserName = curUser.GetName() } } ddmsg.SendUserMessage(dingdingapi.MsgTyeText, user.GetID(), "门店市场变更", fmt.Sprintf("门店:%d-%s,原市场:%s,变更为:%s", store.ID, store.Name, user.GetName(), curUserName)) } } } func SetStoreStatus(ctx *jxcontext.Context, storeID, status int) (err error) { payload := map[string]interface{}{ "status": status, } _, err = UpdateStore(ctx, storeID, payload, ctx.GetUserName()) return err } func EnableHaveRestStores(ctx *jxcontext.Context, isAsync, isContinueWhenError bool) (hint string, err error) { storeInfo, err := GetStores(ctx, "", map[string]interface{}{ "statuss": string(utils.MustMarshal([]int{model.StoreStatusHaveRest})), }, 0, model.UnlimitedPageSize, utils.DefaultTimeValue, utils.DefaultTimeValue, 0, 0) if err != nil { return "", err } if len(storeInfo.Stores) == 0 { return "0", nil } task := tasksch.NewParallelTask("EnableHaveRestStores", tasksch.NewParallelConfig().SetIsContinueWhenError(isContinueWhenError), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { store := batchItemList[0].(*StoreExt) var autoEnableAt time.Time if store.AutoEnableAt != nil { autoEnableAt = *store.AutoEnableAt } else { autoEnableAt = utils.Time2Date(time.Now().Add(12 * time.Hour)) // 临时休息,但没有设置恢复营业时间,缺省是第二天 } if time.Now().Sub(autoEnableAt) >= -2*time.Hour { err = SetStoreStatus(ctx, store.ID, model.StoreStatusOpened) } return nil, err }, storeInfo.Stores) tasksch.ManageTask(task).Run() if !isAsync { if _, err = task.GetResult(0); err == nil { hint = utils.Int2Str(len(storeInfo.Stores)) } } else { hint = task.GetID() } return hint, err } func CreateStore(ctx *jxcontext.Context, storeExt *StoreExt, userName string) (id int, err error) { globals.SugarLogger.Debugf("CreateStore storeExt:%s", utils.Format4Output(storeExt, false)) if err = checkBankBranch(storeExt.PayeeBankBranchName); err != nil { return 0, err } store := &storeExt.Store if store.ID != 0 && !jxutils.IsLegalStoreID(store.ID) { return 0, fmt.Errorf("ID:%d不是合法的京西门店编号", store.ID) } db := dao.GetDB() if globals.EnableWXAuth2 { if err = dao.ValidateRoles(db, store.MarketManRole, store.OperatorRole, store.OperatorRole2); err != nil { return 0, err } } realLinkStoreID, err := getRealLinkStoreID(storeExt.LinkStoreID) if err != nil { return 0, err } storeExt.LinkStoreID = realLinkStoreID existingID := store.ID store.Lng = jxutils.StandardCoordinate2Int(storeExt.FloatLng) store.Lat = jxutils.StandardCoordinate2Int(storeExt.FloatLat) if err = checkCreateStore(&storeExt.Store); err != nil { return 0, err } store.Name = jxutils.FormalizeName(store.Name) store.DeliveryRange = strings.Trim(store.DeliveryRange, ";") if store.PrinterSN != "" { handler := partner.GetPrinterPlatformFromVendorID(store.PrinterVendorID) if handler == nil { return 0, fmt.Errorf("不支持的打印机厂商ID:%d", store.PrinterVendorID) } newID1, newID2, err2 := handler.RegisterPrinter(ctx, store.PrinterSN, store.PrinterKey, store.Name) if err = err2; err != nil { return 0, err } handler.EmptyPrintList(ctx, newID1, newID2) if newID1 != "" { store.PrinterSN = newID1 } if newID2 != "" { store.PrinterKey = newID2 } } dao.WrapAddIDCULDEntity(store, userName) store.ID = existingID if err = dao.CreateEntity(db, store); err == nil { if globals.IsAddEvent { err = AddEventDetail(db, ctx, model.OperateAdd, store.ID, model.ThingTypeStore, store.ID, "", "") } UpdateOrCreateCourierStores(ctx, store.ID, false, false, false) TryAddStoreBossRole4StoreByMobile(ctx, storeExt.ID, []string{storeExt.Tel1, storeExt.Tel2}) return store.ID, err } return 0, err } func getRealLinkStoreID(linkStoreID int) (realLinkStoreID int, err error) { if linkStoreID != 0 { store := &model.Store{} store.ID = linkStoreID if err = dao.GetEntity(dao.GetDB(), store); err == nil { if store.LinkStoreID != 0 { realLinkStoreID = store.LinkStoreID } else { realLinkStoreID = linkStoreID } } } return realLinkStoreID, err } func GetStoreVendorMaps(ctx *jxcontext.Context, db *dao.DaoDB, storeID int, vendorID int) (storeMaps []*model.StoreMap, err error) { cond := map[string]interface{}{ model.FieldStoreID: storeID, } if vendorID != -1 { cond[model.FieldVendorID] = vendorID } return storeMaps, dao.GetEntitiesByKV(db, &storeMaps, cond, false) } // todo 需要对字段做有效性检查 func AddStoreVendorMap(ctx *jxcontext.Context, db *dao.DaoDB, vendorID int, vendorOrgCode string, storeID int, storeMap *model.StoreMap) (outStoreMap *model.StoreMap, err error) { if storeID == 0 { return nil, fmt.Errorf("storeID不能为0") } if vendorID != model.VendorIDJD && (storeMap.AutoPickup == 0) { return nil, fmt.Errorf("非京东平台要求必须自动拣货") } userName := ctx.GetUserName() storeMap.StoreID = storeID storeMap.VendorID = vendorID storeMap.VendorOrgCode = vendorOrgCode storeMap.Status = model.StoreStatusOpened storeMap.DeliveryType = model.StoreDeliveryTypeByStore storeMap.SyncStatus = 0 if vendorID != model.VendorIDJX { if storeMap.VendorOrgCode == "" { return nil, fmt.Errorf("必须指定平台分账号信息") } if handler := CurVendorSync.GetStoreHandler(vendorID); handler != nil { store, err2 := handler.ReadStore(ctx, vendorOrgCode, storeMap.VendorStoreID) if err = err2; err == nil || storeMap.IsSync == 0 { if store != nil { storeMap.DeliveryType = store.DeliveryType storeMap.Status = store.Status } err = nil storeMap.SyncStatus = model.SyncFlagModifiedMask | model.SyncFlagStoreName | model.SyncFlagStoreAddress // 新增绑定门店是修改的概念 } } else { err = ErrCanNotFindVendor } } else { ReCalculateJxPriceLight(db, ctx, storeID) } if err == nil { dao.WrapAddIDCULDEntity(storeMap, userName) if db == nil { db = dao.GetDB() } dao.Begin(db) defer func() { if r := recover(); r != nil { dao.Rollback(db) panic(r) } }() if err = dao.CreateEntity(db, storeMap); err == nil { if globals.IsAddEvent { err = AddEventDetail(db, ctx, model.OperateAdd, vendorID, model.ThingTypeStore, storeID, "", `{"VendorID":`+utils.Int2Str(vendorID)+`}`) } dao.Commit(db) outStoreMap = storeMap _, err = CurVendorSync.SyncStore(ctx, db, storeMap.VendorID, storeID, false, userName) } else { dao.Rollback(db) } } return outStoreMap, err } func DeleteStoreVendorMap(ctx *jxcontext.Context, db *dao.DaoDB, storeID, vendorID int, userName string) (num int64, err error) { if db == nil { db = dao.GetDB() } storeMap := &model.StoreMap{ StoreID: storeID, VendorID: vendorID, } storeMap.DeletedAt = utils.DefaultTimeValue if err = dao.GetEntity(db, storeMap, model.FieldStoreID, model.FieldVendorID, model.FieldDeletedAt); err == nil { if handler := partner.GetPurchasePlatformFromVendorID(vendorID); handler != nil { handler.UpdateStoreCustomID(ctx, storeMap.VendorOrgCode, storeMap.VendorStoreID, utils.Str2Int64WithDefault(storeMap.VendorStoreID, 0)) } num, err = dao.DeleteEntityLogically(db, storeMap, map[string]interface{}{ model.FieldStatus: model.StoreStatusDisabled, }, userName, nil) if globals.IsAddEvent { err = AddEventDetail(db, ctx, model.OperateDelete, vendorID, model.ThingTypeStore, storeID, "", `{"VendorID":`+utils.Int2Str(vendorID)+`}`) } } return num, err } func isStoreMapNeedSync(vendorID int, valid map[string]interface{}) bool { if vendorID != model.VendorIDJX { for k := range valid { if storeMapKeyPropertyMap[k] == 1 { return true } } } return false } func UpdateStoreVendorMap(ctx *jxcontext.Context, db *dao.DaoDB, storeID, vendorID int, payload map[string]interface{}, userName string) (num int64, err error) { if vendorID != model.VendorIDJD { if autoPickup, ok := payload["autoPickup"]; ok && autoPickup == 0 { return 0, fmt.Errorf("非京东平台要求必须自动拣货") } } var storeHandler partner.IPurchasePlatformHandler if vendorID != model.VendorIDJX { storeHandler = CurVendorSync.GetStoreHandler(vendorID) if storeHandler == nil { return 0, ErrCanNotFindVendor } } // 暂时不开放isSync if isSync, ok := payload["isSync"].(int); ok && isSync == 0 { delete(payload, "isSync") } if db == nil { db = dao.GetDB() } storeMap := &model.StoreMap{ StoreID: storeID, VendorID: vendorID, } storeMap.DeletedAt = utils.DefaultTimeValue if err = dao.GetEntity(db, storeMap, model.FieldStoreID, model.FieldVendorID, model.FieldDeletedAt); err != nil { return 0, err } var beforeStoreMap = *storeMap syncStatus := model.SyncFlagModifiedMask valid := dao.StrictMakeMapByStructObject(payload, storeMap, userName) if valid["status"] != nil { syncStatus |= model.SyncFlagStoreStatus } if vendorStoreName, ok := valid["vendorStoreName"].(string); ok { if utf8.RuneCountInString(vendorStoreName) > jdapi.MaxStoreNameLen && vendorID == model.VendorIDJD { return 0, fmt.Errorf("门店名称不允许超过13位!") } syncStatus |= model.SyncFlagStoreName } for _, v := range [][]string{ []string{ "pricePercentagePack", model.ConfigTypePricePack, }, []string{ "freightDeductionPack", model.ConfigTypeFreightPack, }, } { if valid[v[0]] != nil { if value := utils.Interface2String(valid[v[0]]); value != "" { _, err2 := dao.QueryConfigs(db, value, v[1], "") if err = err2; err != nil { return 0, err } } } } if vendorID != model.VendorIDJX { if vendorStoreID := utils.Interface2String(valid["vendorStoreID"]); vendorStoreID != "" { vendorStoreInfo, err2 := storeHandler.ReadStore(ctx, storeMap.VendorOrgCode, vendorStoreID) if err = err2; err == nil { valid["deliveryType"] = vendorStoreInfo.DeliveryType } err = nil // todo 忽略读不到DeliveryType的错误 } } if err == nil { // globals.SugarLogger.Debug(utils.Format4Output(valid, false)) if len(valid) > 0 { dao.Begin(db) defer func() { if r := recover(); r != nil { dao.Rollback(db) panic(r) } }() if isStoreMapNeedSync(vendorID, valid) { // 对于store vendor map,只有Status改变才需要同步到厂商 num, err = dao.UpdateEntityLogicallyAndUpdateSyncStatus(db, storeMap, valid, userName, map[string]interface{}{ model.FieldStoreID: storeID, model.FieldVendorID: vendorID, }, model.FieldSyncStatus, syncStatus) } else { num, err = dao.UpdateEntityLogically(db, storeMap, valid, userName, map[string]interface{}{ model.FieldStoreID: storeID, model.FieldVendorID: vendorID, }) } if err != nil { dao.Rollback(db) return 0, err } if num > 0 { if globals.IsAddEvent { mapBefore := refutil.FindMapAndStructMixed(valid, beforeStoreMap) err = AddEventDetail(db, ctx, model.OperateUpdate, vendorID, model.ThingTypeStore, storeID, BuildDiffData(mapBefore), BuildDiffData(valid)) } if vendorID != model.VendorIDJX { if valid["pricePercentage"] != nil || valid["pricePercentagePack"] != nil { storeSkuBind := &model.StoreSkuBind{} if num, err = dao.UpdateEntityLogicallyAndUpdateSyncStatus(db, storeSkuBind, nil, userName, map[string]interface{}{ model.FieldStoreID: storeID, }, dao.GetSyncStatusStructField(model.VendorNames[vendorID]), model.SyncFlagPriceMask); err != nil { dao.Rollback(db) return 0, err } } } else { if valid["pricePercentage"] != nil || valid["pricePercentagePack"] != nil { ReCalculateJxPriceLight(db, ctx, storeID) } } } dao.Commit(db) if isStoreMapNeedSync(vendorID, valid) { _, err = CurVendorSync.SyncStore(ctx, db, vendorID, storeID, false, userName) } } } return num, err } func DeleteStore(ctx *jxcontext.Context, storeID int) (num int64, err error) { db := dao.GetDB() store := &model.Store{} store.ID = storeID if err = dao.GetEntity(db, store); err != nil { return 0, err } if store.Status == model.StoreStatusOpened { return 0, fmt.Errorf("删除京西门店前必须将所有门店解绑且门店处于关店状态") } sql := ` SELECT (SELECT COUNT(*) FROM store_map t1 WHERE t1.store_id = ? AND t1.deleted_at = ?) + (SELECT COUNT(*) FROM store_courier_map t1 WHERE t1.store_id = ? AND t1.deleted_at = ?) ct ` ct := 0 if err = dao.GetRow(db, &ct, sql, storeID, utils.DefaultTimeValue, storeID, utils.DefaultTimeValue); err != nil { return 0, err } globals.SugarLogger.Debugf("DeleteStore storeID:%d, ct=%d", storeID, ct) if ct > 0 { return 0, fmt.Errorf("删除京西门店前必须将所有门店解绑且门店处于关店状态") } dao.Begin(db) defer dao.Rollback(db) now := time.Now() for _, tableName := range []string{"store_sku_bind", "store_sku_category_map", "store_op_request"} { sql = fmt.Sprintf(` UPDATE %s t1 SET t1.deleted_at = ?, t1.updated_at = ?, t1.last_operator = ? WHERE t1.store_id = ? AND t1.deleted_at = ? `, tableName) if _, err = dao.ExecuteSQL(db, sql, now, now, ctx.GetUserName(), storeID, utils.DefaultTimeValue); err != nil { return 0, err } } if num, err = dao.DeleteEntityLogically(db, store, nil, ctx.GetUserName(), nil); err != nil { return 0, err } if globals.IsAddEvent { err = AddEventDetail(db, ctx, model.OperateDelete, storeID, model.ThingTypeStore, storeID, "", "") } dao.Commit(db) return num, err // return 0, errors.New("暂不支持删除京西门店") } // 状态是未解决,且初始是2星以下 func TmpGetJxBadCommentsNo(ctx *jxcontext.Context, storeID int) (count int, err error) { db := dao.GetDB() var ctInfo struct { Ct int } if err = dao.GetRow(db, &ctInfo, ` SELECT COUNT(*) ct FROM jx_bad_comments WHERE status = ? AND jxstoreid = ? AND score <= ? `, orderman.COMMENT_NOT_RESOLVED, utils.Int2Str(storeID), orderman.JX_BAD_COMMENTS_MAX_LEVEL); err == nil { count = ctInfo.Ct } return count, err } func TmpGetJxBadCommentsByStoreId(ctx *jxcontext.Context, keyword string, storeIDs []int, offset, pageSize, commentType int, fromTime, toTime time.Time) (retVal map[string]interface{}, err error) { db := dao.GetDB() sql := ` SELECT SQL_CALC_FOUND_ROWS t1.*, t2.name store_name, t3.name city_name, t4.vendor_order_id2 FROM jx_bad_comments t1 LEFT JOIN store t2 ON t2.id = t1.jxstoreid LEFT JOIN place t3 ON t3.code = t2.city_code LEFT JOIN goods_order t4 ON t4.vendor_order_id = t1.order_id AND t4.vendor_id = t1.order_flag WHERE 1 = 1 ` sqlParams := []interface{}{} if keyword != "" { keywordLike := "%" + keyword + "%" sql += ` AND (t1.order_id LIKE ? OR t1.jxstoreid LIKE ? OR t1.userphone LIKE ? OR t1.scorecontent LIKE ? OR t1.vendertags LIKE ? OR t1.updated_scorecontent LIKE ? OR t1.updated_vendertags LIKE ? OR t2.name LIKE ?)` sqlParams = append(sqlParams, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike) } if len(storeIDs) > 0 { sql += " AND t1.jxstoreid IN (" + dao.GenQuestionMarks(len(storeIDs)) + ")" sqlParams = append(sqlParams, storeIDs) } if commentType != GET_ALL_COMMENTS_TYPE { sql += " AND t1.status = ?" if commentType == GET_BAD_COMMENTS_TYPE { sql += " AND t1.score <= ?" sqlParams = append(sqlParams, orderman.COMMENT_NOT_RESOLVED, orderman.JX_BAD_COMMENTS_MAX_LEVEL) } else { sqlParams = append(sqlParams, orderman.COMMENT_RESOLVED) } } if !utils.IsTimeZero(fromTime) { sql += " AND t1.createtime >= ?" sqlParams = append(sqlParams, fromTime) } if !utils.IsTimeZero(toTime) { sql += " AND t1.createtime < ?" sqlParams = append(sqlParams, toTime) } sql += " ORDER BY t1.createtime DESC" pageSize = jxutils.FormalizePageSize(pageSize) offset = jxutils.FormalizePageOffset(offset) sql += " LIMIT ? OFFSET ?" sqlParams = append(sqlParams, pageSize, offset) var commentList []*JxBadCommentsExt dao.Begin(db) defer func() { dao.Rollback(db) }() // globals.SugarLogger.Debug(sql) // globals.SugarLogger.Debug(utils.Format4Output(sqlParams, false)) if err = dao.GetRows(db, &commentList, sql, sqlParams...); err == nil { retVal = map[string]interface{}{ "total": dao.GetLastTotalRowCount(db), "list": commentList, } dao.Commit(db) } return retVal, err } func GetStoreCourierMaps(ctx *jxcontext.Context, db *dao.DaoDB, storeID int, vendorID int) (storeCourierMaps []*model.StoreCourierMap, err error) { cond := map[string]interface{}{ model.FieldStoreID: storeID, } if vendorID != -1 { cond[model.FieldVendorID] = vendorID } return storeCourierMaps, dao.GetEntitiesByKV(db, &storeCourierMaps, cond, false) } func AddStoreCourierMap(ctx *jxcontext.Context, db *dao.DaoDB, storeID, vendorID int, storeCourierMap *model.StoreCourierMap) (outStoreCourierMap *model.StoreCourierMap, err error) { return addStoreCourierMap(ctx, db, storeID, vendorID, storeCourierMap, true) } func addStoreCourierMap(ctx *jxcontext.Context, db *dao.DaoDB, storeID, vendorID int, storeCourierMap *model.StoreCourierMap, isNeedUpdateRemote bool) (outStoreCourierMap *model.StoreCourierMap, err error) { storeCourierMap.StoreID = storeID storeCourierMap.VendorID = vendorID globals.SugarLogger.Debugf("addStoreCourierMap %s, storeCourierMap:%s, isNeedUpdateRemote:%t", model.VendorChineseNames[vendorID], utils.Format4Output(storeCourierMap, true), isNeedUpdateRemote) if handler := partner.GetDeliveryPlatformFromVendorID(vendorID); handler != nil { if db == nil { db = dao.GetDB() } if isNeedUpdateRemote { storeDetail, err2 := dao.GetStoreDetail2(db, storeID, "", vendorID) if err = err2; err != nil { return nil, err } if storeDetail.VendorStoreID != "" { return nil, fmt.Errorf("门店已经绑定了%s, ID:%s, 如需重新绑定, 请先解绑", model.VendorChineseNames[vendorID], storeDetail.VendorStoreID) } storeDetail.VendorID = vendorID storeDetail.VendorStoreID = storeCourierMap.VendorStoreID if err = updateCourierStore(ctx, storeDetail); err != nil { if vendorID != model.VendorIDMTPS { return nil, err } // 如果是美团配送,强制忽略更新错 globals.SugarLogger.Infof("addStoreCourierMap storeID:%d, vendorID:%d failed with err:%v", storeID, vendorID, err) err = nil } } dao.WrapAddIDCULDEntity(storeCourierMap, ctx.GetUserName()) if err = dao.CreateEntity(db, storeCourierMap); err != nil { return nil, err } if globals.IsAddEvent { err = AddEventDetail(db, ctx, model.OperateAdd, vendorID, model.ThingTypeStore, storeID, "", `{"VendorID":`+utils.Int2Str(vendorID)+`}`) } outStoreCourierMap = storeCourierMap } else { err = ErrCanNotFindVendor } return outStoreCourierMap, err } func DeleteStoreCourierMap(ctx *jxcontext.Context, db *dao.DaoDB, storeID, vendorID int, userName string) (num int64, err error) { if db == nil { db = dao.GetDB() } storeCourierMap := &model.StoreCourierMap{} num, err = dao.DeleteEntityLogically(db, storeCourierMap, map[string]interface{}{ model.FieldStatus: model.StoreStatusDisabled, }, userName, map[string]interface{}{ model.FieldStoreID: storeID, model.FieldVendorID: vendorID, }) if globals.IsAddEvent { err = AddEventDetail(db, ctx, model.OperateDelete, vendorID, model.ThingTypeStore, storeID, "", `{"VendorID":`+utils.Int2Str(vendorID)+`}`) } return num, err } func UpdateStoreCourierMap(ctx *jxcontext.Context, db *dao.DaoDB, storeID, vendorID int, payload map[string]interface{}, userName string) (num int64, err error) { if db == nil { db = dao.GetDB() } storeCourier := &model.StoreCourierMap{ StoreID: storeID, VendorID: vendorID, } storeCourier.DeletedAt = utils.DefaultTimeValue if err = dao.GetEntity(db, storeCourier, model.FieldStoreID, model.FieldVendorID, model.FieldDeletedAt); err != nil { return 0, err } var beforeStoreCourier = *storeCourier delete(payload, "auditStatus") // 不允许直接修改auditStatus的值 valid := dao.NormalMakeMapByStructObject(payload, storeCourier, userName) if len(valid) > 0 { if storeCourier.AuditStatus != model.StoreAuditStatusOnline { if status := utils.Interface2Int64WithDefault(valid["status"], 0); status == model.StoreStatusOpened { // 没有通过审核的禁止改状态为正常 return 0, fmt.Errorf("此快递门店还没有通过审核,不启用") } } num, err = dao.UpdateEntityLogically(db, storeCourier, valid, userName, nil) if globals.IsAddEvent { mapBefore := refutil.FindMapAndStructMixed(valid, beforeStoreCourier) err = AddEventDetail(db, ctx, model.OperateUpdate, vendorID, model.ThingTypeStore, storeID, BuildDiffData(mapBefore), BuildDiffData(valid)) } } return num, err } func updateCourierStore(ctx *jxcontext.Context, storeDetail *dao.StoreDetail2) (err error) { globals.SugarLogger.Debugf("updateCourierStore %s, storeID:%d, vendorStoreID:%s", model.VendorChineseNames[storeDetail.VendorID], storeDetail.ID, storeDetail.VendorStoreID) if handlerInfo := partner.GetDeliveryPlatformFromVendorID(storeDetail.VendorID); handlerInfo != nil && handlerInfo.Use4CreateWaybill { if updateHandler, ok := handlerInfo.Handler.(partner.IDeliveryUpdateStoreHandler); ok { err = updateHandler.UpdateStore(ctx, formalizeStore4Courier(storeDetail)) } } else { err = fmt.Errorf("配送平台:%s不被支持", model.VendorChineseNames[storeDetail.VendorID]) } return err } func updateCourierStores(ctx *jxcontext.Context, storeID int) (err error) { globals.SugarLogger.Debugf("updateCourierStores storeID:%d", storeID) db := dao.GetDB() errList := errlist.New() for k, v := range partner.DeliveryPlatformHandlers { if v.Use4CreateWaybill { if _, ok := v.Handler.(partner.IDeliveryUpdateStoreHandler); ok { storeDetail, err2 := dao.GetStoreDetail2(db, storeID, "", k) if err = err2; err2 == nil { if storeDetail.VendorStoreID != "" && storeDetail.AuditStatus == model.StoreAuditStatusOnline { err = updateCourierStore(ctx, storeDetail) } } errList.AddErr(err) } } } return errList.GetErrListAsOne() } func updateOrCreateCourierStore(ctx *jxcontext.Context, storeDetail *dao.StoreDetail2) (isCreated bool, err error) { globals.SugarLogger.Debugf("updateOrCreateCourierStore %s, storeID:%d, vendorStoreID:%s", model.VendorChineseNames[storeDetail.VendorID], storeDetail.ID, storeDetail.VendorStoreID) if handlerInfo := partner.GetDeliveryPlatformFromVendorID(storeDetail.VendorID); handlerInfo != nil && handlerInfo.Use4CreateWaybill { if storeDetail.DistrictName == "" { return false, fmt.Errorf("门店的区码有问题,请检查") } if storeDetail.CityName == "" { return false, fmt.Errorf("门店的城市码有问题,请检查") } formalizeStore4Courier(storeDetail) needUpdate := false remoteStoreDetail, err2 := handlerInfo.Handler.GetStore(ctx, 0, storeDetail.VendorStoreID) if err = err2; err != nil { if handlerInfo.Handler.IsErrStoreNotExist(err) { storeDetail.VendorStoreID, storeDetail.AuditStatus, err = handlerInfo.Handler.CreateStore(ctx, storeDetail) if err == nil { isCreated = true } else if handlerInfo.Handler.IsErrStoreExist(err) { storeDetail.AuditStatus = model.StoreAuditStatusCreated err = nil } } } else { storeDetail.CourierStatus = remoteStoreDetail.CourierStatus storeDetail.AuditStatus = remoteStoreDetail.AuditStatus needUpdate = true } if err == nil && needUpdate { if updateHandler, _ := handlerInfo.Handler.(partner.IDeliveryUpdateStoreHandler); updateHandler != nil { err = updateHandler.UpdateStore(ctx, storeDetail) } else { // err = fmt.Errorf("快递平台%s不支持更新门店信息,请手动处理", model.VendorChineseNames[storeDetail.VendorID]) } } if err != nil { err = fmt.Errorf("门店ID:%d,门店名:%s,错误描述:%s", storeDetail.Store.ID, storeDetail.Name, err.Error()) globals.SugarLogger.Debugf("updateOrCreateCourierStore storeID:%d failed with error:%v", storeDetail.ID, err) } } return isCreated, err } func UpdateOrCreateCourierStores(ctx *jxcontext.Context, storeID int, isForceUpdate, isAsync, isContinueWhenError bool) (hint string, err error) { globals.SugarLogger.Debugf("UpdateOrCreateCourierStores storeID:%d", storeID) var storeIDs []int if storeID != 0 { storeIDs = []int{storeID} } else { storesInfo, err2 := GetStores(ctx, "", map[string]interface{}{}, 0, -1, utils.DefaultTimeValue, utils.DefaultTimeValue, 0, 0) if err = err2; err != nil { return "", err } for _, v := range storesInfo.Stores { storeIDs = append(storeIDs, v.ID) } } task := tasksch.NewParallelTask("UpdateOrCreateCourierStores", tasksch.NewParallelConfig().SetIsContinueWhenError(isContinueWhenError), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { var resultList []interface{} storeID := batchItemList[0].(int) errList := errlist.New() db := dao.GetDB() for vendorID, v := range partner.DeliveryPlatformHandlers { if v.Use4CreateWaybill { if _, ok := v.Handler.(partner.IDeliveryUpdateStoreHandler); ok { storeDetail, err2 := dao.GetStoreDetail2(db, storeID, "", vendorID) if err = err2; err2 == nil { isNeedAdd := storeDetail.VendorStoreID == "" if isForceUpdate || isNeedAdd { if isNeedAdd { storeDetail.VendorID = vendorID storeDetail.VendorStoreID = utils.Int2Str(storeDetail.ID) } if _, err = updateOrCreateCourierStore(ctx, storeDetail); err == nil && isNeedAdd { storeCourier := &model.StoreCourierMap{ VendorStoreID: storeDetail.VendorStoreID, Status: model.StoreStatusOpened, AuditStatus: storeDetail.AuditStatus, } if storeDetail.AuditStatus != model.StoreAuditStatusOnline { storeCourier.Status = model.StoreStatusDisabled } if _, err = addStoreCourierMap(ctx, db, storeDetail.ID, storeDetail.VendorID, storeCourier, false); err == nil { resultList = append(resultList, 1) } } } } errList.AddErr(err) } } } return resultList, errList.GetErrListAsOne() }, storeIDs) tasksch.HandleTask(task, nil, len(storeIDs) > 1).Run() if !isAsync { resultList, err2 := task.GetResult(0) if err = err2; err == nil { hint = utils.Int2Str(len(resultList)) } } else { hint = task.ID } return hint, err } func formalizeStore4Courier(storeDetail *dao.StoreDetail2) *dao.StoreDetail2 { storeDetail.Name = fmt.Sprintf("%s-%s-%s", model.ShopChineseNames[model.VendorIDJD], storeDetail.CityName, storeDetail.Name) if storeDetail.PayeeName == "" { storeDetail.PayeeName = "店主" } return storeDetail } func ExportShopsHealthInfo(ctx *jxcontext.Context, vendorIDs, storeIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) { db := dao.GetDB() vendorID := model.VendorIDEBAI storeMapList, err := dao.GetStoresMapList(db, []int{vendorID}, storeIDs, nil, model.StoreStatusAll, model.StoreIsSyncYes, "") if err != nil { return "", err } storeMap2 := make(map[string]*model.StoreMap) for _, v := range storeMapList { storeMap2[v.VendorStoreID] = v } if len(storeMapList) > 0 { var healthInfoList []interface{} var excelBin []byte var excelURL string task := tasksch.NewSeqTask(fmt.Sprintf("ExportShopHealthInfo[%s]", model.VendorChineseNames[vendorID]), ctx, func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { switch step { case 0: subTask := tasksch.NewParallelTask(fmt.Sprintf("ExportShopHealthInfo2[%s]", model.VendorChineseNames[vendorID]), tasksch.NewParallelConfig().SetIsContinueWhenError(isContinueWhenError), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { storeMap := batchItemList[0].(*model.StoreMap) healthInfo, err := ebai.CurPurchaseHandler.GetShopHealthInfo(storeMap.VendorStoreID) if err == nil { retVal = []map[string]interface{}{healthInfo} } return retVal, err }, storeMapList) tasksch.AddChild(task, subTask).Run() healthInfoList, err = subTask.GetResult(0) if isContinueWhenError && err != nil && len(healthInfoList) > 0 { err = nil } case 1: var healthInfoList2 []map[string]interface{} for _, v := range healthInfoList { mapInfo := v.(map[string]interface{}) mapInfo["real_shop_id"] = storeMap2[utils.Interface2String(mapInfo["merchant_id"])].StoreID healthInfoList2 = append(healthInfoList2, mapInfo) } excelConf := &excel.Obj2ExcelSheetConfig{ Title: "饿百门店情况导出", Data: healthInfoList2, CaptionList: []string{ "real_shop_id", "merchant_id", "merchant_name", "hours", "sku_num", "target_jiedan", "is_healthy", "bad_order_rate", }, } excelBin = excel.Obj2Excel([]*excel.Obj2ExcelSheetConfig{excelConf}) case 2: keyPart := []string{ ctx.GetUserName(), "饿百门店情况", } keyPart = append(keyPart, time.Now().Format("20060102T150405")+".xlsx") key := "export/" + strings.Join(keyPart, "_") excelURL, err = jxutils.UploadExportContent(excelBin, key) if err == nil { task.SetNoticeMsg(excelURL) } globals.SugarLogger.Debugf("导出饿百门店情况excelURL:%s, err:%v", excelURL, err) } return nil, err }, 3) tasksch.HandleTask(task, nil, true).Run() if !isAsync { _, err = task.GetResult(0) if err == nil { hint = excelURL } } else { hint = task.GetID() } } return hint, err } func GetCorporationInfo(ctx *jxcontext.Context, licenceCode string) (corporationInfo *jdapi.CorporationInfo, err error) { // 门店ID随便一个合法的京东门店ID就可以 corporationInfo, err = api.JdAPI.GetCorporationInfo("11734851", licenceCode) return corporationInfo, err } func GetStoresVendorSnapshot(ctx *jxcontext.Context, parentTask tasksch.ITask, vendorIDs, storeIDs []int) (vendorStoreSnapshotList []*model.VendorStoreSnapshot, err error) { db := dao.GetDB() storeMapList, err := dao.GetStoresMapList(db, vendorIDs, storeIDs, nil, model.StoreStatusAll, model.StoreIsSyncYes, "") if err != nil { return nil, err } task := tasksch.NewParallelTask("GetStoresVendorSnapshot", tasksch.NewParallelConfig().SetIsContinueWhenError(true), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { storeMap := batchItemList[0].(*model.StoreMap) if model.IsVendorRemote(storeMap.VendorID) { if handler := partner.GetPurchasePlatformFromVendorID(storeMap.VendorID); handler != nil { store, err2 := handler.ReadStore(ctx, storeMap.VendorOrgCode, storeMap.VendorStoreID) if err = err2; err == nil { retVal = []interface{}{&model.VendorStoreSnapshot{ StoreID: storeMap.StoreID, VendorID: storeMap.VendorID, VendorStoreID: storeMap.VendorStoreID, Status: store.Status, OpenTime1: store.OpenTime1, CloseTime1: store.CloseTime1, OpenTime2: store.OpenTime2, CloseTime2: store.CloseTime2, DeliveryType: store.DeliveryType, StoreName: store.OriginalName, IsAutoOrder: store.IsAutoOrder, }} } } } return retVal, err }, storeMapList) tasksch.HandleTask(task, parentTask, true).Run() resultList, err := task.GetResult(0) if len(resultList) > 0 { for _, v := range resultList { dao.WrapAddIDCULDEntity(v, ctx.GetUserName()) vendorStoreSnapshotList = append(vendorStoreSnapshotList, v.(*model.VendorStoreSnapshot)) } } return vendorStoreSnapshotList, err } func getCurrentSnapshotAt(now time.Time) (snapshotAt time.Time) { return jxutils.GetLastTimeFromList(now, WatchVendorStoreTimeList) } func updateVendorStoreStatusBySnapshot(db *dao.DaoDB, curSnapshotList []*model.VendorStoreSnapshot) (err error) { storeMapList, err := dao.GetStoresMapList(db, nil, nil, nil, model.StoreStatusAll, model.StoreIsSyncAll, "") if err != nil { return err } snapshotMap := make(map[int64]*model.VendorStoreSnapshot) for _, v := range curSnapshotList { snapshotMap[jxutils.Combine2Int(v.StoreID, v.VendorID)] = v } dao.Begin(db) defer func() { if r := recover(); r != nil || err != nil { dao.Rollback(db) if r != nil { panic(r) } } }() for _, v := range storeMapList { if snapshot := snapshotMap[jxutils.Combine2Int(v.StoreID, v.VendorID)]; snapshot != nil && (v.Status != snapshot.Status || v.DeliveryType != snapshot.DeliveryType || v.StoreName != snapshot.StoreName) { v.Status = snapshot.Status v.DeliveryType = snapshot.DeliveryType v.StoreName = snapshot.StoreName if _, err = dao.UpdateEntity(db, v, model.FieldStatus, "DeliveryType", "StoreName"); err != nil { return err } } } dao.Commit(db) utils.CallFuncAsync(func() { for _, v := range storeMapList { if snapshot := snapshotMap[jxutils.Combine2Int(v.StoreID, v.VendorID)]; snapshot != nil { if snapshot.IsAutoOrder == 1 { if handler, ok := partner.GetPurchasePlatformFromVendorID(snapshot.VendorID).(partner.IStoreHandler); ok { handler.EnableAutoAcceptOrder(jxcontext.AdminCtx, v.VendorOrgCode, v.StoreID, v.VendorStoreID, false) } } } } }) return err } func SaveStoresVendorSnapshot(db *dao.DaoDB, snapshotAt time.Time, curSnapshotList []*model.VendorStoreSnapshot) (err error) { dao.Begin(db) defer func() { if r := recover(); r != nil || err != nil { dao.Rollback(db) if r != nil { panic(r) } } }() if err = dao.DeleteVendorStoreSnapshot(db, snapshotAt.Add(-48*time.Hour)); err != nil { return err } for _, v := range curSnapshotList { v.SnapshotAt = snapshotAt dao.DeleteEntity(db, v, "VendorStoreID", "VendorID", "SnapshotAt") if err = dao.CreateEntity(db, v); err != nil { return err } } dao.Commit(db) return err } func isVendorStoresStatusNotOk(storeMapList []*model.VendorStoreSnapshot) bool { statusMap := make(map[int]int) maxStatus := model.StoreStatusClosed for _, v := range storeMapList { statusMap[v.Status] = 1 if v.Status > maxStatus { maxStatus = v.Status } } return len(statusMap) > 1 && maxStatus == model.StoreStatusOpened } func getAllUsers4Store(ctx *jxcontext.Context, db *dao.DaoDB, store *model.Store) (userList []*model.User) { storeID := store.ID var userIDs []string userMap := make(map[string]int) // 门店老板 if roleUserIDList, err := GetRoleUserList(ctx, autils.NewStoreBossRole(storeID)); err == nil && len(roleUserIDList) > 0 { userIDs = append(userIDs, roleUserIDList...) } // 全局相关角色(市场或运营) for _, v := range []string{store.MarketManRole, store.OperatorRole, store.OperatorRole2} { if v != "" { if roleUserIDList, err := GetRoleUserList(ctx, autils.NewRole(v, 0)); err == nil && len(roleUserIDList) > 0 { userIDs = append(userIDs, roleUserIDList...) } } } if len(userIDs) > 0 { userList, _, _ = dao.GetUsers(db, 0, "", userIDs, nil, nil, 0, -1) for _, v := range userList { userMap[v.GetID()] = 1 } } // 直接电话信息相关人员 for _, mobile := range []string{store.Tel1, store.Tel2, store.MarketManPhone, store.OperatorPhone, store.OperatorPhone2} { if mobile != "" { if user, err2 := dao.GetUserByID(db, "mobile", mobile); err2 == nil { if userMap[user.GetID()] == 0 { userMap[user.GetID()] = 1 userList = append(userList, user) } } } } return userList } type tStoreIDList struct { StoreIDList []int StoreMap map[int]*model.Store } func (l *tStoreIDList) Len() int { return len(l.StoreIDList) } func (l *tStoreIDList) Less(i, j int) bool { storei := l.StoreMap[l.StoreIDList[i]] storej := l.StoreMap[l.StoreIDList[j]] if storei.CityCode == storej.CityCode { return storei.ID < storej.ID } return storei.CityCode < storej.CityCode } func (l *tStoreIDList) Swap(i, j int) { l.StoreIDList[i], l.StoreIDList[j] = l.StoreIDList[j], l.StoreIDList[i] } func SendAlarmVendorSnapshot(ctx *jxcontext.Context, parentTask tasksch.ITask, prevSnapshotList, curSnapshotList []*model.VendorStoreSnapshot) (err error) { if len(prevSnapshotList) == 0 { return nil } prevSnapshotMap := make(map[string]*model.VendorStoreSnapshot) prevSnapshotMap2 := make(map[int]map[int]*model.VendorStoreSnapshot) for _, v := range prevSnapshotList { prevSnapshotMap[v.GenMapKey()] = v if prevSnapshotMap2[v.StoreID] == nil { prevSnapshotMap2[v.StoreID] = make(map[int]*model.VendorStoreSnapshot) } prevSnapshotMap2[v.StoreID][v.VendorID] = v } curSnapshotMap := make(map[string]*model.VendorStoreSnapshot) curSnapshotMap2 := make(map[int]map[int]*model.VendorStoreSnapshot) curSnapshotGroupMap := make(map[int][]*model.VendorStoreSnapshot) alarmSnapshotMap := make(map[int][]*model.VendorStoreSnapshot) txtAlarmSnapshotMap := make(map[int][]*model.VendorStoreSnapshot) // 之前是开店当前是关店的,或营业时间缩短的 for _, v := range curSnapshotList { curSnapshotGroupMap[v.StoreID] = append(curSnapshotGroupMap[v.StoreID], v) if curSnapshotMap2[v.StoreID] == nil { curSnapshotMap2[v.StoreID] = make(map[int]*model.VendorStoreSnapshot) } curSnapshotMap2[v.StoreID][v.VendorID] = v curSnapshotMap[v.GenMapKey()] = v prevSnapshot := prevSnapshotMap[v.GenMapKey()] if prevSnapshot != nil { if (prevSnapshot.Status == model.StoreStatusOpened && v.Status != model.StoreStatusOpened) || v.CompareOperationTime(prevSnapshot) < 0 { alarmSnapshotMap[v.StoreID] = append(alarmSnapshotMap[v.StoreID], v) txtAlarmSnapshotMap[prevSnapshot.StoreID] = append(txtAlarmSnapshotMap[prevSnapshot.StoreID], prevSnapshot) } } } //当前门店,不同平台门店状态不一致的 for storeID, list := range curSnapshotGroupMap { if isVendorStoresStatusNotOk(list) { alarmSnapshotMap[storeID] = list } } // 之前有店(且是开店状态),当前无店的 for _, v := range prevSnapshotList { if v.Status == model.StoreStatusOpened && curSnapshotMap[v.GenMapKey()] == nil { alarmSnapshotMap[v.StoreID] = append(alarmSnapshotMap[v.StoreID], v) txtAlarmSnapshotMap[v.StoreID] = append(txtAlarmSnapshotMap[v.StoreID], v) } } db := dao.GetDB() storeDetailMap := make(map[int]*dao.StoreDetail) userMap2 := make(map[string]*model.User) userMap := make(map[string]map[int]int) userMapTxt := make(map[string][]*model.VendorStoreSnapshot) for storeID, list := range alarmSnapshotMap { storeDetail, _ := dao.GetStoreDetail(db, storeID, list[0].VendorID) if storeDetail != nil { storeDetailMap[storeID] = storeDetail userList := getAllUsers4Store(ctx, db, &storeDetail.Store) for _, user := range userList { userID := user.GetID() if userMap[userID] == nil { userMap[userID] = make(map[int]int) userMap2[userID] = user } userMap[userID][storeID] = 1 if txtAlarmSnapshotMap[storeID] != nil { userMapTxt[userID] = append(userMapTxt[userID], txtAlarmSnapshotMap[storeID]...) } } } } var userList []*model.User for _, user := range userMap2 { userList = append(userList, user) } if len(userList) > 0 { allStores, err := dao.GetStoreList(db, nil, nil, nil, nil, "") if err != nil { return err } allStoreMap := make(map[int]*model.Store) for _, v := range allStores { allStoreMap[v.ID] = v } const fixTitle = "门店状态变化" title := fmt.Sprintf("%s:%s-->%s", fixTitle, utils.Time2Str(prevSnapshotList[0].SnapshotAt), utils.Time2Str(curSnapshotList[0].SnapshotAt)) task := tasksch.NewParallelTask("SendAlarmVendorSnapshot", tasksch.NewParallelConfig().SetIsContinueWhenError(true), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { user := batchItemList[0].(*model.User) var excelURL string if user.Type&model.UserTypeOperator != 0 { var dataList []map[string]interface{} captionList := []string{"京西门店ID", "门店名", "城市"} isFirstRow := true storeIDList := &tStoreIDList{ StoreMap: allStoreMap, } for storeID := range userMap[user.GetID()] { storeIDList.StoreIDList = append(storeIDList.StoreIDList, storeID) } sort.Sort(storeIDList) for _, storeID := range storeIDList.StoreIDList { prevAlarmMap := prevSnapshotMap2[storeID] curAlarmMap := curSnapshotMap2[storeID] data := map[string]interface{}{ "京西门店ID": storeID, "门店名": storeDetailMap[storeID].Store.Name, "城市": storeDetailMap[storeID].CityName, } for _, vendorID := range []int{model.VendorIDJD, model.VendorIDEBAI, model.VendorIDMTWM} { if isFirstRow { captionList = append(captionList, model.VendorChineseNames[vendorID]+"ID", model.VendorChineseNames[vendorID]+"之前状态", model.VendorChineseNames[vendorID]+"当前状态", model.VendorChineseNames[vendorID]+"之前营业时间", model.VendorChineseNames[vendorID]+"当前营业时间") } if prevAlarmMap != nil { data[model.VendorChineseNames[vendorID]+"当前状态"] = "" data[model.VendorChineseNames[vendorID]+"当前营业时间"] = "" if prevSnapshot := prevAlarmMap[vendorID]; prevSnapshot != nil { data[model.VendorChineseNames[vendorID]+"ID"] = prevSnapshot.VendorStoreID data[model.VendorChineseNames[vendorID]+"之前状态"] = model.StoreStatusName[prevSnapshot.Status] data[model.VendorChineseNames[vendorID]+"之前营业时间"] = jxutils.OperationTimeStr4VendorStore(prevSnapshot) if snapshot := curSnapshotMap[prevSnapshot.GenMapKey()]; snapshot != nil { data[model.VendorChineseNames[vendorID]+"当前状态"] = model.StoreStatusName[snapshot.Status] data[model.VendorChineseNames[vendorID]+"当前营业时间"] = jxutils.OperationTimeStr4VendorStore(snapshot) } } else { data[model.VendorChineseNames[vendorID]+"ID"] = "" data[model.VendorChineseNames[vendorID]+"之前状态"] = "" data[model.VendorChineseNames[vendorID]+"之前营业时间"] = "" } } else if curAlarmMap != nil { data[model.VendorChineseNames[vendorID]+"之前状态"] = "" data[model.VendorChineseNames[vendorID]+"之前营业时间"] = "" if curSnapshot := curAlarmMap[vendorID]; curSnapshot != nil { data[model.VendorChineseNames[vendorID]+"ID"] = curSnapshot.VendorStoreID data[model.VendorChineseNames[vendorID]+"当前状态"] = model.StoreStatusName[curSnapshot.Status] data[model.VendorChineseNames[vendorID]+"当前营业时间"] = jxutils.OperationTimeStr4VendorStore(curSnapshot) } else { data[model.VendorChineseNames[vendorID]+"ID"] = "" data[model.VendorChineseNames[vendorID]+"当前状态"] = "" data[model.VendorChineseNames[vendorID]+"当前营业时间"] = "" } } } dataList = append(dataList, data) isFirstRow = false } excelConf := &excel.Obj2ExcelSheetConfig{ Title: fixTitle, Data: dataList, CaptionList: captionList, } excelBin := excel.Obj2Excel([]*excel.Obj2ExcelSheetConfig{excelConf}) keyPart := []string{ "store_status", user.GetMobile(), time.Now().Format("20060102T150405") + ".xlsx", } key := "export/" + strings.Join(keyPart, "_") excelURL, err = jxutils.UploadExportContent(excelBin, key) if err != nil { globals.SugarLogger.Warnf("SendAlarmVendorSnapshot, %s upload %s failed with error:%v", user.GetName(), key, err) } var txtAlarm []string for _, v := range userMapTxt[user.GetID()] { curSnapshot := curSnapshotMap[v.GenMapKey()] storeDetail := storeDetailMap[v.StoreID] curStoreStatus := "无店" curOpTimeStr := "无店" if curSnapshot != nil { curStoreStatus = model.StoreStatusName[curSnapshot.Status] curOpTimeStr = jxutils.OperationTimeStr4VendorStore(curSnapshot) } txtAlarm = append(txtAlarm, fmt.Sprintf(`## 门店: %s(%d) - 城市: %s - 平台: %s - 平台ID: %s - 之前状态: %s - 当前状态: %s - 之前营业时间: %s - 当前营业时间: %s `, storeDetail.Store.Name, v.StoreID, storeDetail.CityName, model.VendorChineseNames[v.VendorID], v.VendorStoreID, model.StoreStatusName[v.Status], curStoreStatus, jxutils.OperationTimeStr4VendorStore(v), curOpTimeStr)) } alarmTextStr := "# " + title + " \n" + fmt.Sprintf("[详情点我](%s/billshow/?normal=true&path=%s) \n", globals.BackstageHost, excelURL) + strings.Join(txtAlarm, " \n") sendStoreStatusInfo2Mobile(user, dingdingapi.MsgTypeMarkdown, title, alarmTextStr) } else if len(userMapTxt[user.GetID()]) > 0 { var txtAlarm []string for _, v := range userMapTxt[user.GetID()] { curSnapshot := curSnapshotMap[v.GenMapKey()] // storeDetail := storeDetailMap[v.StoreID] curStoreStatus := "下线" if curSnapshot != nil { curStoreStatus = model.StoreStatusName[curSnapshot.Status] } txtAlarm = append(txtAlarm, fmt.Sprintf("您的门店:%s,平台:%s,%s了", v.StoreName, model.VendorChineseNames[v.VendorID], curStoreStatus)) } alarmTextStr := strings.Join(txtAlarm, ",\n") sendStoreStatusInfo2Mobile(user, dingdingapi.MsgTyeText, title, alarmTextStr) } return nil, nil }, userList) tasksch.HandleTask(task, parentTask, true).Run() _, err = task.GetResult(0) } return err } func sendStoreStatusInfo2Mobile(user *model.User, msgType, title, txtAlarm string) { msg.SendUserMessage(msgType, user, title, txtAlarm) } func SaveAndSendAlarmVendorSnapshot(ctx *jxcontext.Context, vendorIDs, storeIDs []int, isAsync bool) (err error) { curSnapshotAt := getCurrentSnapshotAt(time.Now()) prevSnapshotAt := getCurrentSnapshotAt(curSnapshotAt.Add(-1 * time.Second)) db := dao.GetDB() var curSnapshotList, prevSnapshotList []*model.VendorStoreSnapshot task := tasksch.NewParallelTask("SaveAndSendAlarmVendorSnapshot", tasksch.NewParallelConfig().SetIsContinueWhenError(true).SetParallelCount(1), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { step := batchItemList[0].(int) switch step { case 0: curSnapshotList, err = GetStoresVendorSnapshot(ctx, task, vendorIDs, storeIDs) if len(curSnapshotList) == 0 { task.Cancel() } else { updateVendorStoreStatusBySnapshot(db, curSnapshotList) } case 1: err = SaveStoresVendorSnapshot(db, curSnapshotAt, curSnapshotList) case 2: prevSnapshotList, err = dao.GetVendorStoreSnapshot(db, prevSnapshotAt) case 3: err = SendAlarmVendorSnapshot(ctx, task, prevSnapshotList, curSnapshotList) } return nil, err }, []int{0, 1, 2, 3}) tasksch.ManageTask(task).Run() if !isAsync { _, err = task.GetResult(0) } return err } func SyncStoresCourierInfo(ctx *jxcontext.Context, storeIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) { db := dao.GetDB() storeList2, err := dao.GetStoreList(db, storeIDs, nil, nil, nil, "") var storeList []*model.Store for _, v := range storeList2 { if v.Status != model.StoreStatusDisabled { storeList = append(storeList, v) } } if err == nil && len(storeList) > 0 { task := tasksch.NewParallelTask(fmt.Sprintf("同步门店快递信息1:%v", storeIDs), tasksch.NewParallelConfig().SetParallelCount(2).SetIsContinueWhenError(isContinueWhenError), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { store := batchItemList[0].(*model.Store) subTask := tasksch.NewParallelTask(fmt.Sprintf("同步门店快递信息2:%d", store.ID), tasksch.NewParallelConfig().SetIsContinueWhenError(true), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { vendorID := batchItemList[0].(int) storeDetail2, err := dao.GetStoreDetail2(db, store.ID, "", vendorID) if err == nil && storeDetail2.VendorStoreID != "" /*&& storeDetail2.CourierStatus != model.StoreStatusDisabled*/ { if handler := partner.GetDeliveryPlatformFromVendorID(vendorID); handler != nil { if updateHandler, ok := handler.Handler.(partner.IDeliveryUpdateStoreHandler); ok { updateHandler.UpdateStore(ctx, storeDetail2) } storeCourier, err2 := handler.Handler.GetStore(ctx, store.ID, storeDetail2.VendorStoreID) if err = err2; err == nil { if storeDetail2.AuditStatus == model.StoreAuditStatusCreated { // 如果已经通过审核,更新本地状态 partner.CurStoreManager.OnCourierStoreStatusChanged(ctx, storeCourier.VendorStoreID, vendorID, storeCourier.AuditStatus) } distance := jxutils.EarthDistance(jxutils.IntCoordinate2Standard(store.Lng), jxutils.IntCoordinate2Standard(store.Lat), jxutils.IntCoordinate2Standard(storeCourier.Lng), jxutils.IntCoordinate2Standard(storeCourier.Lat)) params := map[string]interface{}{ "Lng": storeCourier.Lng, "Lat": storeCourier.Lat, "Remark": fmt.Sprintf("%d", int(distance*1000)), } _, err = dao.UpdateEntityLogically(dao.GetDB(), &model.StoreCourierMap{}, params, ctx.GetUserName(), map[string]interface{}{ "StoreID": store.ID, "VendorID": vendorID, }) if distance > 0.2 { globals.SugarLogger.Infof("SyncStoresCourierInfo [运营2]门店:%s-%d的%s配送门店坐标不一致,京西:%d,%d,平台:%d,%d,距离:%f公里", store.Name, store.ID, model.VendorChineseNames[vendorID], store.Lng, store.Lat, storeCourier.Lng, storeCourier.Lat, distance) retVal = [][]interface{}{ []interface{}{ store, storeDetail2, }, } } } else if handler.Handler.IsErrStoreNotExist(err) { if storeDetail2.AuditStatus == model.StoreAuditStatusCreated { err = nil } else { globals.SugarLogger.Infof("SyncStoresCourierInfo [运营2]门店:%s-%d的%s配送门店%s获取出错:%v", store.Name, store.ID, model.VendorChineseNames[vendorID], storeDetail2.VendorStoreID, err) } } } } else if dao.IsNoRowsError(err) { err = nil } return retVal, err }, partner.UseableDeliveryVendorIDs) tasksch.HandleTask(subTask, task, true).Run() retVal, err = subTask.GetResult(0) return retVal, err }, storeList) 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 SyncStoresQualify(ctx *jxcontext.Context, storeIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) { if len(storeIDs) > 0 { db := dao.GetDB() task := tasksch.NewParallelTask(fmt.Sprintf("上传门店资质:%v", storeIDs), tasksch.NewParallelConfig().SetParallelCount(1).SetIsContinueWhenError(isContinueWhenError), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { vendorID := model.VendorIDJD if handler := partner.GetPurchasePlatformFromVendorID(vendorID).(partner.IStoreSyncQualifyHandler); handler != nil { storeID := batchItemList[0].(int) storeDetail, err2 := dao.GetStoreDetail(db, storeID, vendorID) if err = err2; err == nil { if err = handler.SyncQualify(ctx, storeDetail); err == nil { retVal = []int{1} } } } else { err = fmt.Errorf("平台%s不支持此操作", model.VendorChineseNames[vendorID]) } 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 GetStoreListByLocation(ctx *jxcontext.Context, lng, lat float64, needWalkDistance bool) (storeList []*Store4User, err error) { const ( maxRadius = 5000 maxStoreCount4User = 5 ) lng2, _ := jxutils.ConvertDistanceToLogLat(lng, lat, float64(maxRadius), 90) _, lat2 := jxutils.ConvertDistanceToLogLat(lng, lat, float64(maxRadius), 0) lng1 := lng - (lng2 - lng) lat1 := lat - (lat2 - lat) // globals.SugarLogger.Debugf("%f,%f,%f,%f\n", lng1, lng2, lat1, lat2) sql := ` SELECT t1.*, city.name city_name FROM store t1 JOIN place city ON city.code = t1.city_code WHERE t1.deleted_at = ? AND t1.status <> ? AND t1.lng > ? AND t1.lng < ? AND t1.lat > ? AND t1.lat < ? ORDER BY t1.id ` sqlParams := []interface{}{ utils.DefaultTimeValue, model.StoreStatusDisabled, jxutils.StandardCoordinate2Int(lng1), jxutils.StandardCoordinate2Int(lng2), jxutils.StandardCoordinate2Int(lat1), jxutils.StandardCoordinate2Int(lat2), } var storeList1 []*Store4User if err = dao.GetRows(dao.GetDB(), &storeList1, sql, sqlParams...); err == nil { var storeList2 []*Store4User for _, v := range storeList1 { if distance := jxutils.Point2StoreDistance(lng, lat, v.Lng, v.Lat, v.DeliveryRangeType, v.DeliveryRange); distance > 0 { v.Distance = distance storeList2 = append(storeList2, v) } } // 为了审核用 if len(storeList2) == 0 { sqlParams = []interface{}{ utils.DefaultTimeValue, model.StoreStatusDisabled, jxutils.StandardCoordinate2Int(0), jxutils.StandardCoordinate2Int(10000), jxutils.StandardCoordinate2Int(0), jxutils.StandardCoordinate2Int(10000), } dao.GetRows(dao.GetDB(), &storeList2, sql, sqlParams...) if len(storeList2) > 1 { storeList2 = storeList2[:1] } } // 如果要求以步行距离来算 if needWalkDistance { var coordList []*autonavi.Coordinate for _, v := range storeList2 { coordList = append(coordList, &autonavi.Coordinate{ Lng: v.FloatLng, Lat: v.FloatLat, }) } if distanceList, err2 := api.AutonaviAPI.BatchWalkingDistance(lng, lat, coordList); err2 == nil { for k, v := range storeList2 { v.WalkDistance = int(distanceList[k]) } } else { return nil, err2 } } sort.Sort(Store4UserList(storeList2)) storeList = storeList2 if len(storeList) > maxStoreCount4User { storeList = storeList[:maxStoreCount4User] } } return storeList, err } func JdStoreInfoCoordinateRecover(ctx *jxcontext.Context, vendorOrgCode string, files []*multipart.FileHeader) (err error) { if len(files) == 0 { return errors.New("没有文件上传!") } fileHeader := files[0] file1, err := fileHeader.Open() defer file1.Close() db := dao.GetDB() storeList, err := dao.GetStoresMapList(db, []int{model.VendorIDJD}, nil, nil, model.StoreStatusAll, model.StoreIsSyncYes, "") if err == nil { var validStoreList []*dao.StoreDetail for _, v := range storeList { if v.Status != model.StoreStatusDisabled && v.CreatedAt.Sub(utils.Str2Time("2019-10-01")) > 0 { storeInfo, err := jd.GetAPI(vendorOrgCode).GetStoreInfoByStationNo2(v.VendorStoreID) if err == nil && storeInfo.CreateTime.GoTime().Sub(utils.Str2Time("2019-10-25")) > 0 { if storeDetail, err := dao.GetStoreDetail(db, v.StoreID, v.VendorID); err == nil { validStoreList = append(validStoreList, storeDetail) } } } } getStoreList := func(lng, lat, lng2, lat2 int) (vendorStoreIDs []string) { for _, v := range validStoreList { if v.Lng >= lng && v.Lng <= lng2 && v.Lat >= lat && v.Lat <= lat2 { vendorStoreIDs = append(vendorStoreIDs, v.VendorStoreID) } } return vendorStoreIDs } sheetName := "老格明细" file, err2 := excelize.OpenReader(file1) if err = err2; err == nil { rows, err2 := file.GetRows(sheetName) if err = err2; err == nil { str2Coords := func(str string) (lng, lat int) { list := strings.Split(str, ",") if len(list) >= 2 { lng, lat = jxutils.StandardCoordinate2Int(utils.Str2Float64WithDefault(list[1], 0)), jxutils.StandardCoordinate2Int(utils.Str2Float64WithDefault(list[0], 0)) } return lng, lat } for i := 1; i < len(rows); i++ { lng, lat := str2Coords(rows[i][8]) lng2, lat2 := str2Coords(rows[i][7]) vendorStoreIDs := getStoreList(lng, lat, lng2, lat2) countInfo := fmt.Sprintf("京西已拓%d", len(vendorStoreIDs)) axis, _ := excelize.CoordinatesToCellName(5, i+1) file.SetCellStr(sheetName, axis, countInfo) axis2, _ := excelize.CoordinatesToCellName(6, i+1) file.SetCellStr(sheetName, axis2, strings.Join(vendorStoreIDs, ",")) } filename := ExecuteFileName(fileHeader.Filename) buf := bytes.NewBuffer(nil) if _, err = io.Copy(buf, file1); err != nil { return err } baseapi.SugarLogger.Debugf("WriteToExcel:save %s success", filename) downloadURL, err := jxutils.UploadExportContent(buf.Bytes(), filename) if err != nil { baseapi.SugarLogger.Errorf("WriteToExcel:upload %s, failed error:%v", filename, err) } else { if authInfo, err := ctx.GetV2AuthInfo(); err == nil { noticeMsg := fmt.Sprintf("path=%s\n", downloadURL) ddmsg.SendUserMessage(dingdingapi.MsgTyeText, authInfo.UserID, "导出老格恢复拓店进度成功", noticeMsg) } baseapi.SugarLogger.Debugf("WriteToExcel:upload %s success, downloadURL:%s", filename, downloadURL) } } } } return err } func ExecuteFileName(filename string) (name string) { filePrefix := filename[strings.LastIndex(filename, "."):len(filename)] fileRealName := filename[0:strings.LastIndex(filename, ".")] name = fileRealName + utils.Int64ToStr(time.Now().Unix()) + filePrefix return name } func GetVendorStoreInfo(ctx *jxcontext.Context, vendorIDList []int, isAsync, isContinueWhenError bool) (hint string, err error) { var ( storeListJD []VendorStoreExcel storeListMT []VendorStoreExcel storeListEB []VendorStoreExcel storeIDs []string ) taskSeqFunc := func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { switch step { case 0: for _, vendorID := range vendorIDList { iStoreHandler, _ := partner.GetPurchasePlatformFromVendorID(vendorID).(partner.IStoreHandler) if vendorID == model.VendorIDEBAI { storeIDs, err = ebai.CurPurchaseHandler.GetShopIDsByPage() } else { storeIDs, err = iStoreHandler.GetAllStoresVendorID(ctx, "") } if err != nil { return "", err } taskFunc := func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { var storeDetail *dao.StoreDetail storeID := batchItemList[0].(string) if partner.IsMultiStore(vendorID) { multiHandler, _ := partner.GetPurchasePlatformFromVendorID(vendorID).(partner.IMultipleStoresHandler) storeDetail, err = multiHandler.ReadStore(ctx, "", storeID) if err != nil { return retVal, err } } else { singleHandler, _ := partner.GetPurchasePlatformFromVendorID(vendorID).(partner.ISingleStoreHandler) storeDetail, err = singleHandler.ReadStore(ctx, "", storeID) if err != nil { return retVal, err } } db := dao.GetDB() storeDetail2, err := dao.GetStoreDetailByVendorStoreID(db, storeDetail.VendorStoreID, vendorID) if err != nil { return "", err } if storeDetail.Status != model.StoreStatusOpened { if storeDetail2 != nil { var storeExcel = VendorStoreExcel{ StoreID: storeDetail2.ID, VendorStoreName: storeDetail.Name, Tel1: storeDetail.Tel1, Tel2: storeDetail.Tel2, Address: storeDetail.Address, Status: StoreStatus2Chinese(storeDetail.Status), CityName: storeDetail.CityName, MarketManName: storeDetail2.MarketManName, OperatorName: storeDetail2.OperatorName, OperatorName2: storeDetail2.OperatorName2, } retVal = []VendorStoreExcel{storeExcel} } } return retVal, err } taskParallel := tasksch.NewParallelTask("获取各平台未营业门店", tasksch.NewParallelConfig().SetIsContinueWhenError(isContinueWhenError), ctx, taskFunc, storeIDs) tasksch.HandleTask(taskParallel, task, true).Run() storeList, _ := taskParallel.GetResult(0) for _, v := range storeList { if vendorID == model.VendorIDJD { storeListJD = append(storeListJD, v.(VendorStoreExcel)) } if vendorID == model.VendorIDEBAI { storeListEB = append(storeListEB, v.(VendorStoreExcel)) } if vendorID == model.VendorIDMTWM { storeListMT = append(storeListMT, v.(VendorStoreExcel)) } } } case 1: WriteToExcelStore(task, storeListJD, storeListMT, storeListEB) } return result, err } taskSeq := tasksch.NewSeqTask2("导出各平台未营业门店-序列任务", ctx, isContinueWhenError, taskSeqFunc, 2) tasksch.HandleTask(taskSeq, nil, true).Run() if !isAsync { _, err = taskSeq.GetResult(0) hint = "1" } else { hint = taskSeq.GetID() } return hint, err } func WriteToExcelStore(task *tasksch.SeqTask, storeListJD, storeListMT, storeListEB []VendorStoreExcel) (err error) { var sheetList []*excel.Obj2ExcelSheetConfig var downloadURL, fileName string if len(storeListJD) > 0 { excelConf := &excel.Obj2ExcelSheetConfig{ Title: "京东平台", Data: storeListJD, CaptionList: titleListStore, } sheetList = append(sheetList, excelConf) } if len(storeListMT) > 0 { excelConf := &excel.Obj2ExcelSheetConfig{ Title: "美团平台", Data: storeListMT, CaptionList: titleListStore, } sheetList = append(sheetList, excelConf) } if len(storeListEB) > 0 { excelConf := &excel.Obj2ExcelSheetConfig{ Title: "饿百平台", Data: storeListEB, CaptionList: titleListStore, } sheetList = append(sheetList, excelConf) } if len(sheetList) == 0 { return errors.New("所选平台没有未营业的门店!") } else { downloadURL, fileName, err = jxutils.UploadExeclAndPushMsg(sheetList, "各平台未营业门店统计") if err != nil { baseapi.SugarLogger.Errorf("WriteToExcel:upload %s failed error:%v", fileName, err) } else { noticeMsg := fmt.Sprintf("[详情点我]path=%s \n", downloadURL) task.SetNoticeMsg(noticeMsg) baseapi.SugarLogger.Debugf("WriteToExcel:upload %s success, downloadURL:%s", fileName, downloadURL) } } return err } func StoreStatus2Chinese(status int) (str string) { if status == model.StoreStatusOpened { return "正常营业" } else if status == model.StoreStatusDisabled { return "暂停营业" } else if status == model.StoreStatusClosed { return "休息中" } else { return "未知的营业状态" } } func GetStorePriceScore(ctx *jxcontext.Context, storeIDs, vendorIDs []int, fromScore, toScore, sort int, snapDate string, offset, pageSize int) (pagedInfo *model.PagedInfo, err error) { var snapDateParam time.Time db := dao.GetDB() if snapDate != "" { snapDateParam = utils.Str2Time(snapDate) } storePriceScore, totalCount, err := dao.GetStorePriceScore(db, storeIDs, vendorIDs, fromScore, toScore, sort, snapDateParam, offset, pageSize) pagedInfo = &model.PagedInfo{ Data: storePriceScore, TotalCount: totalCount, } return pagedInfo, err } func CreateStorePriceScore(ctx *jxcontext.Context) (err error) { db := dao.GetDB() var snapshotAt time.Time snapshotAt = utils.Time2Date(time.Now().AddDate(0, 0, -1)) storePriceScoreSnapshot, err := dao.GetStorePriceScoreSnapshot(db, snapshotAt) if len(storePriceScoreSnapshot) > 0 { dao.Begin(db) defer func() { if r := recover(); r != nil || err != nil { dao.Rollback(db) if r != nil { panic(r) } } }() priceReferSnapshotDeleteHis := &model.StorePriceScoreSnapshot{SnapshotAt: snapshotAt.AddDate(0, 0, -7)} priceReferSnapshotDelete := &model.StorePriceScoreSnapshot{SnapshotAt: snapshotAt} dao.DeleteEntity(db, priceReferSnapshotDeleteHis, "SnapshotAt") dao.DeleteEntity(db, priceReferSnapshotDelete, "SnapshotAt") for _, v := range storePriceScoreSnapshot { dao.WrapAddIDCULDEntity(v, ctx.GetUserName()) v.SnapshotAt = snapshotAt if err = dao.CreateEntity(db, v); err != nil { return err } } dao.Commit(db) globals.SugarLogger.Debugf("CreateStorePriceScore") } return err } func RefreshJdLevel(ctx *jxcontext.Context) (err error) { db := dao.GetDB() storeMapList, err := dao.GetStoresMapList(db, []int{model.VendorIDJD}, nil, nil, model.StoreStatusOpened, -1, "") if len(storeMapList) > 0 { dao.Begin(db) defer func() { if r := recover(); r != nil || err != nil { dao.Rollback(db) if r != nil { panic(r) } } }() task := tasksch.NewParallelTask("更新京东门店等级", tasksch.NewParallelConfig().SetIsContinueWhenError(true), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { v := batchItemList[0].(*model.StoreMap) var ( pageLimit = 5 pageNo = 1 level string ) for ; pageNo < pageLimit+1; pageNo++ { level, err = jd.GetAPI(v.VendorOrgCode).GetJdStoreLevel(v.VendorOrgCode, v.VendorStoreID, pageNo) if err != nil { return retVal, err } if level != "" { break } if pageNo == pageLimit { level = "" } } v.JdStoreLevel = level _, err = dao.UpdateEntity(db, v, "JdStoreLevel") return retVal, err }, storeMapList) tasksch.HandleTask(task, nil, true).Run() _, err = task.GetResult(0) dao.Commit(db) } return err }