package cms import ( "errors" "fmt" "math" "sort" "strconv" "strings" "time" "git.rosy.net.cn/jx-callback/business/authz/autils" "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/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/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:"-"` OrderCount int `json:"orderCount"` } 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"` } 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, } WatchVendorStoreTimeList = []string{ "8:00:00", "10:00:00", "11:00:00", "15:00:00", "20:00:00", } ) 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]int if err = utils.UnmarshalUseNumber([]byte(mapCondsStr), &vendorStoreConds); err != nil { return "", nil, "", nil, err } sqlVendorStoreCond := "" for vendor, cond := range vendorStoreConds { tableAlias := tableName + vendor if cond != 0 { 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 = ?" sqlFromParams = append(sqlFromParams, vendor, utils.DefaultTimeValue) if cond == 1 { sqlVendorStoreCond += " " + mapCond + " " + tableAlias + ".id IS NOT NULL" } else { sqlVendorStoreCond += " " + mapCond + " " + tableAlias + ".id IS 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 = t1.market_man_role OR t11.v1 = t1.operator_role OR t11.v1 = t1.operator_role2) ) > 0` sqlWhereParams = append(sqlWhereParams, keyword+"%", autils.NewStoreBossRole(-1).GetFullName()) // 必须要前缀,不然不能用过些会很慢 } 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("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) (err error) { storeMapList, err := dao.GetStoresMapList(db, nil, storeIDs, model.StoreStatusAll, model.StoreIsSyncAll, "") if err != nil { return err } storeCourierList, err := dao.GetStoreCourierList(db, storeIDs, model.StoreStatusAll) if err != nil { return err } storeMapMap := make(map[int][]*model.StoreMap) for _, v := range storeMapList { storeMapMap[v.StoreID] = append(storeMapMap[v.StoreID], v) } storeCourierMap := make(map[int][]*model.StoreCourierMap) for _, v := range storeCourierList { storeCourierMap[v.StoreID] = append(storeCourierMap[v.StoreID], v) } for _, v := range storesInfo.Stores { for _, v2 := range storeMapMap[v.ID] { v.StoreMaps = append(v.StoreMaps, utils.Struct2FlatMap(v2)) } for _, v2 := range storeCourierMap[v.ID] { v.CourierMaps = append(v.CourierMaps, utils.Struct2FlatMap(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) { 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) 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, vendorStoreID string, vendorID int) (retVal *StoreExt, err error) { if handler := CurVendorSync.GetStoreHandler(vendorID); handler != nil { result, err2 := handler.ReadStore(ctx, 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 } valid := dao.StrictMakeMapByStructObject(payload, store, 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) } } for _, v := range []string{ "lng", "lat", "cityCode", "address", "deliveryRange", } { if valid[v] != nil { syncStatus |= model.SyncFlagStoreAddress break } } if valid["status"] != nil { syncStatus |= model.SyncFlagStoreStatus } if valid["deliveryRange"] != nil { valid["deliveryRange"] = strings.Trim(valid["deliveryRange"].(string), ";") } // 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 { 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 status := int(utils.Interface2Int64WithDefault(valid["status"], 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) } } } else { globals.SugarLogger.Debugf("UpdateStore track:%s, store:%s", ctx.GetTrackInfo(), utils.Format4Output(store, true)) } return num, err } 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) 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 } } 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 { UpdateOrCreateCourierStores(ctx, store.ID, false, false, false) TryAddStoreBossRole4StoreByMobile(ctx, storeExt.ID, []string{storeExt.Tel1, storeExt.Tel2}) return store.ID, err } return 0, 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) } func AddStoreVendorMap(ctx *jxcontext.Context, db *dao.DaoDB, storeID, vendorID 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() if handler := CurVendorSync.GetStoreHandler(vendorID); handler != nil { store, err2 := handler.ReadStore(ctx, storeMap.VendorStoreID) if err = err2; err == nil || storeMap.IsSync == 0 { dao.WrapAddIDCULDEntity(storeMap, userName) storeMap.StoreID = storeID storeMap.VendorID = vendorID if store != nil { storeMap.DeliveryType = store.DeliveryType storeMap.Status = store.Status } err = nil storeMap.SyncStatus = model.SyncFlagModifiedMask | model.SyncFlagStoreName | model.SyncFlagStoreAddress // 新增绑定门店是修改的概念 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 { outStoreMap = storeMap if store != nil { _, err = CurVendorSync.SyncStore(ctx, db, storeMap.VendorID, storeID, false, userName) } } if err != nil { dao.Rollback(db) } else { dao.Commit(db) } } } else { err = ErrCanNotFindVendor } 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.VendorStoreID, utils.Str2Int64WithDefault(storeMap.VendorStoreID, 0)) } num, err = dao.DeleteEntityLogically(db, storeMap, map[string]interface{}{ model.FieldStatus: model.StoreStatusDisabled, }, userName, nil) } return num, err } 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("非京东平台要求必须自动拣货") } } storeHandler := CurVendorSync.GetStoreHandler(vendorID) if storeHandler == nil { return 0, ErrCanNotFindVendor } 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 } syncStatus := model.SyncFlagModifiedMask valid := dao.StrictMakeMapByStructObject(payload, storeMap, userName) if valid["status"] != nil { syncStatus |= model.SyncFlagStoreStatus } if valid["pricePercentagePack"] != nil { if pricePercentagePack := utils.Interface2String(valid["pricePercentagePack"]); pricePercentagePack != "" { _, err2 := dao.QueryConfigs(db, pricePercentagePack, model.ConfigTypePricePack, "") if err = err2; err != nil { return 0, err } } } if vendorStoreID := utils.Interface2String(valid["vendorStoreID"]); vendorStoreID != "" { vendorStoreInfo, err2 := storeHandler.ReadStore(ctx, 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() { dao.Rollback(db) }() if valid["status"] != nil { // 对于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 && num > 0 { 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 { return 0, err } } dao.Commit(db) if valid["status"] != nil { _, 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 } dao.Commit(db) return num, err // return 0, errors.New("暂不支持删除京西门店") } 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 = 0 AND jxstoreid = ?", utils.Int2Str(storeID)); err == nil { count = ctInfo.Ct } return count, err } func TmpGetJxBadCommentsByStoreId(ctx *jxcontext.Context, 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 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 { sqlParams = append(sqlParams, 0) } else { sqlParams = append(sqlParams, 1) } } 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) if offset < 0 { offset = 0 } 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 } 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, }) 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 } 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) } 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, 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) { corporationInfo, err = api.JdAPI.GetCorporationInfo("", 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, 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 storeMap.VendorID != model.VendorIDWSC { if handler := partner.GetPurchasePlatformFromVendorID(storeMap.VendorID); handler != nil { store, err2 := handler.ReadStore(ctx, 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, 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.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, "", "", 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, "") 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 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 }