Files
jx-callback/business/jxstore/cms/store_sku.go

2060 lines
73 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"
"sort"
"strconv"
"sync"
"time"
"git.rosy.net.cn/jx-callback/business/jxutils/tasksch"
"git.rosy.net.cn/jx-callback/business/partner/purchase/jd"
"git.rosy.net.cn/jx-callback/business/partner"
"git.rosy.net.cn/baseapi/utils"
"git.rosy.net.cn/jx-callback/business/auth2/authprovider/weixin"
"git.rosy.net.cn/jx-callback/business/jxutils"
"git.rosy.net.cn/jx-callback/business/jxutils/jxcontext"
"git.rosy.net.cn/jx-callback/business/jxutils/weixinmsg"
"git.rosy.net.cn/jx-callback/business/model"
"git.rosy.net.cn/jx-callback/business/model/dao"
"git.rosy.net.cn/jx-callback/globals"
"github.com/astaxie/beego/orm"
)
const (
MaxSkuUnitPrice = 500000
CopyStoreSkuModeFresh = "fresh" // 全新复制
CopyStoreSkuModeUpdate = "update" // 增量复制
CopyStoreSkuModeUpdatePrice = "updatePrice" // 增量复制价格
)
type StoreSkuExt struct {
NameID int `orm:"column(name_id)" json:"nameID"`
SkuID int `orm:"column(sku_id)" json:"id"`
Comment string `orm:"size(255)" json:"comment"`
SkuCategoryID int `orm:"column(sku_category_id)" json:"categoryID"`
SkuSpecQuality float32 `json:"specQuality"`
SkuSpecUnit string `orm:"size(8)" json:"specUnit"` // 质量或容量
Weight int `json:"weight"` // 重量/质量单位为克当相应的SkuName的SpecUnit为g或kg时必须等于SpecQuality
JdID string `orm:"column(sku_jd_id);null;index" json:"jdID"`
SkuStatus int `json:"status"`
BindCreatedAt time.Time `orm:"auto_now_add;type(datetime)" json:"createdAt"`
BindUpdatedAt time.Time `orm:"auto_now;type(datetime)" json:"updatedAt"`
BindLastOperator string `orm:"size(32)" json:"lastOperator"` // 最后操作员
BindDeletedAt time.Time `orm:"type(datetime)" json:"deletedAt"`
SubStoreID int `orm:"column(sub_store_id)" json:"subStoreID"`
BindPrice int `json:"price"` // 单位为分不用int64的原因是这里不需要累加
UnitPrice int `json:"unitPrice"` // 这个是一斤的门店商品价放在这里的原因是避免额外增加一张store sku_name表逻辑上要保证同一SKU NAME中的所有SKU这个字段的数据一致
StoreSkuStatus int `json:"storeSkuStatus"`
EbaiID string `orm:"column(ebai_id);index" json:"ebaiID"`
MtwmID string `orm:"column(mtwm_id)" json:"mtwmID"` // 这个也不是必须的只是为了DAO取数据语句一致
WscID string `orm:"column(wsc_id);index" json:"wscID"` // 表示微盟skuId
WscID2 string `orm:"column(wsc_id2);index" json:"wscID2"` // 表示微盟goodsId
JdSyncStatus int8 `orm:"default(2)" json:"jdSyncStatus"`
EbaiSyncStatus int8 `orm:"default(2)" json:"ebaiSyncStatus"`
MtwmSyncStatus int8 `orm:"default(2)" json:"mtwmSyncStatus"`
WscSyncStatus int8 `orm:"default(2)" json:"wscSyncStatus"`
JdPrice int `json:"jdPrice"`
EbaiPrice int `json:"ebaiPrice"`
MtwmPrice int `json:"mtwmPrice"`
WscPrice int `json:"wscPrice"`
AutoSaleAt time.Time `orm:"type(datetime);null" json:"autoSaleAt"`
ActPrice int `json:"actPrice"`
EarningPrice int `json:"earningPrice"`
RealEarningPrice int `json:"realEarningPrice"`
Count int `json:"count"`
Times int `json:"times"`
}
// GetStoreSkus用
type StoreSkuNameExt struct {
StoreID int `orm:"column(store_id)" json:"storeID"`
StoreName string `json:"storeName"`
model.SkuName
UnitPrice int `json:"unitPrice"`
Skus []map[string]interface{} `orm:"-" json:"skus2,omitempty"`
Skus2 []*StoreSkuExt `orm:"-" json:"skus,omitempty"`
SkusStr string `json:"-"`
PendingOpType int8 `json:"pendingOpType"` // 取值同 StoreOpRequest.Type
PendingUnitPrice int `json:"pendingUnitPrice"` // 这个是待审核的价格申请
PayPercentage int `json:"-"`
}
// GetStoreSkus用
type StoreSkuNamesInfo struct {
TotalCount int `json:"totalCount"`
SkuNames []*StoreSkuNameExt `json:"skuNames"`
}
// UpdateStoreSku用API调用时
type StoreSkuBindSkuInfo struct {
SkuID int `json:"skuID"`
IsSale int `json:"isSale,omitempty"` // -1不可售0忽略1可售
// ElmID int64 `json:"elmID,omitempty"`
// EbaiID int64 `json:"ebaiID,omitempty"`
}
// UpdateStoreSku用API调用时
type StoreSkuBindInfo struct {
StoreID int `json:"storeID"`
NameID int `json:"nameID"`
UnitPrice int `json:"unitPrice"` // 对于是份的SKU就是单价每斤价格其它则为总价
IsFocus int `json:"isFocus"` // -1不关注0忽略1关注
IsSale int `json:"isSale"` // -1不可售0忽略1可售
SubStoreID int `json:"subStoreID,omitempty"`
Skus []*StoreSkuBindSkuInfo `json:"skus,omitempty"`
}
type tStoreSkuBindAndSpec struct {
model.StoreSkuBind
SkuStatus int
SkuNameStatus int
Name string
SpecQuality float32
SpecUnit string
SkuNamePrice int
SkuNameUnit string
RealSkuID int `orm:"column(real_sku_id)"`
ChangePriceType int8 `json:"changePriceType"` // 修改价格类型,即是否需要审核
}
type SkuSaleInfo struct {
StoreID int `orm:"column(store_id)"`
SkuID int `orm:"column(sku_id)"`
Times int // 销售的次数
Count int // 销售的总份数
}
type StoreOpRequestInfo struct {
model.StoreOpRequest
StoreName string `json:"storeName"`
SkuNamePrefix string `json:"skuNamePrefix"`
SkuNameName string `json:"skuNameName"`
UnitPrice int `json:"unitPrice"`
}
type tStoreNameBind struct {
StoreID int `orm:"column(store_id)"`
NameID int `orm:"column(name_id)"`
Name string
}
type tGetStoresSkusInfo struct {
StoreID int `orm:"column(store_id)"`
StoreName string
model.SkuName
StoreSkuExt
}
const (
maxStoreNameBind = 3000 // 最大门店SkuName bind个数
maxStoreNameBind2 = 10000 // 最大门店乘SkuName个数
AutoSaleAtStr = "20:25:00"
)
var (
asyncOpMobileMap = map[string]int{
"18982250714": 1, // 老赵
"18180948107": 1, // 徐
// "13684045763": 1, // 周
}
)
func GetStoreSkus(ctx *jxcontext.Context, storeID int, skuIDs []int, isFocus bool, keyword string, isBySku, isAct bool, params map[string]interface{}, offset, pageSize int) (skuNamesInfo *StoreSkuNamesInfo, err error) {
return GetStoresSkus(ctx, []int{storeID}, skuIDs, isFocus, keyword, isBySku, isAct, params, offset, pageSize)
}
func getGetStoresSkusBaseSQL(db *dao.DaoDB, storeIDs, skuIDs []int, isFocus bool, keyword string, isBySku, isAct bool, params map[string]interface{}) (sql string, sqlParams []interface{}, err error) {
sql = `
FROM sku_name t1
JOIN sku t2 FORCE INDEX(PRIMARY) ON t1.id = t2.name_id AND t2.deleted_at = ?/* AND t2.status = ?*/
JOIN store t3 ON t3.deleted_at = ?
`
sqlParams = []interface{}{
utils.DefaultTimeValue,
// model.SkuStatusNormal,
utils.DefaultTimeValue,
}
if isAct {
sql += `
JOIN (
SELECT t2.store_id, t2.sku_id,
MIN(IF(t3.actual_act_price = 0, NULL, t3.actual_act_price)) actual_act_price, MIN(IF(t2.earning_price = 0, NULL, t2.earning_price)) earning_price /*non-zero min value*/
FROM act t1
JOIN act_store_sku t2 ON t2.act_id = t1.id AND t2.deleted_at = ?
JOIN act_store_sku_map t3 ON t3.bind_id = t2.id AND t3.act_id = t1.id AND (t3.sync_status & ? = 0 OR t1.type = ?)
JOIN act_map t4 ON t4.act_id = t1.id AND t4.vendor_id = t3.vendor_id AND t4.deleted_at = ? AND (t4.sync_status & ? = 0 OR t1.type = ?)
WHERE t1.deleted_at = ? AND t1.status = ? AND NOT (t1.begin_at > ? OR t1.end_at < ?)`
sqlParams = append(sqlParams, []interface{}{
utils.DefaultTimeValue,
model.SyncFlagNewMask,
model.ActSkuFake,
utils.DefaultTimeValue,
model.SyncFlagNewMask,
model.ActSkuFake,
utils.DefaultTimeValue,
model.ActStatusCreated,
time.Now(),
time.Now(),
})
if len(storeIDs) > 0 {
sql += " AND t2.store_id IN (" + dao.GenQuestionMarks(len(storeIDs)) + ")"
sqlParams = append(sqlParams, storeIDs)
}
if len(skuIDs) > 0 {
sql += " AND t2.sku_id IN (" + dao.GenQuestionMarks(len(skuIDs)) + ")"
sqlParams = append(sqlParams, skuIDs)
}
sql += `
GROUP BY 1,2
) ta ON ta.store_id = t3.id AND ta.sku_id = t2.id`
}
if !isFocus {
sql += " LEFT"
}
sql += `
JOIN store_sku_bind t4 ON t4.store_id = t3.id AND t4.sku_id = t2.id AND t4.deleted_at = ?
LEFT JOIN sku_name_place_bind t5 ON t1.id = t5.name_id AND t3.city_code = t5.place_code
WHERE t1.deleted_at = ? AND (t1.is_global = 1 OR t5.id IS NOT NULL OR 1 = ?)/* AND t1.status = ?*/
`
sqlParams = append(sqlParams, []interface{}{
utils.DefaultTimeValue,
utils.DefaultTimeValue,
utils.Bool2Int(isFocus),
// model.SkuStatusNormal,
})
if isFocus {
sql += " AND ((t2.status = ? AND t1.status = ?) OR t4.status = ?)"
sqlParams = append(sqlParams, model.SkuStatusNormal, model.SkuStatusNormal, model.SkuStatusNormal)
} else {
sql += " AND t4.sku_id IS NULL AND (t2.status = ? AND t1.status = ?)"
sqlParams = append(sqlParams, model.SkuStatusNormal, model.SkuStatusNormal)
}
if keyword != "" {
keywordLike := "%" + keyword + "%"
sql += " AND (t1.name LIKE ? OR t1.prefix LIKE ? OR t1.upc LIKE ? OR t2.comment LIKE ?"
sqlParams = append(sqlParams, keywordLike, keywordLike, keywordLike, keywordLike)
if keywordInt64, err2 := strconv.ParseInt(keyword, 10, 64); err2 == nil {
sql += " OR t1.id = ? OR t2.id = ? OR t2.jd_id = ?"
sqlParams = append(sqlParams, keywordInt64, keywordInt64, keywordInt64)
if isFocus {
sql += " OR t4.ebai_id = ? OR t4.mtwm_id = ?"
sqlParams = append(sqlParams, keywordInt64, keywordInt64)
}
}
sql += ")"
}
if params["nameID"] != nil {
sql += " AND t1.id = ?"
sqlParams = append(sqlParams, params["nameID"].(int))
}
if params["nameIDs"] != nil {
var nameIDs []int
if err = utils.UnmarshalUseNumber([]byte(params["nameIDs"].(string)), &nameIDs); err != nil {
return "", nil, err
}
if len(nameIDs) > 0 {
sql += " AND t1.id IN (" + dao.GenQuestionMarks(len(nameIDs)) + ")"
sqlParams = append(sqlParams, nameIDs)
}
}
if params["categoryID"] != nil {
cat := &model.SkuCategory{}
cat.ID = params["categoryID"].(int)
if err = dao.GetEntity(db, cat); err != nil {
return "", nil, err
}
sql += " AND (t1.category_id = ?"
sqlParams = append(sqlParams, cat.ID)
if cat.Level == 1 {
sql += " OR t1.category_id IN (SELECT id FROM sku_category WHERE parent_id = ?)"
sqlParams = append(sqlParams, cat.ID)
}
sql += ")"
}
if params["jdID"] != nil {
sql += " AND t1.jd_id = ?"
sqlParams = append(sqlParams, params["jdID"].(int))
}
if params["name"] != nil {
sql += " AND t1.name LIKE ?"
sqlParams = append(sqlParams, "%"+params["name"].(string)+"%")
}
if params["prefix"] != nil {
sql += " AND t1.prefix LIKE ?"
sqlParams = append(sqlParams, "%"+params["prefix"].(string)+"%")
}
if params["unit"] != nil {
sql += " AND t1.unit = ?"
sqlParams = append(sqlParams, params["unit"].(string))
}
if len(storeIDs) > 0 {
sql += " AND t3.id IN (" + dao.GenQuestionMarks(len(storeIDs)) + ")"
sqlParams = append(sqlParams, storeIDs)
}
if len(skuIDs) > 0 {
sql += " AND t2.id IN (" + dao.GenQuestionMarks(len(skuIDs)) + ")"
sqlParams = append(sqlParams, skuIDs)
} else if params["skuID"] != nil {
skuID, ok := params["skuID"].(int)
if ok {
skuIDs = append(skuIDs, skuID)
sql += " AND t2.id = ?"
sqlParams = append(sqlParams, skuID)
}
}
if isFocus {
if params["fromStatus"] != nil {
fromStatus := params["fromStatus"].(int)
toStatus := fromStatus
if params["toStatus"] != nil {
toStatus = params["toStatus"].(int)
}
sql += " AND t4.status >= ? AND t4.status <= ?"
sqlParams = append(sqlParams, fromStatus, toStatus)
}
if params["jdSyncStatus"] != nil || params["ebaiSyncStatus"] != nil || params["mtwmSyncStatus"] != nil {
realVendorMap, err2 := getValidStoreVendorMap(db, storeIDs)
if err = err2; err != nil {
return "", nil, err
}
sql += " AND ( 1 = 0"
if params["jdSyncStatus"] != nil && realVendorMap[model.VendorIDJD] == 1 {
sql += " OR (t4.jd_sync_status & ? <> 0 AND t2.jd_id <> 0 AND t1.status = ? AND t2.status = ?)"
sqlParams = append(sqlParams, params["jdSyncStatus"], model.SkuStatusNormal, model.SkuStatusNormal)
}
if params["ebaiSyncStatus"] != nil && realVendorMap[model.VendorIDEBAI] == 1 {
sql += " OR (t4.ebai_sync_status & ? <> 0 AND NOT (t4.ebai_sync_status & ? <> 0 AND (t4.status <> ? OR t2.status <> ?)) )"
sqlParams = append(sqlParams, params["ebaiSyncStatus"], model.SyncFlagNewMask, model.SkuStatusNormal, model.SkuStatusNormal)
}
if params["mtwmSyncStatus"] != nil && realVendorMap[model.VendorIDMTWM] == 1 {
sql += " OR (t4.mtwm_sync_status & ? <> 0 AND NOT (t4.mtwm_sync_status & ? <> 0 AND (t4.status <> ? OR t2.status <> ?)) )"
sqlParams = append(sqlParams, params["mtwmSyncStatus"], model.SyncFlagNewMask, model.SkuStatusNormal, model.SkuStatusNormal)
}
sql += ")"
}
}
return sql, sqlParams, err
}
func GetStoresSkus(ctx *jxcontext.Context, storeIDs, skuIDs []int, isFocus bool, keyword string, isBySku, isAct bool, params map[string]interface{}, offset, pageSize int) (skuNamesInfo *StoreSkuNamesInfo, err error) {
return GetStoresSkusNew(ctx, storeIDs, skuIDs, isFocus, keyword, isBySku, isAct, params, offset, pageSize)
}
func GetStoresSkusNew(ctx *jxcontext.Context, storeIDs, skuIDs []int, isFocus bool, keyword string, isBySku, isAct bool, params map[string]interface{}, offset, pageSize int) (skuNamesInfo *StoreSkuNamesInfo, err error) {
if !isFocus && !isBySku && (len(storeIDs) > 1 || len(storeIDs) == 0) {
return nil, fmt.Errorf("未关注按SkuName只能查询单店")
}
if len(storeIDs) == 0 && len(skuIDs) == 0 && pageSize == -1 {
return nil, fmt.Errorf("GetStoresSkus必须指定storeIDs或skuIDs或分页")
}
db := dao.GetDB()
sql, sqlParams, err := getGetStoresSkusBaseSQL(db, storeIDs, skuIDs, isFocus, keyword, isBySku, isAct, params)
if err != nil {
return nil, err
}
pageSize = jxutils.FormalizePageSize(pageSize)
offset = jxutils.FormalizePageOffset(offset)
sqlOffset := offset
sqlPageSize := pageSize
isSaleInfo := params["stFromTime"] != nil
if isSaleInfo {
sqlOffset = 0
sqlPageSize = jxutils.FormalizePageSize(-1)
}
sqlParamsPage := []interface{}{sqlPageSize, sqlOffset}
dao.Begin(db)
defer func() {
if r := recover(); r != nil {
dao.Rollback(db)
panic(r)
}
}()
catOrderBy := ""
if params["categoryID"] != nil {
catOrderBy = "t2.seq, "
}
skuNamesInfo = &StoreSkuNamesInfo{}
if !isBySku && sqlPageSize != model.UnlimitedPageSize {
sql2 := `
SELECT SQL_CALC_FOUND_ROWS
t3.id store_id, t1.id name_id
` + sql + `
GROUP BY 1, 2
ORDER BY 1, 2
LIMIT ? OFFSET ?
`
sqlParams2 := append([]interface{}{}, sqlParams...)
sqlParams2 = append(sqlParams2, sqlParamsPage)
var storeNameList []*tStoreNameBind
beginTime := time.Now()
if err = dao.GetRows(db, &storeNameList, sql2, sqlParams2...); err != nil {
dao.Rollback(db)
return nil, err
}
globals.SugarLogger.Debugf("GetStoresSkusNew get result1:%v", time.Now().Sub(beginTime))
skuNamesInfo.TotalCount = dao.GetLastTotalRowCount(db)
sql += " AND (1 = 0"
for _, v := range storeNameList {
sql += " OR (t1.id = ? AND t3.id = ?)"
sqlParams = append(sqlParams, v.NameID, v.StoreID)
}
sql += fmt.Sprintf(`)
ORDER BY %s t3.id, t2.name_id, t2.id`, catOrderBy)
} else {
if isFocus {
sql += fmt.Sprintf(`
ORDER BY %s t3.id, t2.name_id, t2.id`, catOrderBy)
}
sql += `
LIMIT ? OFFSET ?`
sqlParams = append(sqlParams, sqlParamsPage)
}
sql = `
SELECT SQL_CALC_FOUND_ROWS
t3.id store_id, t3.name store_name, t3.pay_percentage,
t1.*,
t2.name_id, t2.id sku_id, t2.spec_quality sku_spec_quality, t2.spec_unit sku_spec_unit, t2.weight, t2.jd_id sku_jd_id,
t2.comment, t2.category_id sku_category_id, t2.status sku_status,
t4.created_at bind_created_at, t4.updated_at bind_updated_at, t4.last_operator bind_last_operator, t4.deleted_at bind_deleted_at,
t4.sub_store_id, t4.price bind_price, IF(t4.unit_price IS NOT NULL, t4.unit_price, t1.price) unit_price, t4.status store_sku_status, t4.auto_sale_at,
t4.ebai_id, t4.mtwm_id, t4.wsc_id, t4.wsc_id2,
t4.jd_sync_status, t4.ebai_sync_status, t4.mtwm_sync_status, t4.wsc_sync_status,
t4.jd_price, t4.ebai_price, t4.mtwm_price, t4.wsc_price
` + sql
var tmpList []*tGetStoresSkusInfo
beginTime := time.Now()
if err = dao.GetRows(db, &tmpList, sql, sqlParams...); err != nil {
dao.Rollback(db)
return nil, err
}
if isBySku {
skuNamesInfo.TotalCount = dao.GetLastTotalRowCount(db)
}
dao.Commit(db)
globals.SugarLogger.Debugf("GetStoresSkusNew get result2:%v", time.Now().Sub(beginTime))
storeNameMap := make(map[int64]*StoreSkuNameExt)
for _, v := range tmpList {
var storeName *StoreSkuNameExt
index := jxutils.Combine2Int(v.StoreID, v.ID)
if isBySku || storeNameMap[index] == nil {
storeName = &StoreSkuNameExt{
StoreID: v.StoreID,
StoreName: v.StoreName,
SkuName: v.SkuName,
UnitPrice: v.UnitPrice,
}
if !isBySku {
storeNameMap[index] = storeName
}
skuNamesInfo.SkuNames = append(skuNamesInfo.SkuNames, storeName)
} else {
storeName = storeNameMap[index]
}
storeName.Skus2 = append(storeName.Skus2, &v.StoreSkuExt)
}
if err == nil {
if isSaleInfo {
beginTime := time.Now()
err = updateSaleInfo4StoreSkuName(ctx, db, storeIDs, skuIDs, params, skuNamesInfo, offset, pageSize)
globals.SugarLogger.Debugf("GetStoresSkusNew updateSaleInfo4StoreSkuName:%v", time.Now().Sub(beginTime))
}
if err == nil {
if true { //!(offset == 0 && pageSize == model.UnlimitedPageSize) {
storeIDs, skuIDs = GetStoreAndSkuIDsFromInfo(skuNamesInfo)
}
beginTime := time.Now()
err = updateActPrice4StoreSkuNameNew(db, storeIDs, skuIDs, skuNamesInfo)
globals.SugarLogger.Debugf("GetStoresSkusNew updateActPrice4StoreSkuName:%v", time.Now().Sub(beginTime))
if !isFocus {
err = updateUnitPrice4StoreSkuNameNew(db, skuNamesInfo)
}
}
}
// globals.SugarLogger.Debug(utils.Format4Output(skuNamesInfo, false))
return skuNamesInfo, err
}
func GetStoreAndSkuIDsFromInfo(skuNamesInfo *StoreSkuNamesInfo) (storeIDs, skuIDs []int) {
storeIDMap := make(map[int]int)
skuIDMap := make(map[int]int)
for _, skuName := range skuNamesInfo.SkuNames {
storeIDMap[skuName.StoreID] = 1
for _, sku := range skuName.Skus2 {
skuIDMap[sku.SkuID] = 1
}
}
return jxutils.IntMap2List(storeIDMap), jxutils.IntMap2List(skuIDMap)
}
// 根据已经部分关注的商品,得到已经存在的门店商品单价
func updateUnitPrice4StoreSkuNameNew(db *dao.DaoDB, skuNamesInfo *StoreSkuNamesInfo) (err error) {
storeIDMap := make(map[int]int)
skuNameIDMap := make(map[int]int)
for _, skuName := range skuNamesInfo.SkuNames {
storeIDMap[skuName.StoreID] = 1
skuNameIDMap[skuName.SkuName.ID] = 1
}
storeSkuNameInfo, err := dao.GetExistingStoreSkuNameInfo(db, jxutils.IntMap2List(storeIDMap), jxutils.IntMap2List(skuNameIDMap))
if err == nil {
infoMap := make(map[int64]*dao.StoreSkuNameInfo)
for _, v := range storeSkuNameInfo {
infoMap[jxutils.Combine2Int(v.StoreID, v.NameID)] = v
}
for _, skuName := range skuNamesInfo.SkuNames {
if tmpInfo := infoMap[jxutils.Combine2Int(skuName.StoreID, skuName.SkuName.ID)]; tmpInfo != nil {
skuName.UnitPrice = int(tmpInfo.UnitPrice)
}
}
}
return err
}
// skuIDs为空会导致性能极低所以要skuIDs必须有值
func updateActPrice4StoreSkuNameNew(db *dao.DaoDB, storeIDs, skuIDs []int, skuNamesInfo *StoreSkuNamesInfo) (err error) {
if len(skuIDs) == 0 {
return nil
}
actStoreSkuList, err := dao.GetEffectiveActStoreSkuInfo(db, 0, nil, storeIDs, skuIDs, time.Now(), time.Now())
if err != nil {
globals.SugarLogger.Errorf("updateActPrice4StoreSkuNameNew can not get sku promotion info for error:%v", err)
return err
}
actStoreSkuMap4Act := jxutils.NewActStoreSkuMap(actStoreSkuList, true)
actStoreSkuMap4EarningPrice := jxutils.NewActStoreSkuMap(actStoreSkuList, false)
for _, skuName := range skuNamesInfo.SkuNames {
if len(skuName.Skus2) > 0 {
for _, v := range skuName.Skus2 {
if actStoreSku := actStoreSkuMap4Act.GetActStoreSku(skuName.StoreID, v.SkuID, -1); actStoreSku != nil {
v.ActPrice = int(actStoreSku.ActualActPrice)
}
if actStoreSku := actStoreSkuMap4EarningPrice.GetActStoreSku(skuName.StoreID, v.SkuID, -1); actStoreSku != nil {
v.EarningPrice = int(actStoreSku.EarningPrice)
}
v.RealEarningPrice = v.EarningPrice
if v.RealEarningPrice == 0 {
v.RealEarningPrice = int(jxutils.CaculateSkuEarningPrice(int64(v.BindPrice), int64(v.BindPrice), skuName.PayPercentage))
}
}
} else {
skuName.UnitPrice = skuName.Price
}
}
return err
}
func updateSaleInfo4StoreSkuName(ctx *jxcontext.Context, db *dao.DaoDB, storeIDs, skuIDs []int, params map[string]interface{}, skuNamesInfo *StoreSkuNamesInfo, offset, pageSize int) (err error) {
var (
saleInfoList []*SkuSaleInfo
timeList []time.Time
fromCount, toCount int
)
saleInfoMap := make(map[int64]*SkuSaleInfo)
toTimeStr := ""
if params["stToTime"] != nil {
toTimeStr = params["stToTime"].(string)
}
if timeList, err = jxutils.BatchStr2Time(params["stFromTime"].(string), toTimeStr); err != nil {
return err
}
if params["stFromCount"] != nil {
fromCount = params["stFromCount"].(int)
}
toCount = math.MaxInt32
if params["stToCount"] != nil {
toCount = params["stToCount"].(int)
}
// 不能用SQL筛除否则不能区分是没有销量还是不在条件中
if saleInfoList, err = GetStoresSkusSaleInfo(ctx, storeIDs, skuIDs, timeList[0], timeList[1], 0, math.MaxInt32); err != nil {
return err
}
for _, saleInfo := range saleInfoList {
saleInfoMap[jxutils.Combine2Int(saleInfo.StoreID, saleInfo.SkuID)] = saleInfo
}
var newSkuNames []*StoreSkuNameExt
for _, skuName := range skuNamesInfo.SkuNames {
var newSkus []*StoreSkuExt
for _, sku := range skuName.Skus2 {
saleInfo := saleInfoMap[jxutils.Combine2Int(skuName.StoreID, sku.SkuID)]
if saleInfo == nil && fromCount == 0 {
saleInfo = &SkuSaleInfo{}
}
if saleInfo != nil && saleInfo.Count >= fromCount && saleInfo.Count <= toCount {
sku.Times = saleInfo.Times
sku.Count = saleInfo.Count
newSkus = append(newSkus, sku)
}
}
if len(newSkus) > 0 {
skuName.Skus2 = newSkus
newSkuNames = append(newSkuNames, skuName)
}
}
skuNamesInfo.TotalCount = len(newSkuNames)
skuNamesInfo.SkuNames = nil
if offset < skuNamesInfo.TotalCount {
endIndex := offset + pageSize
if endIndex > skuNamesInfo.TotalCount {
endIndex = skuNamesInfo.TotalCount
}
skuNamesInfo.SkuNames = newSkuNames[offset:endIndex]
}
return err
}
func getValidStoreVendorMap(db *dao.DaoDB, storeIDs []int) (realVendorMap map[int]int, err error) {
storeMapList, err := dao.GetStoresMapList(db, nil, storeIDs, model.StoreStatusAll, model.StoreIsSyncYes, "")
if err != nil {
return nil, err
}
realVendorMap = make(map[int]int)
for _, v := range storeMapList {
if v.IsSync != 0 {
realVendorMap[v.VendorID] = 1
}
}
return realVendorMap, nil
}
func GetStoreAbnormalSkuCount(ctx *jxcontext.Context, storeID, syncStatus int, isBySku bool, params map[string]interface{}) (count int, err error) {
db := dao.GetDB()
realVendorMap, err2 := getValidStoreVendorMap(db, []int{storeID})
if err = err2; err != nil {
return 0, err
}
sql := `
SELECT COUNT(*) ct`
if !isBySku {
sql += `
FROM (
SELECT DISTINCT t3.id`
}
sql += `
FROM store_sku_bind t1
JOIN sku t2 ON t2.id = t1.sku_id AND t2.deleted_at = ?
JOIN sku_name t3 ON t3.id = t2.name_id AND t3.deleted_at = ?
WHERE t1.deleted_at = ? AND t1.store_id = ? AND
((t2.status = ? AND t3.status = ?) OR t1.status = ?) AND
(1 = 0`
sqlParams := []interface{}{
utils.DefaultTimeValue,
utils.DefaultTimeValue,
utils.DefaultTimeValue,
storeID,
model.SkuStatusNormal,
model.SkuStatusNormal,
model.SkuStatusNormal,
}
for _, vendorID := range []int{model.VendorIDJD, model.VendorIDEBAI, model.VendorIDMTWM} {
if realVendorMap[vendorID] != 0 {
prefix := dao.ConvertDBFieldPrefix(model.VendorNames[vendorID])
sql += fmt.Sprintf(" OR (t1.%s_sync_status & ? <> 0 AND t1.%s_sync_status & ? = 0", prefix, prefix)
sqlParams = append(sqlParams, syncStatus, model.SyncFlagDeletedMask|model.SyncFlagNewMask)
if model.MultiStoresVendorMap[vendorID] == 1 {
sql += fmt.Sprintf(" AND t2.%s_id <> 0 AND t2.status = ? AND t3.status = ?", prefix)
sqlParams = append(sqlParams, model.SkuStatusNormal, model.SkuStatusNormal)
}
sql += ")"
}
}
sql += ")"
if params["fromStatus"] != nil {
fromStatus := params["fromStatus"].(int)
toStatus := fromStatus
if params["toStatus"] != nil {
toStatus = params["toStatus"].(int)
}
sql += " AND t1.status >= ? AND t1.status <= ?"
sqlParams = append(sqlParams, fromStatus, toStatus)
}
if !isBySku {
sql += `
) t1`
}
err = dao.GetRow(db, &count, sql, sqlParams...)
return count, err
}
func GetStoresSkusSaleInfo(ctx *jxcontext.Context, storeIDs []int, skuIDs []int, fromTime, toTime time.Time, fromCount, toCount int) (saleInfoList []*SkuSaleInfo, err error) {
globals.SugarLogger.Debugf("GetStoresSkusSaleInfo storeIDs:%v, fromTime:%v, toTime:%v, fromCount:%d, toCount:%d", storeIDs, fromTime, toTime, fromCount, toCount)
db := dao.GetDB()
sql := `
SELECT IF(t2.jx_store_id <> 0, jx_store_id, store_id) store_id, t1.sku_id, COUNT(*) times, SUM(count) count
FROM order_sku t1
JOIN goods_order t2 ON t1.vendor_order_id = t2.vendor_order_id AND t1.vendor_id = t2.vendor_id AND t2.status = ?
WHERE t1.order_created_at >= ? AND t1.order_created_at <= ?
`
if utils.IsTimeZero(toTime) {
toTime = time.Now()
}
sqlParams := []interface{}{
model.OrderStatusFinished,
fromTime,
toTime,
}
if len(storeIDs) > 0 {
sql += `
AND IF(t2.jx_store_id <> 0, jx_store_id, store_id) IN (` + dao.GenQuestionMarks(len(storeIDs)) + `)
`
sqlParams = append(sqlParams, storeIDs)
}
if len(skuIDs) > 0 {
sql += `
AND IF(t1.jx_sku_id <> 0, t1.jx_sku_id, t1.sku_id) IN (` + dao.GenQuestionMarks(len(skuIDs)) + `)`
sqlParams = append(sqlParams, skuIDs)
}
sql += `
GROUP BY 1,2
HAVING count >= ? AND count <= ?
`
sqlParams = append(sqlParams, fromCount, toCount)
// fmt.Println(sql)
// fmt.Println(utils.Format4Output(sqlParams, false))
if err = dao.GetRows(db, &saleInfoList, sql, sqlParams...); err == nil {
// globals.SugarLogger.Debug(utils.Format4Output(saleInfoList, false))
return saleInfoList, nil
}
return nil, err
}
func asyncStoreSkuOpFilter(ctx *jxcontext.Context, isAsync bool) bool {
if !isAsync {
authType := ctx.GetLoginInfo().GetAuthType()
if authType == weixin.AuthTypeMini || authType == weixin.AuthTypeMP {
userMobile, _ := ctx.GetMobileAndUserID()
isAsync = asyncOpMobileMap[userMobile] == 0
}
}
return isAsync
}
func UpdateStoreSku(ctx *jxcontext.Context, storeID int, skuBindInfo *StoreSkuBindInfo, isAsync, isContinueWhenError bool) (hint string, err error) {
return UpdateStoreSkus(ctx, storeID, []*StoreSkuBindInfo{skuBindInfo}, isAsync, isContinueWhenError)
}
func UpdateStoreSkus(ctx *jxcontext.Context, storeID int, skuBindInfos []*StoreSkuBindInfo, isAsync, isContinueWhenError bool) (hint string, err error) {
return UpdateStoresSkus(ctx, []int{storeID}, skuBindInfos, isAsync, isContinueWhenError)
}
func UpdateStoresSkus(ctx *jxcontext.Context, storeIDs []int, skuBindInfos []*StoreSkuBindInfo, isAsync, isContinueWhenError bool) (hint string, err error) {
globals.SugarLogger.Debugf("UpdateStoresSkus:%s, storeIDs:%v, skuBindInfos:%s", ctx.GetTrackInfo(), storeIDs, utils.Format4Output(skuBindInfos, true))
var num int64
db := dao.GetDB()
skuIDs, err := updateStoresSkusWithoutSync(ctx, db, storeIDs, skuBindInfos)
if err != nil {
return "", err
}
isAsync = asyncStoreSkuOpFilter(ctx, isAsync)
num = int64(len(skuIDs))
if num > 0 {
hint, err = CurVendorSync.SyncStoresSkus(ctx, db, nil, storeIDs, skuIDs, false, isAsync, isContinueWhenError)
}
if num == 0 || !isAsync || hint == "" {
hint = utils.Int64ToStr(num)
}
return hint, err
}
func UpdateStoresSkusByBind(ctx *jxcontext.Context, skuBindInfos []*StoreSkuBindInfo, isAsync, isContinueWhenError bool) (hint string, err error) {
if len(skuBindInfos) > maxStoreNameBind {
return "", fmt.Errorf("门店商品信息大于%d", maxStoreNameBind)
}
skuBindInfosMap := make(map[int][]*StoreSkuBindInfo)
storeIDMap := make(map[int]int)
for _, v := range skuBindInfos {
if v.StoreID > 0 {
skuBindInfosMap[v.StoreID] = append(skuBindInfosMap[v.StoreID], v)
storeIDMap[v.StoreID] = 1
}
}
storeIDs := jxutils.IntMap2List(storeIDMap)
sort.Ints(storeIDs)
var num int64
skuIDMap := make(map[int]int)
db := dao.GetDB()
dao.Begin(db)
defer func() {
if r := recover(); r != nil {
dao.Rollback(db)
panic(r)
}
}()
for _, storeID := range storeIDs {
skuIDs, err2 := updateStoresSkusWithoutSync(ctx, db, []int{storeID}, skuBindInfosMap[storeID])
if err = err2; err != nil {
dao.Rollback(db)
return "", err
}
for _, v := range skuIDs {
skuIDMap[v] = 1
}
num += int64(len(skuIDs))
}
dao.Commit(db)
isAsync = asyncStoreSkuOpFilter(ctx, isAsync)
if num > 0 {
skuIDs := jxutils.IntMap2List(skuIDMap)
hint, err = CurVendorSync.SyncStoresSkus(ctx, db, nil, storeIDs, skuIDs, false, isAsync, isContinueWhenError)
}
if num == 0 || !isAsync || hint == "" {
hint = utils.Int64ToStr(num)
}
return hint, err
}
func checkStoresSkusSaleCity(ctx *jxcontext.Context, db *dao.DaoDB, storeIDs []int, skuBindInfos []*StoreSkuBindInfo) (err error) {
sql := `
SELECT t1.id store_id, t2.id name_id, t2.name
FROM store t1
JOIN sku_name t2 ON t2.is_global = 0 AND t2.deleted_at = ?
LEFT JOIN sku_name_place_bind t3 ON t2.id = t3.name_id AND t1.city_code = t3.place_code
WHERE t3.id IS NULL
`
sql += " AND t1.id IN (" + dao.GenQuestionMarks(len(storeIDs)) + ")"
nameIDs := make([]int, 0)
for _, v := range skuBindInfos {
if v.IsFocus == 1 {
nameIDs = append(nameIDs, v.NameID)
} else {
for _, v2 := range v.Skus {
if v2.IsSale == 1 {
nameIDs = append(nameIDs, v.NameID)
break
}
}
}
}
if len(nameIDs) == 0 {
return nil
}
sql += " AND t2.id IN (" + dao.GenQuestionMarks(len(nameIDs)) + ")"
var invalidList []*tStoreNameBind
if err = dao.GetRows(db, &invalidList, sql, utils.DefaultTimeValue, storeIDs, nameIDs); err == nil {
if len(invalidList) > 0 {
errMsg := ""
for _, v := range invalidList {
errMsg += fmt.Sprintf("门店:%dName:%d(%s)销售区域错误!\n", v.StoreID, v.NameID, v.Name)
}
err = errors.New(errMsg)
}
}
return err
}
func uniqueStoreIDs(storeIDs []int) []int {
storeIDMap := make(map[int]int)
for _, v := range storeIDs {
storeIDMap[v] = 1
}
return jxutils.IntMap2List(storeIDMap)
}
func uniqueStoreNameBind(skuBindInfos []*StoreSkuBindInfo) (outSkuBindInfos []*StoreSkuBindInfo) {
nameIDMap := make(map[int]int)
for _, v := range skuBindInfos {
if nameIDMap[v.NameID] != 1 {
outSkuBindInfos = append(outSkuBindInfos, v)
nameIDMap[v.NameID] = 1
}
}
return outSkuBindInfos
}
func updateStoresSkusWithoutSync(ctx *jxcontext.Context, db *dao.DaoDB, storeIDs []int, skuBindInfos []*StoreSkuBindInfo) (needSyncSkus []int, err error) {
if len(storeIDs)*len(skuBindInfos) > maxStoreNameBind2 {
return nil, fmt.Errorf("门店商品信息大于%d", maxStoreNameBind2)
}
storeIDs = uniqueStoreIDs(storeIDs)
skuBindInfos = uniqueStoreNameBind(skuBindInfos)
sort.Ints(storeIDs)
// globals.SugarLogger.Debugf("updateStoresSkusWithoutSync, storeIDs:%v, skuBindInfos:%s", storeIDs, utils.Format4Output(skuBindInfos, false))
if db == nil {
db = dao.GetDB()
}
// if err = checkStoresSkusSaleCity(ctx, db, storeIDs, skuBindInfos); err != nil {
// return nil, err
// }
// if storeIDs, skuBindInfos, err = filterStorePriceChange(ctx, storeIDs, skuBindInfos); err != nil {
// return nil, err
// }
// globals.SugarLogger.Debugf("updateStoresSkusWithoutSync2, storeIDs:%v, skuBindInfos:%s", storeIDs, utils.Format4Output(skuBindInfos, false))
isUserCanDirectChangePrice := true
if user := ctx.GetFullUser(); user != nil {
isUserCanDirectChangePrice = user.Type&model.UserTypeOperator != 0
}
userName := ctx.GetUserName()
needSyncIDMap := make(map[int]int)
dao.Begin(db)
defer func() {
if r := recover(); r != nil {
dao.Rollback(db)
panic(r)
}
}()
for _, storeID := range storeIDs {
for _, skuBindInfo := range skuBindInfos {
// 关注且没有给价时需要尝试从store_sku_bind中得到已有的单价
needGetExistingUnitPrice := skuBindInfo.UnitPrice == 0 && skuBindInfo.IsFocus == 1
inSkuBinds := skuBindInfo.Skus
var allBinds []*tStoreSkuBindAndSpec
sql := `
SELECT
t2.*,
t1.id real_sku_id, t1.status sku_status, t1.spec_quality, t1.spec_unit,`
if needGetExistingUnitPrice {
sql += " IF(t5.unit_price > 0, t5.unit_price, t3.price) sku_name_price,"
}
sql += `
t3.unit sku_name_unit, t3.name, t3.status sku_name_status,
ts.change_price_type
FROM sku t1
JOIN store ts ON ts.id = ? AND ts.deleted_at = ?
LEFT JOIN store_sku_bind t2 ON t2.sku_id = t1.id AND t2.store_id = ts.id AND t2.deleted_at = ?
JOIN sku_name t3 ON t1.name_id = t3.id AND t3.deleted_at = ?`
sqlParams := []interface{}{
storeID,
utils.DefaultTimeValue,
utils.DefaultTimeValue,
utils.DefaultTimeValue,
}
if needGetExistingUnitPrice {
sql += `
LEFT JOIN (
SELECT t7.store_id, t8.name_id, CAST(AVG(t7.unit_price) AS SIGNED) unit_price
FROM store_sku_bind t7
JOIN sku t8 ON t8.id = t7.sku_id AND t8.name_id = ?
WHERE t7.deleted_at = ? AND t7.store_id = ?
GROUP BY 1,2
) t5 ON t5.store_id = ts.id AND t5.name_id = t1.name_id`
sqlParams = append(sqlParams, skuBindInfo.NameID, utils.DefaultTimeValue, storeID)
}
sql += `
WHERE t1.name_id = ? AND t1.deleted_at = ?
FOR UPDATE`
sqlParams = append(sqlParams, skuBindInfo.NameID, utils.DefaultTimeValue)
// globals.SugarLogger.Debug(sql)
if err = dao.GetRows(db, &allBinds, sql, sqlParams...); err == nil {
if len(allBinds) > 0 {
// globals.SugarLogger.Debug(utils.Format4Output(allBinds, false))
inSkuBinsMap := make(map[int]*StoreSkuBindSkuInfo, len(inSkuBinds))
for _, v := range inSkuBinds {
inSkuBinsMap[v.SkuID] = v
}
unitPrice := 0
if skuBindInfo.UnitPrice != 0 {
if skuBindInfo.UnitPrice > MaxSkuUnitPrice {
dao.Rollback(db)
return nil, fmt.Errorf("商品:%s价格:%s太夸张", allBinds[0].Name, jxutils.IntPrice2StandardCurrencyString(int64(skuBindInfo.UnitPrice)))
}
unitPrice = skuBindInfo.UnitPrice
} else {
unitPrice = allBinds[0].UnitPrice
if unitPrice == 0 {
unitPrice = allBinds[0].SkuNamePrice
}
}
for _, v := range allBinds {
var num int64
inSkuBind := inSkuBinsMap[v.RealSkuID]
isCanChangePrice := (isUserCanDirectChangePrice || v.ChangePriceType != model.StoreChangePriceTypeBossDisabled)
// globals.SugarLogger.Debug(utils.Format4Output(inSkuBind, false))
var skuBind *model.StoreSkuBind
if v.ID == 0 {
if skuBindInfo.IsFocus == 1 && v.SkuNameStatus == model.SkuStatusNormal && v.SkuStatus == model.SkuStatusNormal && isCanChangePrice {
skuBind = &model.StoreSkuBind{
StoreID: storeID,
SkuID: v.RealSkuID,
SubStoreID: skuBindInfo.SubStoreID, // todo 这个应该从用户信息中自动获得
UnitPrice: unitPrice,
Price: jxutils.CaculateSkuPrice(unitPrice, v.SpecQuality, v.SpecUnit, v.SkuNameUnit),
Status: model.StoreSkuBindStatusDontSale, // 缺省不可售?
}
if tmpStatus := getSkuSaleStatus(inSkuBind, skuBindInfo); tmpStatus != model.StoreSkuBindStatusNA {
skuBind.Status = tmpStatus
}
setStoreSkuBindStatus(skuBind, model.SyncFlagNewMask)
dao.WrapAddIDCULDEntity(skuBind, userName)
globals.SugarLogger.Debug(utils.Format4Output(skuBind, false))
if err = dao.CreateEntity(db, skuBind); err != nil {
dao.Rollback(db)
return nil, err
}
num = 1
}
} else {
skuBind = &v.StoreSkuBind
if skuBindInfo.IsFocus == -1 && isCanChangePrice {
if num, err = dao.DeleteEntityLogically(db, skuBind, map[string]interface{}{
model.FieldStatus: model.StoreSkuBindStatusDeleted,
model.FieldJdSyncStatus: model.SyncFlagDeletedMask,
model.FieldEbaiSyncStatus: model.SyncFlagDeletedMask,
model.FieldMtwmSyncStatus: model.SyncFlagDeletedMask,
model.FieldWscSyncStatus: model.SyncFlagDeletedMask,
}, userName, nil); err != nil {
dao.Rollback(db)
return nil, err
}
} else {
// 用了SELECT FOR UPDATE后只更新修改字段是没有必要的暂时保留
updateFieldMap := make(map[string]int)
if skuBindInfo.IsFocus == 1 { // 关注之后再关注不操作
// skuBind.Status = model.StoreSkuBindStatusDontSale // 缺省不可售?
// skuBind.DeletedAt = utils.DefaultTimeValue
// skuBind.UnitPrice = unitPrice
// skuBind.Price = jxutils.CaculateSkuPrice(unitPrice, v.SpecQuality, v.SpecUnit, v.SkuNameUnit)
// setStoreSkuBindStatus(skuBind, model.SyncFlagPriceMask|model.SyncFlagSaleMask)
// updateFieldMap[model.FieldStatus] = 1
// updateFieldMap[model.FieldDeletedAt] = 1
// updateFieldMap["UnitPrice"] = 1
// updateFieldMap["Price"] = 1
}
if tmpStatus := getSkuSaleStatus(inSkuBind, skuBindInfo); tmpStatus != model.StoreSkuBindStatusNA {
skuBind.Status = tmpStatus
setStoreSkuBindStatus(skuBind, model.SyncFlagSaleMask)
updateFieldMap[model.FieldStatus] = 1
}
if skuBindInfo.UnitPrice != 0 && isCanChangePrice { // 这里是否需要加此条件限制
skuBind.UnitPrice = unitPrice
skuBind.Price = jxutils.CaculateSkuPrice(unitPrice, v.SpecQuality, v.SpecUnit, v.SkuNameUnit)
setStoreSkuBindStatus(skuBind, model.SyncFlagPriceMask)
updateFieldMap["UnitPrice"] = 1
updateFieldMap["Price"] = 1
}
// todo 这里应该是不需处理这个信息的吧?
// if inSkuBind != nil && inSkuBind.EbaiID != 0 {
// skuBind.EbaiID = inSkuBind.EbaiID
// updateFieldMap["EbaiID"] = 1
// }
// if inSkuBind != nil && inSkuBind.ElmID != 0 {
// skuBind.ElmID = inSkuBind.ElmID
// updateFieldMap["ElmID"] = 1
// }
if len(updateFieldMap) > 0 {
updateFieldMap[model.FieldJdSyncStatus] = 1
updateFieldMap[model.FieldEbaiSyncStatus] = 1
updateFieldMap[model.FieldMtwmSyncStatus] = 1
updateFieldMap[model.FieldWscSyncStatus] = 1
updateFieldMap[model.FieldUpdatedAt] = 1
updateFieldMap[model.FieldLastOperator] = 1
// setStoreSkuBindStatus(skuBind, model.SyncFlagModifiedMask)
dao.WrapUpdateULEntity(skuBind, userName)
skuBind.AutoSaleAt = utils.DefaultTimeValue
if num, err = dao.UpdateEntity(db, skuBind /*, utils.Map2KeySlice(updateFieldMap)...*/); err != nil {
dao.Rollback(db)
return nil, err
}
}
}
}
if skuBind != nil && num == 1 {
needSyncIDMap[skuBind.SkuID] = 1
}
}
}
} else {
dao.Rollback(db)
return nil, err
}
}
}
dao.Commit(db)
skuIDs := jxutils.IntMap2List(needSyncIDMap)
return skuIDs, err
}
func getSkuSaleStatus(inSkuBind *StoreSkuBindSkuInfo, skuBindInfo *StoreSkuBindInfo) int {
tempSale := 0
if inSkuBind != nil {
tempSale = inSkuBind.IsSale
} else {
tempSale = skuBindInfo.IsSale
}
if tempSale == -1 {
return model.StoreSkuBindStatusDontSale
} else if tempSale == 1 {
return model.StoreSkuBindStatusNormal
}
return model.StoreSkuBindStatusNA
}
func formatAutoSaleTime(autoSaleTime time.Time) (outAutoSaleTime time.Time) {
if utils.IsTimeZero(autoSaleTime) {
outAutoSaleTime = utils.DefaultTimeValue
} else {
outAutoSaleTime = jxutils.GetNextTimeFromList(time.Now(), []string{AutoSaleAtStr})
}
return outAutoSaleTime
}
// todo 应该用updateStoresSkusWithoutSync实现
func updateStoreSkusSaleWithoutSync(ctx *jxcontext.Context, storeID int, skuBindSkuInfos []*StoreSkuBindSkuInfo, autoSaleTime time.Time, ignoreDontSale bool, userName string) (needSyncSkus []int, err error) {
var num int64
db := dao.GetDB()
needSyncIDMap := make(map[int]int)
skuIDMap := make(map[int]int)
skuBindSkuInfosMap := make(map[int]*StoreSkuBindSkuInfo)
for _, v := range skuBindSkuInfos {
skuIDMap[v.SkuID] = 1
skuBindSkuInfosMap[v.SkuID] = v
}
dao.Begin(db)
defer func() {
if r := recover(); r != nil {
dao.Rollback(db)
panic(r)
}
}()
storeSkuList, err := dao.GetStoresSkusInfo(db, []int{storeID}, jxutils.IntMap2List(skuIDMap))
if err != nil {
dao.Rollback(db)
return nil, err
}
autoSaleTime = formatAutoSaleTime(autoSaleTime)
for _, skuBind := range storeSkuList {
if v := skuBindSkuInfosMap[skuBind.SkuID]; v != nil && v.IsSale != 0 {
if !(!utils.IsTimeZero(autoSaleTime) && ignoreDontSale && skuBind.Status == model.StoreSkuBindStatusDontSale) {
if v.IsSale == -1 || !utils.IsTimeZero(autoSaleTime) {
skuBind.Status = model.StoreSkuBindStatusDontSale
} else if v.IsSale == 1 {
skuBind.Status = model.StoreSkuBindStatusNormal
}
kvs := map[string]interface{}{
model.FieldLastOperator: ctx.GetUserName(),
model.FieldUpdatedAt: time.Now(),
model.FieldStatus: skuBind.Status,
model.FieldJdSyncStatus: skuBind.JdSyncStatus | model.SyncFlagSaleMask,
model.FieldEbaiSyncStatus: skuBind.EbaiSyncStatus | model.SyncFlagSaleMask,
model.FieldMtwmSyncStatus: skuBind.MtwmSyncStatus | model.SyncFlagSaleMask,
model.FieldWscSyncStatus: skuBind.WscSyncStatus | model.SyncFlagSaleMask,
}
if utils.IsTimeZero(autoSaleTime) || skuBind.Status == model.SkuStatusNormal {
kvs["AutoSaleAt"] = utils.DefaultTimeValue
} else {
kvs["AutoSaleAt"] = autoSaleTime
}
if num, err = dao.UpdateEntityLogically(db, skuBind, kvs, userName, nil); err != nil {
dao.Rollback(db)
return nil, err
}
if num == 1 {
needSyncIDMap[v.SkuID] = 1
}
}
}
}
dao.Commit(db)
needSyncSkus = jxutils.IntMap2List(needSyncIDMap)
return needSyncSkus, err
}
func uniqueStoreSkuBind(skuBindSkuInfos []*StoreSkuBindSkuInfo) (outSkuBindSkuInfos []*StoreSkuBindSkuInfo) {
skuIDMap := make(map[int]int)
for _, v := range skuBindSkuInfos {
if skuIDMap[v.SkuID] != 1 {
outSkuBindSkuInfos = append(outSkuBindSkuInfos, v)
skuIDMap[v.SkuID] = 1
}
}
return outSkuBindSkuInfos
}
func UpdateStoresSkusSale(ctx *jxcontext.Context, storeIDs []int, skuBindSkuInfos []*StoreSkuBindSkuInfo, autoSaleTime time.Time, ignoreDontSale bool, userName string, isAsync, isContinueWhenError bool) (hint string, err error) {
storeIDs = uniqueStoreIDs(storeIDs)
skuBindSkuInfos = uniqueStoreSkuBind(skuBindSkuInfos)
var num int64
for _, storeID := range storeIDs {
skuIDs, err2 := updateStoreSkusSaleWithoutSync(ctx, storeID, skuBindSkuInfos, autoSaleTime, ignoreDontSale, userName)
if err = err2; err != nil {
return "", err
}
num += int64(len(skuIDs))
}
isAsync = asyncStoreSkuOpFilter(ctx, isAsync)
if num > 0 {
skuIDs := make([]int, 0)
for _, v := range skuBindSkuInfos {
skuIDs = append(skuIDs, v.SkuID)
}
db := dao.GetDB()
hint, err = CurVendorSync.SyncStoresSkus(ctx, db, nil, storeIDs, skuIDs, false, isAsync, isContinueWhenError)
}
if num == 0 || !isAsync || hint == "" {
hint = utils.Int64ToStr(num)
}
return hint, err
}
func CopyStoreSkus(ctx *jxcontext.Context, fromStoreID, toStoreID int, copyMode string, params map[string]interface{}, userName string) (num int64, err error) {
if copyMode != CopyStoreSkuModeFresh && copyMode != CopyStoreSkuModeUpdate && copyMode != CopyStoreSkuModeUpdatePrice {
return 0, fmt.Errorf("不支持的拷贝模式:%s", copyMode)
}
db := dao.GetDB()
if err = checkStoreExisting(db, fromStoreID); err != nil {
return 0, err
}
if err = checkStoreExisting(db, toStoreID); err != nil {
return 0, err
}
sqlCatAndSku := ""
sqlCatAndSkuParams := make([]interface{}, 0)
if params["categoryIDs"] != nil {
var cats []int
if err = utils.UnmarshalUseNumber([]byte(params["categoryIDs"].(string)), &cats); err != nil {
return 0, err
}
if len(cats) > 0 {
sqlCatAndSku += " AND (t3.category_id IN (" + dao.GenQuestionMarks(len(cats)) + ") OR t4.parent_id IN (" + dao.GenQuestionMarks(len(cats)) + "))"
sqlCatAndSkuParams = append(sqlCatAndSkuParams, cats, cats)
}
}
if params["skuIDs"] != nil {
var skus []int
if err = utils.UnmarshalUseNumber([]byte(params["skuIDs"].(string)), &skus); err != nil {
return 0, err
}
if len(skus) > 0 {
sqlCatAndSku += " AND t1.sku_id IN (" + dao.GenQuestionMarks(len(skus)) + ")"
sqlCatAndSkuParams = append(sqlCatAndSkuParams, skus)
}
}
pricePercentage := 100
if params["pricePercentage"] != nil {
pricePercentage = params["pricePercentage"].(int)
}
now := time.Now()
if fromStoreID == toStoreID {
sql := `
UPDATE store_sku_bind t1
JOIN sku t2 ON t1.sku_id = t2.id AND t2.deleted_at = ?
JOIN sku_name t3 ON t2.name_id = t3.id AND t2.deleted_at = ?
LEFT JOIN sku_category t4 ON t3.category_id = t4.id AND t2.deleted_at = ?
SET t1.last_operator = ?,
t1.updated_at = ?,
t1.price = t1.price * ? / 100,
t1.unit_price = t1.unit_price * ? / 100,
t1.jd_sync_status = t1.jd_sync_status | ?,
t1.wsc_sync_status = t1.wsc_sync_status | ?,
t1.mtwm_sync_status = t1.mtwm_sync_status | ?,
t1.ebai_sync_status = t1.ebai_sync_status | ?
WHERE t1.store_id = ? AND t1.deleted_at = ?
`
sqlParams := []interface{}{
utils.DefaultTimeValue,
utils.DefaultTimeValue,
utils.DefaultTimeValue,
userName,
now,
pricePercentage,
pricePercentage,
model.SyncFlagPriceMask,
model.SyncFlagPriceMask,
model.SyncFlagPriceMask,
model.SyncFlagPriceMask,
toStoreID,
utils.DefaultTimeValue,
}
sql += sqlCatAndSku
sqlParams = append(sqlParams, sqlCatAndSkuParams)
num, err = dao.ExecuteSQL(db, sql, sqlParams)
return num, err
}
dao.Begin(db)
defer func() {
dao.Rollback(db)
if r := recover(); r != nil {
panic(r)
}
}()
if copyMode == CopyStoreSkuModeFresh {
// 将toStore中存在但fromStore中不存在的置删除标志
sqlDelete := `
UPDATE store_sku_bind t1
LEFT JOIN store_sku_bind t0 ON t0.store_id = ? AND t0.sku_id = t1.sku_id AND t0.deleted_at = ?
JOIN sku t2 ON t1.sku_id = t2.id/* AND t2.deleted_at = ?*/
JOIN sku_name t3 ON t2.name_id = t3.id/* AND t2.deleted_at = ?*/
LEFT JOIN sku_category t4 ON t3.category_id = t4.id AND t2.deleted_at = ?
SET t1.deleted_at = ?,
t1.updated_at = ?,
t1.last_operator = ?,
t1.status = ?,
t1.jd_sync_status = IF((t1.jd_sync_status & ?) <> 0, 0, ?),
t1.wsc_sync_status = IF((t1.wsc_sync_status & ?) <> 0, 0, ?),
t1.mtwm_sync_status = IF((t1.mtwm_sync_status & ?) <> 0, 0, ?),
t1.ebai_sync_status = IF((t1.ebai_sync_status & ?) <> 0, 0, ?)
WHERE t1.store_id = ? AND t1.deleted_at = ? AND t0.id IS NULL
`
sqlDeleteParams := []interface{}{
fromStoreID,
utils.DefaultTimeValue,
// utils.DefaultTimeValue,
// utils.DefaultTimeValue,
utils.DefaultTimeValue,
now,
now,
userName,
model.StoreSkuBindStatusDeleted,
model.SyncFlagNewMask,
model.SyncFlagDeletedMask,
model.SyncFlagNewMask,
model.SyncFlagDeletedMask,
model.SyncFlagNewMask,
model.SyncFlagDeletedMask,
model.SyncFlagNewMask,
model.SyncFlagDeletedMask,
toStoreID,
utils.DefaultTimeValue,
}
sqlDelete += sqlCatAndSku
sqlDeleteParams = append(sqlDeleteParams, sqlCatAndSkuParams)
// globals.SugarLogger.Debug(sqlDelete)
if num, err = dao.ExecuteSQL(db, sqlDelete, sqlDeleteParams); err != nil {
return 0, err
}
globals.SugarLogger.Debugf("CopyStoreSkus trackInfo:%s num1:%d", ctx.GetTrackInfo(), num)
}
isModifyStatus := 1
syncStatus := model.SyncFlagStoreSkuOnlyMask
if copyMode == CopyStoreSkuModeUpdatePrice {
isModifyStatus = 0
syncStatus = model.SyncFlagPriceMask
}
// 处理toStore中与fromStore中都存在的
sql := `
UPDATE store_sku_bind t1
LEFT JOIN store_sku_bind t0 ON t0.store_id = ? AND t0.sku_id = t1.sku_id AND t0.deleted_at = ?
JOIN sku t2 ON t1.sku_id = t2.id/* AND t2.deleted_at = ?*/
JOIN sku_name t3 ON t2.name_id = t3.id/* AND t3.deleted_at = ?*/
LEFT JOIN sku_category t4 ON t3.category_id = t4.id AND t4.deleted_at = ?
SET t1.last_operator = ?,
t1.updated_at = ?,
t1.sub_store_id = 0,
t1.price = IF(t0.price * ? / 100 > 0, t0.price * ? / 100, 1),
t1.unit_price = IF(t0.unit_price * ? / 100 > 0, t0.unit_price * ? / 100, 1),
t1.status = IF(? = 0, t1.status, t0.status),
t1.jd_sync_status = t1.jd_sync_status | ?,
t1.wsc_sync_status = t1.wsc_sync_status | ?,
t1.mtwm_sync_status = t1.mtwm_sync_status | ?,
t1.ebai_sync_status = t1.ebai_sync_status | ?
WHERE t1.store_id = ? AND t1.deleted_at = ? AND t0.id IS NOT NULL
`
sqlParams := []interface{}{
fromStoreID,
utils.DefaultTimeValue,
// utils.DefaultTimeValue,
// utils.DefaultTimeValue,
utils.DefaultTimeValue,
userName,
now,
pricePercentage,
pricePercentage,
pricePercentage,
pricePercentage,
isModifyStatus,
syncStatus,
syncStatus,
syncStatus,
syncStatus,
toStoreID,
utils.DefaultTimeValue,
}
sql += sqlCatAndSku
sqlParams = append(sqlParams, sqlCatAndSkuParams)
// globals.SugarLogger.Debug(sql)
num, err = dao.ExecuteSQL(db, sql, sqlParams)
globals.SugarLogger.Debugf("CopyStoreSkus trackInfo:%s num2:%d", ctx.GetTrackInfo(), num)
if err != nil {
return 0, err
}
// 添加toStore中不存在但fromStore存在的
sql = `
INSERT INTO store_sku_bind(created_at, updated_at, last_operator, deleted_at, store_id, sku_id, sub_store_id, price, unit_price, status,
jd_sync_status, wsc_sync_status, ebai_sync_status, mtwm_sync_status)
SELECT ?, ?, ?, ?, ?,
t1.sku_id, 0, IF(t1.price * ? / 100 > 0, t1.price * ? / 100, 1), IF(t1.unit_price * ? / 100 > 0, t1.unit_price * ? / 100, 1),
IF(? = 0, ?, t1.status), ?, ?, ?, ?
FROM store_sku_bind t1
JOIN sku t2 ON t1.sku_id = t2.id AND t2.deleted_at = ?
JOIN sku_name t3 ON t2.name_id = t3.id AND t3.deleted_at = ?
LEFT JOIN sku_category t4 ON t3.category_id = t4.id AND t4.deleted_at = ?
LEFT JOIN store_sku_bind t0 ON t1.sku_id = t0.sku_id AND t0.store_id = ? AND t0.deleted_at = ?
WHERE t1.store_id = ? AND t1.deleted_at = ?
`
sqlParams = []interface{}{
now, now, userName, utils.DefaultTimeValue, toStoreID,
pricePercentage,
pricePercentage,
pricePercentage,
pricePercentage,
isModifyStatus,
model.SkuStatusDontSale,
model.SyncFlagNewMask,
model.SyncFlagNewMask,
model.SyncFlagNewMask,
model.SyncFlagNewMask,
utils.DefaultTimeValue,
utils.DefaultTimeValue,
utils.DefaultTimeValue,
toStoreID,
utils.DefaultTimeValue,
fromStoreID,
utils.DefaultTimeValue,
}
sql += sqlCatAndSku + " AND t0.id IS NULL"
sqlParams = append(sqlParams, sqlCatAndSkuParams)
num, err = dao.ExecuteSQL(db, sql, sqlParams)
if err != nil {
return 0, err
}
globals.SugarLogger.Debugf("CopyStoreSkus trackInfo:%s num3:%d", ctx.GetTrackInfo(), num)
dao.Commit(db)
return num, err
}
func shouldPendingStorePriceChange(ctx *jxcontext.Context, storeID int, skuBindInfo *StoreSkuBindInfo) (shouldPending bool, err error) {
if globals.EnablePendingChange {
if skuBindInfo.IsFocus != 1 && (ctx.GetLoginType() == weixin.AuthTypeMP || ctx.GetLoginType() == weixin.AuthTypeMini || ctx.GetUserName() == "fakeboss") {
db := dao.GetDB()
store := &model.Store{}
store.ID = storeID
if err = dao.GetEntity(db, store); err != nil {
return false, err
}
return store.ChangePriceType == model.StoreChangePriceTypeNeedApprove, nil
}
}
return false, nil
}
func filterStorePriceChange(ctx *jxcontext.Context, storeIDs []int, skuBindInfos []*StoreSkuBindInfo) (filteredStoreIDs []int, filteredSkuBindInfos []*StoreSkuBindInfo, err error) {
globals.SugarLogger.Debug("filterStorePriceChange")
if globals.EnablePendingChange {
db := dao.GetDB()
dao.Begin(db)
defer dao.Rollback(db)
for _, storeID := range storeIDs {
for _, skuBindInfo := range skuBindInfos {
shouldPending, err2 := shouldPendingStorePriceChange(ctx, storeID, skuBindInfo)
if err = err2; err != nil {
return nil, nil, err
}
if shouldPending && (skuBindInfo.UnitPrice != 0 || skuBindInfo.IsFocus == 1) {
var (
opInfoList []*StoreOpRequestInfo
sql string
sqlParams []interface{}
opType int8
)
if skuBindInfo.IsFocus == 1 {
opType = model.RequestTypeFocusSkuName
sql = `
SELECT DISTINCT t1.*, t3.unit_price
FROM store_op_request t1
JOIN sku t2 ON t2.name_id = t1.item_id AND t2.deleted_at = ?
LEFT JOIN store_sku_bind t3 ON t3.store_id = t1.store_id AND t3.sku_id = t2.id AND t3.deleted_at = ?
WHERE t1.store_id = ? AND t1.item_id = ? AND t1.deleted_at = ? AND t1.type IN (?, ?)`
sqlParams = []interface{}{
utils.DefaultTimeValue,
utils.DefaultTimeValue,
storeID,
skuBindInfo.NameID,
utils.DefaultTimeValue,
model.RequestTypeChangePrice,
model.RequestTypeFocusSkuName,
}
} else {
opType = model.RequestTypeChangePrice
sql = `
SELECT DISTINCT t3.*, t1.unit_price
FROM store_sku_bind t1
JOIN sku t2 ON t2.id = t1.sku_id AND t2.name_id = ? AND t2.deleted_at = ?
LEFT JOIN store_op_request t3 ON t3.store_id = t1.store_id AND t3.item_id = t2.name_id AND t3.deleted_at = ? AND t3.type IN (?, ?)
WHERE t1.store_id = ? AND t1.deleted_at = ?`
sqlParams = []interface{}{
skuBindInfo.NameID,
utils.DefaultTimeValue,
utils.DefaultTimeValue,
model.RequestTypeChangePrice,
model.RequestTypeFocusSkuName,
storeID,
utils.DefaultTimeValue,
}
}
if err = dao.GetRows(db, &opInfoList, sql, sqlParams...); err != nil {
return nil, nil, err
}
if len(opInfoList) > 1 {
panic(fmt.Sprintf("filterStorePriceChange more than one row, storeID:%d, skuBindInfo:%s, result:%s", storeID, utils.Format4Output(skuBindInfo, false), utils.Format4Output(opInfoList, false)))
}
existRow := len(opInfoList) == 1
existRequestOp := existRow && opInfoList[0].ID != 0
var changeReq *model.StoreOpRequest
if existRequestOp {
if opInfoList[0].Type != opType {
return nil, nil, fmt.Errorf("filterStorePriceChange关注与修改价格只应该存在一个待审核请求已存在的:%s新来的,storeID:%d, skuBindInfo:%s", utils.Format4Output(opInfoList[0], false), storeID, utils.Format4Output(skuBindInfo, false))
}
changeReq = &opInfoList[0].StoreOpRequest
} else {
changeReq = &model.StoreOpRequest{}
}
if existRequestOp && opInfoList[0].UnitPrice == skuBindInfo.UnitPrice {
changeReq.Status = model.RequestStatusCanceled
changeReq.DeletedAt = time.Now()
} else {
changeReq.Status = model.RequestStatusNew
changeReq.Type = opType
changeReq.StoreID = storeID
changeReq.ItemID = skuBindInfo.NameID
changeReq.UserID = ctx.GetUserName()
changeReq.IntParam1 = skuBindInfo.UnitPrice
changeReq.IntParam2 = skuBindInfo.IsSale
if len(skuBindInfo.Skus) > 0 {
changeReq.JsonParam = string(utils.MustMarshal(skuBindInfo.Skus))
}
}
if existRequestOp {
dao.WrapUpdateULEntity(changeReq, ctx.GetUserName())
if _, err = dao.UpdateEntity(db, changeReq); err != nil {
return nil, nil, err
}
} else {
if !existRow || opInfoList[0].UnitPrice != skuBindInfo.UnitPrice {
dao.WrapAddIDCULDEntity(changeReq, ctx.GetUserName())
if err = dao.CreateEntity(db, changeReq); err != nil {
return nil, nil, err
}
}
}
// 去除价格相关的部分
if skuBindInfo.IsFocus == 1 {
skuBindInfo.IsFocus = 0
}
skuBindInfo.UnitPrice = 0
}
}
}
dao.Commit(db)
}
return storeIDs, skuBindInfos, nil
}
func AcceptStoreOpRequests(ctx *jxcontext.Context, reqIDs []int) (err error) {
if globals.EnablePendingChange {
if len(reqIDs) > 0 {
subErrors := make(map[int]error)
infoMap, err2 := getStoreOpRequestsInfo(reqIDs)
if err = err2; err != nil {
return err
}
for reqID, op := range infoMap {
if op.Status == model.RequestStatusNew {
skuBindInfo := &StoreSkuBindInfo{
NameID: op.ItemID,
UnitPrice: op.IntParam1,
IsSale: op.IntParam2,
}
if op.Type == model.RequestTypeFocusSkuName {
skuBindInfo.IsFocus = 1
}
if op.JsonParam != "" {
if err2 = utils.UnmarshalUseNumber([]byte(op.JsonParam), &skuBindInfo.Skus); err2 != nil {
subErrors[reqID] = err2
}
}
if err2 == nil {
_, err2 := UpdateStoresSkus(ctx, []int{op.StoreID}, []*StoreSkuBindInfo{skuBindInfo}, false, false)
isLocalSucess := true
if err2 != nil {
subErrors[reqID] = err2
if !isSyncError(err2) {
isLocalSucess = false
}
}
if isLocalSucess {
weixinmsg.NotifyStoreOpRequestStatus(true, op.StoreID, op.ItemID, jxutils.ComposeSpuName(op.SkuNamePrefix, op.SkuNameName, 0), op.UnitPrice, op.IntParam1, "")
if err2 := changeStoreOpStatus(ctx, []int{reqID}, model.RequestStatusAccepted, ""); err2 != nil {
subErrors[reqID] = err2
}
}
}
}
}
if len(subErrors) > 0 {
errMsg := ""
for k, v := range subErrors {
errMsg += fmt.Sprintf("req:%d, error:%s\n", k, v.Error())
}
err = errors.New(errMsg)
}
}
}
return err
}
func RejectStoreOpRequests(ctx *jxcontext.Context, reqIDs []int, rejectReason string) (err error) {
infoMap, err := getStoreOpRequestsInfo(reqIDs)
if err != nil {
return err
}
if err = changeStoreOpStatus(ctx, reqIDs, model.RequestStatusRejected, rejectReason); err == nil {
for _, info := range infoMap {
weixinmsg.NotifyStoreOpRequestStatus(false, info.StoreID, info.ItemID, jxutils.ComposeSpuName(info.SkuNamePrefix, info.SkuNameName, 0), info.UnitPrice, info.IntParam1, rejectReason)
}
}
return err
}
// 当前些函数只针对type为 RequestTypeChangePrice与RequestTypeFocusSkuName的查询才有效
func GetStoreOpRequests(ctx *jxcontext.Context, fromTime, toTime time.Time, keyword string, storeIDs, itemIDs, typeList, statusList []int, offset, pageSize int) (pagedInfo *model.PagedInfo, err error) {
if globals.EnablePendingChange {
sql := `
SELECT SQL_CALC_FOUND_ROWS
t1.id, t1.created_at, t1.updated_at, t1.last_operator, t1.deleted_at,
t1.type, t1.store_id, t1.item_id, t1.status, t1.user_id, t1.int_param1, t1.int_param2, t1.remark,
t2.name store_name, t3.prefix sku_name_prefix, t3.name sku_name_name, MAX(IF(t1.status = ?, t5.unit_price, t1.int_param0)) unit_price
FROM store_op_request t1
JOIN store t2 ON t1.store_id = t2.id
JOIN sku_name t3 ON t1.item_id = t3.id AND t3.deleted_at = ?
JOIN sku t4 ON t3.id = t4.name_id AND t4.deleted_at = ?
LEFT JOIN store_sku_bind t5 ON t1.store_id = t5.store_id AND t4.id = t5.sku_id AND t5.deleted_at = ?
WHERE 1 = 1`
sqlParams := []interface{}{
model.RequestStatusNew,
utils.DefaultTimeValue,
utils.DefaultTimeValue,
utils.DefaultTimeValue,
}
if keyword != "" {
keywordLike := "%" + keyword + "%"
sql += " AND ( t2.name LIKE ? OR t3.name LIKE ?"
sqlParams = append(sqlParams, keywordLike, keywordLike)
if keywordInt64, err2 := strconv.ParseInt(keyword, 10, 64); err2 == nil {
sql += " OR t1.store_id = ? OR t1.item_id = ?"
sqlParams = append(sqlParams, keywordInt64, keywordInt64)
}
sql += ")"
}
if fromTime != utils.DefaultTimeValue {
sql += " AND t1.created_at >= ?"
sqlParams = append(sqlParams, fromTime)
}
if toTime != utils.DefaultTimeValue {
sql += " AND t1.created_at <= ?"
sqlParams = append(sqlParams, toTime)
}
if len(storeIDs) > 0 {
sql += " AND t1.store_id IN (" + dao.GenQuestionMarks(len(storeIDs)) + ")"
sqlParams = append(sqlParams, storeIDs)
}
if len(itemIDs) > 0 {
sql += " AND t1.item_id IN (" + dao.GenQuestionMarks(len(itemIDs)) + ")"
sqlParams = append(sqlParams, itemIDs)
}
if len(typeList) > 0 {
sql += " AND t1.type IN (" + dao.GenQuestionMarks(len(typeList)) + ")"
sqlParams = append(sqlParams, typeList)
}
if len(statusList) > 0 {
sql += " AND t1.status IN (" + dao.GenQuestionMarks(len(statusList)) + ")"
sqlParams = append(sqlParams, statusList)
}
sql += `
GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
ORDER BY 1 DESC
LIMIT ? OFFSET ?`
pageSize = jxutils.FormalizePageSize(pageSize)
sqlOffset := offset
sqlPageSize := pageSize
sqlParams = append(sqlParams, sqlPageSize, sqlOffset)
db := dao.GetDB()
// globals.SugarLogger.Debug(sql)
// globals.SugarLogger.Debug(utils.Format4Output(sqlParams, false))
var requestList []*StoreOpRequestInfo
dao.Begin(db)
defer dao.Commit(db)
if err = dao.GetRows(db, &requestList, sql, sqlParams...); err == nil {
return &model.PagedInfo{
TotalCount: dao.GetLastTotalRowCount(db),
Data: requestList,
}, nil
}
}
return nil, err
}
func getStoreOpRequestsInfo(reqIDs []int) (infoMap map[int]*StoreOpRequestInfo, err error) {
infoMap = make(map[int]*StoreOpRequestInfo)
if len(reqIDs) > 0 {
sql := `
SELECT DISTINCT t1.*, t4.name store_name, t2.prefix sku_name_prefix, t2.name sku_name_name, t3.unit_price
FROM store_op_request t1
JOIN sku_name t2 ON t2.id = t1.item_id
JOIN sku t5 ON t5.name_id = t1.item_id AND t5.deleted_at = ?
LEFT JOIN store_sku_bind t3 ON t3.store_id = t1.store_id AND t3.sku_id = t5.id AND t3.deleted_at = ?
JOIN store t4 ON t4.id = t1.store_id
WHERE t1.id IN (` + dao.GenQuestionMarks(len(reqIDs)) + ")"
sqlParams := []interface{}{
utils.DefaultTimeValue,
utils.DefaultTimeValue,
reqIDs,
}
db := dao.GetDB()
var infoList []*StoreOpRequestInfo
if err = dao.GetRows(db, &infoList, sql, sqlParams...); err == nil {
for _, v := range infoList {
infoMap[v.ID] = v
}
if len(reqIDs) > len(infoMap) {
missingReqIDs := []int{}
for _, reqID := range reqIDs {
if infoMap[reqID] == nil {
missingReqIDs = append(missingReqIDs, reqID)
}
}
err = fmt.Errorf("不能找到如下的请求ID:%s", utils.Format4Output(missingReqIDs, true))
}
}
} else {
err = errors.New("没有找到指定的请求ID")
}
return infoMap, err
}
func changeStoreOpStatus(ctx *jxcontext.Context, reqIDs []int, status int8, rejectReason string) (err error) {
globals.SugarLogger.Debugf("changeStoreOpStatus, reqIDs:%v", reqIDs)
if globals.EnablePendingChange {
if len(reqIDs) > 0 {
infoMap, err2 := getStoreOpRequestsInfo(reqIDs)
if err = err2; err != nil {
return err
}
db := dao.GetDB()
dao.Begin(db)
defer dao.Rollback(db)
for _, reqID := range reqIDs {
op := &model.StoreOpRequest{}
op.Remark = rejectReason
op.Status = status
if infoMap[reqID] != nil {
op.IntParam0 = infoMap[reqID].UnitPrice
}
dao.WrapUpdateULEntity(op, ctx.GetUserName())
op.DeletedAt = time.Now()
op.ID = reqID
// globals.SugarLogger.Debug(utils.Format4Output(op, false))
if _, err = dao.UpdateEntity(db, op, "IntParam0", "Remark", "Status", "DeletedAt", "LastOperator", "UpdatedAt"); err != nil {
return err
}
}
dao.Commit(db)
}
}
return err
}
func setStoreSkuBindStatus(skuBind *model.StoreSkuBind, status int8) {
skuBind.JdSyncStatus |= status
skuBind.EbaiSyncStatus |= status
skuBind.MtwmSyncStatus |= status
skuBind.WscSyncStatus |= status
}
func checkStoreExisting(db *dao.DaoDB, storeID int) (err error) {
store := &model.Store{}
store.ID = storeID
if err = dao.GetEntity(db, store); err != nil {
if err == orm.ErrNoRows {
return fmt.Errorf("门店:%d不存在", storeID)
}
return err
}
return nil
}
func RefreshStoresSkuByVendor(ctx *jxcontext.Context, storeIDs []int, vendorID int, isAsync bool) (hint string, err error) {
if vendorID != model.VendorIDJD {
return "", fmt.Errorf("此功能当前只支持京东到家平台")
}
db := dao.GetDB()
storeMapList, err := dao.GetStoresMapList(db, nil, storeIDs, model.StoreStatusAll, model.StoreIsSyncAll, "")
if err != nil {
return "", err
}
if len(storeMapList) != len(storeIDs) {
return "", fmt.Errorf("门店绑定信息不匹配,请确定门店绑定且只绑定了京东平台")
}
storeMap := make(map[int]*dao.StoreDetail)
for _, v := range storeMapList {
if v.VendorID != vendorID {
return "", fmt.Errorf("门店%d绑定的不是京东", v.StoreID)
}
storeMap[v.StoreID], err = dao.GetStoreDetail(db, v.StoreID, v.VendorID)
if err != nil {
return "", err
}
}
skuList, err := dao.GetSkus(db, nil, nil, nil, nil)
if err != nil {
return "", err
}
skuNameMap := make(map[int]*model.SkuName)
skuMap := make(map[int]*model.SkuAndName)
var bareStoreSkuList []*partner.StoreSkuInfo
for _, sku := range skuList {
if skuNameMap[sku.NameID] == nil {
skuNameMap[sku.NameID] = &model.SkuName{
Unit: sku.Unit,
}
}
skuMap[sku.ID] = sku
bareStoreSkuList = append(bareStoreSkuList, &partner.StoreSkuInfo{
SkuID: sku.ID,
VendorSkuID: utils.Int64ToStr(sku.JdID),
})
}
handler := partner.GetPurchasePlatformFromVendorID(vendorID).(partner.IPurchasePlatformStoreSkuHandler)
var storeSkuList []*model.StoreSkuBind
rootTask := tasksch.NewParallelTask(fmt.Sprintf("根据厂家门店商品信息相应刷新本地数据:%v", storeIDs), tasksch.NewParallelConfig().SetParallelCount(1), ctx,
func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) {
oneStoreMap := batchItemList[0].(*model.StoreMap)
subTask := tasksch.NewSeqTask(fmt.Sprintf("根据厂家门店商品信息相应刷新本地数据:%s", model.VendorChineseNames[oneStoreMap.VendorID]), ctx,
func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) {
switch step {
case 0:
bareStoreSkuList, err2 := handler.GetStoreSkusBareInfo(ctx, task, oneStoreMap.StoreID, oneStoreMap.VendorStoreID, bareStoreSkuList)
// globals.SugarLogger.Debug(utils.Format4Output(bareStoreSkuList, false))
if err = err2; err == nil || len(bareStoreSkuList) > 0 {
err = nil // todo 如果部分失败,强制忽略错误
for _, v := range bareStoreSkuList {
storeSkuList = append(storeSkuList, &model.StoreSkuBind{
StoreID: oneStoreMap.StoreID,
SkuID: v.SkuID,
Status: v.Status,
Price: int(v.VendorPrice),
})
}
}
case 1:
if len(storeSkuList) > 0 {
for _, v := range storeSkuList {
sku := skuMap[v.SkuID]
skuName := skuNameMap[sku.NameID]
if skuName.IsGlobal == 0 && (jxutils.IsSkuSpecial(sku.SpecQuality, sku.SpecUnit) || skuName.Unit != model.SpecialUnit) {
skuName.Price = v.Price // 标准价
skuName.IsGlobal = 1
}
}
for _, v := range storeSkuList {
sku := skuMap[v.SkuID]
skuName := skuNameMap[sku.NameID]
if skuName.IsGlobal == 0 {
if skuName.Price == 0 {
skuName.Price = jxutils.CaculateUnitPrice(v.Price, sku.SpecQuality, sku.SpecUnit, skuName.Unit)
} else {
skuName.Price = (skuName.Price + jxutils.CaculateUnitPrice(v.Price, sku.SpecQuality, sku.SpecUnit, skuName.Unit)) / 2
}
}
}
for _, v := range storeSkuList {
pricePercentage := jxutils.GetPricePercentageByVendorPrice(storeMap[v.StoreID].PricePercentagePackObj, v.Price, int(storeMap[v.StoreID].PricePercentage))
skuName := skuNameMap[skuMap[v.SkuID].NameID]
v.Price = jxutils.CaculateSkuPriceFromVendor(v.Price, pricePercentage)
v.UnitPrice = jxutils.CaculateSkuPriceFromVendor(skuName.Price, pricePercentage)
dao.WrapAddIDCULDEntity(v, ctx.GetUserName())
setStoreSkuBindStatus(v, model.SyncFlagNewMask)
v.JdSyncStatus = 0
}
}
case 2:
if len(storeSkuList) > 0 {
dao.Begin(db)
defer func() {
if r := recover(); r != nil || err != nil {
dao.Rollback(db)
if r != nil {
panic(r)
}
}
}()
if _, err = dao.ExecuteSQL(db, `
DELETE t1
FROM store_sku_bind t1
WHERE t1.store_id IN (
`+dao.GenQuestionMarks(len(storeIDs))+")", storeIDs); err == nil {
if err = dao.CreateMultiEntities(db, storeSkuList); err == nil {
hint = utils.Int2Str(len(storeSkuList))
dao.Commit(db)
}
}
}
}
return nil, err
}, 3)
tasksch.HandleTask(subTask, task, true).Run()
_, err = subTask.GetResult(0)
return retVal, err
}, storeMapList)
tasksch.ManageTask(rootTask).Run()
if isAsync {
hint = rootTask.GetID()
} else {
_, err = rootTask.GetResult(0)
}
return hint, err
}
func GetVendorStoreSkusInfo(ctx *jxcontext.Context, storeID int, vendorIDs, skuIDs []int, isContinueWhenError bool) (skuVendorMap map[int][]*partner.StoreSkuInfo, err error) {
globals.SugarLogger.Debugf("GetVendorStoreSkusInfo, storeID:%d, vendorIDs:%v, skuID:%v", storeID, vendorIDs, skuIDs)
db := dao.GetDB()
var locker sync.RWMutex
skuVendorMap = make(map[int][]*partner.StoreSkuInfo)
_, err = CurVendorSync.LoopStoresMap(ctx, db, fmt.Sprintf("GetVendorStoreSkusInfo storeID:%d", storeID), false, false, vendorIDs, []int{storeID},
func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (interface{}, error) {
loopMapInfo := batchItemList[0].(*LoopStoreMapInfo)
if handler, _ := partner.GetPurchasePlatformFromVendorID(loopMapInfo.VendorID).(partner.IPurchasePlatformStoreSkuHandler); handler != nil {
storeSkuList, err2 := dao.GetStoreSkus2(db, loopMapInfo.VendorID, storeID, skuIDs, false)
if err = err2; err == nil && len(storeSkuList) > 0 {
bareStoreSkuInfoList := make([]*partner.StoreSkuInfo, len(skuIDs))
for k, v := range storeSkuList {
bareStoreSkuInfoList[k] = &partner.StoreSkuInfo{
SkuID: v.SkuID,
VendorSkuID: v.VendorSkuID,
}
}
outBareStoreSkuInfoList, err2 := handler.GetStoreSkusBareInfo(ctx, t, loopMapInfo.StoreMapList[0].StoreID, loopMapInfo.StoreMapList[0].VendorStoreID, bareStoreSkuInfoList)
if err = err2; err == nil && outBareStoreSkuInfoList != nil {
locker.Lock()
defer locker.Unlock()
skuVendorMap[loopMapInfo.VendorID] = outBareStoreSkuInfoList
}
}
}
return nil, err
}, true)
if err != nil {
skuVendorMap = nil
}
return skuVendorMap, err
}
func SyncJdStoreProducts(ctx *jxcontext.Context, storeIDs, skuIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) {
db := dao.GetDB()
isManageIt := len(storeIDs) != 1 || len(skuIDs) == 0 || len(skuIDs) > 8
hint, err = CurVendorSync.LoopStoresMap(ctx, db, fmt.Sprintf("京东商家商品状态同步:%v", storeIDs), isAsync, isManageIt, []int{model.VendorIDJD}, storeIDs,
func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) {
loopMapInfo := batchItemList[0].(*LoopStoreMapInfo)
if handler := partner.GetPurchasePlatformFromVendorID(loopMapInfo.VendorID); handler != nil {
jdHandler := handler.(*jd.PurchaseHandler)
hint, err2 := jdHandler.SyncStoreProducts(ctx, t, loopMapInfo.StoreMapList[0].StoreID, skuIDs, false, isContinueWhenError)
if err = err2; err == nil {
retVal = []interface{}{hint}
}
}
return retVal, partner.AddVendorInfo2Err(err, loopMapInfo.VendorID)
}, isContinueWhenError)
return hint, err
}
func GetMissingStoreSkuFromOrder(ctx *jxcontext.Context, fromTime time.Time) (missingList []*StoreSkuBindInfo, err error) {
storeSkuList, err := dao.GetMissingStoreSkuFromOrder(dao.GetDB(), nil, fromTime)
if err == nil {
storeSkuNameMap := make(map[int64]*StoreSkuBindInfo)
for _, v := range storeSkuList {
skuName := storeSkuNameMap[jxutils.Combine2Int(v.StoreID, v.NameID)]
if skuName == nil {
skuName = &StoreSkuBindInfo{
StoreID: v.StoreID,
NameID: v.NameID,
IsFocus: 1,
IsSale: 1,
// 这里没有考虑平台价格比例
UnitPrice: jxutils.CaculateUnitPrice(v.RefPrice, v.SpecQuality, v.SpecUnit, v.Unit),
}
missingList = append(missingList, skuName)
}
skuName.Skus = append(skuName.Skus, &StoreSkuBindSkuInfo{
SkuID: v.SkuID,
})
}
}
return missingList, err
}
func AutoSaleStoreSku(ctx *jxcontext.Context, storeIDs []int, isNeedSync bool) (err error) {
db := dao.GetDB()
storeSkuList, err := dao.GetAutoSaleStoreSku(db, storeIDs)
if err != nil {
return err
}
storeSkuMap := make(map[int][]*model.StoreSkuBind)
for _, v := range storeSkuList {
storeSkuMap[v.StoreID] = append(storeSkuMap[v.StoreID], v)
}
now := time.Now()
for storeID, storeSkuList := range storeSkuMap {
var skuIDs []int
for _, storeSku := range storeSkuList {
if now.Sub(storeSku.AutoSaleAt) > 0 {
storeSku.AutoSaleAt = utils.DefaultTimeValue
if storeSku.Status != model.SkuStatusNormal {
storeSku.Status = model.SkuStatusNormal
skuIDs = append(skuIDs, storeSku.SkuID)
}
if _, err = dao.UpdateEntity(db, storeSku, "AutoSaleAt", model.FieldStatus); err != nil {
return err
}
}
}
if isNeedSync && len(skuIDs) > 0 {
if _, err = CurVendorSync.SyncStoresSkus(ctx, db, nil, []int{storeID}, skuIDs, false, true, true); err != nil {
return err
}
}
}
return err
}