Files
jx-callback/business/jxstore/cms/store.go
gazebo b51614946f - 重构authz结构
- 角色管理初版完成
2019-08-08 17:06:58 +08:00

1705 lines
59 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}