package cms import ( "errors" "fmt" "math" "strconv" "strings" "time" "git.rosy.net.cn/jx-callback/business/auth2" "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 FloatLng float64 `json:"lng"` FloatLat float64 `json:"lat"` 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", } mobileGroupMap = map[string]map[string]int{ "18650801532": map[string]int{ // 陈磊 "13540967462": 1, // 蒋龙丹 "13708196093": 1, // 顾子杭 "13980795039": 1, // 赵凌兰 }, } ) // 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 := ` SELECT SQL_CALC_FOUND_ROWS CAST(t1.lng AS DECIMAL(15,6))/1000000 float_lng, CAST(t1.lat AS DECIMAL(15,6))/1000000 float_lat, t1.id, t1.created_at, t1.updated_at, t1.last_operator, t1.deleted_at, t1.name, t1.city_code, t1.district_code, t1.address, t1.tel1, t1.tel2, t1.open_time1, t1.close_time1, t1.open_time2, t1.close_time2, t1.delivery_range_type, t1.delivery_range, t1.status, t1.change_price_type, t1.id_card_front, t1.id_card_back, t1.id_card_hand, t1.licence, t1.licence_code, t1.printer_sn, t1.printer_key, t1.printer_vendor_id, t1.licence_type, t1.licence_corp_name, t1.licence_owner_name, t1.licence_address, t1.licence_valid, t1.licence_expire, t1.id_name, t1.id_code, t1.id_valid, t1.id_expire, t1.licence2_image, t1.licence2_code, t1.licence2_valid, t1.licence2_expire, t1.market_man_name, t1.market_man_phone, t1.jx_brand_fee_factor, t1.market_add_fee_factor, t1.payee_name, t1.payee_account_no, t1.payee_bank_branch_name, t1.payee_bank_code, bank.value payee_bank_name, t1.pay_percentage, t1.operator_name, t1.operator_phone, t1.printer_disabled, city.name city_name, district.name district_name, CONCAT('[', GROUP_CONCAT(DISTINCT CONCAT('{"vendorStoreID":"', m1.vendor_store_id, '", "vendorID":', m1.vendor_id, ', "status":', m1.status, ', "pricePercentage":', m1.price_percentage, ', "pricePercentagePack":"', m1.price_percentage_pack, '", "vendorStoreName":"', CASE m1.vendor_id WHEN 0 THEN IF(jd.name IS NULL, '', jd.name) WHEN 3 THEN IF(eb.col_name IS NULL, '', eb.col_name) ELSE '' END, '", "isSync":', m1.is_sync, '}')), ']') store_map_str, CONCAT('[', GROUP_CONCAT(DISTINCT CONCAT('{"vendorStoreID":"', m2.vendor_store_id, '", "vendorID":', m2.vendor_id, ', "status":', m2.status, '}')), ']') courier_map_str FROM store t1 LEFT JOIN new_config bank ON 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 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 ebde_shops eb ON eb.col_baidu_shop_id = m1.vendor_store_id LEFT JOIN jde_store jd ON jd.jdid = m1.vendor_store_id LEFT JOIN store_courier_map m2 ON t1.id = m2.store_id AND m2.deleted_at = ? ` sqlParams := []interface{}{ model.ConfigTypeBank, 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, 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" } } sql += "\nLEFT JOIN " + tableName + " " + tableAlias + " ON " + tableAlias + ".vendor_id = ? AND " + tableAlias + ".store_id = t1.id AND " + tableAlias + ".deleted_at = ?" sqlParams = append(sqlParams, 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.market_man_phone LIKE ? OR t1.last_operator LIKE ? OR city.name LIKE ? OR t1.address LIKE ? OR t1.printer_sn LIKE ?` sqlWhereParams = append(sqlWhereParams, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike) if keywordInt64, err2 := strconv.ParseInt(keyword, 10, 64); err2 == nil { if jxutils.IsLegalMobileNumber(keywordInt64) { sql += ` LEFT JOIN weixins wx1 ON t1.id = wx1.jxstoreid AND wx1.parentid = -1 AND wx1.tel = ? LEFT JOIN weixins wx2 ON t1.id = wx2.jxstoreid AND wx2.parentid = -1 LEFT JOIN weixins wx3 ON wx3.parentid = wx2.id AND wx3.tel = ? ` sqlParams = append(sqlParams, keywordInt64, keywordInt64) sqlWhere += " OR wx1.id IS NOT NULL OR wx3.id IS NOT NULL" } 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, keywordInt64, utils.DefaultTimeValue, keywordInt64) } } 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, 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["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, err } if len(statuss) > 0 { sqlWhere += " AND t1.status IN (" + dao.GenQuestionMarks(len(statuss)) + ")" sqlWhereParams = append(sqlWhereParams, statuss) } } sql += sqlWhere + ` GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55 ORDER BY t1.id DESC /*LIMIT ? OFFSET ?*/` pageSize = jxutils.FormalizePageSize(pageSize) if offset < 0 { offset = 0 } sqlParams = append(sqlParams, sqlWhereParams...) // sqlParams = append(sqlParams, pageSize, offset) retVal = &StoresInfo{} db := dao.GetDB() // dao.Begin(db) // defer func() { // if r := recover(); r != nil { // dao.Rollback(db) // panic(r) // } // }() // globals.SugarLogger.Debug(utils.Format4Output(sqlParams, false)) // globals.SugarLogger.Debug(sql) var storeList []*StoreExt mapLimit := false if err = dao.GetRows(db, &storeList, sql, sqlParams...); err == nil { // retVal.TotalCount = dao.GetLastTotalRowCount(db) // dao.Commit(db) // globals.SugarLogger.Debugf("GetStores, len(storeList):%d", len(storeList)) var ( mapLatitude, mapLongitude float64 mapRadius int ) if mapLongitude2, ok := params["mapLongitude"].(string); ok { 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 { if v.StoreMapStr != "" { if err = utils.UnmarshalUseNumber([]byte(v.StoreMapStr), &v.StoreMaps); err != nil { return nil, err } } if v.CourierMapStr != "" { if err = utils.UnmarshalUseNumber([]byte(v.CourierMapStr), &v.CourierMaps); err != nil { return nil, err } } retVal.Stores = append(retVal.Stores, v) } } retVal.Stores, err = filterStoreByOrderInfo(db, retVal.Stores, orderTimeFrom, orderTimeTo, orderCountFrom, orderCountTo) retVal.TotalCount = len(retVal.Stores) if offset >= retVal.TotalCount { retVal.Stores = nil } else { if offset+pageSize > retVal.TotalCount { pageSize = retVal.TotalCount - offset } retVal.Stores = retVal.Stores[offset : offset+pageSize] } // if mapLimit { // retVal.TotalCount = len(retVal.Stores) // } } else { // dao.Rollback(db) } if mapLimit && len(retVal.Stores) > 0 { 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 lngMean := float64(0) latMean := float64(0) for _, store := range storeList { lngMean += (store.FloatLng - lngAvg) * (store.FloatLng - lngAvg) latMean += (store.FloatLat - latAvg) * (store.FloatLat - latAvg) } lngMean = math.Sqrt(lngMean / storeListLenFloat) latMean = math.Sqrt(latMean / storeListLenFloat) newStoreList := []*StoreExt{} for _, store := range storeList { if store.FloatLng >= lngMean-lngAvg && store.FloatLng <= lngMean+lngAvg && store.FloatLat >= latMean-latAvg && store.FloatLat <= latMean+latAvg { 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 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 = dao.ValidateRoles(db, utils.Interface2String(valid["marketManRole"]), utils.Interface2String(valid["OperatorRole"])); 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)) // 网络打印机处理 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 { BindMobile2Store(ctx, utils.Interface2String(valid["tel1"]), storeID) } 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 err = dao.ValidateRoles(db, store.MarketManRole, store.OperatorRole); 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) BindMobile2Store(ctx, storeExt.Tel1, storeExt.ID) 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 { dao.Commit(db) outStoreMap = storeMap if store != nil { _, err = CurVendorSync.SyncStore(ctx, db, storeMap.VendorID, storeID, false, userName) } } if err != nil { dao.Rollback(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{} if num, err = dao.DeleteEntityLogically(db, storeMap, map[string]interface{}{ model.FieldSyncStatus: model.SyncFlagDeletedMask, model.FieldStatus: model.StoreStatusDisabled, }, userName, map[string]interface{}{ model.FieldStoreID: storeID, model.FieldVendorID: vendorID, }); err == nil && num > 0 { _, err = CurVendorSync.SyncStore(ctx, db, vendorID, storeID, false, userName) } 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 { return nil, err } } 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, }} } } } return retVal, err }, storeMapList) tasksch.HandleTask(task, parentTask, true).Run() resultList, err := task.GetResult(0) if len(resultList) > 0 { err = nil // 强制忽略 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.Status = snapshot.Status v.DeliveryType = snapshot.DeliveryType v.LastOperator = model.AdminName if _, err = dao.UpdateEntity(db, v, model.FieldLastOperator, model.FieldUpdatedAt, model.FieldStatus, "DeliveryType"); err != nil { return err } } } dao.Commit(db) 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 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) alarmSnapshotMap := make(map[int][]*model.VendorStoreSnapshot) curSnapshotGroupMap := 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) 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 for _, mobile := range []string{storeDetail.Tel1, storeDetail.MarketManPhone, storeDetail.OperatorPhone} { if mobile != "" { if userMap[mobile] == nil { userMap[mobile] = make(map[int]int) } userMap[mobile][storeID] = 1 if txtAlarmSnapshotMap[storeID] != nil { userMapTxt[mobile] = append(userMapTxt[mobile], txtAlarmSnapshotMap[storeID]...) } } } } } var mobileList []string for mobile := range userMap { if mobile != "" && !shouldSkipMobile4SendStoreStatusInfo(mobile) { mobileList = append(mobileList, mobile) } } if len(mobileList) > 0 { 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) { mobile := batchItemList[0].(string) var dataList []map[string]interface{} captionList := []string{"京西门店ID", "门店名", "城市"} isFirstRow := true for storeID := range userMap[mobile] { 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", mobile, time.Now().Format("20060102T150405") + ".xlsx", } key := "export/" + strings.Join(keyPart, "_") excelURL, err2 := jxutils.UploadExportContent(excelBin, key) if err2 != nil { globals.SugarLogger.Warnf("SendAlarmVendorSnapshot, send %s failed with error:%v", key, err2) } var txtAlarm []string for _, v := range userMapTxt[mobile] { 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(mobile, title, alarmTextStr) return nil, nil }, mobileList) tasksch.HandleTask(task, parentTask, true).Run() _, err = task.GetResult(0) } return err } func shouldSkipMobile4SendStoreStatusInfo(mobile string) bool { return userProvider.GetUser(mobile, auth2.AuthTypeMobile) == nil } func sendStoreStatusInfo2Mobile(mobile, title, txtAlarm string) { mobileList := []string{mobile} for mobile := range mobileGroupMap[mobile] { mobileList = append(mobileList, mobile) } for _, mobile := range mobileList { if user := userProvider.GetUser(mobile, auth2.AuthTypeMobile); user != nil { globals.SugarLogger.Debugf("sendStoreStatusInfo2Mobile %s, txtAlarm:\n%s", mobile, txtAlarm) msg.SendUserMessage(dingdingapi.MsgTypeMarkdown, user.GetID(), 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.NewSeqTask("SaveAndSendAlarmVendorSnapshot", ctx, func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { 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 }, 4) tasksch.ManageTask(task).Run() if !isAsync { _, err = task.GetResult(0) } return err }