diff --git a/business/jxcallback/orderman/financial.go b/business/jxcallback/orderman/financial.go index 1647af77a..42c0cb83e 100644 --- a/business/jxcallback/orderman/financial.go +++ b/business/jxcallback/orderman/financial.go @@ -102,7 +102,7 @@ func (c *OrderManager) SaveOrderFinancialInfo(order *model.OrderFinancial, opera } if err = dao.CreateEntity(db, sku); err != nil { if !dao.IsDuplicateError(err) { - globals.SugarLogger.Warnf("On SaveOrderSkuFinancialInfo order.VendorOrderID:%s err: order is err", order.VendorOrderID) + globals.SugarLogger.Warnf("On SaveOrderSkuFinancialInfo order.VendorOrderID:%s err:%v", order.VendorOrderID, err) return err } dao.Rollback(db) @@ -120,7 +120,7 @@ func (c *OrderManager) SaveOrderFinancialInfo(order *model.OrderFinancial, opera sku.JxShopMoney = order.JxShopMoney - sku.JxShopMoney if err = dao.CreateEntity(db, sku); err != nil { if !dao.IsDuplicateError(err) { - globals.SugarLogger.Warnf("On SaveOrderSkuFinancialInfo order.VendorOrderID:%s err: order is err", order.VendorOrderID) + globals.SugarLogger.Warnf("On SaveOrderSkuFinancialInfo order.VendorOrderID:%s err:%v", order.VendorOrderID, err) return err } dao.Rollback(db) diff --git a/business/jxcallback/orderman/order.go b/business/jxcallback/orderman/order.go index 44f11826d..fb9a9a441 100644 --- a/business/jxcallback/orderman/order.go +++ b/business/jxcallback/orderman/order.go @@ -2,7 +2,6 @@ package orderman import ( "fmt" - "math" "time" "git.rosy.net.cn/baseapi" @@ -11,7 +10,6 @@ import ( "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model/dao" - "git.rosy.net.cn/jx-callback/business/msghub" "git.rosy.net.cn/jx-callback/globals" "github.com/astaxie/beego/orm" ) @@ -51,7 +49,8 @@ func (c *OrderManager) LoadPendingOrders() []*model.GoodsOrder { // msgVendorStatus的意思是事件本身的类型,类似有时收到NewOrder事件去取,订单状态不一定就是New的 // OnOrderAdjust也类似,而OrderStatus要记录的是消息,所以添加这个 -func (c *OrderManager) OnOrderNew(order *model.GoodsOrder, msgVendorStatus string) (err error) { +func (c *OrderManager) OnOrderNew(order *model.GoodsOrder, orderStatus *model.OrderStatus) (err error) { + globals.SugarLogger.Debugf("OnOrderNew orderID:%s", order.VendorOrderID) if order.ConsigneeMobile2 == "" && !jxutils.IsMobileFake(order.ConsigneeMobile) { order.ConsigneeMobile2 = order.ConsigneeMobile } @@ -59,6 +58,7 @@ func (c *OrderManager) OnOrderNew(order *model.GoodsOrder, msgVendorStatus strin db := dao.GetDB() dao.Begin(db) defer func() { + globals.SugarLogger.Debugf("OnOrderNew exit orderID:%s", order.VendorOrderID) if r := recover(); r != nil { dao.Rollback(db) panic(r) @@ -67,12 +67,7 @@ func (c *OrderManager) OnOrderNew(order *model.GoodsOrder, msgVendorStatus strin if order.Status == model.OrderStatusUnknown { order.Status = model.OrderStatusNew } - status := model.Order2Status(order) - if status.Status > model.OrderStatusNew { - status.Status = model.OrderStatusNew - } - status.VendorStatus = msgVendorStatus - isDuplicated, err := addOrderOrWaybillStatus(status, db) + isDuplicated, err := addOrderOrWaybillStatus(orderStatus, db) if err == nil && !isDuplicated { isDuplicated, err = c.SaveOrder(order, false, db) } @@ -88,7 +83,7 @@ func (c *OrderManager) OnOrderNew(order *model.GoodsOrder, msgVendorStatus strin } // todo 调整单的处理可能还需要再细化一点,当前只是简单的删除重建 -func (c *OrderManager) OnOrderAdjust(order *model.GoodsOrder, msgVendorStatus string) (err error) { +func (c *OrderManager) OnOrderAdjust(order *model.GoodsOrder, orderStatus *model.OrderStatus) (err error) { if order.ConsigneeMobile2 == "" && !jxutils.IsMobileFake(order.ConsigneeMobile) { order.ConsigneeMobile2 = order.ConsigneeMobile } @@ -101,13 +96,12 @@ func (c *OrderManager) OnOrderAdjust(order *model.GoodsOrder, msgVendorStatus st panic(r) } }() - if order.Status == model.OrderStatusUnknown { + // 出现过调整单后,状态回到新订单状态,比如:911350836000622 + // 不完全确定,加一个处理 + if order.Status < model.OrderStatusAccepted { order.Status = model.OrderStatusAccepted } - status := model.Order2Status(order) - status.Status = model.OrderStatusAdjust - status.VendorStatus = msgVendorStatus - isDuplicated, err := addOrderOrWaybillStatus(status, db) + isDuplicated, err := addOrderOrWaybillStatus(orderStatus, db) if err == nil && !isDuplicated { err = utils.CallFuncLogError(func() error { _, err = db.Db.Raw("DELETE FROM order_sku WHERE vendor_order_id = ? AND vendor_id = ?", order.VendorOrderID, order.VendorID).Exec() @@ -128,10 +122,9 @@ func (c *OrderManager) OnOrderAdjust(order *model.GoodsOrder, msgVendorStatus st if err == nil { dao.Commit(db) if !isDuplicated { - msghub.OnNewOrder(order) // 因为订单调度器需要的是真实状态,所以用order的状态 _ = scheduler.CurrentScheduler.OnOrderNew(order, false) - _ = scheduler.CurrentScheduler.OnOrderStatusChanged(order, model.Order2Status(order), false) + _ = scheduler.CurrentScheduler.OnOrderStatusChanged(order, orderStatus, false) } } else { dao.Rollback(db) @@ -171,7 +164,7 @@ func (c *OrderManager) OnOrderMsg(order *model.GoodsOrder, vendorStatus, remark VendorStatus: vendorStatus, Status: model.OrderStatusMsg, StatusTime: time.Now(), - Remark: remark, + Remark: utils.LimitUTF8StringLen(remark, 255), }, nil) return err } @@ -207,11 +200,12 @@ func (c *OrderManager) SaveOrder(order *model.GoodsOrder, isAdjust bool, db *dao order.Status = orderStatus.Status order.VendorStatus = orderStatus.VendorStatus order.StatusTime = orderStatus.StatusTime + + jxutils.RefreshOrderSkuRelated(order) } } } - order.OrderCreatedAt = order.StatusTime // globals.SugarLogger.Debugf("saveOrder isAdjust:%t, order:%v", isAdjust, order) created, _, err2 := db.Db.ReadOrCreate(order, "VendorOrderID", "VendorID") if err = err2; err == nil { @@ -223,22 +217,8 @@ func (c *OrderManager) SaveOrder(order *model.GoodsOrder, isAdjust bool, db *dao } if _, _, err = db.Db.ReadOrCreate(originalOrder, "VendorOrderID", "VendorID"); err == nil { if created { - sql := `INSERT INTO order_sku(vendor_order_id, vendor_id, count, vendor_sku_id, sku_id, jx_sku_id, sku_name, - shop_price, sale_price, weight, sku_type, promotion_type, order_created_at) VALUES` - params := []interface{}{} - for _, sku := range order.Skus { - sql += "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)," - // 有时不是通过京西平台建立的SKU,不范围要超过 - skuID := 0 - if sku.SkuID < math.MaxInt32 { - skuID = sku.SkuID - } - params = append(params, sku.VendorOrderID, sku.VendorID, sku.Count, sku.VendorSkuID, skuID, sku.JxSkuID, sku.SkuName, - sku.ShopPrice, sku.SalePrice, sku.Weight, sku.SkuType, sku.PromotionType, order.OrderCreatedAt) - } - sql = sql[:len(sql)-1] + ";" - if _, err = db.Db.Raw(sql, params...).Exec(); err != nil { - baseapi.SugarLogger.Warnf("saveOrder insert order:%v, order_sku error:%v", order, err) + if err = dao.CreateMultiEntities(db, order.Skus); err != nil { + baseapi.SugarLogger.Warnf("saveOrder orderID:%s, save order_sku failed with error:%v", order.VendorOrderID, err) } } else { isDuplicated = true @@ -256,12 +236,35 @@ func (c *OrderManager) SaveOrder(order *model.GoodsOrder, isAdjust bool, db *dao return isDuplicated, err } +func getPromotionSkuPriceMap(db *dao.DaoDB, storeID int, skuIDs []int) (skuPriceMap map[int64]*model.PromotionSku, err error) { + sql := ` + SELECT t3.* + FROM promotion t1 + JOIN promotion_store t2 ON t2.promotion_id = t1.id AND t2.store_id = ? + JOIN promotion_sku t3 ON t3.promotion_id = t1.id AND t3.sku_id IN (` + dao.GenQuestionMarks(len(skuIDs)) + `) AND t3.earning_price > 0 + WHERE t1.deleted_at = ? AND (t1.status = ? OR t1.status = ?) AND (t1.begin_at <= NOW() AND t1.end_at >= NOW()) AND t1.vendor_id = ?` + var skuPriceList []*model.PromotionSku + if err = dao.GetRows(db, &skuPriceList, sql, storeID, skuIDs, utils.DefaultTimeValue, model.PromotionStatusLocalCreated, model.PromotionStatusRemoteCreated, model.VendorIDJX); err != nil { + return nil, err + } + skuPriceMap = make(map[int64]*model.PromotionSku) + for _, v := range skuPriceList { + if v.EarningPrice > 0 { + index := jxutils.Combine2Int(v.SkuID, v.Price) + if skuPriceMap[index] == nil || v.EarningPrice < skuPriceMap[index].EarningPrice { + skuPriceMap[index] = v + } + } + } + return skuPriceMap, err +} + func (c *OrderManager) updateOrderSkuOtherInfo(order *model.GoodsOrder, db *dao.DaoDB) (err error) { globals.SugarLogger.Debugf("updateOrderSkuOtherInfo orderID:%s, VendorStoreID:%s", order.VendorOrderID, order.VendorStoreID) jxStoreID := jxutils.GetShowStoreIDFromOrder(order) var opNumStr string if time.Now().Sub(order.OrderCreatedAt) < 48*time.Hour && order.VendorID != model.VendorIDJD { - opNumStr = "" + opNumStr = "2" } else { opNumStr = "2" } @@ -273,11 +276,16 @@ func (c *OrderManager) updateOrderSkuOtherInfo(order *model.GoodsOrder, db *dao. orderSkus := order.Skus vendorSkuIDs := make([]int64, 0) + skuIDMap := make(map[int]int) for _, v := range orderSkus { intVendorSkuID := utils.Str2Int64WithDefault(v.VendorSkuID, 0) if intVendorSkuID != 0 { vendorSkuIDs = append(vendorSkuIDs, intVendorSkuID) } + + if skuID := jxutils.GetSkuIDFromOrderSku(v); skuID > 0 { + skuIDMap[skuID] = 1 + } } if len(vendorSkuIDs) > 0 { tableName := "t2" @@ -301,7 +309,16 @@ func (c *OrderManager) updateOrderSkuOtherInfo(order *model.GoodsOrder, db *dao. skumapper[v.VendorSkuID] = v } + skuPriceMap, err2 := getPromotionSkuPriceMap(db, jxStoreID, jxutils.IntMap2List(skuIDMap)) + if err = err2; err != nil { + globals.SugarLogger.Errorf("updateOrderSkuOtherInfo can not get sku promotion info for orderID:%s, error:%v", order.VendorOrderID, err) + return err + } + for _, v := range orderSkus { + v.VendorOrderID = order.VendorOrderID + v.VendorID = order.VendorID + intVendorSkuID := utils.Str2Int64WithDefault(v.VendorSkuID, 0) if intVendorSkuID != 0 && v.VendorSkuID != "-70000" { // todo hard code skuBindInfo := skumapper[intVendorSkuID] @@ -317,6 +334,13 @@ func (c *OrderManager) updateOrderSkuOtherInfo(order *model.GoodsOrder, db *dao. } } } + + if skuID := jxutils.GetSkuIDFromOrderSku(v); skuID > 0 && v.StoreSubName != "" { + index := jxutils.Combine2Int(jxStoreID, int(v.SalePrice)) + if skuPriceMap[index] != nil { + v.EarningPrice = int64(skuPriceMap[index].EarningPrice) + } + } } } return nil @@ -336,11 +360,7 @@ func (c *OrderManager) updateOrderOtherInfo(order *model.GoodsOrder, db *dao.Dao } order.JxStoreID = storeMap.StoreID if err = c.updateOrderSkuOtherInfo(order, db); err == nil { - if order.Weight == 0 { - for _, v := range order.Skus { - order.Weight += v.Weight - } - } + jxutils.RefreshOrderSkuRelated(order) } return err } @@ -369,20 +389,21 @@ func (c *OrderManager) addOrderStatus(orderStatus *model.OrderStatus, db *dao.Da updateFields = append(updateFields, "Status", "StatusTime") if order.LockStatus != model.OrderStatusUnknown { order.LockStatus = model.OrderStatusUnknown - order.LockStatusTime = orderStatus.StatusTime - updateFields = append(updateFields, "LockStatus", "LockStatusTime") + updateFields = append(updateFields, "LockStatus") } } else { if model.IsOrderUnlockStatus(orderStatus.Status) { order.LockStatus = model.OrderStatusUnknown - } else { + updateFields = append(updateFields, "LockStatus") + } else if !model.IsOrderFinalStatus(orderStatus.Status) { if order.LockStatus != model.OrderStatusUnknown { globals.SugarLogger.Warnf("addOrderStatus refOrderID:%s, orderID:%s, order.LockStatus:%d, status.LockStatus:%d", orderStatus.RefVendorOrderID, orderStatus.VendorOrderID, order.LockStatus, orderStatus.Status) } + order.Flag &= ^model.OrderFlagMaskUserApplyCancel order.LockStatus = orderStatus.Status + order.LockStatusTime = orderStatus.StatusTime + updateFields = append(updateFields, "LockStatus", "LockStatusTime", "Flag") } - order.LockStatusTime = orderStatus.StatusTime - updateFields = append(updateFields, "LockStatus", "LockStatusTime") } if model.IsOrderFinalStatus(orderStatus.Status) { order.OrderFinishedAt = orderStatus.StatusTime @@ -430,6 +451,7 @@ func (c *OrderManager) loadOrder(vendorOrderID, vendorOrderID2 string, vendorID }, "LoadOrder orderID:%s", vendorOrderID) } if err != nil { + order = nil if err == orm.ErrNoRows { err = ErrCanNotFindOrder } @@ -475,11 +497,12 @@ func (c *OrderManager) loadOrderFinancial(vendorOrderID, vendorOrderID2 string, if err = db.Read(order, keyFields...); err == nil { vendorOrderID = order.VendorOrderID err = utils.CallFuncLogError(func() error { - _, err = db.QueryTable("order_sku_financial").Filter("vendor_order_id", vendorOrderID).Filter("vendor_id", vendorID).All(&order.Skus) + _, err = db.QueryTable("order_sku_financial").Filter("vendor_order_id", vendorOrderID).Filter("vendor_id", vendorID).Filter("is_afs_order", 0).All(&order.Skus) return err }, "LoadOrder orderID:%s", vendorOrderID) } if err != nil { + order = nil if err == orm.ErrNoRows { err = ErrCanNotFindOrder } @@ -488,30 +511,15 @@ func (c *OrderManager) loadOrderFinancial(vendorOrderID, vendorOrderID2 string, return order, err } -func (c *OrderManager) UpdateOrderStatusAndFlag(order *model.GoodsOrder) (err error) { - db := orm.NewOrm() - utils.CallFuncLogError(func() error { - _, err = db.Update(order, "Status", "DeliveryFlag") - return err - }, "UpdateOrderStatusAndFlag orderID:%s failed with error:%v", order.VendorOrderID, err) - return err +func (c *OrderManager) UpdateOrderStatusAndDeliveryFlag(order *model.GoodsOrder) (err error) { + return c.UpdateOrderFields(order, []string{"Status", "DeliveryFlag"}) } -//Waybill -func (c *OrderManager) UpdateWaybillVendorID(bill *model.Waybill, revertStatus bool) (err error) { - globals.SugarLogger.Debugf("UpdateWaybillVendorID bill:%v", bill) +func (c *OrderManager) UpdateOrderFields(order *model.GoodsOrder, fieldList []string) (err error) { db := orm.NewOrm() - params := orm.Params{ - "vendor_waybill_id": bill.VendorWaybillID, - "waybill_vendor_id": bill.WaybillVendorID, - } - // 如果运单被取消,则要保持在已拣货状态 - if revertStatus && bill.WaybillVendorID == model.VendorIDUnknown { - params["status"] = model.OrderStatusFinishedPickup - } utils.CallFuncLogError(func() error { - _, err = db.QueryTable("goods_order").Filter("vendor_order_id", bill.VendorOrderID).Filter("vendor_id", bill.OrderVendorID).Update(params) + _, err = db.Update(order, fieldList...) return err - }, "UpdateWaybillVendorID update order, bill:%v", bill) + }, "UpdateOrderFields orderID:%s failed with error:%v", order.VendorOrderID, err) return err } diff --git a/business/jxcallback/orderman/order_afs.go b/business/jxcallback/orderman/order_afs.go new file mode 100644 index 000000000..08e6ea78f --- /dev/null +++ b/business/jxcallback/orderman/order_afs.go @@ -0,0 +1,351 @@ +package orderman + +import ( + "fmt" + "strings" + + "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-callback/business/jxcallback/scheduler" + "git.rosy.net.cn/jx-callback/business/jxutils" + "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" +) + +func (c *OrderManager) LoadAfsOrder(vendorAfsOrderID string, vendorID int) (afsOrder *model.AfsOrder, err error) { + return c.loadAfsOrder(dao.GetDB(), vendorAfsOrderID, vendorID) +} + +func (c *OrderManager) loadAfsOrder(db *dao.DaoDB, vendorAfsOrderID string, vendorID int) (afsOrder *model.AfsOrder, err error) { + afsOrder = &model.AfsOrder{ + AfsOrderID: vendorAfsOrderID, + VendorID: vendorID, + } + if err = dao.GetEntity(db, afsOrder, "AfsOrderID", "VendorID"); err != nil { + afsOrder = nil + } + return afsOrder, err +} + +func (c *OrderManager) OnAfsOrderAdjust(afsOrder *model.AfsOrder, orderStatus *model.OrderStatus) (err error) { + return c.onAfsOrderNew(afsOrder, orderStatus, true) +} + +func (c *OrderManager) OnAfsOrderNew(afsOrder *model.AfsOrder, orderStatus *model.OrderStatus) (err error) { + return c.onAfsOrderNew(afsOrder, orderStatus, false) +} + +func (c *OrderManager) onAfsOrderNew(afsOrder *model.AfsOrder, orderStatus *model.OrderStatus, isAdjust bool) (err error) { + db := dao.GetDB() + // globals.SugarLogger.Debugf("onAfsOrderNew1 afsOrder:%s", utils.Format4Output(afsOrder, true)) + c.setAfsOrderID(db, orderStatus) + if afsOrder.AfsOrderID == "" { + afsOrder.AfsOrderID = orderStatus.VendorOrderID + } + if afsOrder.VendorStatus == "" { + afsOrder.VendorStatus = orderStatus.VendorStatus + } + if afsOrder.Status == model.OrderStatusUnknown { + afsOrder.Status = orderStatus.Status + } + // globals.SugarLogger.Debugf("onAfsOrderNew2 afsOrder:%s", utils.Format4Output(afsOrder, true)) + dao.Begin(db) + defer func() { + if r := recover(); r != nil || err != nil { + dao.Rollback(db) + panic(r) + } + }() + isDuplicated, err := addOrderOrWaybillStatus(orderStatus, db) + globals.SugarLogger.Debugf("onAfsOrderNew afsOrderID:%s, isDuplicated:%t", afsOrder.AfsOrderID, isDuplicated) + if err != nil || isDuplicated { + if err == nil { + dao.Commit(db) + } + return err + } + var existAfsOrder *model.AfsOrder + if existAfsOrder, err = c.loadAfsOrder(db, afsOrder.AfsOrderID, afsOrder.VendorID); err != nil { + if !dao.IsNoRowsError(err) { + return err + } + } + if existAfsOrder != nil { + existAfsOrder.Status = afsOrder.Status + existAfsOrder.VendorStatus = afsOrder.VendorStatus + if _, err = dao.UpdateEntity(db, existAfsOrder, "Status", "VendorStatus"); err != nil { + return err + } + } else { + // 外退都要先全删除再建 + if afsOrder.RefundType == model.AfsTypeFullRefund { + isAdjust = true + } + if err = c.SaveAfsOrder(db, afsOrder, isAdjust); err != nil { + return err + } + } + dao.Commit(db) + scheduler.CurrentScheduler.OnAfsOrderNew(afsOrder, false) + return err +} + +func (c *OrderManager) SaveAfsOrder(db *dao.DaoDB, afsOrder *model.AfsOrder, isDeleteFirst bool) (err error) { + globals.SugarLogger.Debug(afsOrder.AfsOrderID) + if db == nil { + db = dao.GetDB() + } + if err = c.updateAfsOrderOtherInfo(db, afsOrder); err != nil { + return err + } + dao.Begin(db) + defer func() { + if r := recover(); r != nil || err != nil { + dao.Rollback(db) + if r != nil { + panic(r) + } + } + }() + if isDeleteFirst { + err = utils.CallFuncLogError(func() error { + _, err = dao.DeleteEntity(db, afsOrder, "VendorOrderID", "VendorID") + return err + }, "SaveAfsOrder delete AfsOrder, afsOrderID:%s", afsOrder.AfsOrderID) + if err != nil { + return err + } + err = utils.CallFuncLogError(func() error { + _, err = dao.DeleteEntity(db, &model.OrderSkuFinancial{ + VendorOrderID: afsOrder.VendorOrderID, + VendorID: afsOrder.VendorID, + IsAfsOrder: 1, + }, "VendorOrderID", "VendorID", "IsAfsOrder") + return err + }, "SaveAfsOrder delete OrderSkuFinancial, afsOrderID:%s", afsOrder.AfsOrderID) + if err != nil { + return err + } + } + // 平台结算扣除汇总--平台补贴,售后产生运费,平台收包装费,同城运费、、、 + deductionsByPm := afsOrder.PmSubsidyMoney + afsOrder.AfsFreightMoney + afsOrder.BoxMoney + afsOrder.TongchengFreightMoney + afsOrder.RefundMoneyByCal = afsOrder.SkuUserMoney + afsOrder.FreightUserMoney + deductionsByPm - afsOrder.PmRefundMoney + // order.TotalMoney += order.SkuJxMoney // 退款单京西补贴部分先不作计算 + if err = dao.CreateEntity(db, afsOrder); err != nil { + globals.SugarLogger.Warnf("On SaveAfsOrder afsOrder.AfsOrderID:%s err:%v", afsOrder.AfsOrderID, err) + return err + } + + // 京西结算扣除汇总,先不作计算,计算单条sku最终扣款金额(+该条sku承担的平台结算扣除金额) + for _, orderSku := range afsOrder.Skus[1:] { + orderSku.RefundMoneyByCal = orderSku.PmSkuSubsidyMoney + + utils.Float64TwoInt64(float64(afsOrder.RefundMoneyByCal-afsOrder.PmSkuSubsidyMoney)*float64(orderSku.UserMoney+orderSku.PmSubsidyMoney-orderSku.PmSkuSubsidyMoney)/float64(afsOrder.SkuUserMoney+afsOrder.PmSubsidyMoney-afsOrder.PmSkuSubsidyMoney)) + afsOrder.Skus[0].RefundMoneyByCal += orderSku.RefundMoneyByCal + if err = dao.CreateEntity(db, orderSku); err != nil { + globals.SugarLogger.Warnf("On SaveAfsOrder afsOrder.AfsOrderID:%s err:%v, orderSku:%s", afsOrder.AfsOrderID, err, utils.Format4Output(orderSku, true)) + return err + } + } + if len(afsOrder.Skus) > 0 { + orderSku := afsOrder.Skus[0] + orderSku.RefundMoneyByCal = afsOrder.RefundMoneyByCal - orderSku.RefundMoneyByCal + if err = dao.CreateEntity(db, orderSku); err != nil { + globals.SugarLogger.Warnf("On SaveAfsOrder afsOrder.AfsOrderID:%s err:%v, orderSku:%s", afsOrder.AfsOrderID, err, utils.Format4Output(orderSku, true)) + return err + } + } else { + globals.SugarLogger.Warnf("On SaveAfsOrder afsOrder.AfsOrderID:%s err: afsOrder have no sku", afsOrder.AfsOrderID) + } + dao.Commit(db) + return err +} + +func (c *OrderManager) OnAfsOrderStatusChanged(orderStatus *model.OrderStatus) (err error) { + db := dao.GetDB() + c.setAfsOrderID(db, orderStatus) + dao.Begin(db) + defer func() { + if r := recover(); r != nil { + dao.Rollback(db) + panic(r) + } + }() + isDuplicated, afsOrder, err := c.addAfsOrderStatus(db, orderStatus) + if err != nil || isDuplicated { + if err == nil { + dao.Commit(db) + } else { + dao.Rollback(db) + } + return err + } + dao.Commit(db) + scheduler.CurrentScheduler.OnAfsOrderStatusChanged(afsOrder, orderStatus, false) + return err +} + +func (c *OrderManager) addAfsOrderStatus(db *dao.DaoDB, orderStatus *model.OrderStatus) (isDuplicated bool, order *model.AfsOrder, err error) { + globals.SugarLogger.Debugf("addAfsOrderStatus refOrderID:%s, orderID:%s", orderStatus.RefVendorOrderID, orderStatus.VendorOrderID) + if db == nil { + db = dao.GetDB() + } + isDuplicated, err = addOrderOrWaybillStatus(orderStatus, db) + if err == nil && !isDuplicated && (orderStatus.Status != model.OrderStatusUnknown && orderStatus.Status != model.OrderStatusMsg) { + order = &model.AfsOrder{ + AfsOrderID: orderStatus.VendorOrderID, + VendorID: orderStatus.VendorID, + } + if err = db.Db.ReadForUpdate(order, "AfsOrderID", "VendorID"); err == nil { + if orderStatus.Status > model.OrderStatusUnknown { // todo 要求status不能回绕 + order.VendorStatus = orderStatus.VendorStatus + order.Status = orderStatus.Status + updateFields := []string{ + "VendorStatus", + "Status", + } + if model.IsAfsOrderFinalStatus(orderStatus.Status) { + order.AfsFinishedAt = orderStatus.StatusTime + updateFields = append(updateFields, "AfsFinishedAt") + } + utils.CallFuncLogError(func() error { + _, err = dao.UpdateEntity(db, order, updateFields...) + return err + }, "addAfsOrderStatus update orderID:%s, status:%v", order.VendorOrderID, orderStatus) + } else { + isDuplicated = true + } + } else { + if dao.IsNoRowsError(err) { // todo 消息错序 + err = nil + } else { + globals.SugarLogger.Warnf("addAfsOrderStatus orderID:%s read failed with error:%v", order.VendorOrderID, err) + } + } + } + return isDuplicated, order, err +} + +func (c *OrderManager) updateAfsOrderSkuOtherInfo(db *dao.DaoDB, order *model.AfsOrder) (err error) { + globals.SugarLogger.Debugf("updateAfsOrderSkuOtherInfo orderID:%s, VendorStoreID:%s", order.VendorOrderID, order.VendorStoreID) + jxStoreID := jxutils.GetSaleStoreIDFromAfsOrder(order) + opNumStr := "2" + if jxStoreID == 0 { + globals.SugarLogger.Infof("updateAfsOrderSkuOtherInfo [运营%s]订单在京西与平台都找不到京西门店信息orderID:%s, VendorStoreID:%s", opNumStr, order.VendorOrderID, order.VendorStoreID) + return nil + } + orderSkus := order.Skus + vendorSkuIDs := make([]int64, 0) + for _, v := range orderSkus { + intVendorSkuID := utils.Str2Int64WithDefault(v.VendorSkuID, 0) + if intVendorSkuID != 0 { + vendorSkuIDs = append(vendorSkuIDs, intVendorSkuID) + } + } + if len(vendorSkuIDs) > 0 { + tableName := "t2" + if model.MultiStoresVendorMap[order.VendorID] == 1 { + tableName = "t1" + } + fieldPrefix := dao.ConvertDBFieldPrefix(model.VendorNames[order.VendorID]) + sql := ` + SELECT %s.%s_id vendor_sku_id, t1.id sku_id, t2.price, t1.weight + FROM sku t1 + LEFT JOIN store_sku_bind t2 ON t1.id = t2.sku_id AND t2.deleted_at = ? AND t2.store_id = ? + WHERE t1.deleted_at = ? AND %s.%s_id IN (-1, ` + dao.GenQuestionMarks(len(vendorSkuIDs)) + ")" + sql = fmt.Sprintf(sql, tableName, fieldPrefix, tableName, fieldPrefix) + var skuInfos []*tStoreSkuBindAndVendorSkuID + if err = dao.GetRows(db, &skuInfos, sql, utils.DefaultTimeValue, jxStoreID, utils.DefaultTimeValue, vendorSkuIDs); err != nil { + globals.SugarLogger.Errorf("updateAfsOrderSkuOtherInfo can not get sku info for orderID:%s, error:%v", order.VendorOrderID, err) + return err + } + skumapper := make(map[int64]*tStoreSkuBindAndVendorSkuID) + for _, v := range skuInfos { + skumapper[v.VendorSkuID] = v + } + for _, v := range orderSkus { + v.AfsOrderID = order.AfsOrderID + v.VendorID = order.VendorID + v.VendorOrderID = order.VendorOrderID + v.IsAfsOrder = 1 + v.VendorStoreID = order.VendorStoreID + v.StoreID = order.StoreID + v.JxStoreID = jxStoreID + + intVendorSkuID := utils.Str2Int64WithDefault(v.VendorSkuID, 0) + if intVendorSkuID != 0 && v.VendorSkuID != "-70000" { // todo hard code + skuBindInfo := skumapper[intVendorSkuID] + if skuBindInfo == nil { + globals.SugarLogger.Infof("updateAfsOrderSkuOtherInfo [运营%s]%s订单sku找不到门店价格(或商品映射),orderID:%s, StoreID:%d, VendorSkuID:%s, sku:%v", opNumStr, model.VendorChineseNames[order.VendorID], order.VendorOrderID, jxStoreID, v.VendorSkuID, v) + } else { + v.JxSkuID = skuBindInfo.SkuID + } + } + } + } + return nil +} + +func (c *OrderManager) updateAfsOrderOtherInfo(db *dao.DaoDB, afsOrder *model.AfsOrder) (err error) { + globals.SugarLogger.Debugf("updateAfsOrderOtherInfo orderID:%s, VendorStoreID:%s", afsOrder.VendorOrderID, afsOrder.VendorStoreID) + if afsOrder.VendorStoreID != "" { + if storeDetail, err := dao.GetStoreDetailByVendorStoreID(db, afsOrder.VendorStoreID, 0); err == nil { + afsOrder.JxStoreID = storeDetail.Store.ID + } + } + if afsOrder.StoreID == 0 && afsOrder.JxStoreID == 0 { + if order, err2 := c.LoadOrder(afsOrder.VendorOrderID, afsOrder.VendorID); err2 == nil { + afsOrder.JxStoreID = order.JxStoreID + if afsOrder.StoreID == 0 { + afsOrder.StoreID = order.StoreID + } + if afsOrder.VendorStoreID == "" { + afsOrder.VendorStoreID = order.VendorStoreID + } + } + } + if err == nil { + if err = c.updateAfsOrderSkuOtherInfo(db, afsOrder); err == nil { + jxutils.RefreshAfsOrderSkuRelated(afsOrder) + } + } + return err +} + +func (c *OrderManager) UpdateAfsOrderFields(afsOrder *model.AfsOrder, fieldList []string) (err error) { + db := orm.NewOrm() + utils.CallFuncLogError(func() error { + _, err = db.Update(afsOrder, fieldList...) + return err + }, "UpdateAfsOrderFields orderID:%s failed with error:%v", afsOrder.VendorOrderID, err) + return err +} + +func (c *OrderManager) setAfsOrderID(db *dao.DaoDB, orderStatus *model.OrderStatus) { + // globals.SugarLogger.Debugf("setAfsOrderID1 orderStatus:%v", utils.Format4Output(orderStatus, true)) + if dao.IsVendorThingIDEmpty(orderStatus.VendorOrderID) { + index := 1 + if afsOrderList, err2 := dao.GetAfsOrders(db, orderStatus.RefVendorID, orderStatus.RefVendorOrderID, ""); err2 == nil { + if len(afsOrderList) > 0 { + list := strings.Split(afsOrderList[0].AfsOrderID, "-") + if len(list) > 1 { + index = int(utils.Str2Int64WithDefault(list[1], 0)) + if afsOrderList[0].Status >= model.AfsOrderStatusFinished { + index++ + } + } + } + } else { + globals.SugarLogger.Warnf("setAfsOrderID err2:%v", err2) + } + orderStatus.VendorOrderID = composeAfsOrderID(orderStatus.RefVendorOrderID, index) + } + // globals.SugarLogger.Debugf("setAfsOrderID2 orderStatus:%v", utils.Format4Output(orderStatus, true)) +} + +func composeAfsOrderID(vendorOrderID string, index int) (afsOrderID string) { + return strings.Join([]string{ + vendorOrderID, + utils.Int2Str(index), + }, "-") +} diff --git a/business/jxcallback/orderman/oder_comment.go b/business/jxcallback/orderman/order_comment.go similarity index 95% rename from business/jxcallback/orderman/oder_comment.go rename to business/jxcallback/orderman/order_comment.go index a180e95cd..8dab8c5ab 100644 --- a/business/jxcallback/orderman/oder_comment.go +++ b/business/jxcallback/orderman/order_comment.go @@ -102,12 +102,16 @@ func (c *OrderManager) OnOrderComments(orderCommentList []*model.OrderComment) ( comment2.Maxmodifytime = int(orderComment.ModifyDuration) if orderComment.VendorID != model.VendorIDELM { var order *model.GoodsOrder - if orderComment.VendorID != model.VendorIDEBAI { - order, err = partner.CurOrderManager.LoadOrder(orderComment.VendorOrderID, orderComment.VendorID) + if true /*orderComment.VendorID != model.VendorIDEBAI*/ { + order, _ = partner.CurOrderManager.LoadOrder(orderComment.VendorOrderID, orderComment.VendorID) } if order != nil { orderComment.StoreID = jxutils.GetSaleStoreIDFromOrder(order) - orderComment.ConsigneeMobile = order.ConsigneeMobile + if order.ConsigneeMobile2 != "" { + orderComment.ConsigneeMobile = order.ConsigneeMobile2 + } else { + orderComment.ConsigneeMobile = order.ConsigneeMobile + } } if orderComment.StoreID > 0 { comment2.Jxstoreid = utils.Int2Str(orderComment.StoreID) diff --git a/business/jxcallback/orderman/orderman.go b/business/jxcallback/orderman/orderman.go index f1b406cdc..1cabb54f1 100644 --- a/business/jxcallback/orderman/orderman.go +++ b/business/jxcallback/orderman/orderman.go @@ -65,8 +65,10 @@ func init() { func addOrderOrWaybillStatus(status *model.OrderStatus, db *dao.DaoDB) (isDuplicated bool, err error) { if status.OrderType == model.OrderTypeOrder { globals.SugarLogger.Debugf("addOrderStatus order:%v", status) - } else { + } else if status.OrderType == model.OrderTypeWaybill { globals.SugarLogger.Debugf("addOrderStatus waybill:%v", status) + } else { + globals.SugarLogger.Debugf("addOrderStatus afsOrder:%v", status) } dao.Begin(db) defer func() { @@ -100,6 +102,17 @@ func addOrderOrWaybillStatus(status *model.OrderStatus, db *dao.DaoDB) (isDuplic return isDuplicated, err } +func (c *OrderManager) GetStatusDuplicatedCount(status *model.OrderStatus) (duplicatedCount int) { + if status == nil { + return 0 + } + db := dao.GetDB() + if err := dao.GetEntity(db, status, "VendorOrderID", "VendorID", "OrderType", "VendorStatus", "StatusTime"); err == nil { + return status.DuplicatedCount + } + return 0 +} + // todo 最好还是改成全事件回放算了 func LoadPendingOrders() { orders := FixedOrderManager.LoadPendingOrders() @@ -140,17 +153,17 @@ func LoadPendingOrders() { if order, ok := item.(*model.GoodsOrder); ok { jxutils.CallMsgHandlerAsync(func() { scheduler.CurrentScheduler.OnOrderNew(order, true) - }, order.VendorOrderID) + }, jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID)) } else if status, ok := item.(*model.OrderStatus); ok { jxutils.CallMsgHandlerAsync(func() { order := orderMap[jxutils.ComposeUniversalOrderID(status.VendorOrderID, status.VendorID)] scheduler.CurrentScheduler.OnOrderStatusChanged(order, status, true) - }, status.VendorOrderID) + }, jxutils.ComposeUniversalOrderID(status.RefVendorOrderID, status.RefVendorID)) } else { bill := item.(*model.Waybill) jxutils.CallMsgHandlerAsync(func() { scheduler.CurrentScheduler.OnWaybillStatusChanged(bill, true) - }, bill.VendorOrderID) + }, jxutils.ComposeUniversalOrderID(bill.VendorOrderID, bill.OrderVendorID)) } curTime := time.Now() timeout := sleepGap - curTime.Sub(lastTime) diff --git a/business/jxcallback/orderman/orderman_ext.go b/business/jxcallback/orderman/orderman_ext.go index 4499a5811..8eca3932f 100644 --- a/business/jxcallback/orderman/orderman_ext.go +++ b/business/jxcallback/orderman/orderman_ext.go @@ -3,8 +3,11 @@ package orderman import ( "fmt" "strconv" + "strings" "time" + "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" + "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/jxutils/excel" @@ -27,33 +30,21 @@ type tWaybillExt struct { StoreID int `json:"storeID" orm:"column(store_id)"` } -//此函数会被GetStoreOrderCountInfo2取代 -func (c *OrderManager) GetStoreOrderCountInfo(ctx *jxcontext.Context, storeID string, lastHours int) (countInfo []*model.GoodsOrderCountInfo, err error) { - globals.SugarLogger.Debugf("GetStoreOrderCountInfo storeID:%s", storeID) - if lastHours > maxLastHours { - lastHours = maxLastHours - } else if lastHours == 0 { - lastHours = defLastHours - } +type StoresOrderSaleInfo struct { + StoreID int `orm:"column(store_id)" json:"storeID"` + VendorID int `orm:"column(vendor_id)" json:"vendorID"` + Status int `json:"status"` + Count int `json:"count"` + ShopPrice int64 `json:"shopPrice"` + VendorPrice int64 `json:"vendorPrice"` + SalePrice int64 `json:"salePrice"` + ActualPayPrice int64 `json:"actualPayPrice"` - db := orm.NewOrm() - _, err = db.Raw(` - SELECT t1.status, COUNT(*) count - FROM goods_order t1 - WHERE t1.vendor_id <> 2 AND IF(t1.vendor_id = ?, t1.store_id, IF(t1.jx_store_id != 0, t1.jx_store_id, t1.store_id) ) = ? - AND t1.order_created_at >= ? AND t1.lock_status = ? - GROUP BY 1 - ORDER BY 1 - `, model.VendorIDWSC, storeID, time.Now().Add(-time.Duration(lastHours)*time.Hour), model.OrderStatusUnknown).QueryRows(&countInfo) - if err == nil { - return countInfo, nil - } - globals.SugarLogger.Infof("GetStoreOrderCountInfo storeID:%s failed with error:%v", storeID, err) - return nil, err + EarningPrice int64 `json:"earningPrice"` // 预估结算给门店老板的钱 } -func (c *OrderManager) GetStoreOrderCountInfo2(ctx *jxcontext.Context, storeID, lastHours int) (countInfo []*model.GoodsOrderCountInfo2, err error) { - globals.SugarLogger.Debugf("GetStoreOrderCountInfo2 storeID:%s", storeID) +func (c *OrderManager) GetStoreOrderCountInfo(ctx *jxcontext.Context, storeID, lastHours int) (countInfo []*model.GoodsOrderCountInfo, err error) { + globals.SugarLogger.Debugf("GetStoreOrderCountInfo storeID:%d", storeID) if lastHours > maxLastHours { lastHours = maxLastHours } else if lastHours == 0 { @@ -72,7 +63,7 @@ func (c *OrderManager) GetStoreOrderCountInfo2(ctx *jxcontext.Context, storeID, if err == nil { return countInfo, nil } - globals.SugarLogger.Infof("GetStoreOrderCountInfo2 storeID:%s failed with error:%v", storeID, err) + globals.SugarLogger.Infof("GetStoreOrderCountInfo storeID:%d failed with error:%v", storeID, err) return nil, err } @@ -86,7 +77,26 @@ func (c *OrderManager) GetOrderSkuInfo(ctx *jxcontext.Context, vendorOrderID str db := dao.GetDB() if vendorID == model.VendorIDELM { err = dao.GetRows(db, &skus, fmt.Sprintf(` - SELECT t1.*, IF(t3.img IS NULL OR t3.img = '', t4.col_imageUrl, t3.img) image, %s full_sku_name + SELECT + t1.id, + t1.vendor_order_id, + t1.vendor_id, + t1.count, + t1.vendor_sku_id, + t1.sku_id, + t1.jx_sku_id, + t1.sku_name, + IF(t1.shop_price = 0, t1.sale_price, t1.shop_price) shop_price, + t1.sale_price, + t1.earning_price, + t1.weight, + t1.sku_type, + t1.promotion_type, + t1.order_created_at, + t1.store_sub_id, + t1.store_sub_name, + t1.vendor_price, + IF(t3.img IS NULL OR t3.img = '', t4.col_imageUrl, t3.img) image, %s full_sku_name FROM order_sku t1 LEFT JOIN sku t2 ON IF(t1.jx_sku_id != 0, t1.jx_sku_id, t1.sku_id) = t2.id/* AND t2.deleted_at = ?*/ LEFT JOIN sku_name t3 ON t2.name_id = t3.id/* AND t3.deleted_at = ?*/ @@ -96,7 +106,26 @@ func (c *OrderManager) GetOrderSkuInfo(ctx *jxcontext.Context, vendorOrderID str `, fullSkuNameSQL), /*, utils.DefaultTimeValue, utils.DefaultTimeValue*/ vendorOrderID, vendorID) } else if vendorID == model.VendorIDJD { err = dao.GetRows(db, &skus, fmt.Sprintf(` - SELECT t1.*, IF(t3.img IS NULL OR t3.img = '', t4.image, t3.img) image, %s full_sku_name + SELECT + t1.id, + t1.vendor_order_id, + t1.vendor_id, + t1.count, + t1.vendor_sku_id, + t1.sku_id, + t1.jx_sku_id, + t1.sku_name, + IF(t1.shop_price = 0, t1.sale_price, t1.shop_price) shop_price, + t1.sale_price, + t1.earning_price, + t1.weight, + t1.sku_type, + t1.promotion_type, + t1.order_created_at, + t1.store_sub_id, + t1.store_sub_name, + t1.vendor_price, + IF(t3.img IS NULL OR t3.img = '', t4.image, t3.img) image, %s full_sku_name FROM order_sku t1 LEFT JOIN sku t2 ON IF(t1.jx_sku_id != 0, t1.jx_sku_id, t1.sku_id) = t2.id/* AND t2.deleted_at = ?*/ LEFT JOIN sku_name t3 ON t2.name_id = t3.id/* AND t3.deleted_at = ?*/ @@ -107,7 +136,26 @@ func (c *OrderManager) GetOrderSkuInfo(ctx *jxcontext.Context, vendorOrderID str } if err != nil || len(skus) == 0 { err = dao.GetRows(db, &skus, fmt.Sprintf(` - SELECT t1.*, t3.img image, %s full_sku_name + SELECT + t1.id, + t1.vendor_order_id, + t1.vendor_id, + t1.count, + t1.vendor_sku_id, + t1.sku_id, + t1.jx_sku_id, + t1.sku_name, + IF(t1.shop_price = 0, t1.sale_price, t1.shop_price) shop_price, + t1.sale_price, + t1.earning_price, + t1.weight, + t1.sku_type, + t1.promotion_type, + t1.order_created_at, + t1.store_sub_id, + t1.store_sub_name, + t1.vendor_price, + t3.img image, %s full_sku_name FROM order_sku t1 LEFT JOIN sku t2 ON IF(t1.jx_sku_id != 0, t1.jx_sku_id, t1.sku_id) = t2.id/* AND t2.deleted_at = ?*/ LEFT JOIN sku_name t3 ON t2.name_id = t3.id/* AND t3.deleted_at = ?*/ @@ -157,19 +205,25 @@ func (c *OrderManager) GetOrderInfo(ctx *jxcontext.Context, vendorOrderID string return nil, err } -func (c *OrderManager) GetOrderWaybillInfo(ctx *jxcontext.Context, vendorOrderID string, vendorID int) (bills []*model.Waybill, err error) { +func (c *OrderManager) GetOrderWaybillInfo(ctx *jxcontext.Context, vendorOrderID string, vendorID int, isNotEnded bool) (bills []*model.Waybill, err error) { globals.SugarLogger.Debugf("GetOrderWaybillInfo orderID:%s", vendorOrderID) - db := orm.NewOrm() - _, err = db.Raw(` + db := dao.GetDB() + sql := ` SELECT t1.* FROM waybill t1 WHERE t1.vendor_order_id = ? AND order_vendor_id = ? - `, vendorOrderID, vendorID).QueryRows(&bills) - if err == nil { - return bills, nil + ` + sqlParams := []interface{}{ + vendorOrderID, + vendorID, } + if isNotEnded { + sql += " AND t1.status < ?" + sqlParams = append(sqlParams, model.OrderStatusEndBegin) + } + err = dao.GetRows(db, &bills, sql, sqlParams...) globals.SugarLogger.Infof("GetOrderWaybillInfo orderID:%s failed with error:%v", vendorOrderID, err) - return nil, err + return bills, err } func (c *OrderManager) ExportMTWaybills(ctx *jxcontext.Context, fromDateStr, toDateStr string) (excelContent []byte, err error) { @@ -220,8 +274,8 @@ func (c *OrderManager) ExportMTWaybills(ctx *jxcontext.Context, fromDateStr, toD return nil, err } -func (c *OrderManager) GetOrders(ctx *jxcontext.Context, fromDateStr, toDateStr string, params map[string]interface{}, offset, pageSize int) (pagedInfo *model.PagedInfo, err error) { - globals.SugarLogger.Debugf("GetOrders from:%s to:%s", fromDateStr, toDateStr) +func (c *OrderManager) getOrders(ctx *jxcontext.Context, isIncludeSku bool, fromDateStr, toDateStr string, params map[string]interface{}, offset, pageSize int) (orders []*model.GoodsOrderExt, totalCount int, err error) { + globals.SugarLogger.Debugf("getOrders from:%s to:%s", fromDateStr, toDateStr) pageSize = jxutils.FormalizePageSize(pageSize) if offset < 0 { @@ -229,11 +283,21 @@ func (c *OrderManager) GetOrders(ctx *jxcontext.Context, fromDateStr, toDateStr } sql := ` SELECT SQL_CALC_FOUND_ROWS t1.*, + CAST(IF(t1.shop_price <= t1.vendor_price, IF(t1.shop_price = 0, t1.sale_price, t1.shop_price), IF(t1.vendor_price = 0, t1.sale_price, t1.vendor_price))*IF(t5.pay_percentage IS NULL OR t5.pay_percentage <= 0, 70, t5.pay_percentage)/100 AS SIGNED) earning_price, t2.status waybill_status, t2.courier_name, t2.courier_mobile, - t2.actual_fee, t2.desired_fee, t2.waybill_created_at, t2.waybill_finished_at + t2.actual_fee, t2.desired_fee, t2.waybill_created_at, t2.waybill_finished_at` + if isIncludeSku { + sql += `, + t3.sku_id, t3.count sku_count2, t3.shop_price sku_shop_price, IF(t3.earning_price <> 0, t3.earning_price, t3.sale_price) sku_sale_price` + } + sql += ` FROM goods_order t1 LEFT JOIN waybill t2 ON t1.vendor_waybill_id = t2.vendor_waybill_id AND t1.waybill_vendor_id = t2.waybill_vendor_id - ` + LEFT JOIN store t5 ON t5.id = IF(t1.jx_store_id <> 0, t1.jx_store_id, t1.store_id)` + if isIncludeSku { + sql += ` + JOIN order_sku t3 ON t3.vendor_order_id = t1.vendor_order_id AND t3.vendor_id = t1.vendor_id` + } var ( sqlWhere string sqlParams []interface{} @@ -257,14 +321,14 @@ func (c *OrderManager) GetOrders(ctx *jxcontext.Context, fromDateStr, toDateStr } else { fromDate, err2 := utils.TryStr2Time(fromDateStr) if err = err2; err != nil { - return nil, err + return nil, 0, err } if toDateStr == "" { toDateStr = fromDateStr } toDate, err2 := utils.TryStr2Time(toDateStr) if err = err2; err != nil { - return nil, err + return nil, 0, err } toDate = toDate.Add(24 * time.Hour) @@ -279,7 +343,7 @@ func (c *OrderManager) GetOrders(ctx *jxcontext.Context, fromDateStr, toDateStr keyword := params["keyword"].(string) keywordLike := "%" + keyword + "%" sqlWhere += ` - AND (t1.store_name LIKE ? OR t1.vendor_order_id LIKE ? OR t1.vendor_store_id LIKE ? + AND (t1.store_name LIKE ? OR t1.vendor_order_id LIKE ? OR t1.vendor_store_id LIKE ? OR t1.consignee_name LIKE ? OR t1.consignee_mobile LIKE ? OR t1.consignee_mobile2 LIKE ? OR t1.consignee_address LIKE ? OR t2.vendor_waybill_id LIKE ? OR t2.courier_name LIKE ? OR t2.courier_mobile LIKE ? ` @@ -293,17 +357,17 @@ func (c *OrderManager) GetOrders(ctx *jxcontext.Context, fromDateStr, toDateStr if params["waybillVendorIDs"] != nil { var waybillVendorIDs []int if err = utils.UnmarshalUseNumber([]byte(params["waybillVendorIDs"].(string)), &waybillVendorIDs); err != nil { - return nil, err + return nil, 0, err } if len(waybillVendorIDs) > 0 { - sqlWhere += " AND t2.waybill_vendor_id IN (" + dao.GenQuestionMarks(len(waybillVendorIDs)) + ")" + sqlWhere += " AND t1.waybill_vendor_id IN (" + dao.GenQuestionMarks(len(waybillVendorIDs)) + ")" sqlParams = append(sqlParams, waybillVendorIDs) } } if params["storeIDs"] != nil { var storeIDs []int if err = utils.UnmarshalUseNumber([]byte(params["storeIDs"].(string)), &storeIDs); err != nil { - return nil, err + return nil, 0, err } if len(storeIDs) > 0 { sqlWhere += " AND IF(t1.vendor_id = ?, t1.store_id, IF(t1.jx_store_id != 0, t1.jx_store_id, t1.store_id) ) IN (" + dao.GenQuestionMarks(len(storeIDs)) + ")" @@ -313,7 +377,7 @@ func (c *OrderManager) GetOrders(ctx *jxcontext.Context, fromDateStr, toDateStr if params["statuss"] != nil { var statuss []int if err = utils.UnmarshalUseNumber([]byte(params["statuss"].(string)), &statuss); err != nil { - return nil, err + return nil, 0, err } if len(statuss) > 0 { sqlWhere += " AND t1.status IN (" + dao.GenQuestionMarks(len(statuss)) + ")" @@ -323,7 +387,7 @@ func (c *OrderManager) GetOrders(ctx *jxcontext.Context, fromDateStr, toDateStr if params["lockStatuss"] != nil { var lockStatuss []int if err = utils.UnmarshalUseNumber([]byte(params["lockStatuss"].(string)), &lockStatuss); err != nil { - return nil, err + return nil, 0, err } if len(lockStatuss) > 0 { sqlWhere += " AND t1.lock_status IN (" + dao.GenQuestionMarks(len(lockStatuss)) + ")" @@ -333,7 +397,7 @@ func (c *OrderManager) GetOrders(ctx *jxcontext.Context, fromDateStr, toDateStr if params["cities"] != nil { var cities []int if err = utils.UnmarshalUseNumber([]byte(params["cities"].(string)), &cities); err != nil { - return nil, err + return nil, 0, err } if len(cities) > 0 { sql += " JOIN store st ON t1.store_id = st.id" @@ -345,39 +409,147 @@ func (c *OrderManager) GetOrders(ctx *jxcontext.Context, fromDateStr, toDateStr if params["vendorIDs"] != nil { var vendorIDs []int if err = utils.UnmarshalUseNumber([]byte(params["vendorIDs"].(string)), &vendorIDs); err != nil { - return nil, err + return nil, 0, err } if len(vendorIDs) > 0 { sqlWhere += " AND t1.vendor_id IN (" + dao.GenQuestionMarks(len(vendorIDs)) + ")" sqlParams = append(sqlParams, vendorIDs) } } - sql += sqlWhere - sql += ` - ORDER BY t1.order_created_at DESC - LIMIT ? OFFSET ? - ` - sqlParams = append(sqlParams, pageSize, offset) - - var orders []*model.GoodsOrderExt db := dao.GetDB() - dao.Begin(db) - defer func() { - if r := recover(); r != nil { - dao.Rollback(db) - panic(r) - } - }() + sql += sqlWhere + if isIncludeSku { + sql += ` + ORDER BY t1.id` + } else { + sql += ` + ORDER BY t1.order_created_at DESC + LIMIT ? OFFSET ?` + sqlParams = append(sqlParams, pageSize, offset) + + dao.Begin(db) + defer func() { + if r := recover(); r != nil { + dao.Rollback(db) + panic(r) + } + }() + } if err = dao.GetRows(db, &orders, sql, sqlParams...); err == nil { + totalCount = dao.GetLastTotalRowCount(db) + } + if !isIncludeSku { + dao.Commit(db) + } + return orders, totalCount, err +} + +func (c *OrderManager) GetOrders(ctx *jxcontext.Context, fromDateStr, toDateStr string, params map[string]interface{}, offset, pageSize int) (pagedInfo *model.PagedInfo, err error) { + globals.SugarLogger.Debugf("GetOrders from:%s to:%s", fromDateStr, toDateStr) + orders, totalCount, err := c.getOrders(ctx, false, fromDateStr, toDateStr, params, offset, pageSize) + if err == nil { pagedInfo = &model.PagedInfo{ - TotalCount: dao.GetLastTotalRowCount(db), + TotalCount: totalCount, Data: orders, } } - dao.Commit(db) return pagedInfo, err } +func (c *OrderManager) ExportOrders(ctx *jxcontext.Context, fromDateStr, toDateStr string, mapParams map[string]interface{}) (hint string, err error) { + globals.SugarLogger.Debugf("ExportOrders from:%s to:%s", fromDateStr, toDateStr) + var ( + orders, orders2 []*model.GoodsOrderExt + order *model.GoodsOrderExt + excelBin []byte + ) + task := tasksch.NewSeqTask("导出订单SKU信息", ctx, + func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { + switch step { + case 0: + orders, _, err = c.getOrders(ctx, true, fromDateStr, toDateStr, mapParams, 0, -1) + case 1: + for _, v := range orders { + skuStr := strings.Join([]string{ + utils.Int2Str(v.SkuID), + utils.Int2Str(v.SkuCount2), + utils.Int2Str(v.SkuShopPrice), + utils.Int2Str(v.SkuSalePrice), + }, ",") + if order == nil || v.ID != order.ID { + order = v + v.CourierVendorName = model.VendorChineseNames[v.WaybillVendorID] + v.Status2 = model.OrderStatusName[v.Status] + v.SkuInfo = skuStr + orders2 = append(orders2, v) + } else { + order.SkuInfo += ";" + skuStr + } + } + case 2: + excelConf := &excel.Obj2ExcelSheetConfig{ + Title: "订单导出", + Data: orders2, + CaptionList: []string{ + "vendorOrderID", + "vendorID", + "vendorStoreID", + "jxStoreID", + "storeName", + "salePrice", + "shopPrice", + "weight", + "consigneeName", + "consigneeMobile", + "consigneeMobile2", + "consigneeAddress", + "skuCount", + "status", + "orderSeq", + "buyerComment", + "businessType", + "expectedDeliveredTime", + "vendorWaybillID", + "waybillVendorID", + "orderCreatedAt", + "orderFinishedAt", + "courierVendorName", + "courierName", + "courierMobile", + "courierMobile", + "desiredFee", + "waybillCreatedAt", + "waybillFinishedAt", + "status2", + "skuInfo", + }, + } + excelBin = excel.Obj2Excel([]*excel.Obj2ExcelSheetConfig{excelConf}) + case 3: + keyPart := []string{ + ctx.GetUserName(), + } + if fromDateStr != "" { + keyPart = append(keyPart, fromDateStr) + } + if toDateStr != "" { + keyPart = append(keyPart, toDateStr) + } + keyPart = append(keyPart, time.Now().Format("20060102T150405")+".xlsx") + key := "export/" + strings.Join(keyPart, "_") + excelURL, err2 := jxutils.UploadExportContent(excelBin, key) + if err = err2; err == nil { + task.SetNoticeMsg(excelURL) + } + globals.SugarLogger.Debugf("导出订单SKU信息excelURL:%s, err:%v", excelURL, err) + } + return nil, err + }, 4) + tasksch.ManageTask(task).Run() + hint = task.GetID() + return hint, err +} + func (c *OrderManager) GetWaybills(ctx *jxcontext.Context, fromDateStr, toDateStr string, params map[string]interface{}, offset, pageSize int) (pagedInfo *model.PagedInfo, err error) { globals.SugarLogger.Debugf("GetWaybills from:%s to:%s", fromDateStr, toDateStr) @@ -472,16 +644,14 @@ func (c *OrderManager) GetOrderStatusList(ctx *jxcontext.Context, vendorOrderID sql := ` SELECT * FROM order_status t1 - WHERE 1 = 1 + WHERE t1.ref_vendor_order_id = ? AND t1.ref_vendor_id = ? ` sqlParams := []interface{}{ vendorOrderID, vendorID, } - if orderType == -1 { - sql += " AND t1.ref_vendor_order_id = ? AND t1.ref_vendor_id = ?" - } else { - sql += " AND t1.vendor_order_id = ? AND t1.vendor_id = ? AND t1.order_type = ?" + if orderType > 0 { + sql += " AND t1.order_type = ?" sqlParams = append(sqlParams, orderType) } sql += " ORDER BY t1.status_time, t1.order_type DESC" @@ -604,3 +774,184 @@ func (c *OrderManager) GetOrdersFinancial(ctx *jxcontext.Context, fromDateStr, t return pagedInfo, err } + +func (c *OrderManager) GetStoresOrderSaleInfo(ctx *jxcontext.Context, storeIDList []int, fromTime time.Time, toTime time.Time, statusList []int) (saleInfoList []*StoresOrderSaleInfo, err error) { + if toTime.Sub(fromTime) > time.Hour*24*60 { + return nil, fmt.Errorf("查询时间范围不能超过60天") + } + // 用int64类型去取float型的数据库返回值,会取不到 + sql := ` + SELECT IF(t1.jx_store_id > 0, t1.jx_store_id, t1.store_id) store_id, t1.vendor_id, IF(t1.status < ?, 0, t1.status) status, + COUNT(*) count, SUM(t1.shop_price) shop_price, SUM(t1.vendor_price) vendor_price, SUM(t1.sale_price) sale_price, SUM(t1.actual_pay_price) actual_pay_price, + CAST(SUM(IF(t1.shop_price <= t1.vendor_price, IF(t1.shop_price = 0, t1.sale_price, t1.shop_price), IF(t1.vendor_price = 0, t1.sale_price, t1.vendor_price))*IF(t5.pay_percentage IS NULL OR t5.pay_percentage <= 0, 70, t5.pay_percentage)/100) AS SIGNED) earning_price + FROM goods_order t1 + LEFT JOIN store t5 ON t5.id = IF(t1.jx_store_id <> 0, t1.jx_store_id, t1.store_id) + WHERE t1.order_created_at >= ? AND t1.order_created_at <= ? + ` + sqlParams := []interface{}{ + model.OrderStatusEndBegin, + fromTime, + toTime, + } + if len(storeIDList) > 0 { + sql += " AND IF(t1.jx_store_id > 0, t1.jx_store_id, t1.store_id) IN (" + dao.GenQuestionMarks(len(storeIDList)) + ")" + sqlParams = append(sqlParams, storeIDList) + } + if len(statusList) > 0 { + sql += " AND t1.status IN (" + dao.GenQuestionMarks(len(statusList)) + ")" + sqlParams = append(sqlParams, statusList) + } + sql += ` + GROUP BY 1,2,3 + ORDER BY 1,2,3` + err = dao.GetRows(dao.GetDB(), &saleInfoList, sql, sqlParams...) + return saleInfoList, err +} + +func (c *OrderManager) GetAfsOrders(ctx *jxcontext.Context, keyword, afsOrderID, vendorOrderID string, vendorIDList, appealTypeList, storeIDList, statusList []int, fromTime, toTime time.Time, offset, pageSize int) (pagedInfo *model.PagedInfo, err error) { + globals.SugarLogger.Debugf("GetAfsOrders") + + pageSize = jxutils.FormalizePageSize(pageSize) + if offset < 0 { + offset = 0 + } + sql := ` + SELECT SQL_CALC_FOUND_ROWS t1.* + FROM afs_order t1 + ` + var ( + sqlWhere string + sqlParams []interface{} + ) + // 如果搜索关键字可能为订单或售后单号,则当成订单或售后单查询 + if keyword != "" { + if jxutils.GetPossibleVendorIDFromAfsOrderID(keyword) > model.VendorIDUnknown && afsOrderID == "" { + afsOrderID = keyword + keyword = "" + } else if jxutils.GetPossibleVendorIDFromVendorOrderID(keyword) > model.VendorIDUnknown && vendorOrderID == "" { + vendorOrderID = keyword + keyword = "" + } + } + if vendorOrderID != "" || afsOrderID != "" { + if vendorOrderID != "" { + sqlWhere = " WHERE (t1.vendor_order_id = ? OR t1.vendor_order_id2 = ?)" + sqlParams = []interface{}{ + vendorOrderID, + vendorOrderID, + } + } else { + sqlWhere = " WHERE (t1.afs_order_id = ?)" + sqlParams = []interface{}{ + afsOrderID, + } + } + } else { + if toTime.Sub(fromTime) > 24*time.Hour*60 { + return nil, fmt.Errorf("售后单查询时间不能超过60天") + } + sqlWhere = ` + WHERE t1.afs_created_at >= ? AND t1.afs_created_at <= ? + ` + sqlParams = []interface{}{ + fromTime, + toTime, + } + if keyword != "" { + keywordLike := "%" + keyword + "%" + sqlWhere += ` + AND (t1.vendor_order_id2 LIKE ? OR t1.vendor_order_id LIKE ? OR t1.afs_order_id LIKE ? + OR t1.vendor_store_id LIKE ? OR t1.reason_desc LIKE ? + ` + sqlParams = append(sqlParams, keywordLike, keywordLike, keywordLike, keywordLike, keywordLike) + if keywordInt64 := utils.Str2Int64WithDefault(keyword, 0); keywordInt64 > 0 { + sqlWhere += " OR t1.store_id = ? OR t1.jx_store_id = ?" + sqlParams = append(sqlParams, keywordInt64, keywordInt64) + } + sqlWhere += ")" + } + if len(storeIDList) > 0 { + sqlWhere += " AND IF(t1.jx_store_id != 0, t1.jx_store_id, t1.store_id) IN (" + dao.GenQuestionMarks(len(storeIDList)) + ")" + sqlParams = append(sqlParams, storeIDList) + } + if len(statusList) > 0 { + sqlWhere += " AND t1.status IN (" + dao.GenQuestionMarks(len(statusList)) + ")" + sqlParams = append(sqlParams, statusList) + } + if len(appealTypeList) > 0 { + sqlWhere += " AND t1.appeal_type IN (" + dao.GenQuestionMarks(len(appealTypeList)) + ")" + sqlParams = append(sqlParams, appealTypeList) + } + } + if len(vendorIDList) > 0 { + sqlWhere += " AND t1.vendor_id IN (" + dao.GenQuestionMarks(len(vendorIDList)) + ")" + sqlParams = append(sqlParams, vendorIDList) + } + + sql += sqlWhere + sql += ` + ORDER BY t1.afs_created_at DESC + LIMIT ? OFFSET ? + ` + sqlParams = append(sqlParams, pageSize, offset) + + var orders []*model.AfsOrder + db := dao.GetDB() + dao.Begin(db) + defer func() { + if r := recover(); r != nil || err != nil { + dao.Rollback(db) + if r != nil { + panic(r) + } + } + }() + if err = dao.GetRows(db, &orders, sql, sqlParams...); err == nil { + pagedInfo = &model.PagedInfo{ + TotalCount: dao.GetLastTotalRowCount(db), + Data: orders, + } + dao.Commit(db) + } + return pagedInfo, err +} + +func (c *OrderManager) GetAfsOrderSkuInfo(ctx *jxcontext.Context, afsOrderID string, vendorID int) (skus []*model.OrderFinancialSkuExt, err error) { + sql := ` + SELECT t1.*, t3.img image + FROM order_sku_financial t1 + JOIN sku t2 ON t2.id = IF(t1.jx_sku_id <> 0, t1.jx_sku_id, t1.sku_id) + JOIN sku_name t3 ON t3.id = t2.name_id + WHERE t1.afs_order_id = ? AND t1.vendor_id = ? + ` + sqlParams := []interface{}{ + afsOrderID, + vendorID, + } + err = dao.GetRows(dao.GetDB(), &skus, sql, sqlParams...) + return skus, err +} + +func (c *OrderManager) GetStoreAfsOrderCountInfo(ctx *jxcontext.Context, storeID, lastHours int) (countInfo []*model.GoodsOrderCountInfo, err error) { + globals.SugarLogger.Debugf("GetStoreAfsOrderCountInfo storeID:%d", storeID) + if lastHours > maxLastHours { + lastHours = maxLastHours + } else if lastHours == 0 { + lastHours = defLastHours + } + + db := dao.GetDB() + err = dao.GetRows(db, &countInfo, ` + SELECT 0 lock_status, t1.status, COUNT(*) count + FROM afs_order t1 + WHERE t1.vendor_id <> 2 AND IF(t1.vendor_id = ?, t1.store_id, IF(t1.jx_store_id != 0, t1.jx_store_id, t1.store_id) ) = ? + AND t1.afs_created_at >= ? + GROUP BY 1,2 + ORDER BY 1,2 + `, model.VendorIDWSC, storeID, time.Now().Add(-time.Duration(lastHours)*time.Hour)) + if err == nil { + return countInfo, nil + } + globals.SugarLogger.Infof("GetStoreAfsOrderCountInfo storeID:%d failed with error:%v", storeID, err) + return nil, err +} diff --git a/business/jxcallback/orderman/waybill.go b/business/jxcallback/orderman/waybill.go index 842df9e3a..0463fddac 100644 --- a/business/jxcallback/orderman/waybill.go +++ b/business/jxcallback/orderman/waybill.go @@ -13,10 +13,10 @@ import ( var ( waybillOrderStatusMap = map[int]int{ - model.WaybillStatusApplyFailedGetGoods: model.WaybillStatusApplyFailedGetGoods, - model.WaybillStatusAgreeFailedGetGoods: model.WaybillStatusAgreeFailedGetGoods, - model.WaybillStatusRefuseFailedGetGoods: model.WaybillStatusRefuseFailedGetGoods, - model.WaybillStatusDeliverFailed: model.WaybillStatusDeliverFailed, + model.WaybillStatusApplyFailedGetGoods: model.OrderStatusApplyFailedGetGoods, + model.WaybillStatusAgreeFailedGetGoods: model.OrderStatusAgreeFailedGetGoods, + model.WaybillStatusRefuseFailedGetGoods: model.OrderStatusRefuseFailedGetGoods, + model.WaybillStatusDeliverFailed: model.OrderStatusDeliverFailed, } ) @@ -92,13 +92,48 @@ func (w *OrderManager) OnWaybillStatusChanged(bill *model.Waybill) (err error) { panic(r) } }() + duplicatedCount := 0 if bill.Status == model.WaybillStatusNew { isDuplicated, err = w.onWaybillNew(bill, db) + if isDuplicated { + duplicatedCount = 1 + } } else { + if bill.Status == model.WaybillStatusAccepted { // 处理美团配送丢失新运单消息的情况 + existingBill, err2 := w.LoadWaybill(bill.VendorWaybillID, bill.WaybillVendorID) + if err2 != nil { + if dao.IsNoRowsError(err2) || err2 == ErrCanNotFindWaybill { + if isDuplicated, err = w.onWaybillNew(bill, db); err != nil { + dao.Rollback(db) + return err + } + existingBill = bill + billCopy := *bill + billCopy.Status = model.WaybillStatusNew + dao.Commit(db) + // 进运单调度器OnWaybillStatusChanged之前要确保事务是提交了的,否则会导致死锁 + scheduler.CurrentScheduler.OnWaybillStatusChanged(&billCopy, false) + dao.Begin(db) + } else { + dao.Rollback(db) + return err2 + } + } + // 运单消息错序,之前已经结束了,直接返回 + if existingBill.Status >= model.WaybillStatusEndBegin { + dao.Commit(db) + return nil + } + } addParams := orm.Params{} if bill.Status >= model.WaybillStatusAccepted && bill.Status < model.WaybillStatusEndBegin { if bill.Status == model.WaybillStatusAccepted { - addParams["desired_fee"] = bill.DesiredFee + if bill.DesiredFee > 0 { + addParams["desired_fee"] = bill.DesiredFee + } + if bill.ActualFee > 0 { + addParams["actual_fee"] = bill.ActualFee + } } if bill.CourierMobile != "" { addParams["courier_name"] = bill.CourierName @@ -107,11 +142,15 @@ func (w *OrderManager) OnWaybillStatusChanged(bill *model.Waybill) (err error) { } else if bill.Status >= model.WaybillStatusEndBegin { addParams["waybill_finished_at"] = bill.StatusTime } - isDuplicated, err = w.addWaybillStatus(bill, db, addParams) + duplicatedCount, err = w.addWaybillStatus(bill, db, addParams) + if err != nil { + dao.Rollback(db) + return err + } } if err == nil { dao.Commit(db) - if !isDuplicated { + if duplicatedCount == 0 { scheduler.CurrentScheduler.OnWaybillStatusChanged(bill, false) } } else { @@ -136,21 +175,27 @@ func (w *OrderManager) OnWaybillStatusChanged(bill *model.Waybill) (err error) { return err } -func (w *OrderManager) addWaybillStatus(bill *model.Waybill, db *dao.DaoDB, addParams orm.Params) (isDuplicated bool, err error) { +func (w *OrderManager) addWaybillStatus(bill *model.Waybill, db *dao.DaoDB, addParams orm.Params) (duplicatedCount int, err error) { waybillStatus := model.Waybill2Status(bill) - isDuplicated, err = addOrderOrWaybillStatus(waybillStatus, db) - if err == nil && !isDuplicated && waybillStatus.Status > model.WaybillStatusUnknown { // todo 这里应该和addOrderStatus一样的改法,状态不能回绕 - params := utils.MergeMaps(orm.Params{ - "status": bill.Status, - "vendor_status": bill.VendorStatus, - "status_time": bill.StatusTime, - }, addParams) - utils.CallFuncLogError(func() error { - _, err = db.Db.QueryTable("waybill").Filter("vendor_waybill_id", bill.VendorWaybillID).Filter("waybill_vendor_id", bill.WaybillVendorID).Filter("status__lte", bill.Status).Update(params) - return err - }, "addWaybillStatus update waybill status, bill:%v", bill) + isDuplicated, err := addOrderOrWaybillStatus(waybillStatus, db) + if err == nil && !isDuplicated { + if waybillStatus.Status > model.WaybillStatusUnknown { // todo 这里应该和addOrderStatus一样的改法,状态不能回绕 + params := utils.MergeMaps(orm.Params{ + "status": bill.Status, + "vendor_status": bill.VendorStatus, + "status_time": bill.StatusTime, + }, addParams) + utils.CallFuncLogError(func() error { + _, err = db.Db.QueryTable("waybill").Filter("vendor_waybill_id", bill.VendorWaybillID).Filter("waybill_vendor_id", bill.WaybillVendorID).Filter("status__lte", bill.Status).Update(params) + return err + }, "addWaybillStatus update waybill status, bill:%v", bill) + } else { + duplicatedCount = -1 + } + } else { + duplicatedCount = 1 } - return isDuplicated, err + return duplicatedCount, err } func (c *OrderManager) LoadWaybill(vendorWaybillID string, waybillVendorID int) (bill *model.Waybill, err error) { @@ -160,6 +205,7 @@ func (c *OrderManager) LoadWaybill(vendorWaybillID string, waybillVendorID int) WaybillVendorID: waybillVendorID, } if err = db.Read(bill, "VendorWaybillID", "WaybillVendorID"); err != nil { + bill = nil if err == orm.ErrNoRows { err = ErrCanNotFindWaybill } diff --git a/business/jxcallback/scheduler/basesch/basesch.go b/business/jxcallback/scheduler/basesch/basesch.go index 67d3965bd..5f7f1d542 100644 --- a/business/jxcallback/scheduler/basesch/basesch.go +++ b/business/jxcallback/scheduler/basesch/basesch.go @@ -5,6 +5,7 @@ import ( "git.rosy.net.cn/jx-callback/business/jxcallback/scheduler" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/model" + "git.rosy.net.cn/jx-callback/business/model/dao" "git.rosy.net.cn/jx-callback/business/partner" "git.rosy.net.cn/jx-callback/globals" ) @@ -54,16 +55,16 @@ func (c *BaseScheduler) PickupGoods(order *model.GoodsOrder, isSelfDelivery bool func (c *BaseScheduler) Swtich2SelfDeliver(order *model.GoodsOrder, userName string) (err error) { globals.SugarLogger.Infof("Swtich2SelfDeliver orderID:%s", order.VendorOrderID) - if /*order.LockStatus == model.OrderStatusUnknown && */ order.Status == model.OrderStatusFinishedPickup { - if c.IsReallyCallPlatformAPI { + if /*order.LockStatus == model.OrderStatusUnknown && */ order.Status >= model.OrderStatusFinishedPickup && order.Status <= model.OrderStatusDelivering { + if order.DeliveryFlag&model.OrderDeliveryFlagMaskPurcahseDisabled == 0 && c.IsReallyCallPlatformAPI { err = utils.CallFuncLogErrorWithInfo(func() error { return partner.GetPurchasePlatformFromVendorID(order.VendorID).Swtich2SelfDeliver(order, userName) }, "Swtich2SelfDeliver orderID:%s", order.VendorOrderID) - if err == nil { // 因为有些平台转自送后,不会再发送订单在配送中消息过来,所以成功后就强制设置状态为配送中 - order.Status = model.OrderStatusDelivering - order.DeliveryFlag |= model.OrderDeliveryFlagMaskPurcahseDisabled - err = partner.CurOrderManager.UpdateOrderStatusAndFlag(order) - } + } + if err == nil { // 因为有些平台转自送后,不会再发送订单在配送中消息过来,所以成功后就强制设置状态为配送中 + order.Status = model.OrderStatusDelivering + order.DeliveryFlag |= model.OrderDeliveryFlagMaskPurcahseDisabled + err = partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order) } } else { if order.LockStatus != model.OrderStatusUnknown || order.Status < model.OrderStatusFinishedPickup || order.VendorID == order.WaybillVendorID { @@ -106,7 +107,7 @@ func (c *BaseScheduler) SelfDeliverDelivering(order *model.GoodsOrder, userName }, "SelfDeliverDelivering orderID:%s", order.VendorOrderID) if err == nil { // 因为有些平台设置配送中后,不会发送订单在配送中消息过来,所以成功后就强制设置状态为配送中 order.Status = model.OrderStatusDelivering - err = partner.CurOrderManager.UpdateOrderStatusAndFlag(order) + err = partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order) } } } else { @@ -147,16 +148,28 @@ func (c *BaseScheduler) CreateWaybill(platformVendorID int, order *model.GoodsOr globals.SugarLogger.Warnf("CreateWaybill orderID:%s, vendorID:%d is not solid!!!", order.VendorOrderID, platformVendorID) return nil, scheduler.ErrOrderIsNotSolid } - if c.IsReallyCallPlatformAPI { - handlerInfo := partner.GetDeliveryPlatformFromVendorID(platformVendorID) - if handlerInfo != nil && handlerInfo.Use4CreateWaybill { + // if order.DeliveryFlag&model.OrderDeliveryFlagMaskScheduleDisabled != 0 { + // waybillList, err := partner.CurOrderManager.GetOrderWaybillInfo(jxcontext.AdminCtx, order.VendorOrderID, order.VendorID, true) + // if err != nil { + // return nil, err + // } + // if len(waybillList) > 0 { + // return nil, fmt.Errorf("转商家自送的订单只允许有一个有效运单,当前已经有%s运单", jxutils.GetVendorName(waybillList[0].WaybillVendorID)) + // } + // } + handlerInfo := partner.GetDeliveryPlatformFromVendorID(platformVendorID) + if handlerInfo != nil && handlerInfo.Use4CreateWaybill { + if c.IsReallyCallPlatformAPI { bill, err = handlerInfo.Handler.CreateWaybill(order, policy) if err != nil { globals.SugarLogger.Infof("CreateWaybill failed orderID:%s vendorID:%d with error:%v", order.VendorOrderID, platformVendorID, err) + } else { + order.DeliveryFlag |= model.WaybillVendorID2Mask(platformVendorID) + err = partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order) } - } else { - err = scheduler.ErrDeliverProviderWrong } + } else { + err = scheduler.ErrDeliverProviderWrong } return bill, err } @@ -170,6 +183,8 @@ func (c *BaseScheduler) CancelWaybill(bill *model.Waybill, cancelReasonID int, c return handlerInfo.Handler.CancelWaybill(bill, cancelReasonID, cancelReason) }, "CancelWaybill bill:%v", bill); err == nil { bill.Status = model.WaybillStatusCanceled + bill.DeliveryFlag |= model.WaybillDeliveryFlagMaskActiveCancel + _, err = dao.UpdateEntity(nil, bill, "Status", "DeliveryFlag") } globals.SugarLogger.Debugf("CancelWaybill bill:%v canceled by myself", bill) } diff --git a/business/jxcallback/scheduler/basesch/basesch_ext.go b/business/jxcallback/scheduler/basesch/basesch_ext.go index b1da476f4..7f258e098 100644 --- a/business/jxcallback/scheduler/basesch/basesch_ext.go +++ b/business/jxcallback/scheduler/basesch/basesch_ext.go @@ -13,25 +13,38 @@ import ( "git.rosy.net.cn/jx-callback/globals" ) -func (c *BaseScheduler) CreateWaybillOnProviders(ctx *jxcontext.Context, order *model.GoodsOrder, policyHandler partner.CreateWaybillPolicy) (bills []*model.Waybill, err error) { +func (c *BaseScheduler) CreateWaybillOnProviders(ctx *jxcontext.Context, order *model.GoodsOrder, courierVendorIDs []int, policyHandler partner.CreateWaybillPolicy, createOnlyOne bool) (bills []*model.Waybill, err error) { userName := ctx.GetUserName() globals.SugarLogger.Infof("CreateWaybillOnProviders orderID:%s userName:%s", order.VendorOrderID, userName) storeCourierList, err := dao.GetStoreCourierList(dao.GetDB(), jxutils.GetSaleStoreIDFromOrder(order), model.StoreStatusOpened) if err != nil { return nil, err } - + var courierVendorIDMap map[int]bool + if len(courierVendorIDs) > 0 { + courierVendorIDMap = make(map[int]bool) + for _, courierVendorID := range courierVendorIDs { + courierVendorIDMap[courierVendorID] = true + } + } var errList []string for _, storeCourier := range storeCourierList { - courierVendorID := storeCourier.VendorID - if order.VendorID != model.VendorIDWSC || courierVendorID != model.VendorIDDada { // 达达作为微商城的自有配送,不参与配送竞争 - bill, err2 := c.CreateWaybill(courierVendorID, order, policyHandler) - if err = err2; err == nil { - globals.SugarLogger.Debugf("CreateWaybillOnProviders orderID:%s userName:%s vendorID:%d bill:%v", order.VendorOrderID, userName, courierVendorID, bill) - bills = append(bills, bill) - } else { - globals.SugarLogger.Debugf("CreateWaybillOnProviders orderID:%s userName:%s vendorID:%d failed with error:%v", order.VendorOrderID, userName, courierVendorID, err) - errList = append(errList, fmt.Sprintf("平台:%s,%s", jxutils.GetVendorName(courierVendorID), err.Error())) + if courierVendorIDMap == nil || courierVendorIDMap[storeCourier.VendorID] { + if handler := partner.GetDeliveryPlatformFromVendorID(storeCourier.VendorID); handler != nil && handler.Use4CreateWaybill { + courierVendorID := storeCourier.VendorID + if order.VendorID != model.VendorIDWSC || courierVendorID != model.VendorIDDada { // 达达作为微商城的自有配送,不参与配送竞争 + bill, err2 := c.CreateWaybill(courierVendorID, order, policyHandler) + if err = err2; err == nil { + globals.SugarLogger.Debugf("CreateWaybillOnProviders orderID:%s userName:%s vendorID:%d bill:%v", order.VendorOrderID, userName, courierVendorID, bill) + bills = append(bills, bill) + if createOnlyOne { + break + } + } else { + globals.SugarLogger.Debugf("CreateWaybillOnProviders orderID:%s userName:%s vendorID:%d failed with error:%v", order.VendorOrderID, userName, courierVendorID, err) + errList = append(errList, fmt.Sprintf("平台:%s,%s", jxutils.GetVendorName(courierVendorID), err.Error())) + } + } } } } @@ -58,8 +71,8 @@ func (c *BaseScheduler) SelfDeliveredAndUpdateStatus(ctx *jxcontext.Context, ven err = c.Swtich2SelfDelivered(order, userName) } if err == nil { - order.Status = model.OrderStatusFinished - if err = partner.CurOrderManager.UpdateOrderStatusAndFlag(order); err == nil { + // order.Status = model.OrderStatusFinished // todo 是否需要强制设置完成状态? + if err = dao.SetOrderFlag(dao.GetDB(), ctx.GetUserName(), order.VendorOrderID, order.VendorID, model.OrderFlagMaskSetDelivered); err == nil { globals.SugarLogger.Infof("SelfDeliveredAndUpdateStatus orderID:%s userName:%s successfully", vendorOrderID, userName) return err } @@ -81,7 +94,7 @@ func (c *BaseScheduler) PickupGoodsAndUpdateStatus(ctx *jxcontext.Context, vendo err = c.PickupGoods(order, c.GetStoreDeliveryType(order, nil) == scheduler.StoreDeliveryTypeByStore, userName) if err == nil { order.Status = model.OrderStatusFinishedPickup - if err = partner.CurOrderManager.UpdateOrderStatusAndFlag(order); err == nil { + if err = partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order); err == nil { globals.SugarLogger.Infof("PickupGoodsAndUpdateStatus orderID:%s userName:%s successfully", vendorOrderID, userName) return err } @@ -134,7 +147,7 @@ func (c *BaseScheduler) AcceptOrRefuseFailedGetOrder(ctx *jxcontext.Context, ord err = partner.GetPurchasePlatformFromVendorID(order.VendorID).AcceptOrRefuseFailedGetOrder(ctx, order, isAcceptIt) } if err == nil { - flag := int8(model.OrderFlagAgreeFailedGetGoods) + flag := model.OrderFlagAgreeFailedGetGoods if !isAcceptIt { flag = model.OrderFlagRefuseFailedGetGoods } @@ -168,7 +181,7 @@ func (c *BaseScheduler) AgreeOrRefuseCancel(ctx *jxcontext.Context, order *model err = partner.GetPurchasePlatformFromVendorID(order.VendorID).AgreeOrRefuseCancel(ctx, order, isAcceptIt, reason) } if err == nil { - flag := int8(model.OrderFlagAgreeUserApplyCancel) + flag := model.OrderFlagAgreeUserApplyCancel if !isAcceptIt { flag = model.OrderFlagRefuseUserApplyCancel } @@ -176,3 +189,43 @@ func (c *BaseScheduler) AgreeOrRefuseCancel(ctx *jxcontext.Context, order *model } return err } + +func (c *BaseScheduler) CancelWaybillByID(ctx *jxcontext.Context, vendorWaybillID string, waybillVendorID int, cancelReasonID int, cancelReason string) (err error) { + bill, err := partner.CurOrderManager.LoadWaybill(vendorWaybillID, waybillVendorID) + if err == nil { + err = c.CancelWaybill(bill, cancelReasonID, cancelReason) + } + return err +} + +func (c *BaseScheduler) AgreeOrRefuseRefund(ctx *jxcontext.Context, afsOrderID string, vendorID, approveType int, reason string) (err error) { + afsOrder, err := partner.CurOrderManager.LoadAfsOrder(afsOrderID, vendorID) + if err == nil { + if c.IsReallyCallPlatformAPI { + err = partner.GetPurchasePlatformFromVendorID(vendorID).AgreeOrRefuseRefund(ctx, afsOrder, approveType, reason) + } + if err == nil { + flag := model.AfsOrderFlagAgreeUserRefund + if approveType == partner.AfsApproveTypeRefused { + flag = model.AfsOrderFlagRefuseUserRefund + afsOrder.RefuseReason = reason + partner.CurOrderManager.UpdateAfsOrderFields(afsOrder, []string{"RefuseReason"}) + } + dao.SetAfsOrderFlag(dao.GetDB(), ctx.GetUserName(), afsOrderID, vendorID, flag) + } + } + return err +} + +func (c *BaseScheduler) ConfirmReceivedReturnGoods(ctx *jxcontext.Context, afsOrderID string, vendorID int) (err error) { + afsOrder, err := partner.CurOrderManager.LoadAfsOrder(afsOrderID, vendorID) + if err == nil { + if c.IsReallyCallPlatformAPI { + err = partner.GetPurchasePlatformFromVendorID(vendorID).ConfirmReceivedReturnGoods(ctx, afsOrder) + } + if err == nil { + dao.SetAfsOrderFlag(dao.GetDB(), ctx.GetUserName(), afsOrderID, vendorID, model.AfsOrderFlagMaskReturnGoods) + } + } + return err +} diff --git a/business/jxcallback/scheduler/defsch/defsch.go b/business/jxcallback/scheduler/defsch/defsch.go index 0cf2a351a..7f86d225e 100644 --- a/business/jxcallback/scheduler/defsch/defsch.go +++ b/business/jxcallback/scheduler/defsch/defsch.go @@ -25,9 +25,10 @@ import ( ) const ( - time2Delivered = 1 * time.Hour // 正常从下单到送达的时间。 - minute2Schedule3rdCarrier = 20 // 收到平台方自有配送的新运单消息后,等待创建三方配送运单的时间(分钟),如果是定时达,会再根据ExpectedDeliveredTime与dingShiDaAheadTime做调整 - minMinute2Schedule3rdCarrier = 5 // 转三方配送最少等待时间(分钟) + time2Delivered = 1 * time.Hour // 正常从下单到送达的时间。 + minute2Schedule3rdCarrier = 20 // 收到平台方自有配送的新运单消息后,等待创建三方配送运单的时间(分钟),如果是定时达,会再根据ExpectedDeliveredTime与dingShiDaAheadTime做调整 + minute2Schedule3rdCarrier4Ebai = 30 // 饿百的最少转自配送需要的时间(分钟) + minMinute2Schedule3rdCarrier = 5 // 转三方配送最少等待时间(分钟) time2AutoPickupMin = 15 * time.Minute // 自动拣货等待时间,这个只有在没有PickDeadline信息才有用,否则会根据PickDeadline设置 second2AutoPickupGap = 60 //随机60秒 @@ -64,13 +65,22 @@ type WatchOrderInfo struct { timerStatusType int // 0表示订单,1表示运单 timerStatus int timer *time.Timer + timerTime time.Time retryCount int // 失败后尝试的次数,调试阶段可能出现死循化,阻止这种情况发生 } type StatusActionConfig struct { partner.StatusActionParams - TimeoutAction func(savedOrderInfo *WatchOrderInfo) (err error) // 超时后需要执行的动作,为nil表示此状态不需要执行监控, nil在GetStatusActionConfig返回时表示不修改缺省 + TimeoutAction func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (err error) // 超时后需要执行的动作,为nil表示此状态不需要执行监控, nil在GetStatusActionConfig返回时表示不修改缺省 + ShouldSetTimer func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool +} + +func (c *StatusActionConfig) CallShouldSetTimer(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool { + if c.ShouldSetTimer != nil { + return c.ShouldSetTimer(savedOrderInfo, bill) + } + return true } // 重要:此调度器要求同一定单的处理逻辑必须是序列化了的,不然会有并发问题 @@ -114,11 +124,6 @@ func (s *WatchOrderInfo) updateOrderStoreFeature(order *model.GoodsOrder) (err e s.autoPickupTimeoutMinute = int(storeMap.AutoPickup) s.storeDeliveryType = FixedScheduler.GetStoreDeliveryType(order, storeMap) globals.SugarLogger.Debugf("updateOrderStoreFeature orderID:%s, s.storeDeliveryType:%d", order.VendorOrderID, s.storeDeliveryType) - // if s.storeDeliveryType == scheduler.StoreDeliveryTypeByStore && (order.DeliveryFlag&model.OrderDeliveryFlagMaskPurcahseDisabled) == 0 { - // order.DeliveryFlag |= model.OrderDeliveryFlagMaskPurcahseDisabled - // err = partner.CurOrderManager.UpdateOrderStatusAndFlag(order) - // } - // globals.SugarLogger.Debugf("updateOrderStoreFeature orderID:%s, s.storeDeliveryType:%d, order.DeliveryFlag:%d", order.VendorOrderID, s.storeDeliveryType, order.DeliveryFlag) } return err } @@ -136,7 +141,7 @@ func init() { TimerType: partner.TimerTypeBaseStatusTime, Timeout: 10 * time.Millisecond, }, - TimeoutAction: func(savedOrderInfo *WatchOrderInfo) (err error) { + TimeoutAction: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (err error) { order := savedOrderInfo.order mobile := order.ConsigneeMobile if order.ConsigneeMobile2 != "" { @@ -144,10 +149,14 @@ func init() { } _ = sch.handleAutoAcceptOrder(order.VendorOrderID, order.VendorID, mobile, jxutils.GetSaleStoreIDFromOrder(order), nil, func(isAcceptIt bool) error { if err = sch.AcceptOrRefuseOrder(order, isAcceptIt, ""); err != nil && err != scheduler.ErrOrderStatusAlreadySatisfyCurOperation { + partner.CurOrderManager.OnOrderMsg(order, "自动接单失败", err.Error()) // 为了解决京东新消息与接单消息乱序的问题 if errWithCode, ok := err.(*utils.ErrorWithCode); ok && errWithCode.Level() == 1 && errWithCode.IntCode() == -1 { if order2, err2 := partner.GetPurchasePlatformFromVendorID(order.VendorID).GetOrder(order.VendorOrderID); err2 == nil && order2.Status > order.Status { - sch.OnOrderStatusChanged(order, model.Order2Status(order2), false) + order.Status = order2.Status + jxutils.CallMsgHandlerAsync(func() { + sch.OnOrderStatusChanged(order, model.Order2Status(order2), false) + }, jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID)) err = nil } else { err = err2 @@ -165,6 +174,9 @@ func init() { }) return nil }, + ShouldSetTimer: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool { + return savedOrderInfo.order.Status == model.OrderStatusNew + }, }, model.OrderStatusAccepted: &StatusActionConfig{ // 自动拣货 StatusActionParams: partner.StatusActionParams{ @@ -172,12 +184,17 @@ func init() { Timeout: time2AutoPickupMin, TimeoutGap: second2AutoPickupGap, }, - TimeoutAction: func(savedOrderInfo *WatchOrderInfo) (err error) { + TimeoutAction: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (err error) { if savedOrderInfo.autoPickupTimeoutMinute > 0 { - return sch.autoPickupGood(savedOrderInfo) + if err = sch.autoPickupGood(savedOrderInfo); err != nil { + partner.CurOrderManager.OnOrderMsg(savedOrderInfo.order, "自动拣货失败", err.Error()) + } } return nil }, + ShouldSetTimer: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool { + return savedOrderInfo.autoPickupTimeoutMinute > 0 + }, }, model.OrderStatusFinishedPickup: &StatusActionConfig{ StatusActionParams: partner.StatusActionParams{ @@ -185,27 +202,62 @@ func init() { Timeout: 1 * time.Second, TimeoutGap: 0, }, - TimeoutAction: func(savedOrderInfo *WatchOrderInfo) (err error) { + TimeoutAction: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (err error) { if savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore { // 自配送商家使用 return sch.createWaybillOn3rdProviders(savedOrderInfo, nil) } return nil }, + ShouldSetTimer: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool { + return savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore + }, }, }, map[int]*StatusActionConfig{ + // todo 平台物流二次创建运单的话,这个TIMER有问题 model.WaybillStatusNew: &StatusActionConfig{ StatusActionParams: partner.StatusActionParams{ TimerType: partner.TimerTypeBaseStatusTime, Timeout: minute2Schedule3rdCarrier * time.Minute, }, - TimeoutAction: func(savedOrderInfo *WatchOrderInfo) (err error) { - if savedOrderInfo.storeDeliveryType != scheduler.StoreDeliveryTypeByStore { // 非自配送商家使用 + TimeoutAction: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (err error) { + // 饿百转自送的时机不太清楚,暂时禁用超时转自送,在饿百运单取消时还是会自动创建 + if savedOrderInfo.storeDeliveryType != scheduler.StoreDeliveryTypeByStore && savedOrderInfo.order.VendorID != model.VendorIDEBAI { // 非自配送商家使用 return sch.createWaybillOn3rdProviders(savedOrderInfo, nil) } return nil }, + ShouldSetTimer: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool { + return savedOrderInfo.storeDeliveryType != scheduler.StoreDeliveryTypeByStore && + savedOrderInfo.order.VendorID == bill.WaybillVendorID && + savedOrderInfo.order.VendorID != model.VendorIDEBAI + }, }, + //* + model.WaybillStatusCanceled: &StatusActionConfig{ + StatusActionParams: partner.StatusActionParams{ + TimerType: partner.TimerTypeBaseNow, + Timeout: 5 * time.Second, + }, + TimeoutAction: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (err error) { + order := savedOrderInfo.order + if (order.Status >= model.OrderStatusFinishedPickup && order.Status < model.OrderStatusEndBegin) && + savedOrderInfo.order.VendorID == bill.WaybillVendorID && + savedOrderInfo.storeDeliveryType != scheduler.StoreDeliveryTypeByStore && + order.VendorID == model.VendorIDEBAI { // 非自配送商家使用 + return sch.createWaybillOn3rdProviders(savedOrderInfo, nil) + } + return nil + }, + ShouldSetTimer: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool { + order := savedOrderInfo.order + return (order.Status >= model.OrderStatusFinishedPickup && order.Status < model.OrderStatusEndBegin) && + savedOrderInfo.order.VendorID == bill.WaybillVendorID && + savedOrderInfo.storeDeliveryType != scheduler.StoreDeliveryTypeByStore && + order.VendorID == model.VendorIDEBAI + }, + }, + //*/ }, } } @@ -243,10 +295,11 @@ func (s *DefScheduler) OnOrderStatusChanged(order *model.GoodsOrder, status *mod globals.SugarLogger.Debugf("OnOrderStatusChanged orderID:%s %s, status:%v", status.VendorOrderID, model.OrderStatusName[status.Status], status) if order == nil { globals.SugarLogger.Warnf("OnOrderStatusChanged order is nil, status:%s", utils.Format4Output(status, true)) + } else if order.Status > model.OrderStatusUnknown && status.Status > model.OrderStatusUnknown && order.Status != status.Status { + globals.SugarLogger.Warnf("OnOrderStatusChanged strange order:%s, status:%s", utils.Format4Output(order, true), utils.Format4Output(status, true)) } savedOrderInfo := s.loadSavedOrderFromMap(status, false) savedOrderInfo.SetOrder(order) - // s.updateOrderByStatus(savedOrderInfo.order, status) // if status.Status == model.OrderStatusNew { // if !isPending { @@ -259,7 +312,7 @@ func (s *DefScheduler) OnOrderStatusChanged(order *model.GoodsOrder, status *mod (order.LockStatus == model.OrderStatusUnknown && (status.Status > model.OrderStatusUnknown || status.Status == model.OrderStatusRefuseFailedGetGoods)) { // 只处理状态转换,一般消息不处理 if status.Status == model.OrderStatusRefuseFailedGetGoods && order.Status != model.OrderStatusFinishedPickup && !model.IsOrderFinalStatus(order.Status) { order.Status = model.OrderStatusFinishedPickup - partner.CurOrderManager.UpdateOrderStatusAndFlag(order) + partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order) } s.resetTimer(savedOrderInfo, nil, isPending) if status.Status >= model.OrderStatusDelivering { @@ -271,12 +324,12 @@ func (s *DefScheduler) OnOrderStatusChanged(order *model.GoodsOrder, status *mod if !(status.Status == model.OrderStatusCanceled) { // 订单取消时,取消所有运单 curWaybill = savedOrderInfo.waybills[savedOrderInfo.order.WaybillVendorID] if status.Status == model.OrderStatusFinished { - if curWaybill != nil && curWaybill.WaybillVendorID != curWaybill.OrderVendorID { + if curWaybill != nil && !model.IsWaybillPlatformOwn(curWaybill) { globals.SugarLogger.Infof("OnOrderStatusChanged [运营2]订单orderID:%s可能被手动点击送达,会对程序状态产生不利影响,请通知门店不要这样操作!", status.VendorOrderID) } } } - s.cancelOtherWaybills(savedOrderInfo, curWaybill, partner.CancelWaybillReasonOther, partner.CancelWaybillReasonStrOrderAlreadyFinished) + s.cancelOtherWaybillsCheckOrderDeliveryFlag(savedOrderInfo, curWaybill, partner.CancelWaybillReasonOther, partner.CancelWaybillReasonStrOrderAlreadyFinished) if status.Status >= model.OrderStatusEndBegin { s.orderMap.Delete(jxutils.GetUniversalOrderIDFromOrderStatus(status)) } @@ -285,12 +338,13 @@ func (s *DefScheduler) OnOrderStatusChanged(order *model.GoodsOrder, status *mod if order.LockStatus != model.OrderStatusUnknown { s.stopTimer(savedOrderInfo) } - if model.IsOrderLockStatus(status.Status) || - model.IsOrderUnlockStatus(status.Status) || - status.Status == model.OrderStatusApplyFailedGetGoods || - status.Status == model.OrderStatusAgreeFailedGetGoods || - status.Status == model.OrderStatusDeliverFailed { - if isPending { + if !isPending { + if status.Status == model.OrderStatusFinishedPickup { + msghub.OnFinishedPickup(savedOrderInfo.order) + } else if status.Status == model.OrderStatusApplyCancel || //model.IsOrderLockStatus(status.Status) || + status.Status == model.OrderStatusApplyFailedGetGoods || //model.IsOrderUnlockStatus(status.Status) || + status.Status == model.OrderStatusAgreeFailedGetGoods || + status.Status == model.OrderStatusDeliverFailed { if status.Status == model.OrderStatusApplyCancel { utils.CallFuncAsync(func() { weixinmsg.NotifyUserApplyCancel(savedOrderInfo.order, status.Remark) @@ -323,10 +377,10 @@ func (s *DefScheduler) OnWaybillStatusChanged(bill *model.Waybill, isPending boo if !isPending { if order.Status > model.OrderStatusEndBegin { s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime) - } else if s.IsOrderHasWaybill(order) { + } else if model.IsOrderHaveWaybill(order) { globals.SugarLogger.Debugf("OnWaybillStatusChanged multiple waybill created, bill:%v", bill) - if s.IsOrderPlatformWaybill(bill) { // 是购物平台运单 - if order.VendorID != order.WaybillVendorID { // 既有运单不是购物平台运单 + if model.IsWaybillPlatformOwn(bill) { // 是购物平台运单 + if !model.IsOrderHaveOwnWaybill(order) { // 既有运单不是购物平台运单 globals.SugarLogger.Infof("OnWaybillStatusChanged bill:%v purchase platform bill came later than others, strange!!!", bill) oldBill := savedOrderInfo.waybills[order.WaybillVendorID] if oldBill != nil { @@ -335,124 +389,146 @@ func (s *DefScheduler) OnWaybillStatusChanged(bill *model.Waybill, isPending boo globals.SugarLogger.Warnf("OnWaybillStatusChanged bill:%v, oldBill is null, strange!!!", bill) } } - bill.WaybillVendorID = model.VendorIDUnknown - s.updateOrderByBill(order, bill, false) + s.updateOrderByBill(order, nil, false) } else { s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime) } } + flag2Clear := model.WaybillVendorID2Mask(bill.WaybillVendorID) + if order.DeliveryFlag&flag2Clear != 0 { + order.DeliveryFlag &= ^flag2Clear + err = partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order) + } } // 只有购物平台的新运单消息才会启动抢单TIMER - if s.IsOrderPlatformWaybill(bill) { + if model.IsWaybillPlatformOwn(bill) { s.resetTimer(savedOrderInfo, bill, isPending) } } else { isBillExist := s.updateBillsInfo(savedOrderInfo, bill) if !isBillExist { + // s.addWaybill2Map(savedOrderInfo, bill) // updateBillsInfo中会添加 globals.SugarLogger.Debugf("OnWaybillStatusChanged bill not exist! orderID:%s, bill:%v", bill.VendorOrderID, bill) } switch bill.Status { - case model.WaybillStatusAccepted: + case model.WaybillStatusAccepted, model.WaybillStatusCourierArrived, model.WaybillStatusDelivering: s.resetTimer(savedOrderInfo, bill, isPending) if (isBillExist || bill.WaybillVendorID != model.VendorIDDada) && !isPending { // todo 达达运单有错序的情况,临时看看 isBillAlreadyCandidate := s.isBillCandidate(order, bill) // todo 购买平台的运单,优先级最高,但这样写也可能带来问题,即在这个时间,因为之前3方已经接单,已经发出了转自送请求(而且可能成功了),所以加个状态判断 - if order.WaybillVendorID == model.VendorIDUnknown || - (s.IsOrderPlatformWaybill(bill) && order.VendorID != order.WaybillVendorID && (order.DeliveryFlag&model.OrderDeliveryFlagMaskPurcahseDisabled) == 0) { - if s.IsOrderHasWaybill(order) { + if !model.IsOrderHaveWaybill(order) || + (model.IsWaybillPlatformOwn(bill) && !model.IsOrderHaveOwnWaybill(order) && (order.DeliveryFlag&model.OrderDeliveryFlagMaskPurcahseDisabled) == 0) { + if model.IsOrderHaveWaybill(order) { // 进到这里的原因是,在这个时间点,购物平台物流已经抢单(但抢单消息还没有被收到)(比如:818810379000941) - globals.SugarLogger.Infof("OnWaybillStatusChanged orderID:%s purchase platform waybill arrvied later, may case problem", order.VendorOrderID) + globals.SugarLogger.Infof("OnWaybillStatusChanged orderID:%s purchase platform waybill arrvied later, may cause problem", order.VendorOrderID) } s.updateOrderByBill(order, bill, false) - s.cancelOtherWaybills(savedOrderInfo, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime) - if !s.IsOrderPlatformWaybill(bill) { - if savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore { - s.SelfDeliverDelivering(savedOrderInfo.order, bill.CourierMobile) + s.cancelOtherWaybillsCheckOrderDeliveryFlag(savedOrderInfo, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime) + + if model.IsWaybillPlatformOwn(bill) { + if bill.Status == model.WaybillStatusDelivering { + // 强制将订单状态置为配送中? + order.Status = model.OrderStatusDelivering + partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order) + } + } else { + if savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore || + model.IsSpecialOrderPlatformWaybill(bill) { + if err := s.SelfDeliverDelivering(savedOrderInfo.order, bill.CourierMobile); err != nil { + partner.CurOrderManager.OnOrderMsg(order, "自送出设置失败", err.Error()) + } + utils.CallFuncAsync(func() { + weixinmsg.NotifyWaybillStatus(bill, order, isBillAlreadyCandidate) + }) } else { s.swtich2SelfDeliverWithRetry(savedOrderInfo, bill, 2, 10*time.Second) } - } else if s.IsSpecialOrderPlatformWaybill(bill) { - s.SelfDeliverDelivering(savedOrderInfo.order, bill.CourierMobile) } } else if !s.isBillCandidate(order, bill) && bill.WaybillVendorID != order.VendorID { // 发生这种情况的原因就是两个接单事件几乎同时到达(来不及取消),也算正常 s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime) globals.SugarLogger.Infof("OnWaybillStatusChanged Accepted orderID:%s got multiple bill:%v", order.VendorOrderID, bill) } - if s.isBillCandidate(order, bill) && order.WaybillVendorID != order.VendorID { - if !isBillAlreadyCandidate || !s.isWaybillCourierSame(savedOrderInfo, bill) { - utils.CallFuncAsync(func() { - weixinmsg.NotifyWaybillStatus(bill, order, isBillAlreadyCandidate) - }) - } + if isBillAlreadyCandidate && !s.isWaybillCourierSame(savedOrderInfo, bill) && !model.IsWaybillPlatformOwn(bill) { + utils.CallFuncAsync(func() { + weixinmsg.NotifyWaybillStatus(bill, order, isBillAlreadyCandidate) + }) + } + flag2Clear := model.WaybillVendorID2Mask(bill.WaybillVendorID) + if order.DeliveryFlag&flag2Clear != 0 { + order.DeliveryFlag &= ^flag2Clear + err = partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order) } } case model.WaybillStatusAcceptCanceled: if s.isBillCandidate(order, bill) { s.resetTimer(savedOrderInfo, bill, isPending) if !isPending { - bill.WaybillVendorID = model.VendorIDUnknown - s.updateOrderByBill(order, bill, false) - - // 取消抢单应该不需要发3方运单 - // s.createWaybillOn3rdProviders(savedOrderInfo, bill) + s.updateOrderByBill(order, nil, true) } - } else if s.IsOrderHasWaybill(order) { + } else if model.IsOrderHaveWaybill(order) { s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime) if !isPending { globals.SugarLogger.Warnf("OnWaybillStatusChanged AcceptCanceled orderID:%s got multiple bill:%v, order details:%v", order.VendorOrderID, bill, order) } } - case model.WaybillStatusCourierArrived: // do nothing - s.resetTimer(savedOrderInfo, bill, isPending) - if s.isBillCandidate(order, bill) { - } else { - // s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime) - globals.SugarLogger.Infof("OnWaybillStatusChanged CourierArrived order(%d, %s) bill(%d, %s), bill:%v shouldn't get here", order.WaybillVendorID, order.VendorWaybillID, bill.WaybillVendorID, bill.VendorWaybillID, bill) - } + // case model.WaybillStatusCourierArrived: // do nothing + // s.resetTimer(savedOrderInfo, bill, isPending) + // if s.isBillCandidate(order, bill) { + // } else { + // // s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime) + // globals.SugarLogger.Infof("OnWaybillStatusChanged CourierArrived order(%d, %s) bill(%d, %s), bill:%v shouldn't get here", order.WaybillVendorID, order.VendorWaybillID, bill.WaybillVendorID, bill.VendorWaybillID, bill) + // } case model.WaybillStatusCanceled, model.WaybillStatusFailed: s.removeWaybillFromMap(savedOrderInfo, bill.WaybillVendorID) if s.isBillCandidate(order, bill) || order.WaybillVendorID == model.VendorIDUnknown { s.resetTimer(savedOrderInfo, nil, isPending) if !isPending { - if s.IsOrderHasWaybill(order) { - bill.WaybillVendorID = model.VendorIDUnknown - s.updateOrderByBill(order, bill, false) + if model.IsOrderHaveWaybill(order) { + s.updateOrderByBill(order, nil, true) } // 3方的运单取消才会重新发起创建3方订单,购物平台的运单取消后,它本身还会再创建新运单(NewWaybill事件有相应TIMER)),至少京东是这样的,暂时按京东的行为来 // 现在发现饿百取消订单后不会再创建运单了,所以饿百运单取消也允许直接创建三方运单 // 之前的条件是order.Status < model.OrderStatusDelivering,但像订单902322817000122确实有在配送中取消状态,改成非订单结束状态都可以 // OrderStatusFinishedPickup状态的订单依赖于TIMER重新建运单 - if order.Status >= model.OrderStatusDelivering && order.Status < model.OrderStatusEndBegin && (bill.WaybillVendorID != order.VendorID || order.VendorID == model.VendorIDEBAI) { - s.createWaybillOn3rdProviders(savedOrderInfo, nil) + if bill.DeliveryFlag&model.WaybillDeliveryFlagMaskActiveCancel == 0 { + if (order.Status >= model.OrderStatusFinishedPickup && order.Status < model.OrderStatusEndBegin) && (bill.WaybillVendorID != order.VendorID /* || bill.WaybillVendorID == model.VendorIDEBAI*/) { + s.createWaybillOn3rdProviders(savedOrderInfo, nil) + } } } } - case model.WaybillStatusDelivering: - s.resetTimer(savedOrderInfo, bill, isPending) - if s.isBillCandidate(order, bill) { - // do nothing - } else { - // s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime) - globals.SugarLogger.Infof("OnWaybillStatusChanged Delivering order(%d, %s) bill(%d, %s), bill:%v shouldn't get here", order.WaybillVendorID, order.VendorWaybillID, bill.WaybillVendorID, bill.VendorWaybillID, bill) - } + // case model.WaybillStatusDelivering: + // s.resetTimer(savedOrderInfo, bill, isPending) + // if s.isBillCandidate(order, bill) { + // // do nothing + // } else { + // // s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime) + // globals.SugarLogger.Infof("OnWaybillStatusChanged Delivering order(%d, %s) bill(%d, %s), bill:%v shouldn't get here", order.WaybillVendorID, order.VendorWaybillID, bill.WaybillVendorID, bill.VendorWaybillID, bill) + // } case model.WaybillStatusDelivered: s.resetTimer(savedOrderInfo, bill, isPending) s.removeWaybillFromMap(savedOrderInfo, bill.WaybillVendorID) - if !s.IsOrderPlatformWaybill(bill) && !isPending { - if savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore { - s.SelfDeliverDelivered(order, "") - } else { - s.Swtich2SelfDelivered(order, "") + if !isPending { + var err2 error + if !model.IsWaybillPlatformOwn(bill) { + if savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore { + err2 = s.SelfDeliverDelivered(order, "") + } else { + err2 = s.Swtich2SelfDelivered(order, "") + } + } else if model.IsSpecialOrderPlatformWaybill(bill) { + err2 = s.SelfDeliverDelivered(savedOrderInfo.order, "") + } + if err2 != nil { + partner.CurOrderManager.OnOrderMsg(order, "送达设置失败", err2.Error()) } - } else if s.IsSpecialOrderPlatformWaybill(bill) { - s.SelfDeliverDelivered(savedOrderInfo.order, "") } if !s.isBillCandidate(order, bill) { // 一般只会消息乱序才会到这里,即新订单消息在运单接单消息后到达 // 典型的一个:1223633660228537567 globals.SugarLogger.Infof("OnWaybillStatusChanged Delivered order(%d, %s) bill(%d, %s), bill:%v shouldn't get here", order.WaybillVendorID, order.VendorWaybillID, bill.WaybillVendorID, bill.VendorWaybillID, bill) - if order.WaybillVendorID == model.VendorIDUnknown { + if !model.IsOrderHaveWaybill(order) { s.updateOrderByBill(order, bill, false) } } @@ -461,14 +537,16 @@ func (s *DefScheduler) OnWaybillStatusChanged(bill *model.Waybill, isPending boo weixinmsg.NotifyWaybillStatus(bill, order, false) }) } - case model.WaybillStatusNeverSend: // 平台不配送,直接创建三方运单 + // case model.WaybillStatusNeverSend: // 平台不配送,直接创建三方运单 + // s.resetTimer(savedOrderInfo, bill, isPending) + // s.removeWaybillFromMap(savedOrderInfo, bill.WaybillVendorID) + // if order.WaybillVendorID == model.VendorIDUnknown { + // s.createWaybillOn3rdProviders(savedOrderInfo, nil) + // } + default: s.resetTimer(savedOrderInfo, bill, isPending) - s.removeWaybillFromMap(savedOrderInfo, bill.WaybillVendorID) - if order.WaybillVendorID == model.VendorIDUnknown { - s.createWaybillOn3rdProviders(savedOrderInfo, nil) - } } - s.updateBillsInfo(savedOrderInfo, bill) // 更新可能的运单状态变化 + // s.updateBillsInfo(savedOrderInfo, bill) // 更新可能的运单状态变化 } // } } @@ -481,7 +559,7 @@ func (s *DefScheduler) isWaybillCourierSame(savedOrderInfo *WatchOrderInfo, bill func (s *DefScheduler) addWaybill2Map(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) { if _, ok := savedOrderInfo.waybills[bill.WaybillVendorID]; ok { - if !s.IsOrderPlatformWaybill(bill) { // 购买平台重复发相同号的新运单是正常的,京东就是 + if !model.IsWaybillPlatformOwn(bill) { // 购买平台重复发相同号的新运单是正常的,京东就是 globals.SugarLogger.Warnf("addWaybill2Map bill:%v already exists", bill) } } @@ -503,17 +581,17 @@ func (s *DefScheduler) createWaybillOn3rdProviders(savedOrderInfo *WatchOrderInf if err = s.canOrderCreateWaybillNormally(order); err == nil { if (order.DeliveryFlag & model.OrderDeliveryFlagMaskScheduleDisabled) == 0 { if savedOrderInfo.retryCount <= maxWaybillRetryCount { - _, err = s.CreateWaybillOnProviders4SavedOrder(jxcontext.AdminCtx, savedOrderInfo, false) + savedOrderInfo.retryCount++ + _, err = s.CreateWaybillOnProviders4SavedOrder(jxcontext.AdminCtx, savedOrderInfo, nil, false, 0, 0) } else { err = fmt.Errorf("订单:%s已经自动创建过了%d次运单,请人工处理", order.VendorOrderID, savedOrderInfo.retryCount) - globals.SugarLogger.Infof("createWaybillOn3rdProviders [运营]同一订单orderID:%s尝试了%d次创建运单失败, 停止调度,如果还需要发单,请人工处理", order.VendorOrderID, savedOrderInfo.retryCount) + globals.SugarLogger.Infof("createWaybillOn3rdProviders [运营2]同一订单orderID:%s尝试了%d次创建运单失败, 停止调度,如果还需要发单,请人工处理", order.VendorOrderID, savedOrderInfo.retryCount) } } else { globals.SugarLogger.Debugf("createWaybillOn3rdProviders, orderID:%s, store:%d dont't support 3rd delivery platform", order.VendorOrderID, jxutils.GetSaleStoreIDFromOrder(order)) } - if err != nil { - partner.CurOrderManager.OnOrderMsg(order, "自动创建三方运单", utils.LimitUTF8StringLen(err.Error(), 255)) + partner.CurOrderManager.OnOrderMsg(order, "自动创建三方运单失败", err.Error()) } } else { err = nil @@ -521,73 +599,79 @@ func (s *DefScheduler) createWaybillOn3rdProviders(savedOrderInfo *WatchOrderInf return err } -func (s *DefScheduler) cancelOtherWaybills(savedOrderInfo *WatchOrderInfo, bill2Keep *model.Waybill, cancelReasonID int, cancelReason string) (err error) { - globals.SugarLogger.Debugf("cancelOtherWaybills, orderID:%s, bill:%v", savedOrderInfo.order.VendorOrderID, bill2Keep) +func (s *DefScheduler) cancelOtherWaybillsCheckOrderDeliveryFlag(savedOrderInfo *WatchOrderInfo, bill2Keep *model.Waybill, cancelReasonID int, cancelReason string) (err error) { + globals.SugarLogger.Debugf("cancelOtherWaybillsCheckOrderDeliveryFlag, orderID:%s, bill:%v", savedOrderInfo.order.VendorOrderID, bill2Keep) if (savedOrderInfo.order.DeliveryFlag & model.OrderDeliveryFlagMaskScheduleDisabled) == 0 { - err = s.cancelOtherWaybills2(savedOrderInfo, bill2Keep, cancelReasonID, cancelReason) + err = s.cancelOtherWaybills(savedOrderInfo, bill2Keep, cancelReasonID, cancelReason) } else { - globals.SugarLogger.Debugf("cancelOtherWaybills, orderID:%s, bill:%v stop schedule", savedOrderInfo.order.VendorOrderID, bill2Keep) + globals.SugarLogger.Debugf("cancelOtherWaybillsCheckOrderDeliveryFlag, orderID:%s, bill:%v stop schedule", savedOrderInfo.order.VendorOrderID, bill2Keep) } return err } -func (s *DefScheduler) cancelOtherWaybills2(savedOrderInfo *WatchOrderInfo, bill2Keep *model.Waybill, cancelReasonID int, cancelReason string) (err error) { - globals.SugarLogger.Debugf("cancelOtherWaybills2, orderID:%s, bill:%v", savedOrderInfo.order.VendorOrderID, bill2Keep) - toBeDeleted := []*model.Waybill{} +func (s *DefScheduler) cancelOtherWaybills(savedOrderInfo *WatchOrderInfo, bill2Keep *model.Waybill, cancelReasonID int, cancelReason string) (err error) { + globals.SugarLogger.Debugf("cancelOtherWaybills, orderID:%s, bill:%v", savedOrderInfo.order.VendorOrderID, bill2Keep) for _, v := range savedOrderInfo.waybills { - if !s.IsOrderPlatformWaybill(v) && (bill2Keep == nil || !(v.WaybillVendorID == bill2Keep.WaybillVendorID && v.VendorWaybillID == bill2Keep.VendorWaybillID)) { - err2 := s.ProxyCancelWaybill(savedOrderInfo.order, v, cancelReasonID, cancelReason) + if v.Status < model.WaybillStatusEndBegin && + !model.IsWaybillPlatformOwn(v) && + (bill2Keep == nil || !(v.WaybillVendorID == bill2Keep.WaybillVendorID && v.VendorWaybillID == bill2Keep.VendorWaybillID)) { + err2 := s.CancelWaybill(v, cancelReasonID, cancelReason) if err2 == nil { - toBeDeleted = append(toBeDeleted, v) - } - // 至少返回一个错误 - if err == nil && err2 != nil { - err = err2 + // 在这里就从map里删除,而不是等收到运单结束事件才删除,可避免不必要的重复取消(第二次取消还会失败) + s.removeWaybillFromMap(savedOrderInfo, v.WaybillVendorID) + } else { + // 至少返回一个错误 + if err == nil { + err = err2 + } + partner.CurOrderManager.OnOrderMsg(savedOrderInfo.order, "取消三方运单失败", err2.Error()) } } } - if len(toBeDeleted) > 0 { - // todo 这里为什么要删除运单,应该只需要在运单完成,取消或失败时才删除 - // for _, v := range toBeDeleted { - // s.removeWaybillFromMap(savedOrderInfo, v.WaybillVendorID) - // } - } else { - globals.SugarLogger.Debugf("cancelOtherWaybills, orderID:%s, bill:%v cancel 0 bills", savedOrderInfo.order.VendorOrderID, bill2Keep) - } return err } func (s *DefScheduler) swtich2SelfDeliverWithRetry(savedOrderInfo *WatchOrderInfo, bill *model.Waybill, retryCount int, duration time.Duration) { order := savedOrderInfo.order globals.SugarLogger.Debugf("swtich2SelfDeliverWithRetry orderID:%s", order.VendorOrderID) - if (order.DeliveryFlag & model.OrderDeliveryFlagMaskPurcahseDisabled) == 0 { - if order.WaybillVendorID != order.VendorID { - if err := s.Swtich2SelfDeliver(order, ""); err != nil && err != scheduler.ErrOrderStatusAlreadySatisfyCurOperation { - globals.SugarLogger.Infof("swtich2SelfDeliverWithRetry failed, bill:%v, err:%v", bill, err) - if retryCount > 0 { - utils.AfterFuncWithRecover(duration, func() { - jxutils.CallMsgHandlerAsync(func() { - s.swtich2SelfDeliverWithRetry(savedOrderInfo, bill, retryCount-1, duration) - }, order.VendorOrderID) - }) - } else { - globals.SugarLogger.Infof("swtich2SelfDeliverWithRetry finally failed, orderID:%s bill:%v, err:%v", order.VendorOrderID, bill, err) - if s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonSwitch2SelfFailed, partner.CancelWaybillReasonStrSwitch2SelfFailed) == nil { - // 转自送失败的取消,要将订单中的运单状态更新 - if s.isBillCandidate(order, bill) { - bill.WaybillVendorID = model.VendorIDUnknown - s.updateOrderByBill(order, bill, false) - } + if order.WaybillVendorID != order.VendorID { + if err := s.Swtich2SelfDeliver(order, ""); err != nil && err != scheduler.ErrOrderStatusAlreadySatisfyCurOperation { + globals.SugarLogger.Infof("swtich2SelfDeliverWithRetry failed, bill:%v, err:%v", bill, err) + if retryCount > 0 { + utils.AfterFuncWithRecover(duration, func() { + jxutils.CallMsgHandlerAsync(func() { + s.swtich2SelfDeliverWithRetry(savedOrderInfo, bill, retryCount-1, duration) + }, jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID)) + }) + } else { + errStr := fmt.Sprintf("订单:%s转自配送失败, 错误信息:%v", order.VendorOrderID, err) + globals.SugarLogger.Info(errStr) + if s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonSwitch2SelfFailed, partner.CancelWaybillReasonStrSwitch2SelfFailed) == nil { + // 转自送失败的取消,要将订单中的运单状态更新 + if s.isBillCandidate(order, bill) { + s.updateOrderByBill(order, nil, false) } } - } else { - s.removeWaybillFromMap(savedOrderInfo, order.VendorID) + order.DeliveryFlag |= model.OrderDeliveryFlagMaskScheduleDisabled + partner.CurOrderManager.UpdateOrderFields(order, []string{"DeliveryFlag"}) + + partner.CurOrderManager.OnOrderMsg(order, "转商家自配送失败", errStr) } } else { - // 进到这里的原因是,在这个时间点,购物平台物流已经抢单(但抢单消息还没有被收到),所以转自送会失败 (比如:818810379000941),更好的做法应该是判断Swtich2SelfDeliver的返回值,这种情况下就不得试了 - globals.SugarLogger.Infof("swtich2SelfDeliverWithRetry orderID:%s status is wrong(maybe purchase platform accepted waybill)", order.VendorOrderID) - // globals.SugarLogger.Warnf("swtich2SelfDeliverWithRetry orderID:%s status is wrong, order details:%v", order.VendorOrderID, order) + utils.CallFuncAsync(func() { + weixinmsg.NotifyWaybillStatus(bill, order, false) + }) + s.removeWaybillFromMap(savedOrderInfo, order.VendorID) } + } else { + s.cancelOtherWaybills(savedOrderInfo, nil, partner.CancelWaybillReasonOther, partner.CancelWaybillReasonStrNotAcceptIntime) + order.DeliveryFlag |= model.OrderDeliveryFlagMaskScheduleDisabled + partner.CurOrderManager.UpdateOrderFields(order, []string{"DeliveryFlag"}) + + // 进到这里的原因是,在这个时间点,购物平台物流已经抢单(但抢单消息还没有被收到),所以转自送会失败 (比如:818810379000941),更好的做法应该是判断Swtich2SelfDeliver的返回值,这种情况下就不得试了 + globals.SugarLogger.Infof("swtich2SelfDeliverWithRetry orderID:%s status is wrong(maybe purchase platform accepted waybill)", order.VendorOrderID) + partner.CurOrderManager.OnOrderMsg(order, "转商家自配送失败", "平台物流已接单") + // globals.SugarLogger.Warnf("swtich2SelfDeliverWithRetry orderID:%s status is wrong, order details:%v", order.VendorOrderID, order) } } @@ -624,7 +708,7 @@ func (s *DefScheduler) stopTimer(savedOrderInfo *WatchOrderInfo) { if savedOrderInfo.timer != nil { globals.SugarLogger.Debugf("stopTimer orderID:%s", savedOrderInfo.order.VendorOrderID) savedOrderInfo.timer.Stop() - savedOrderInfo.timerStatus = 0 + savedOrderInfo.timerStatus = model.OrderStatusUnknown savedOrderInfo.timerStatusType = scheduler.TimerStatusTypeUnknown savedOrderInfo.timer = nil } @@ -641,47 +725,61 @@ func (s *DefScheduler) resetTimer(savedOrderInfo *WatchOrderInfo, bill *model.Wa statusTime = bill.StatusTime } globals.SugarLogger.Debugf("resetTimer, orderID:%s statusType:%d status:%v", order.VendorOrderID, statusType, status) - if statusType != savedOrderInfo.timerStatusType || status >= savedOrderInfo.timerStatus { // 新设置的TIMER不能覆盖状态在其后的TIMER,如果状态回绕,需要注意 + if isStatusNewer(savedOrderInfo.timerStatusType, savedOrderInfo.timerStatus, statusType, status) { // 新设置的TIMER不能覆盖状态在其后的TIMER,如果状态回绕,需要注意 config := s.mergeOrderStatusConfig(savedOrderInfo, statusTime, statusType, status) if config == nil || config.TimerType != partner.TimerTypeByPass { s.stopTimer(savedOrderInfo) } if config != nil && config.TimeoutAction != nil && config.TimerType != partner.TimerTypeByPass { - timeout := config.GetRefTimeout(statusTime) - if config.TimeoutGap != 0 { - timeout += time.Duration(rand.Intn(int(config.TimeoutGap))) * time.Second - } - if isPending && timeout < pendingOrderTimerMaxSecond*time.Second { // 如果是PENDING的订单,则将其分布到2--5秒内,让后续事件有机会执行 - timeout = time.Duration(jxutils.MapValue2Scope(int64(timeout), -pendingOrderTimerMinMinSecond*1000, pendingOrderTimerMaxSecond*1000, pendingOrderTimerMinSecond*1000, pendingOrderTimerMaxSecond*1000)) * time.Millisecond - } else if timeout < 0 { - timeout = 0 - } - if timeout == 0 { - config.TimeoutAction(savedOrderInfo) - } else { - timerName := "" - if statusType == scheduler.TimerStatusTypeOrder { - timerName = model.OrderStatusName[status] - } else if statusType == scheduler.TimerStatusTypeWaybill { - timerName = model.WaybillStatusName[status] + if config.CallShouldSetTimer(savedOrderInfo, bill) { + timeout := config.GetRefTimeout(statusTime, order.OrderCreatedAt) + if config.TimeoutGap != 0 { + timeout += time.Duration(rand.Intn(int(config.TimeoutGap))) * time.Second } - savedOrderInfo.timerStatusType = statusType - savedOrderInfo.timerStatus = status - savedOrderInfo.timer = utils.AfterFuncWithRecover(timeout, func() { - jxutils.CallMsgHandlerAsync(func() { - globals.SugarLogger.Debugf("fire timer:%s, orderID:%s", timerName, order.VendorOrderID) - savedOrderInfo := s.loadSavedOrderFromMap(model.Order2Status(order), true) - config.TimeoutAction(savedOrderInfo) - savedOrderInfo.timerStatus = 0 - savedOrderInfo.timerStatusType = scheduler.TimerStatusTypeUnknown - }, order.VendorOrderID) - }) + if isPending && timeout < pendingOrderTimerMaxSecond*time.Second { // 如果是PENDING的订单,则将其分布到2--5秒内,让后续事件有机会执行 + timeout = time.Duration(jxutils.MapValue2Scope(int64(timeout), -pendingOrderTimerMinMinSecond*1000, pendingOrderTimerMaxSecond*1000, pendingOrderTimerMinSecond*1000, pendingOrderTimerMaxSecond*1000)) * time.Millisecond + } else if timeout < 0 { + timeout = 0 + } + if timeout == 0 { + config.TimeoutAction(savedOrderInfo, bill) + } else { + timerName := "" + if statusType == scheduler.TimerStatusTypeOrder { + timerName = model.OrderStatusName[status] + } else if statusType == scheduler.TimerStatusTypeWaybill { + timerName = model.WaybillStatusName[status] + } + savedOrderInfo.timerStatusType = statusType + savedOrderInfo.timerStatus = status + savedOrderInfo.timerTime = time.Now().Add(timeout) + savedOrderInfo.timer = utils.AfterFuncWithRecover(timeout, func() { + jxutils.CallMsgHandlerAsync(func() { + globals.SugarLogger.Debugf("fire timer:%s, orderID:%s", timerName, order.VendorOrderID) + savedOrderInfo := s.loadSavedOrderFromMap(model.Order2Status(order), true) + config.TimeoutAction(savedOrderInfo, bill) + savedOrderInfo.timerStatus = 0 + savedOrderInfo.timerStatusType = scheduler.TimerStatusTypeUnknown + }, jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID)) + }) + } + globals.SugarLogger.Debugf("resetTimer, orderID:%s, status:%d, timeout:%v", order.VendorOrderID, status, timeout) } - globals.SugarLogger.Debugf("resetTimer, orderID:%s, status:%d, timeout:%v", order.VendorOrderID, status, timeout) } } } +func isStatusNewer(curStatusType, curStatus, statusType, status int) bool { + // 拣货完成及之前的订单事件TIMER不能覆盖运单TIMER(一般是消息错序引起的) + if curStatusType == scheduler.TimerStatusTypeWaybill && statusType == scheduler.TimerStatusTypeOrder && status <= model.OrderStatusFinishedPickup { + return false + } + if curStatusType == scheduler.TimerStatusTypeWaybill { + return curStatus != status + } + return curStatusType != statusType || status >= curStatus +} + func (s *DefScheduler) mergeOrderStatusConfig(savedOrderInfo *WatchOrderInfo, statusTime time.Time, statusType, status int) (retVal *StatusActionConfig) { s.locker.RLock() defer func() { @@ -709,7 +807,7 @@ func (s *DefScheduler) mergeOrderStatusConfig(savedOrderInfo *WatchOrderInfo, st TimeoutGap: -1, } timeout := statusTime.Sub(time.Now()) + minMinute2Schedule3rdCarrier*time.Minute - if vendorActionParams.GetRefTimeout(statusTime) < timeout { // 如果非立即达订单,根据ExpectedDeliveredTime算出来的timeout太早 + if vendorActionParams.GetRefTimeout(statusTime, order.OrderCreatedAt) < timeout { // 如果非立即达订单,根据ExpectedDeliveredTime算出来的timeout太早 vendorActionParams.Timeout = timeout vendorActionParams.TimeoutGap = 0 } @@ -742,10 +840,18 @@ func (s *DefScheduler) mergeOrderStatusConfig(savedOrderInfo *WatchOrderInfo, st } } } else { // 有最后拣货时间,反推 + timeout := order.PickDeadline.Sub(time.Now()) - (time2AutoPickupAhead + second2AutoPickupGap*time.Second) + realSecond2AutoPickupGap := second2AutoPickupGap + if realSecond2AutoPickupGap > int(timeout/time.Second) { + realSecond2AutoPickupGap = int(timeout / time.Second) + if realSecond2AutoPickupGap < 0 { + realSecond2AutoPickupGap = 0 + } + } vendorActionParams = &partner.StatusActionParams{ TimerType: partner.TimerTypeBaseNow, - Timeout: order.PickDeadline.Sub(time.Now()) - time2AutoPickupAhead - second2AutoPickupGap*time.Second, - TimeoutGap: second2AutoPickupGap, + Timeout: timeout, + TimeoutGap: realSecond2AutoPickupGap, } } } @@ -795,40 +901,39 @@ func (s *DefScheduler) handleAutoAcceptOrder(orderID string, vendorID int, userM return handleType } -// func (s *DefScheduler) updateOrderByStatus(order *model.GoodsOrder, status *model.OrderStatus) (retVal *model.GoodsOrder) { -// order.Status = status.Status -// order.VendorStatus = status.VendorStatus -// order.StatusTime = status.StatusTime -// order.LockStatus = status.LockStatus -// return order -// } - func (s *DefScheduler) updateOrderByBill(order *model.GoodsOrder, bill *model.Waybill, revertStatus bool) { if order.Status > model.OrderStatusEndBegin { return } - if bill.WaybillVendorID == model.VendorIDUnknown { - bill.VendorWaybillID = "" + updateFields := []string{ + "WaybillVendorID", + "VendorWaybillID", + } + if bill == nil { + order.WaybillVendorID = model.VendorIDUnknown + order.VendorWaybillID = "" + } else { + order.WaybillVendorID = bill.WaybillVendorID + order.VendorWaybillID = bill.VendorWaybillID } - partner.CurOrderManager.UpdateWaybillVendorID(bill, revertStatus) - order.WaybillVendorID = bill.WaybillVendorID - order.VendorWaybillID = bill.VendorWaybillID if revertStatus { order.Status = model.OrderStatusFinishedPickup - partner.CurOrderManager.UpdateOrderStatusAndFlag(order) + updateFields = append(updateFields, "Status") } + partner.CurOrderManager.UpdateOrderFields(order, updateFields) } func (s *DefScheduler) updateBillsInfo(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (isBillExist bool) { if savedOrderInfo != nil { if savedBill := savedOrderInfo.waybills[bill.WaybillVendorID]; savedBill != nil { isBillExist = true - if savedBill.Status > bill.Status { - bill.Status = savedBill.Status - } else if bill.Status > savedBill.Status { - savedBill.Status = bill.Status - } + // if savedBill.Status > bill.Status { + // bill.Status = savedBill.Status + // } else if bill.Status > savedBill.Status { + // savedBill.Status = bill.Status + // } } + savedOrderInfo.waybills[bill.WaybillVendorID] = bill } return isBillExist } @@ -848,7 +953,10 @@ func (s *DefScheduler) isBillCandidate(order *model.GoodsOrder, bill *model.Wayb func (s *DefScheduler) ProxyCancelWaybill(order *model.GoodsOrder, bill *model.Waybill, cancelReasonID int, cancelReason string) (err error) { globals.SugarLogger.Debugf("ProxyCancelWaybill orderID:%s", order.VendorOrderID) if (order.DeliveryFlag & model.OrderDeliveryFlagMaskScheduleDisabled) == 0 { - return s.CancelWaybill(bill, cancelReasonID, cancelReason) + if err = s.CancelWaybill(bill, cancelReasonID, cancelReason); err != nil { + partner.CurOrderManager.OnOrderMsg(order, "取消三方运单失败", err.Error()) + } + return err } globals.SugarLogger.Debugf("ProxyCancelWaybill orderID:%s stop schedule, bypass CancelWaybill", order.VendorOrderID) return nil @@ -868,18 +976,3 @@ func OnDefSchConfChanged(key, value string) { } } } - -// 判断是否是购买平台自有物流 -// 对于京东,饿百来说,就是其自有的物流,对于微商城来说,是达达 -func (s *DefScheduler) IsOrderPlatformWaybill(bill *model.Waybill) bool { - return bill.OrderVendorID == bill.WaybillVendorID || s.IsSpecialOrderPlatformWaybill(bill) -} - -// 是否是特殊物流 -func (s *DefScheduler) IsSpecialOrderPlatformWaybill(bill *model.Waybill) bool { - return (bill.OrderVendorID == model.VendorIDWSC && bill.WaybillVendorID == model.VendorIDDada) -} - -func (s *DefScheduler) IsOrderHasWaybill(order *model.GoodsOrder) bool { - return order.WaybillVendorID != model.VendorIDUnknown -} diff --git a/business/jxcallback/scheduler/defsch/defsch_afs.go b/business/jxcallback/scheduler/defsch/defsch_afs.go new file mode 100644 index 000000000..7e3044ca2 --- /dev/null +++ b/business/jxcallback/scheduler/defsch/defsch_afs.go @@ -0,0 +1,27 @@ +package defsch + +import ( + "git.rosy.net.cn/jx-callback/business/jxutils/weixinmsg" + "git.rosy.net.cn/jx-callback/business/model" + "git.rosy.net.cn/jx-callback/business/msghub" +) + +func (s *DefScheduler) OnAfsOrderNew(order *model.AfsOrder, isPending bool) (err error) { + if order.Status == model.AfsOrderStatusWait4Approve { + if !isPending { + msghub.OnNewWait4ApproveAfsOrder(order) + weixinmsg.NotifyAfsOrderStatus(order) + } + } + return err +} + +func (s *DefScheduler) OnAfsOrderStatusChanged(order *model.AfsOrder, status *model.OrderStatus, isPending bool) (err error) { + if status.Status == model.AfsOrderStatusWait4ReceiveGoods { + if !isPending { + msghub.OnKeyAfsOrderStatusChanged(order) + weixinmsg.NotifyAfsOrderStatus(order) + } + } + return err +} diff --git a/business/jxcallback/scheduler/defsch/defsch_ext.go b/business/jxcallback/scheduler/defsch/defsch_ext.go index 33cda68f8..c7b2b5f4a 100644 --- a/business/jxcallback/scheduler/defsch/defsch_ext.go +++ b/business/jxcallback/scheduler/defsch/defsch_ext.go @@ -15,23 +15,25 @@ import ( "git.rosy.net.cn/jx-callback/globals" ) +func (s *DefScheduler) loadSavedOrderByID(vendorOrderID string, vendorID int, isForceLoad bool) *WatchOrderInfo { + return s.loadSavedOrderFromMap(&model.OrderStatus{ + RefVendorOrderID: vendorOrderID, + RefVendorID: vendorID, + }, isForceLoad) +} + func (s *DefScheduler) SelfDeliveringAndUpdateStatus(ctx *jxcontext.Context, vendorOrderID string, vendorID int, userName string) (err error) { jxutils.CallMsgHandler(func() { err = func() (err error) { globals.SugarLogger.Infof("SelfDeliveringAndUpdateStatus orderID:%s userName:%s", vendorOrderID, userName) - status := &model.OrderStatus{ - RefVendorOrderID: vendorOrderID, - RefVendorID: vendorID, - } - savedOrderInfo := s.loadSavedOrderFromMap(status, true) + savedOrderInfo := s.loadSavedOrderByID(vendorOrderID, vendorID, true) if savedOrderInfo != nil { order := savedOrderInfo.order - err = s.cancelOtherWaybills(savedOrderInfo, nil, partner.CancelWaybillReasonOther, partner.CancelWaybillReasonStrActive) - if err == nil { - // todo - if true { //order.DeliveryFlag&model.OrderDeliveryFlagMaskPurcahseDisabled == 0 { + if err = s.isPossibleSwitch2SelfDelivery(order); err == nil { + err = s.cancelOtherWaybillsCheckOrderDeliveryFlag(savedOrderInfo, nil, partner.CancelWaybillReasonOther, partner.CancelWaybillReasonStrActive) + if err == nil { if savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore { - if order.Status <= model.OrderStatusFinishedPickup { + if order.Status < model.OrderStatusDelivering { storeDetail, err2 := dao.GetStoreDetail(dao.GetDB(), order.StoreID, order.VendorID) phone := userName if err = err2; err == nil { @@ -40,7 +42,7 @@ func (s *DefScheduler) SelfDeliveringAndUpdateStatus(ctx *jxcontext.Context, ven err = s.SelfDeliverDelivering(order, phone) } } else { - if order.Status <= model.OrderStatusFinishedPickup { + if order.Status < model.OrderStatusDelivering { err = s.Swtich2SelfDeliver(order, userName) } else if order.VendorID == order.WaybillVendorID { // 状态为配送中,且是购物平台运单,不能转自送了 err = scheduler.ErrOrderStatusIsNotSuitable4CurOperation @@ -51,7 +53,7 @@ func (s *DefScheduler) SelfDeliveringAndUpdateStatus(ctx *jxcontext.Context, ven if err == nil { order.Status = model.OrderStatusDelivering order.DeliveryFlag |= model.OrderDeliveryFlagMaskScheduleDisabled | model.OrderDeliveryFlagMaskPurcahseDisabled - if err = partner.CurOrderManager.UpdateOrderStatusAndFlag(order); err == nil { + if err = partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order); err == nil { s.stopTimer(savedOrderInfo) globals.SugarLogger.Infof("SelfDeliveringAndUpdateStatus orderID:%s userName:%s successfully", vendorOrderID, userName) return err @@ -70,25 +72,47 @@ func (s *DefScheduler) SelfDeliveringAndUpdateStatus(ctx *jxcontext.Context, ven func (s *DefScheduler) canOrderCreateWaybillNormally(order *model.GoodsOrder) (err error) { if !(order.LockStatus != model.OrderStatusLocked && order.Status >= model.OrderStatusFinishedPickup && order.Status < model.OrderStatusEndBegin) { err = fmt.Errorf("当前订单%s没有处于拣货完成且没有结束没有锁定的订单才能进行召唤配送操作", order.VendorOrderID) - } else if s.IsOrderHasWaybill(order) { + } else if model.IsOrderHaveWaybill(order) { err = fmt.Errorf("当前订单%s已经有了有效的承运人%s了", order.VendorOrderID, jxutils.GetVendorName(order.WaybillVendorID)) } return err } -func (s *DefScheduler) CreateWaybillOnProviders4SavedOrder(ctx *jxcontext.Context, savedOrderInfo *WatchOrderInfo, forceCreate bool) (bills []*model.Waybill, err error) { +func (s *DefScheduler) isPossibleSwitch2SelfDelivery(order *model.GoodsOrder) (err error) { + if scheduler.StoreDeliveryTypeByStore != s.GetStoreDeliveryType(order, nil) { + if order.Status < model.OrderStatusFinishedPickup { + err = fmt.Errorf("拣货完成后才能转自配送") + } else if order.Status >= model.OrderStatusFinishedPickup && order.Status < model.OrderStatusDelivering { + if time.Now().Sub(order.StatusTime) < minMinute2Schedule3rdCarrier*time.Minute { + err = fmt.Errorf("非自配送门店转3方配送至少要求拣货完成后%d分钟才能操作", minMinute2Schedule3rdCarrier) + } + } else if order.Status >= model.OrderStatusDelivering && order.Status < model.OrderStatusEndBegin { + if model.IsOrderHaveOwnWaybill(order) { + err = fmt.Errorf("%s物流已在配送中,不能转自配送", jxutils.GetVendorName(order.VendorID)) + } + } else { + err = fmt.Errorf("订单%s已经结束,请刷新状态", order.VendorOrderID) + } + } + return err +} + +func (s *DefScheduler) CreateWaybillOnProviders4SavedOrder(ctx *jxcontext.Context, savedOrderInfo *WatchOrderInfo, courierVendorIDs []int, forceCreate bool, maxAddFee, maxDiffFee2Mtps int64) (bills []*model.Waybill, err error) { order := savedOrderInfo.order - err = s.canOrderCreateWaybillNormally(order) - if forceCreate || err == nil { - err = nil + if !forceCreate { + err = s.canOrderCreateWaybillNormally(order) + } + if err == nil { feeHandler := delivery.DefCreateWaybillPolicy if forceCreate { feeHandler = delivery.NullCreateWaybillPolicy + } else if maxAddFee != 0 { + feeHandler = delivery.CreateWaybillPolicy(maxDiffFee2Mtps, maxAddFee) } - if bills, err = s.CreateWaybillOnProviders(ctx, order, feeHandler); err == nil { + if bills, err = s.CreateWaybillOnProviders(ctx, order, courierVendorIDs, feeHandler, forceCreate); err == nil { if forceCreate { order.DeliveryFlag |= model.OrderDeliveryFlagMaskScheduleDisabled - err = partner.CurOrderManager.UpdateOrderStatusAndFlag(order) + err = partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order) } if err == nil { if forceCreate { @@ -105,7 +129,7 @@ func (s *DefScheduler) CreateWaybillOnProviders4SavedOrder(ctx *jxcontext.Contex return nil, err } -func (s *DefScheduler) CreateWaybillOnProvidersEx(ctx *jxcontext.Context, vendorOrderID string, vendorID int, forceCreate bool) (bills []*model.Waybill, err error) { +func (s *DefScheduler) CreateWaybillOnProvidersEx(ctx *jxcontext.Context, vendorOrderID string, vendorID int, courierVendorIDs []int, forceCreate bool, maxAddFee, maxDiffFee2Mtps int64) (bills []*model.Waybill, err error) { jxutils.CallMsgHandler(func() { bills, err = func() (bills []*model.Waybill, err error) { userName := ctx.GetUserName() @@ -113,19 +137,15 @@ func (s *DefScheduler) CreateWaybillOnProvidersEx(ctx *jxcontext.Context, vendor if vendorID == model.VendorIDELM { return nil, fmt.Errorf("不要直接使用饿了么订单号,请使用相应的饿百订单号") } - status := &model.OrderStatus{ - RefVendorOrderID: vendorOrderID, - RefVendorID: vendorID, - } - savedOrderInfo := s.loadSavedOrderFromMap(status, true) + savedOrderInfo := s.loadSavedOrderByID(vendorOrderID, vendorID, true) if savedOrderInfo != nil { order := savedOrderInfo.order - if scheduler.StoreDeliveryTypeByStore != s.GetStoreDeliveryType(order, nil) && - order.Status == model.OrderStatusFinishedPickup && - time.Now().Sub(order.StatusTime) < minMinute2Schedule3rdCarrier*time.Minute { - return nil, fmt.Errorf("非自配送门店转3方配送至少要求拣货完成后%d分钟才能操作", minMinute2Schedule3rdCarrier) + if !forceCreate { + err = s.isPossibleSwitch2SelfDelivery(order) + } + if err == nil { + bills, err = s.CreateWaybillOnProviders4SavedOrder(ctx, savedOrderInfo, courierVendorIDs, forceCreate, maxAddFee, maxDiffFee2Mtps) } - bills, err = s.CreateWaybillOnProviders4SavedOrder(ctx, savedOrderInfo, forceCreate) } else { err = scheduler.ErrCanNotFindOrder } @@ -141,13 +161,9 @@ func (s *DefScheduler) CancelAll3rdWaybills(ctx *jxcontext.Context, vendorOrderI jxutils.CallMsgHandler(func() { err = func() (err error) { globals.SugarLogger.Infof("CancelAll3rdWaybills orderID:%s userName:%s", vendorOrderID, ctx.GetUserName()) - status := &model.OrderStatus{ - RefVendorOrderID: vendorOrderID, - RefVendorID: vendorID, - } - savedOrderInfo := s.loadSavedOrderFromMap(status, true) + savedOrderInfo := s.loadSavedOrderByID(vendorOrderID, vendorID, true) if savedOrderInfo != nil { - err = s.cancelOtherWaybills2(savedOrderInfo, nil, partner.CancelWaybillReasonOther, partner.CancelWaybillReasonStrActive) + err = s.cancelOtherWaybills(savedOrderInfo, nil, partner.CancelWaybillReasonOther, partner.CancelWaybillReasonStrActive) } else { err = scheduler.ErrCanNotFindOrder } @@ -155,7 +171,7 @@ func (s *DefScheduler) CancelAll3rdWaybills(ctx *jxcontext.Context, vendorOrderI if err == nil && isStopSchedule { order := savedOrderInfo.order order.DeliveryFlag |= model.OrderDeliveryFlagMaskScheduleDisabled - if err = partner.CurOrderManager.UpdateOrderStatusAndFlag(order); err == nil { + if err = partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order); err == nil { s.stopTimer(savedOrderInfo) globals.SugarLogger.Infof("CancelAll3rdWaybills orderID:%s userName:%s successfully", vendorOrderID, ctx.GetUserName()) } @@ -165,3 +181,80 @@ func (s *DefScheduler) CancelAll3rdWaybills(ctx *jxcontext.Context, vendorOrderI }, jxutils.ComposeUniversalOrderID(vendorOrderID, vendorID)) return err } + +func (s *DefScheduler) QueryOrderWaybillFeeInfoEx(ctx *jxcontext.Context, vendorOrderID string, vendorID int) (deliveryFeeMap map[int]*partner.WaybillFeeInfo, err error) { + jxutils.CallMsgHandler(func() { + deliveryFeeMap, err = func() (deliveryFeeMap map[int]*partner.WaybillFeeInfo, err error) { + userName := ctx.GetUserName() + globals.SugarLogger.Infof("GetWaybillsInfoEx orderID:%s userName:%s", vendorOrderID, userName) + + db := dao.GetDB() + order, err := partner.CurOrderManager.LoadOrder(vendorOrderID, vendorID) + if err != nil { + return nil, err + } + storeCourierList, err := dao.GetStoreCourierList(db, jxutils.GetSaleStoreIDFromOrder(order), model.StoreStatusAll) + if err != nil { + return nil, err + } + waybillList, err := partner.CurOrderManager.GetOrderWaybillInfo(ctx, vendorOrderID, vendorID, true) + if err != nil { + return nil, err + } + waybillMap := make(map[int]*model.Waybill) + for _, bill := range waybillList { + waybillMap[bill.WaybillVendorID] = bill + } + deliveryFeeMap = make(map[int]*partner.WaybillFeeInfo) + + var timeoutSecond int + if savedOrderInfo := s.loadSavedOrderByID(vendorOrderID, vendorID, false); savedOrderInfo != nil { + if savedOrderInfo.timerStatusType == scheduler.TimerStatusTypeWaybill && savedOrderInfo.timerStatus == model.WaybillStatusNew { + timeoutSecond = int(savedOrderInfo.timerTime.Sub(time.Now()) / time.Second) + } + } + for _, storeCourier := range storeCourierList { + var feeInfo *partner.WaybillFeeInfo + if waybillMap[storeCourier.VendorID] != nil { + feeInfo = &partner.WaybillFeeInfo{ + Waybill: waybillMap[storeCourier.VendorID], + } + } else { + if storeCourier.Status != model.StoreStatusOpened { + feeInfo = &partner.WaybillFeeInfo{ + ErrCode: partner.WaybillFeeErrCodeCourierNotOpen, + ErrStr: fmt.Sprintf("%d配送门店没有启用", storeCourier.VendorID), + } + } else { + if handler := partner.GetDeliveryPlatformFromVendorID(storeCourier.VendorID); handler != nil { + if handler.Use4CreateWaybill { + if feeInfo, err = handler.Handler.GetWaybillFee(order); err != nil { + feeInfo = &partner.WaybillFeeInfo{ + ErrCode: partner.WaybillFeeErrCodeCourierOthers, + ErrStr: err.Error(), + } + } else { + feeInfo.TimeoutSecond = timeoutSecond + } + } else { + feeInfo = &partner.WaybillFeeInfo{ + ErrCode: partner.WaybillFeeErrCodeCourierForbidden, + ErrStr: fmt.Sprintf("内部错误,%d不能用于创建运单", storeCourier.VendorID), + } + } + } else { + feeInfo = &partner.WaybillFeeInfo{ + ErrCode: partner.WaybillFeeErrCodeCourierNotSupported, + ErrStr: fmt.Sprintf("内部错误,%d不被支持", storeCourier.VendorID), + } + } + } + } + deliveryFeeMap[storeCourier.VendorID] = feeInfo + } + err = nil + return deliveryFeeMap, err + }() + }, jxutils.ComposeUniversalOrderID(vendorOrderID, vendorID)) + return deliveryFeeMap, err +} diff --git a/business/jxcallback/scheduler/scheduler.go b/business/jxcallback/scheduler/scheduler.go index 2c66d3a60..e4a57e155 100644 --- a/business/jxcallback/scheduler/scheduler.go +++ b/business/jxcallback/scheduler/scheduler.go @@ -40,4 +40,8 @@ type IScheduler interface { // 以下是运单 OnWaybillStatusChanged(bill *model.Waybill, isPending bool) (err error) + + // 以下是售后单 + OnAfsOrderNew(order *model.AfsOrder, isPending bool) (err error) + OnAfsOrderStatusChanged(order *model.AfsOrder, status *model.OrderStatus, isPending bool) (err error) } diff --git a/business/jxstore/act/act.go b/business/jxstore/act/act.go new file mode 100644 index 000000000..e97ed4a05 --- /dev/null +++ b/business/jxstore/act/act.go @@ -0,0 +1,355 @@ +package act + +import ( + "fmt" + "time" + + "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-callback/business/jxutils" + "git.rosy.net.cn/jx-callback/business/partner" + "git.rosy.net.cn/jx-callback/globals" + + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "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" +) + +const ( + ActionTypeNA = 0 +) + +type ActOrderRuleParam struct { + SalePrice int64 `orm:"" json:"salePrice"` // 满的价格 + DeductPrice int64 `orm:"" json:"deductPrice"` // 减的价格 +} + +type ActStoreSkuParam struct { + Action int // -1删除,1修改,2新增 + + StoreID int `orm:"column(store_id)" json:"storeID"` + SkuID int `orm:"column(sku_id)" json:"skuID"` + + PricePercentage int `orm:"" json:"pricePercentage"` // 单品级活动用,SKU级的价格比例,非0覆盖Act中的PricePercentage + ActPrice int64 `orm:"" json:"actPrice"` // 单品级活动用,SKU级指定的价格,非0覆盖CustomPricePercentage与Act中的PricePercentage + + Stock int `orm:"" json:"stock"` // 订单级活动用 +} + +type ActDetail struct { + model.Act2 +} + +func ActStoreSkuParam2Map(actStoreSku []*ActStoreSkuParam) (actStoreSkuMap map[int][]*ActStoreSkuParam) { + if len(actStoreSku) > 0 { + for _, v := range actStoreSku { + actStoreSkuMap[v.StoreID] = append(actStoreSkuMap[v.StoreID], v) + } + } + return actStoreSkuMap +} + +func genStoreSkuMapKey(storeID, skuID int) int64 { + return int64(storeID) + int64(skuID)*1000000 +} + +func ActStoreSkuParam2Model(ctx *jxcontext.Context, act *model.Act, vendorIDs []int, actStoreSku []*ActStoreSkuParam) (actMapList []*model.ActMap, actStoreMapList []*model.ActStoreMap, actStoreSkuList []*model.ActStoreSku, actStoreSkuMapList []*model.ActStoreSkuMap, err error) { + if len(actStoreSku) > 0 { + storeIDMap := make(map[int]int) + skuIDMap := make(map[int]int) + storeSkuParamMap := make(map[int][]*ActStoreSkuParam) + for _, v := range actStoreSku { + storeIDMap[v.StoreID] = 1 + skuIDMap[v.SkuID] = 1 + storeSkuParamMap[v.StoreID] = append(storeSkuParamMap[v.StoreID], v) + } + db := dao.GetDB() + + storeSkuList, err2 := dao.GetStoresSkusInfo(db, jxutils.IntMap2List(storeIDMap), jxutils.IntMap2List(skuIDMap)) + if err = err2; err != nil { + return nil, nil, nil, nil, err + } + storeSkuMap := make(map[int64]*model.StoreSkuBind) + for _, v := range storeSkuList { + storeSkuMap[genStoreSkuMapKey(v.StoreID, v.SkuID)] = v + } + + wholeValidVendorMap := make(map[int]int) + for storeID, oneStoreSkuParam := range storeSkuParamMap { + validVendorMap := make(map[int]int) + validSkuMap := make(map[int]int) + for _, vendorID := range vendorIDs { + storeDetail, err2 := dao.GetStoreDetail(db, storeID, vendorID) + if err = err2; err == nil { + for _, v := range oneStoreSkuParam { + if storeSkuInfo := storeSkuMap[genStoreSkuMapKey(v.StoreID, v.SkuID)]; storeSkuInfo != nil { + validVendorMap[vendorID] = 1 + validSkuMap[v.SkuID] = 1 + actSkuMap := &model.ActStoreSkuMap{ + ActID: act.ID, + StoreID: storeID, + SkuID: v.SkuID, + VendorID: vendorID, + + SyncStatus: model.SyncFlagNewMask, + } + if v.ActPrice != 0 { + actSkuMap.ActualActPrice = v.ActPrice + } else { + percentage := act.PricePercentage + if v.PricePercentage != 0 { + percentage = v.PricePercentage + } + percentage = percentage * int(storeDetail.PricePercentage) / 100 + actSkuMap.ActualActPrice = int64(jxutils.CaculateSkuVendorPrice(storeSkuInfo.Price, percentage, 0)) + } + dao.WrapAddIDCULDEntity(actSkuMap, ctx.GetUserName()) + actStoreSkuMapList = append(actStoreSkuMapList, actSkuMap) + } + } + } else if !dao.IsNoRowsError(err) { + return nil, nil, nil, nil, err + } else { + err = nil + } + } + + for _, v := range oneStoreSkuParam { + if validSkuMap[v.SkuID] == 1 { + if storeSkuInfo := storeSkuMap[genStoreSkuMapKey(v.StoreID, v.SkuID)]; storeSkuInfo != nil { + storeSku := &model.ActStoreSku{ + ActID: act.ID, + StoreID: v.StoreID, + SkuID: v.SkuID, + OriginalPrice: int64(storeSkuInfo.Price), + PricePercentage: v.PricePercentage, + ActPrice: v.ActPrice, + Stock: v.Stock, + } + dao.WrapAddIDCULDEntity(storeSku, ctx.GetUserName()) + actStoreSkuList = append(actStoreSkuList, storeSku) + } + } + } + for vendorID := range validVendorMap { + wholeValidVendorMap[vendorID] = 1 + actStoreMap := &model.ActStoreMap{ + ActID: act.ID, + StoreID: storeID, + VendorID: vendorID, + + SyncStatus: model.SyncFlagNewMask, + } + dao.WrapAddIDCULDEntity(actStoreMap, ctx.GetUserName()) + actStoreMapList = append(actStoreMapList, actStoreMap) + } + } + for vendorID := range wholeValidVendorMap { + actMap := &model.ActMap{ + ActID: act.ID, + VendorID: vendorID, + + SyncStatus: model.SyncFlagNewMask, + } + dao.WrapAddIDCULDEntity(actMap, ctx.GetUserName()) + actMapList = append(actMapList, actMap) + } + } + return actMapList, actStoreMapList, actStoreSkuList, actStoreSkuMapList, err +} + +func CreateAct(ctx *jxcontext.Context, act *model.Act, vendorIDs []int, actRules []*ActOrderRuleParam, actStoreSku []*ActStoreSkuParam) (actID int, err error) { + vendorIDMap := make(map[int]bool) + for _, v := range vendorIDs { + vendorIDMap[v] = true + } + db := dao.GetDB() + dao.WrapAddIDCULDEntity(act, ctx.GetUserName()) + dao.Begin(db) + defer func() { + if r := recover(); r != nil { + dao.Rollback(db) + panic(r) + } + }() + err = dao.CreateEntity(db, act) + if err != nil { + return 0, err + } + actMapList, actStoreMapList, actStoreSkuList, actStoreSkuMapList, err := ActStoreSkuParam2Model(ctx, act, vendorIDs, actStoreSku) + if err != nil { + return 0, err + } + isEmptyAct := true + for _, list := range []interface{}{ + actMapList, actStoreMapList, actStoreSkuList, actStoreSkuMapList, + } { + if len(utils.Interface2Slice(list)) > 0 { + err = dao.CreateMultiEntities(db, list) + if err != nil { + dao.Rollback(db) + return 0, err + } + isEmptyAct = false + } + } + if isEmptyAct { + dao.Rollback(db) + return 0, fmt.Errorf("没有门店及SKU满足需求,空操作") + } + dao.Commit(db) + actID = act.ID + err = SyncAct(ctx, actID, nil, nil, nil) + return actID, err +} + +func QueryActs(ctx *jxcontext.Context, actID int, keyword string, statusList []int, actTypeList []int, storeID, skuID int, beginAt, endAt time.Time) (actList []*model.Act, err error) { + return actList, err +} + +func GetActDetail(ctx *jxcontext.Context, actID int) (actDetail *ActDetail, err error) { + return actDetail, err +} + +// func GetAcVendorInfo(ctx *jxcontext.Context, actID int) (err error) { +// return err +// } + +// func GetAcStoresVendorInfo(ctx *jxcontext.Context, actID int, storeIDs []int) (err error) { +// return err +// } + +// func GetAcStoresSkusVendorInfo(ctx *jxcontext.Context, actID int, storeIDs, skuIDs []int) (err error) { +// return err +// } + +func parseActStoreSkuParam(actStoreSku []*ActStoreSkuParam) { + +} + +func UpdateAct(ctx *jxcontext.Context, act *model.Act, actRules []*ActOrderRuleParam, actStoreSku []*ActStoreSkuParam) (err error) { + return err +} + +func CancelAct(ctx *jxcontext.Context, actID int) (err error) { + db := dao.GetDB() + act := &model.Act{} + act.ID = actID + if err = dao.GetEntity(db, act); err != nil { + return err + } + dao.Begin(db) + defer func() { + if r := recover(); r != nil { + dao.Rollback(db) + panic(r) + } + }() + + dao.UpdateEntityLogically(db, act, map[string]interface{}{ + model.FieldStatus: model.ActStatusCanceled, + }, ctx.GetUserName(), nil) + _, err = dao.UpdateEntityLogicallyAndUpdateSyncStatus(db, &model.ActMap{}, nil, ctx.GetUserName(), map[string]interface{}{ + model.FieldActID: actID, + }, model.FieldSyncStatus, model.SyncFlagModifiedMask) + if err == nil { + dao.Commit(db) + globals.SugarLogger.Debugf("CancelAct track:%s", ctx.GetTrackInfo()) + err = SyncAct(ctx, actID, nil, nil, nil) + } else { + dao.Rollback(db) + } + return err +} + +func SyncAct(ctx *jxcontext.Context, actID int, vendorIDs, storeIDs, skuIDs []int) (err error) { + var actOrderRules []*model.ActOrderRule + db := dao.GetDB() + actMap, err := dao.GetActVendorInfo(db, actID, vendorIDs) + if err != nil { + return err + } + actStoreMap, err := dao.GetActStoreVendorInfo(db, actID, vendorIDs, storeIDs) + if err != nil { + return err + } + actStoreSkuMap, err := dao.GetActStoreSkuVendorInfo(db, actID, vendorIDs, storeIDs, skuIDs) + if err != nil { + return err + } + var realVendorIDs []int + for vendorID := range actMap { + realVendorIDs = append(realVendorIDs, vendorID) + } + + task := tasksch.NewParallelTask("SyncAct", nil, ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + vendorID := batchItemList[0].(int) + handler := partner.GetPurchasePlatformFromVendorID(vendorID) + if handler == nil { + err = fmt.Errorf("不被支持的vendorID:%d", vendorID) + } else { + act := actMap[vendorID] + actStore := actStoreMap[vendorID] + actStoreSku := actStoreSkuMap[vendorID] + // globals.SugarLogger.Debugf("%s", utils.Format4Output(act, false)) + // globals.SugarLogger.Debugf("%s", utils.Format4Output(actStore, false)) + // globals.SugarLogger.Debugf("%s", utils.Format4Output(actStoreSku, false)) + if act != nil && actStore != nil && actStoreSku != nil { + if model.IsSyncStatusNeedCreate(act.SyncStatus) { + err = handler.CreateAct(ctx, task, act, actOrderRules, actStore, actStoreSku) + } else if model.IsSyncStatusNeedUpdate(act.SyncStatus) { + if act.Status == model.ActStatusCanceled { + err = handler.CancelAct(ctx, task, act, actStore, actStoreSku) + } else { + // actStoreMap2Remove, actStoreMap2Add, actStoreMap2Update := splitActStore(actStore) + // err = handler.UpdateAct(ctx, task, act, actOrderRules, actStoreMap2Remove, actStoreMap2Add, actStoreMap2Update, actStoreSku) + } + } + if err == nil { + actMap := &model.ActMap{} + actMap.ID = act.MapID + dao.UpdateEntityLogically(db, actMap, map[string]interface{}{ + model.FieldSyncStatus: 0, + model.FieldVendorActID: act.VendorActID, + }, ctx.GetUserName(), nil) + for _, v := range actStore { + storeMap := model.ActStoreMap{} + storeMap.ID = v.MapID + dao.UpdateEntityLogically(db, storeMap, map[string]interface{}{ + model.FieldSyncStatus: 0, + model.FieldVendorActID: v.VendorActID, + }, ctx.GetUserName(), nil) + } + for _, v := range actStoreSku { + storeSkuMap := model.ActStoreSkuMap{} + storeSkuMap.ID = v.MapID + dao.UpdateEntityLogically(db, storeSkuMap, map[string]interface{}{ + model.FieldSyncStatus: 0, + model.FieldVendorActID: v.VendorActID, + }, ctx.GetUserName(), nil) + } + } + } + } + return nil, err + }, realVendorIDs) + tasksch.ManageTask(task).Run() + _, err = task.GetResult(0) + return err +} + +func splitActStore(actStore []*model.ActStore2) (actStoreMap2Remove, actStoreMap2Add, actStoreMap2Update []*model.ActStore2) { + for _, v := range actStore { + if model.IsSyncStatusNeedDelete(v.SyncStatus) { + if !dao.IsVendorThingIDEmpty(v.VendorActID) { + actStoreMap2Remove = append(actStoreMap2Remove, v) + } + } else if model.IsSyncStatusNeedCreate(v.SyncStatus) { + actStoreMap2Add = append(actStoreMap2Add, v) + } else if model.IsSyncStatusNeedUpdate(v.SyncStatus) { + actStoreMap2Update = append(actStoreMap2Update, v) + } + } + return actStoreMap2Remove, actStoreMap2Add, actStoreMap2Update +} diff --git a/business/jxstore/act/act_test.go b/business/jxstore/act/act_test.go new file mode 100644 index 000000000..7e91f6eea --- /dev/null +++ b/business/jxstore/act/act_test.go @@ -0,0 +1,68 @@ +package act + +import ( + "testing" + + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "git.rosy.net.cn/jx-callback/business/model/dao" + "git.rosy.net.cn/jx-callback/globals/testinit" + + "git.rosy.net.cn/jx-callback/business/model" + "git.rosy.net.cn/jx-callback/globals" + + _ "git.rosy.net.cn/jx-callback/business/partner/purchase/ebai" + _ "git.rosy.net.cn/jx-callback/business/partner/purchase/jd" + _ "git.rosy.net.cn/jx-callback/business/partner/purchase/mtwm" +) + +func init() { + testinit.Init() +} + +func TestInitDb(t *testing.T) { + dao.ExecuteSQL(dao.GetDB(), ` + DROP TABLE IF EXISTS act,act_map, act_order_rule, act_store_map, act_store_sku, act_store_sku_map; + `) +} + +func TestCreateAct(t *testing.T) { + actID, err := CreateAct(jxcontext.AdminCtx, &model.Act{ + Name: "测试活动2", + PricePercentage: 80, + }, []int{0, 1, 3}, nil, []*ActStoreSkuParam{ + &ActStoreSkuParam{ + StoreID: 100119, + SkuID: 30828, + }, + &ActStoreSkuParam{ + StoreID: 100119, + SkuID: 30827, + }, + &ActStoreSkuParam{ + StoreID: 100118, + SkuID: 30592, + }, + &ActStoreSkuParam{ + StoreID: 100118, + SkuID: 30565, + }, + }) + if err != nil { + t.Fatal(err) + } + globals.SugarLogger.Debug(actID) +} + +func TestCancelAct(t *testing.T) { + err := CancelAct(jxcontext.AdminCtx, 1) + if err != nil { + t.Fatal(err) + } +} + +func TestSyncAct(t *testing.T) { + err := SyncAct(jxcontext.AdminCtx, 1, nil, nil, nil) + if err != nil { + t.Fatal(err) + } +} diff --git a/business/jxstore/cms/cms.go b/business/jxstore/cms/cms.go index a9c18fae0..e9307cb87 100644 --- a/business/jxstore/cms/cms.go +++ b/business/jxstore/cms/cms.go @@ -20,7 +20,12 @@ const ( ) var ( - serviceInfo map[string]interface{} + serviceInfo map[string]interface{} + allowUpdatePlaceFieldsMap = map[string]bool{ + "name": true, + "enabled": true, + "mtpsPrice": true, + } ) func InitServiceInfo(version string, buildTime time.Time, gitCommit string) { @@ -55,6 +60,8 @@ func InitServiceInfo(version string, buildTime time.Time, gitCommit string) { "shopChineseNames": model.ShopChineseNames, "printerVendorInfo": model.PrinterVendorInfo, "purchaseVendorInfo": model.PurchaseVendorInfo, + "afsReasonTypeName": model.AfsReasonTypeName, + "afsAppealTypeName": model.AfsAppealTypeName, }, } Init() @@ -124,12 +131,18 @@ func UpdatePlaces(ctx *jxcontext.Context, places []map[string]interface{}, userN if len(places) == 0 { return 0, ErrMissingInput } + updateFields := []string{} + for k := range places[0] { + if allowUpdatePlaceFieldsMap[k] { + updateFields = append(updateFields, k) + } + } for _, place := range places { if place["code"] == nil { return 0, ErrMissingInput } placeid := &model.Place{} - valid := dao.NormalMakeMapByFieldList(place, []string{"jdCode", "enabled", "mtpsPrice"}, userName) + valid := dao.NormalMakeMapByFieldList(place, updateFields, userName) if num, err = dao.UpdateEntityLogically(nil, placeid, valid, userName, utils.Params2Map("Code", place["code"])); err != nil { return num, err } diff --git a/business/jxstore/cms/message.go b/business/jxstore/cms/message.go index 5bf65bc11..87895192f 100644 --- a/business/jxstore/cms/message.go +++ b/business/jxstore/cms/message.go @@ -13,6 +13,12 @@ import ( "git.rosy.net.cn/jx-callback/globals" ) +type MessageStatusExt struct { + model.MessageStatus + + Title string `json:"title"` +} + func SendStoreMessage(ctx *jxcontext.Context, title, content string, storeIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) { db := dao.GetDB() dao.Begin(db) @@ -128,8 +134,11 @@ func GetStoreMessages(ctx *jxcontext.Context, msgIDs, storeIDs, types []int, fro func GetStoreMessageStatuses(ctx *jxcontext.Context, msgIDs, storeIDs []int, fromReadCount, toReadCount int, fromTime, toTime time.Time, keyword string, offset, pageSize int) (pagedInfo *model.PagedInfo, err error) { sql := ` - SELECT SQL_CALC_FOUND_ROWS t1.* + SELECT SQL_CALC_FOUND_ROWS + t1.*, + t2.title FROM message_status t1 + JOIN message t2 ON t2.id = t1.message_id WHERE 1 = 1 ` sqlParams := []interface{}{} @@ -167,7 +176,7 @@ func GetStoreMessageStatuses(ctx *jxcontext.Context, msgIDs, storeIDs []int, fro db := dao.GetDB() dao.Begin(db) defer dao.Commit(db) - var msgStatusList []*model.MessageStatus + var msgStatusList []*MessageStatusExt // globals.SugarLogger.Debug(sql) // globals.SugarLogger.Debug(utils.Format4Output(sqlParams, false)) if err = dao.GetRows(db, &msgStatusList, sql, sqlParams...); err == nil { diff --git a/business/jxstore/cms/sku.go b/business/jxstore/cms/sku.go index 001564ecf..b54947091 100644 --- a/business/jxstore/cms/sku.go +++ b/business/jxstore/cms/sku.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "git.rosy.net.cn/baseapi/platformapi/ebaiapi" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" @@ -14,6 +15,7 @@ import ( "git.rosy.net.cn/jx-callback/business/model/dao" "git.rosy.net.cn/jx-callback/business/partner" "git.rosy.net.cn/jx-callback/globals" + "git.rosy.net.cn/jx-callback/globals/api" ) type SkuNamesInfo struct { @@ -25,6 +27,19 @@ var ( ErrInputCatsDoesntMatch = errors.New("输入的类别列表不合法,需要输入一个父ID下的所有子类别") ) +var ( + ebaiUploadRTFShopID string // 饿百找一个店用于调用SkuUploadRTF +) + +func getAndSetEbaiUploadRTFShopID() (shopID string) { + if ebaiUploadRTFShopID == "" { + if storeDetail, err := dao.GetStoreDetail(dao.GetDB(), 0, model.VendorIDEBAI); err == nil { + ebaiUploadRTFShopID = utils.Int2Str(storeDetail.Store.ID) + } + } + return ebaiUploadRTFShopID +} + // parentID 为-1表示所有 func GetVendorCategories(ctx *jxcontext.Context, vendorID int, parentID string) (vendorCats []*model.SkuVendorCategory, err error) { cond := map[string]interface{}{ @@ -72,7 +87,7 @@ func AddCategory(ctx *jxcontext.Context, cat *model.SkuCategory, userName string dao.WrapAddIDCULDEntity(cat, userName) cat.JdSyncStatus = model.SyncFlagNewMask - cat.JdID = 0 //jxutils.GenFakeID() + cat.JdID = 0 cat.Name = strings.Trim(cat.Name, " ") if cat.Seq <= 0 { var maxSeq struct { @@ -93,19 +108,83 @@ func AddCategory(ctx *jxcontext.Context, cat *model.SkuCategory, userName string func UpdateCategory(ctx *jxcontext.Context, categoryID int, payload map[string]interface{}, userName string) (num int64, err error) { cat := &model.SkuCategory{} cat.ID = categoryID - valid := dao.NormalMakeMapByStructObject(payload, cat, userName) + db := dao.GetDB() + if err = dao.GetEntity(db, cat); err != nil { + return 0, err + } + valid := dao.StrictMakeMapByStructObject(payload, cat, userName) if len(valid) > 0 { + syncStatus := 0 if valid["name"] != nil { valid["name"] = strings.Trim(valid["name"].(string), " ") + syncStatus = model.SyncFlagModifiedMask } - db := dao.GetDB() - if num, err = dao.UpdateEntityLogicallyAndUpdateSyncStatus(db, cat, valid, userName, nil, model.FieldJdSyncStatus, model.SyncFlagModifiedMask); err == nil { + if num, err = dao.UpdateEntityLogicallyAndUpdateSyncStatus(db, cat, valid, userName, nil, model.FieldJdSyncStatus, syncStatus); err == nil { + SetStoreCategorySyncStatus2(db, nil, []int{categoryID}, model.SyncFlagModifiedMask) + if valid["jdCategoryID"] != nil || valid["ebaiCategoryID"] != nil || valid["mtwmCategoryID"] != nil || + valid["jdPricePercentage"] != nil || valid["ebaiPricePercentage"] != nil || valid["mtwmPricePercentage"] != nil { + if skuList, err2 := dao.GetSkuByCats(db, []int{categoryID}); err2 == nil && len(skuList) > 0 { + var skuIDs []int + for _, sku := range skuList { + skuIDs = append(skuIDs, sku.ID) + } + if valid["jdCategoryID"] != nil { + dao.SetSkuSyncStatus(db, model.VendorIDJD, skuIDs, model.SyncFlagModifiedMask) + } + + // todo 如下逻辑,在不同平台同时改pricePercentage与平台分类映射时,会不必要的打上多余的标记 + var vendorIDs []int + syncStatus := model.SyncFlagModifiedMask + if valid["jdPricePercentage"] != nil { + vendorIDs = append(vendorIDs, model.VendorIDJD) + syncStatus |= model.SyncFlagPriceMask + } + + if valid["ebaiPricePercentage"] != nil { + vendorIDs = append(vendorIDs, model.VendorIDEBAI) + syncStatus |= model.SyncFlagPriceMask + } else if valid["ebaiCategoryID"] != nil { + vendorIDs = append(vendorIDs, model.VendorIDEBAI) + } + + if valid["mtwmPricePercentage"] != nil { + vendorIDs = append(vendorIDs, model.VendorIDMTWM) + syncStatus |= model.SyncFlagPriceMask + } else if valid["mtwmCategoryID"] != nil { + vendorIDs = append(vendorIDs, model.VendorIDMTWM) + } + if len(vendorIDs) > 0 { + SetStoreSkuSyncStatus2(db, nil, vendorIDs, skuIDs, syncStatus) + } + } + } _, err = CurVendorSync.SyncCategory(ctx, db, categoryID, false, userName) } } return num, err } +func SetStoreCategorySyncStatus2(db *dao.DaoDB, storeIDs []int, catIDs []int, syncStatus int) (num int64, err error) { + dao.Begin(db) + defer func() { + if r := recover(); r != nil || err != nil { + dao.Rollback(db) + if r != nil { + panic(r) + } + } + }() + for _, vendorID := range CurVendorSync.SingleStoreVendorIDs { + num2, err2 := dao.SetStoreCategorySyncStatus(db, vendorID, storeIDs, catIDs, syncStatus) + if err = err2; err != nil { + return 0, err + } + num += num2 + } + dao.Commit(db) + return num, nil +} + func ReorderCategories(ctx *jxcontext.Context, parentID int, categoryIDs []int, userName string) (err error) { var cats []*model.SkuCategory parentCat := &model.SkuCategory{} @@ -211,8 +290,8 @@ func GetSkuNames(ctx *jxcontext.Context, keyword string, isBySku bool, params ma sqlParams = append(sqlParams, keywordLike, keywordLike, keywordLike, keywordLike) if keywordInt64, err2 := strconv.ParseInt(keyword, 10, 64); err2 == nil { - sql += " OR t2.jd_id = ? OR t1.id = ? OR t1.category_id = ?" - sqlParams = append(sqlParams, keywordInt64, keywordInt64, keywordInt64) + sql += " OR t1.id = ? OR t2.id = ? OR t2.jd_id = ? OR t1.category_id = ?" + sqlParams = append(sqlParams, keywordInt64, keywordInt64, keywordInt64, keywordInt64) } sql += ")" } @@ -340,6 +419,7 @@ func GetSkuNames(ctx *jxcontext.Context, keyword string, isBySku bool, params ma t1.name, t1.brand_id, t1.category_id, + t1.jd_category_id, t1.is_global, t1.unit, t1.price, @@ -348,6 +428,7 @@ func GetSkuNames(ctx *jxcontext.Context, keyword string, isBySku bool, params ma t1.status, t1.is_spu, t1.img_hash_code, + t1.desc_img, t1.upc` if isBySku { sql += `, @@ -365,6 +446,7 @@ func GetSkuNames(ctx *jxcontext.Context, keyword string, isBySku bool, params ma t1.name, t1.brand_id, t1.category_id, + t1.jd_category_id, t1.is_global, t1.unit, t1.price, @@ -373,6 +455,7 @@ func GetSkuNames(ctx *jxcontext.Context, keyword string, isBySku bool, params ma t1.status, t1.is_spu, t1.img_hash_code, + t1.desc_img, t1.upc, t1.jd_id, t1.jd_sync_status, @@ -463,30 +546,39 @@ func AddSkuName(ctx *jxcontext.Context, skuNameExt *model.SkuNameExt, userName s skuNameExt.SpecQuality = skuNameExt.Skus[0].SpecQuality skuNameExt.SpecUnit = skuNameExt.Skus[0].SpecUnit } - imgContent, imgMD5, err := jxutils.DownloadFileByURL(skuNameExt.Img) - if err != nil { - dao.Rollback(db) - return nil, err - } - if skuNameExt.ImgHashCode == "" { - skuNameExt.ImgHashCode = imgMD5 - } else if skuNameExt.ImgHashCode != imgMD5 { - dao.Rollback(db) - return nil, errors.New("图片HASH值不同") - } - imgHintMap, err := UploadImg2Platforms(ctx, nil, skuNameExt.Img, imgContent, "") - if err != nil { - dao.Rollback(db) - return nil, err - } - skuNameExt.ImgWeimob = imgHintMap[model.VendorIDWSC] - skuNameExt.ImgEbai = imgHintMap[model.VendorIDEBAI] + if globals.EnableStoreWrite { + imgContent, imgMD5, err := jxutils.DownloadFileByURL(skuNameExt.Img) + if err != nil { + dao.Rollback(db) + return nil, err + } + if skuNameExt.ImgHashCode == "" { + skuNameExt.ImgHashCode = imgMD5 + } else if skuNameExt.ImgHashCode != imgMD5 { + dao.Rollback(db) + return nil, errors.New("图片HASH值不同") + } + imgHintMap, err := UploadImg2Platforms(ctx, nil, skuNameExt.Img, imgContent, "") + if err != nil { + dao.Rollback(db) + return nil, err + } + skuNameExt.ImgWeimob = imgHintMap[model.VendorIDWSC] + skuNameExt.ImgEbai = imgHintMap[model.VendorIDEBAI] + + if skuNameExt.DescImg != "" && getAndSetEbaiUploadRTFShopID() != "" { + skuNameExt.DescImgEbai, err = api.EbaiAPI.SkuUploadRTF(getAndSetEbaiUploadRTFShopID(), ebaiapi.BuildRFTFromImgs(skuNameExt.DescImg)) + } + if err != nil { + dao.Rollback(db) + return nil, err + } + } if err = dao.CreateEntity(db, &skuNameExt.SkuName); err != nil { dao.Rollback(db) return nil, err } - // beginJDID := jxutils.GenFakeID() for _, sku := range skuNameExt.Skus { dao.WrapAddIDCULDEntity(sku, userName) sku.NameID = skuNameExt.ID @@ -496,7 +588,6 @@ func AddSkuName(ctx *jxcontext.Context, skuNameExt *model.SkuNameExt, userName s dao.Rollback(db) return nil, err } - // beginJDID++ } for _, placeCode := range skuNameExt.Places { placeBind := &model.SkuNamePlaceBind{} @@ -528,9 +619,12 @@ func UpdateSkuName(ctx *jxcontext.Context, nameID int, payload map[string]interf if err = dao.GetEntity(db, skuName); err != nil { return 0, err } - if _, ok := payload["isSpu"]; ok { - delete(payload, "isSpu") - } + delete(payload, "isSpu") + delete(payload, "ImgHashCode") + delete(payload, "ImgWeimob") + delete(payload, "ImgEbai") + delete(payload, "descImgEbai") + valid := dao.StrictMakeMapByStructObject(payload, skuName, userName) valid = utils.RemoveGeneralMapKeys(valid, model.FieldSpecQuality, model.FieldSpecUnit) _, hasPlaces := payload["places"] @@ -550,19 +644,35 @@ func UpdateSkuName(ctx *jxcontext.Context, nameID int, payload map[string]interf } err = nil } - if valid["img"] != nil { - imgContent, imgMD5, err2 := jxutils.DownloadFileByURL(valid["img"].(string)) - if err = err2; err != nil { - return 0, err + if globals.EnableStoreWrite { + if valid["img"] != nil { + imgContent, imgMD5, err2 := jxutils.DownloadFileByURL(valid["img"].(string)) + if err = err2; err != nil { + return 0, err + } + valid["ImgHashCode"] = imgMD5 + imgHintMap, err := UploadImg2Platforms(ctx, nil, valid["img"].(string), imgContent, "") + if err != nil { + dao.Rollback(db) + return 0, err + } + valid["ImgWeimob"] = imgHintMap[model.VendorIDWSC] + valid["ImgEbai"] = imgHintMap[model.VendorIDEBAI] } - valid["ImgHashCode"] = imgMD5 - imgHintMap, err := UploadImg2Platforms(ctx, nil, valid["img"].(string), imgContent, "") - if err != nil { - dao.Rollback(db) - return 0, err + if valid["descImg"] != nil { + descImg := valid["descImg"].(string) + if descImg != "" { + if getAndSetEbaiUploadRTFShopID() != "" { + valid["descImgEbai"], err = api.EbaiAPI.SkuUploadRTF(getAndSetEbaiUploadRTFShopID(), ebaiapi.BuildRFTFromImgs(descImg)) + if err != nil { + dao.Rollback(db) + return 0, err + } + } + } else { + valid["descImgEbai"] = "" + } } - valid["ImgWeimob"] = imgHintMap[model.VendorIDWSC] - valid["ImgEbai"] = imgHintMap[model.VendorIDEBAI] } if num, err = dao.UpdateEntityLogicallyAndUpdateSyncStatus(db, skuName, valid, userName, nil, model.FieldJdSyncStatus, model.SyncFlagModifiedMask); err == nil && num == 1 { if utils.Interface2Int64WithDefault(payload["isGlobal"], 0) == 0 && payload["places"] != nil { @@ -588,8 +698,14 @@ func UpdateSkuName(ctx *jxcontext.Context, nameID int, payload map[string]interf model.FieldNameID: nameID, }, model.FieldJdSyncStatus, model.SyncFlagModifiedMask) if err == nil { - dao.Commit(db) - _, err = CurVendorSync.SyncSku(ctx, db, nameID, -1, false, false, userName) + skuIDs, err2 := dao.GetSkuIDByNames(db, []int{nameID}) + if err = err2; err == nil && len(skuIDs) > 0 { + _, err = SetStoreSkuSyncStatus2(db, nil, CurVendorSync.SingleStoreVendorIDs, skuIDs, model.SyncFlagModifiedMask) + } + if err == nil { + dao.Commit(db) + _, err = CurVendorSync.SyncSku(ctx, db, nameID, -1, false, false, userName) + } } } } @@ -597,6 +713,27 @@ func UpdateSkuName(ctx *jxcontext.Context, nameID int, payload map[string]interf return num, err } +func SetStoreSkuSyncStatus2(db *dao.DaoDB, storeIDs []int, vendorIDs, skuIDs []int, syncStatus int) (num int64, err error) { + dao.Begin(db) + defer func() { + if r := recover(); r != nil || err != nil { + dao.Rollback(db) + if r != nil { + panic(r) + } + } + }() + for _, vendorID := range vendorIDs { + num2, err2 := dao.SetStoreSkuSyncStatus(db, vendorID, storeIDs, skuIDs, syncStatus) + if err = err2; err != nil { + return 0, err + } + num += num2 + } + dao.Commit(db) + return num, nil +} + func DeleteSkuName(ctx *jxcontext.Context, nameID int, userName string) (num int64, err error) { db := dao.GetDB() dao.Begin(db) @@ -647,7 +784,7 @@ func AddSku(ctx *jxcontext.Context, nameID int, sku *model.Sku, userName string) dao.WrapAddIDCULDEntity(sku, userName) sku.JdSyncStatus = model.SyncFlagNewMask sku.NameID = nameID - sku.JdID = 0 //jxutils.GenFakeID() + sku.JdID = 0 if err = dao.CreateEntity(db, sku); err == nil { result, err2 := GetSkuNames(ctx, "", false, utils.Params2Map("skuID", sku.ID), 0, 0) if err = err2; err == nil { @@ -691,8 +828,10 @@ func UpdateSku(ctx *jxcontext.Context, skuID int, payload map[string]interface{} `, utils.DefaultTimeValue, skuID, model.SpecialUnit); err != nil { return 0, err } - dao.Commit(db) - _, err = CurVendorSync.SyncSku(ctx, db, -1, sku.ID, false, false, userName) + if _, err = SetStoreSkuSyncStatus2(db, nil, CurVendorSync.SingleStoreVendorIDs, []int{skuID}, model.SyncFlagModifiedMask); err == nil { + dao.Commit(db) + _, err = CurVendorSync.SyncSku(ctx, db, -1, sku.ID, false, false, userName) + } } else { err = ErrEntityNotExist } diff --git a/business/jxstore/cms/store.go b/business/jxstore/cms/store.go index e3a768f48..60fa9e744 100644 --- a/business/jxstore/cms/store.go +++ b/business/jxstore/cms/store.go @@ -10,8 +10,10 @@ import ( "git.rosy.net.cn/baseapi/platformapi/dadaapi" "git.rosy.net.cn/baseapi/platformapi/feieapi" + "git.rosy.net.cn/baseapi/platformapi/jdapi" "git.rosy.net.cn/baseapi/utils" "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/netprinter" "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" @@ -19,6 +21,7 @@ import ( "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" ) @@ -59,6 +62,37 @@ var ( ErrCanNotFindVendor = errors.New("vendorID参数不合法") ) +var ( + dadaDistrictMap = map[string]string{ + "苏州工业园区": "工业园区", + "郫都区": "郫县", + "管城回族区": "管城区", + "昆山市": "1", + "常熟市": "1", + "太仓市": "1", + "虞山街道": "虞山镇", + "常福街道": "虞山镇", + } + + 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, + } +) + // todo 门店绑定信息可以考虑以数组形式返回,而不是现在这样 func GetStores(ctx *jxcontext.Context, keyword string, params map[string]interface{}, offset, pageSize int) (retVal *StoresInfo, err error) { sql := ` @@ -92,17 +126,46 @@ func GetStores(ctx *jxcontext.Context, keyword string, params map[string]interfa 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_name, + + t1.pay_percentage, + t1.operator_name, + t1.operator_phone, + 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, ', "vendorStoreName":"', + CONCAT('[', GROUP_CONCAT(DISTINCT CONCAT('{"vendorStoreID":"', m1.vendor_store_id, '", "vendorID":', m1.vendor_id, + ', "status":', m1.status, ', "pricePercentage":', m1.price_percentage, ', "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, - '"}')), ']') store_map_str, - CONCAT("[", GROUP_CONCAT(DISTINCT CONCAT('{"vendorStoreID":"', m2.vendor_store_id, '", "vendorID":', m2.vendor_id, ', "status":', m2.status, "}")), "]") courier_map_str + '", "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 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 @@ -228,7 +291,7 @@ func GetStores(ctx *jxcontext.Context, keyword string, params map[string]interfa } 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 + 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 ORDER BY t1.id DESC LIMIT ? OFFSET ?` pageSize = jxutils.FormalizePageSize(pageSize) @@ -249,8 +312,9 @@ func GetStores(ctx *jxcontext.Context, keyword string, params map[string]interfa // 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 { - mapLimit := false + // globals.SugarLogger.Debugf("GetStores, len(storeList):%d", len(storeList)) var ( mapLatitude, mapLongitude float64 mapRadius int @@ -289,8 +353,9 @@ func GetStores(ctx *jxcontext.Context, keyword string, params map[string]interfa } } dao.Commit(db) - - retVal.MapCenterLng, retVal.MapCenterLat = getMapCenter(retVal.Stores) + if mapLimit { + retVal.MapCenterLng, retVal.MapCenterLat = getMapCenter(retVal.Stores) + } return retVal, err } @@ -365,6 +430,15 @@ func GetVendorStore(ctx *jxcontext.Context, vendorStoreID string, vendorID int) return nil, ErrCanNotFindVendor } +func isUpdateStoreNeedSync(valid map[string]interface{}) bool { + for k := range valid { + if storeKeyPropertyMap[k] == 1 { + return true + } + } + return false +} + 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, false)) @@ -458,14 +532,18 @@ func UpdateStore(ctx *jxcontext.Context, storeID int, payload map[string]interfa dao.Rollback(db) }() if num, err = dao.UpdateEntityLogically(db, store, valid, userName, nil); err == nil && num == 1 { - dummy := &model.StoreMap{} - _, err2 := dao.UpdateEntityLogicallyAndUpdateSyncStatus(db, dummy, nil, userName, map[string]interface{}{ - model.FieldStoreID: store.ID, - }, model.FieldSyncStatus, syncStatus) - if err = err2; err == nil { + if isUpdateStoreNeedSync(valid) { + dummy := &model.StoreMap{} + _, err2 := dao.UpdateEntityLogicallyAndUpdateSyncStatus(db, dummy, nil, 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) + } + } else { dao.Commit(db) - globals.SugarLogger.Debugf("UpdateStore track:%s, before call SyncStore", ctx.GetTrackInfo()) - _, err = CurVendorSync.SyncStore(ctx, db, -1, store.ID, false, userName) } } } else { @@ -474,6 +552,42 @@ func UpdateStore(ctx *jxcontext.Context, storeID int, payload map[string]interfa 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) + 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)) store := &storeExt.Store @@ -547,6 +661,9 @@ func AddStoreVendorMap(ctx *jxcontext.Context, db *dao.DaoDB, storeID, vendorID 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(storeMap.VendorStoreID) @@ -605,6 +722,11 @@ func DeleteStoreVendorMap(ctx *jxcontext.Context, db *dao.DaoDB, storeID, vendor } 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 @@ -651,7 +773,7 @@ func UpdateStoreVendorMap(ctx *jxcontext.Context, db *dao.DaoDB, storeID, vendor storeSkuBind := &model.StoreSkuBind{} if num, err = dao.UpdateEntityLogicallyAndUpdateSyncStatus(db, storeSkuBind, nil, userName, map[string]interface{}{ model.FieldStoreID: storeID, - }, dao.GetSyncStatusStructField(model.VendorNames[vendorID]), model.SyncFlagModifiedMask); err != nil { + }, dao.GetSyncStatusStructField(model.VendorNames[vendorID]), model.SyncFlagPriceMask); err != nil { return 0, err } } @@ -797,9 +919,22 @@ func AddStoreCourierMap(ctx *jxcontext.Context, db *dao.DaoDB, storeID, vendorID dao.WrapAddIDCULDEntity(storeCourierMap, userName) storeCourierMap.StoreID = storeID storeCourierMap.VendorID = vendorID + if db == nil { db = dao.GetDB() } + if vendorID == model.VendorIDDada { + storeList, err2 := dao.GetMissingDadaStores(db, storeID, false) + if err = err2; err == nil && len(storeList) > 0 { + storeList[0].DadaStoreID = storeCourierMap.VendorStoreID + err = updateOrCreateDadaStore(storeList[0]) + } else { + globals.SugarLogger.Debugf("AddStoreCourierMap GetMissingDadaStores error:%v, len(storeList):%d", err, len(storeList)) + } + } + if err != nil { + return nil, err + } dao.Begin(db) defer func() { if r := recover(); r != nil { @@ -809,15 +944,6 @@ func AddStoreCourierMap(ctx *jxcontext.Context, db *dao.DaoDB, storeID, vendorID }() if err = dao.CreateEntity(db, storeCourierMap); err == nil { dao.Commit(db) - if vendorID == model.VendorIDDada { - storeList, err := dao.GetMissingDadaStores(db, storeID, false) - if err == nil && len(storeList) > 0 { - storeList[0].DadaStoreID = storeCourierMap.VendorStoreID - err = updateOrCreateDadaStore(storeList[0]) - } else { - globals.SugarLogger.Debugf("AddStoreCourierMap GetMissingDadaStores error:%v, len(storeList):%d", err, len(storeList)) - } - } outStoreCourierMap = storeCourierMap if err == nil { _, err = CurVendorSync.SyncStore(ctx, db, storeCourierMap.VendorID, storeID, false, userName) @@ -871,46 +997,74 @@ func RefreshMissingDadaStores(ctx *jxcontext.Context, storeID int, isAsync, isCo task := tasksch.NewParallelTask("RefreshMissingDadaStores", tasksch.NewParallelConfig().SetIsContinueWhenError(isContinueWhenError), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { storeDetail := batchItemList[0].(*dao.StoreDetail2) + var resultList []interface{} if storeDetail.DadaStoreID == "" { + if storeDetail.DistrictName == "" || storeDetail.CityName == "" { + return nil, fmt.Errorf("门店:%s的城市码或区码有错误", storeDetail.Name) + } db := dao.GetDB() - _, err = AddStoreCourierMap(ctx, db, storeDetail.ID, model.VendorIDDada, &model.StoreCourierMap{ + if _, err = AddStoreCourierMap(ctx, db, storeDetail.ID, model.VendorIDDada, &model.StoreCourierMap{ VendorStoreID: utils.Int2Str(storeDetail.ID), Status: model.StoreStatusOpened, - }) + }); err == nil { + resultList = append(resultList, 1) + } } - return nil, err + return resultList, err }, storeList) tasksch.HandleTask(task, nil, true).Run() - hint = task.ID if !isAsync { - _, err = task.GetResult(0) + resultList, err2 := task.GetResult(0) + if err = err2; err == nil { + hint = utils.Int2Str(len(resultList)) + } + } else { + hint = task.ID } return hint, err } func updateOrCreateDadaStore(storeDetail *dao.StoreDetail2) (err error) { - _, err = api.DadaAPI.ShopDetail(storeDetail.DadaStoreID) - if err != nil { - if codeErr, ok := err.(*utils.ErrorWithCode); ok && codeErr.IntCode() == dadaapi.ResponseCodeShopNotExist { - _, err = api.DadaAPI.ShopAdd(storeDetail.DadaStoreID, composeDadaStoreName(storeDetail), dadaapi.BusinessTypeConvStore, storeDetail.CityName, - storeDetail.DistrictName, storeDetail.Address, jxutils.IntCoordinate2Standard(storeDetail.Lng), jxutils.IntCoordinate2Standard(storeDetail.Lat), - storeDetail.Tel1, storeDetail.Tel1, nil) + if storeDetail.DistrictName == "" { + return fmt.Errorf("门店的区码有问题,请检查") + } + if storeDetail.CityName == "" { + return fmt.Errorf("门店的城市码有问题,请检查") + } + if dadaDistrictMap[storeDetail.DistrictName] != "" { + if dadaDistrictMap[storeDetail.DistrictName] == "1" { // 区镇信息 + storeDetail.CityName = storeDetail.DistrictName + storeDetail.DistrictName, _ = api.AutonaviAPI.GetCoordinateTownInfo(jxutils.IntCoordinate2Standard(storeDetail.Lng), jxutils.IntCoordinate2Standard(storeDetail.Lat)) } - } else { - params := map[string]interface{}{ - "station_name": composeDadaStoreName(storeDetail), - "business": dadaapi.BusinessTypeConvStore, - "city_name": storeDetail.CityName, - "area_name": storeDetail.DistrictName, - "station_address": storeDetail.Address, - "lng": jxutils.IntCoordinate2Standard(storeDetail.Lng), - "lat": jxutils.IntCoordinate2Standard(storeDetail.Lat), - "contact_name": storeDetail.Tel1, - "phone": storeDetail.Tel1, + if dadaDistrictMap[storeDetail.DistrictName] != "" { + storeDetail.DistrictName = dadaDistrictMap[storeDetail.DistrictName] + } + } + if globals.EnableStoreWrite { + _, err = api.DadaAPI.ShopDetail(storeDetail.DadaStoreID) + if err != nil { + if codeErr, ok := err.(*utils.ErrorWithCode); ok && codeErr.IntCode() == dadaapi.ResponseCodeShopNotExist { + _, err = api.DadaAPI.ShopAdd(storeDetail.DadaStoreID, composeDadaStoreName(storeDetail), dadaapi.BusinessTypeConvStore, storeDetail.CityName, + storeDetail.DistrictName, storeDetail.Address, jxutils.IntCoordinate2Standard(storeDetail.Lng), jxutils.IntCoordinate2Standard(storeDetail.Lat), + storeDetail.Tel1, storeDetail.Tel1, nil) + } + } else { + params := map[string]interface{}{ + "station_name": composeDadaStoreName(storeDetail), + "business": dadaapi.BusinessTypeConvStore, + "city_name": storeDetail.CityName, + "area_name": storeDetail.DistrictName, + "station_address": storeDetail.Address, + "lng": jxutils.IntCoordinate2Standard(storeDetail.Lng), + "lat": jxutils.IntCoordinate2Standard(storeDetail.Lat), + "contact_name": storeDetail.Tel1, + "phone": storeDetail.Tel1, + } + err = api.DadaAPI.ShopUpdate(storeDetail.DadaStoreID, params) } - err = api.DadaAPI.ShopUpdate(storeDetail.DadaStoreID, params) } if err != nil { + err = fmt.Errorf("门店ID:%d,门店名:%s,错误描述:%s", storeDetail.Store.ID, storeDetail.Name, err.Error()) globals.SugarLogger.Debugf("updateOrCreateDadaStore storeID:%d failed with error:%v", storeDetail.ID, err) } return err @@ -919,3 +1073,91 @@ func updateOrCreateDadaStore(storeDetail *dao.StoreDetail2) (err error) { func composeDadaStoreName(storeDetail *dao.StoreDetail2) (storeName string) { return storeDetail.Name + "-" + storeDetail.DadaStoreID } + +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) + 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 +} diff --git a/business/jxstore/cms/store_sku.go b/business/jxstore/cms/store_sku.go index 0b030011a..04e3a82d9 100644 --- a/business/jxstore/cms/store_sku.go +++ b/business/jxstore/cms/store_sku.go @@ -6,8 +6,14 @@ import ( "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/jxcallback/auth/weixin" "git.rosy.net.cn/jx-callback/business/jxutils" @@ -20,6 +26,8 @@ import ( ) const ( + MaxSkuUnitPrice = 100000 + CopyStoreSkuModeFresh = "fresh" CopyStoreSkuModeUpdate = "update" // CopyStoreSkuModeAdd = "add" @@ -48,24 +56,26 @@ type StoreSkuNamesInfo struct { // UpdateStoreSku用,API调用时 type StoreSkuBindSkuInfo struct { SkuID int `json:"skuID"` - IsSale int `json:"isSale"` // -1:不可售,0:忽略,1:可售 + IsSale int `json:"isSale,omitempty"` // -1:不可售,0:忽略,1:可售 - ElmID int64 `json:"elmID"` - EbaiID int64 `json:"ebaiID"` + 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"` - Skus []*StoreSkuBindSkuInfo `json:"skus"` + SubStoreID int `json:"subStoreID,omitempty"` + Skus []*StoreSkuBindSkuInfo `json:"skus,omitempty"` } type tStoreSkuBindAndSpec struct { model.StoreSkuBind + Name string SpecQuality float32 SpecUnit string SkuNamePrice int @@ -88,6 +98,11 @@ type StoreOpRequestInfo struct { UnitPrice int `json:"unitPrice"` } +const ( + maxStoreNameBind = 3000 // 最大门店SkuName bind个数 + maxStoreNameBind2 = 10000 // 最大门店乘SkuName个数 +) + func GetStoreSkus(ctx *jxcontext.Context, storeID int, isFocus bool, keyword string, isBySku bool, params map[string]interface{}, offset, pageSize int) (skuNamesInfo *StoreSkuNamesInfo, err error) { return GetStoresSkus(ctx, []int{storeID}, isFocus, keyword, isBySku, params, offset, pageSize) } @@ -102,7 +117,7 @@ func GetStoresSkus(ctx *jxcontext.Context, storeIDs []int, isFocus bool, keyword JOIN store t3 ON t3.id IN (` + dao.GenQuestionMarks(len(storeIDs)) + `) LEFT JOIN store_sku_bind t4 ON t4.sku_id = t2.id AND t4.deleted_at = ? AND t4.store_id = t3.id 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 t4.status = ?)/* AND t1.status = ?*/ + WHERE t1.deleted_at = ? AND (t1.is_global = 1 OR t5.id IS NOT NULL OR 1 = ?)/* AND t1.status = ?*/ ` sqlParams := []interface{}{ utils.DefaultTimeValue, @@ -110,7 +125,7 @@ func GetStoresSkus(ctx *jxcontext.Context, storeIDs []int, isFocus bool, keyword storeIDs, utils.DefaultTimeValue, utils.DefaultTimeValue, - model.SkuStatusNormal, + utils.Bool2Int(isFocus), // model.SkuStatusNormal, } if isFocus { @@ -122,12 +137,12 @@ func GetStoresSkus(ctx *jxcontext.Context, storeIDs []int, isFocus bool, keyword } if keyword != "" { keywordLike := "%" + keyword + "%" - sql += " AND (t1.name LIKE ? OR t1.prefix LIKE ? OR t2.comment LIKE ?" - sqlParams = append(sqlParams, keywordLike, keywordLike, keywordLike) + 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 t2.jd_id = ? OR t4.id = ?" - sqlParams = append(sqlParams, keywordInt64, keywordInt64) + sql += " OR t1.id = ? OR t2.id = ? OR t2.jd_id = ? OR t4.ebai_id = ? OR t4.mtwm_id = ?" + sqlParams = append(sqlParams, keywordInt64, keywordInt64, keywordInt64, keywordInt64, keywordInt64) } sql += ")" } @@ -197,14 +212,36 @@ func GetStoresSkus(ctx *jxcontext.Context, storeIDs []int, isFocus bool, keyword sqlParams = append(sqlParams, skuIDs) } } - if isFocus && params["fromStatus"] != nil { - fromStatus := params["fromStatus"].(int) - toStatus := fromStatus - if params["toStatus"] != nil { - toStatus = params["toStatus"].(int) + 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 t4.jd_sync_status & ? = 0 AND t2.jd_id <> 0 AND t1.status = ? AND t2.status = ?)" + sqlParams = append(sqlParams, params["jdSyncStatus"], model.SyncFlagDeletedMask|model.SyncFlagNewMask, model.SkuStatusNormal, model.SkuStatusNormal) + } + if params["ebaiSyncStatus"] != nil && realVendorMap[model.VendorIDEBAI] == 1 { + sql += " OR (t4.ebai_sync_status & ? <> 0 AND t4.ebai_sync_status & ? = 0)" + sqlParams = append(sqlParams, params["ebaiSyncStatus"], model.SyncFlagDeletedMask|model.SyncFlagNewMask) + } + if params["mtwmSyncStatus"] != nil && realVendorMap[model.VendorIDMTWM] == 1 { + sql += " OR (t4.mtwm_sync_status & ? <> 0 AND t4.mtwm_sync_status & ? = 0)" + sqlParams = append(sqlParams, params["mtwmSyncStatus"], model.SyncFlagDeletedMask|model.SyncFlagNewMask) + } + sql += ")" } - sql += " AND t4.status >= ? AND t4.status <= ?" - sqlParams = append(sqlParams, fromStatus, toStatus) } sql += ` GROUP BY @@ -392,6 +429,80 @@ func GetStoresSkus(ctx *jxcontext.Context, storeIDs []int, isFocus bool, keyword return skuNamesInfo, err } +func getValidStoreVendorMap(db *dao.DaoDB, storeIDs []int) (realVendorMap map[int]int, err error) { + storeMapList, err := dao.GetStoresMapList(db, nil, storeIDs, model.StoreStatusAll) + 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) @@ -436,24 +547,65 @@ func UpdateStoreSku(ctx *jxcontext.Context, storeID int, skuBindInfo *StoreSkuBi } func UpdateStoreSkus(ctx *jxcontext.Context, storeID int, skuBindInfos []*StoreSkuBindInfo, isAsync, isContinueWhenError bool) (hint string, err error) { - // skuIDs, err := updateStoresSkusWithoutSync(ctx, []int{storeID}, skuBindInfos) - // num = int64(len(skuIDs)) - // if err == nil && num > 0 { - // db := dao.GetDB() - // _, err = CurVendorSync.SyncStoresSkus(ctx, db, nil, []int{storeID}, skuIDs, false, false) - // return int64(len(skuIDs)), err - // } - // return 0, err return UpdateStoresSkus(ctx, []int{storeID}, skuBindInfos, isAsync, isContinueWhenError) } func UpdateStoresSkus(ctx *jxcontext.Context, storeIDs []int, skuBindInfos []*StoreSkuBindInfo, isAsync, isContinueWhenError bool) (hint string, err error) { var num int64 - skuIDs, err := updateStoresSkusWithoutSync(ctx, storeIDs, skuBindInfos) + db := dao.GetDB() + skuIDs, err := updateStoresSkusWithoutSync(ctx, db, storeIDs, skuBindInfos) + if err != nil { + return "", err + } num = int64(len(skuIDs)) if num > 0 { - db := dao.GetDB() - hint, err = CurVendorSync.SyncStoresSkus(ctx, db, nil, storeIDs, skuIDs, isAsync, isContinueWhenError) + 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 > 9 { + 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) + 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) @@ -504,13 +656,41 @@ func checkStoresSkusSaleCity(ctx *jxcontext.Context, db *dao.DaoDB, storeIDs []i return err } -func updateStoresSkusWithoutSync(ctx *jxcontext.Context, storeIDs []int, skuBindInfos []*StoreSkuBindInfo) (needSyncSkus []int, err error) { +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)) - db := dao.GetDB() - if err = checkStoresSkusSaleCity(ctx, db, storeIDs, skuBindInfos); err != nil { - return nil, err + 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 } @@ -527,27 +707,45 @@ func updateStoresSkusWithoutSync(ctx *jxcontext.Context, storeIDs []int, skuBind }() 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.spec_quality, t1.spec_unit, - t3.price sku_name_price, t3.unit sku_name_unit + t1.id real_sku_id, 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 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 = ? - WHERE t1.name_id = ? AND t1.deleted_at = ? - FOR UPDATE` + JOIN sku_name t3 ON t1.name_id = t3.id AND t3.deleted_at = ?` sqlParams := []interface{}{ storeID, utils.DefaultTimeValue, utils.DefaultTimeValue, utils.DefaultTimeValue, - skuBindInfo.NameID, - 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)) @@ -557,6 +755,10 @@ func updateStoresSkusWithoutSync(ctx *jxcontext.Context, storeIDs []int, skuBind } 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 @@ -650,7 +852,7 @@ func updateStoresSkusWithoutSync(ctx *jxcontext.Context, storeIDs []int, skuBind updateFieldMap[model.FieldUpdatedAt] = 1 updateFieldMap[model.FieldLastOperator] = 1 - setStoreSkuBindStatus(skuBind, model.SyncFlagModifiedMask) + // setStoreSkuBindStatus(skuBind, model.SyncFlagModifiedMask) dao.WrapUpdateULEntity(skuBind, userName) if num, err = dao.UpdateEntity(db, skuBind /*, utils.Map2KeySlice(updateFieldMap)...*/); err != nil { dao.Rollback(db) @@ -712,11 +914,11 @@ func updateStoreSkusSaleWithoutSync(ctx *jxcontext.Context, storeID int, skuBind } if num, err = dao.UpdateEntityLogically(db, skuBind, map[string]interface{}{ model.FieldStatus: skuBind.Status, - model.FieldJdSyncStatus: skuBind.JdSyncStatus | model.SyncFlagSaleMask | model.SyncFlagModifiedMask, - model.FieldEbaiSyncStatus: skuBind.EbaiSyncStatus | model.SyncFlagSaleMask | model.SyncFlagModifiedMask, - model.FieldMtwmSyncStatus: skuBind.MtwmSyncStatus | model.SyncFlagSaleMask | model.SyncFlagModifiedMask, - model.FieldElmSyncStatus: skuBind.ElmSyncStatus | model.SyncFlagSaleMask | model.SyncFlagModifiedMask, - model.FieldWscSyncStatus: skuBind.WscSyncStatus | model.SyncFlagSaleMask | model.SyncFlagModifiedMask, + model.FieldJdSyncStatus: skuBind.JdSyncStatus | model.SyncFlagSaleMask, + model.FieldEbaiSyncStatus: skuBind.EbaiSyncStatus | model.SyncFlagSaleMask, + model.FieldMtwmSyncStatus: skuBind.MtwmSyncStatus | model.SyncFlagSaleMask, + model.FieldElmSyncStatus: skuBind.ElmSyncStatus | model.SyncFlagSaleMask, + model.FieldWscSyncStatus: skuBind.WscSyncStatus | model.SyncFlagSaleMask, }, userName, map[string]interface{}{ model.FieldStoreID: storeID, model.FieldSkuID: v.SkuID, @@ -734,7 +936,21 @@ func updateStoreSkusSaleWithoutSync(ctx *jxcontext.Context, storeID int, skuBind 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, 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, userName) @@ -749,7 +965,7 @@ func UpdateStoresSkusSale(ctx *jxcontext.Context, storeIDs []int, skuBindSkuInfo skuIDs = append(skuIDs, v.SkuID) } db := dao.GetDB() - hint, err = CurVendorSync.SyncStoresSkus(ctx, db, nil, storeIDs, skuIDs, isAsync, isContinueWhenError) + hint, err = CurVendorSync.SyncStoresSkus(ctx, db, nil, storeIDs, skuIDs, false, isAsync, isContinueWhenError) } if num == 0 || !isAsync || hint == "" { hint = utils.Int64ToStr(num) @@ -823,11 +1039,11 @@ func CopyStoreSkus(ctx *jxcontext.Context, fromStoreID, toStoreID int, copyMode now, pricePercentage, pricePercentage, - model.SyncFlagModifiedMask | model.SyncFlagPriceMask, - model.SyncFlagModifiedMask | model.SyncFlagPriceMask, - model.SyncFlagModifiedMask | model.SyncFlagPriceMask, - model.SyncFlagModifiedMask | model.SyncFlagPriceMask, - model.SyncFlagModifiedMask | model.SyncFlagPriceMask, + model.SyncFlagPriceMask, + model.SyncFlagPriceMask, + model.SyncFlagPriceMask, + model.SyncFlagPriceMask, + model.SyncFlagPriceMask, toStoreID, utils.DefaultTimeValue, } @@ -927,11 +1143,11 @@ func CopyStoreSkus(ctx *jxcontext.Context, fromStoreID, toStoreID int, copyMode pricePercentage, pricePercentage, pricePercentage, - model.SyncFlagModifiedMask | model.SyncFlagPriceMask | model.SyncFlagSaleMask, - model.SyncFlagModifiedMask | model.SyncFlagPriceMask | model.SyncFlagSaleMask, - model.SyncFlagModifiedMask | model.SyncFlagPriceMask | model.SyncFlagSaleMask, - model.SyncFlagModifiedMask | model.SyncFlagPriceMask | model.SyncFlagSaleMask, - model.SyncFlagModifiedMask | model.SyncFlagPriceMask | model.SyncFlagSaleMask, + model.SyncFlagStoreSkuOnlyMask, + model.SyncFlagStoreSkuOnlyMask, + model.SyncFlagStoreSkuOnlyMask, + model.SyncFlagStoreSkuOnlyMask, + model.SyncFlagStoreSkuOnlyMask, toStoreID, utils.DefaultTimeValue, } @@ -1349,3 +1565,186 @@ func checkStoreExisting(db *dao.DaoDB, storeID int) (err error) { } 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) + if err != nil { + return "", err + } + if len(storeMapList) != len(storeIDs) { + return "", fmt.Errorf("门店绑定信息不匹配,请确定门店绑定且只绑定了京东平台") + } + storeMap := make(map[int]*model.StoreMap) + for _, v := range storeMapList { + if v.VendorID != vendorID { + return "", fmt.Errorf("门店%d绑定的不(只)是京东", v.StoreID) + } + storeMap[v.StoreID] = v + } + + handler := partner.GetPurchasePlatformFromVendorID(vendorID) + var storeSkuList []*model.StoreSkuBind + rootTask := tasksch.NewSeqTask(fmt.Sprintf("根据厂家门店商品信息相应刷新本地数据:%v", storeIDs), ctx, + func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { + switch step { + case 0: + storeSkuList, err = handler.GetStoresSku(ctx, task, storeIDs) + case 1: + if len(storeSkuList) > 0 { + var skuList []*model.SkuAndName + skuList, err = dao.GetSkus(db, nil, nil, nil, nil) + if err == nil { + skuNameMap := make(map[int]*model.SkuName) + skuMap := make(map[int]*model.SkuAndName) + for _, sku := range skuList { + if skuNameMap[sku.NameID] == nil { + skuNameMap[sku.NameID] = &model.SkuName{ + Unit: sku.Unit, + } + } + skuMap[sku.ID] = sku + } + 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 := int(storeMap[v.StoreID].PricePercentage) + skuName := skuNameMap[skuMap[v.SkuID].NameID] + v.Price = jxutils.CaculateSkuPriceFromVendor(v.Price, pricePercentage, 0) + v.UnitPrice = jxutils.CaculateSkuPriceFromVendor(skuName.Price, pricePercentage, 0) + 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.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.BareStoreSkuInfo, 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.BareStoreSkuInfo) + _, 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 := CurVendorSync.GetStoreHandler(loopMapInfo.VendorID); handler != nil { + storeSkuList, err2 := dao.GetStoreSkus2(db, loopMapInfo.VendorID, storeID, skuIDs, false) + if err = err2; err == nil && len(storeSkuList) > 0 { + bareStoreSkuInfoList := make([]*partner.BareStoreSkuInfo, len(skuIDs)) + for k, v := range storeSkuList { + bareStoreSkuInfoList[k] = &partner.BareStoreSkuInfo{ + SkuID: v.SkuID, + VendorSkuID: v.VendorSkuID, + } + } + outBareStoreSkuInfoList, err2 := handler.GetStoreSkusInfo(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 +} diff --git a/business/jxstore/cms/storeman.go b/business/jxstore/cms/storeman.go index 1dd9b8cce..0d17f7789 100644 --- a/business/jxstore/cms/storeman.go +++ b/business/jxstore/cms/storeman.go @@ -21,6 +21,7 @@ func init() { } func (s *StoreManager) OnStoreStatusChanged(vendorStoreID string, vendorID int, storeStatus int) (err error) { + return err globals.SugarLogger.Debugf("OnStoreStatusChanged venvendorStoreID:%s, storeStatus:%d", vendorStoreID, storeStatus) db := dao.GetDB() storeDetail, err := dao.GetStoreDetailByVendorStoreID(db, vendorStoreID, vendorID) diff --git a/business/jxstore/cms/sync.go b/business/jxstore/cms/sync.go index 2a91f51bd..d9a7ce333 100644 --- a/business/jxstore/cms/sync.go +++ b/business/jxstore/cms/sync.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "reflect" + "strings" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxutils" @@ -81,14 +82,14 @@ func Init() { } func (p *MultiStoreHandlerWrapper) DeleteCategory(db *dao.DaoDB, cat *model.SkuCategory, userName string) (err error) { - if jxutils.IsFakeID(cat.JdID) { + if jxutils.IsEmptyID(cat.JdID) { return nil } return p.IMultipleStoresHandler.DeleteCategory(db, cat, userName) } func (p *MultiStoreHandlerWrapper) UpdateCategory(db *dao.DaoDB, cat *model.SkuCategory, userName string) (err error) { - if jxutils.IsFakeID(cat.JdID) { + if jxutils.IsEmptyID(cat.JdID) { globals.SugarLogger.Warnf("UpdateCategory fakeid cat:%s should not get here", utils.Format4Output(cat, true)) return nil } @@ -97,14 +98,14 @@ func (p *MultiStoreHandlerWrapper) UpdateCategory(db *dao.DaoDB, cat *model.SkuC func (p *MultiStoreHandlerWrapper) DeleteSku(db *dao.DaoDB, sku *model.Sku, userName string) (err error) { globals.SugarLogger.Debugf("wrapper DeleteSku, sku:%s", utils.Format4Output(sku, false)) - if jxutils.IsFakeID(sku.JdID) { + if jxutils.IsEmptyID(sku.JdID) { return nil } return p.IMultipleStoresHandler.DeleteSku(db, sku, userName) } func (p *MultiStoreHandlerWrapper) UpdateSku(db *dao.DaoDB, sku *model.Sku, userName string) (err error) { - if jxutils.IsFakeID(sku.JdID) { + if jxutils.IsEmptyID(sku.JdID) { globals.SugarLogger.Warnf("UpdateSku fakeid sku:%s should not get here", utils.Format4Output(sku, true)) return nil } @@ -213,32 +214,36 @@ func (v *VendorSync) SyncStore(ctx *jxcontext.Context, db *dao.DaoDB, vendorID, vendorID, } } - hint, err = v.LoopStoresMap(ctx, db, fmt.Sprintf("同步门店信息:%d", storeID), isAsync, false, vendorIDs, []int{storeID}, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (interface{}, error) { + hint, err = v.LoopStoresMap(ctx, db, fmt.Sprintf("同步门店信息:%d", storeID), isAsync, false, vendorIDs, []int{storeID}, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (resultList interface{}, err error) { loopMapInfo := batchItemList[0].(*LoopStoreMapInfo) handler := v.GetStoreHandler(loopMapInfo.VendorID) if len(loopMapInfo.StoreMapList) > 1 { loopStoreTask := tasksch.NewParallelTask(fmt.Sprintf("处理平台%s", model.VendorChineseNames[loopMapInfo.VendorID]), nil, ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + var resultList []interface{} storeMap := batchItemList[0].(*model.StoreMap) if err = handler.UpdateStore(db, storeMap.StoreID, userName); err == nil { storeMap.SyncStatus = 0 _, err = dao.UpdateEntity(db, storeMap, model.FieldSyncStatus) + resultList = append(resultList, 1) } - return nil, err + return resultList, err }, loopMapInfo.StoreMapList) t.AddChild(loopStoreTask).Run() - _, err = loopStoreTask.GetResult(0) - return nil, err + resultList, err = loopStoreTask.GetResult(0) + } else { + storeMap := loopMapInfo.StoreMapList[0] + if err = handler.UpdateStore(db, storeMap.StoreID, userName); err == nil { + storeMap.SyncStatus = 0 + _, err = dao.UpdateEntity(db, storeMap, model.FieldSyncStatus) + } + if err == nil { + resultList = []interface{}{1} + } } - storeMap := loopMapInfo.StoreMapList[0] - if err = handler.UpdateStore(db, storeMap.StoreID, userName); err == nil { - storeMap.SyncStatus = 0 - _, err = dao.UpdateEntity(db, storeMap, model.FieldSyncStatus) - } - err = jxutils.AddVendorInfo2Err(err, loopMapInfo.VendorID) - return nil, err - }) - return hint, err + return resultList, partner.AddVendorInfo2Err(err, loopMapInfo.VendorID) + }, true) + return hint, makeSyncError(err) } func (v *VendorSync) SyncSku(ctx *jxcontext.Context, db *dao.DaoDB, nameID, skuID int, isAsync, isContinueWhenError bool, userName string) (hint string, err error) { @@ -259,6 +264,7 @@ func (v *VendorSync) SyncSkus(ctx *jxcontext.Context, db *dao.DaoDB, nameIDs []i globals.SugarLogger.Debugf("SyncSku trackInfo:%s, nameIDs:%v, skuIDs:%v, userName:%s", ctx.GetTrackInfo(), nameIDs, skuIDs, userName) return v.LoopMultiStoresVendors(ctx, db, fmt.Sprintf("同步商品信息, nameIDs:%v, skuIDs:%v", nameIDs, skuIDs), isAsync, userName, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (interface{}, error) { + var resultList []interface{} vendorID := batchItemList[0].(int) multiStoresHandler := v.GetMultiStoreHandler(vendorID) syncStatusFieldName := dao.GetSyncStatusStructField(model.VendorNames[multiStoresHandler.GetVendorID()]) @@ -297,6 +303,7 @@ func (v *VendorSync) SyncSkus(ctx *jxcontext.Context, db *dao.DaoDB, nameIDs []i // todo 同一skuName下的sku顺序处理的原因是京东SPU特殊类型必须要序列化同步才能正常处理, db可能会有多线程问题 task := tasksch.NewParallelTask(fmt.Sprintf("处理平台%s", model.VendorChineseNames[vendorID]), tasksch.NewParallelConfig().SetParallelCount(10).SetIsContinueWhenError(isContinueWhenError), ctx, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (interface{}, error) { + var resultList []interface{} skuName := batchItemList[0].(*model.SkuName) var skuList []*model.Sku if err = dao.GetRows(db, &skuList, fmt.Sprintf(` @@ -333,6 +340,8 @@ func (v *VendorSync) SyncSkus(ctx *jxcontext.Context, db *dao.DaoDB, nameIDs []i refutil.SetObjFieldByName(sku, syncStatusFieldName, int8(0)) if _, err = dao.UpdateEntity(db, sku, updateFields...); err != nil { break + } else { + resultList = append(resultList, 1) } } } @@ -342,22 +351,28 @@ func (v *VendorSync) SyncSkus(ctx *jxcontext.Context, db *dao.DaoDB, nameIDs []i refutil.SetObjFieldByName(skuName, syncStatusFieldName, int8(0)) _, err = dao.UpdateEntity(db, skuName, syncStatusFieldName) } - return nil, err + return resultList, err }, skuNameList) t.AddChild(task).Run() - _, err = task.GetResult(0) + result, err2 := task.GetResult(0) + if err = err2; err == nil { + resultList = append(resultList, result...) + } } - return nil, err + return resultList, err }) } -func (v *VendorSync) SyncStoresCategory(ctx *jxcontext.Context, db *dao.DaoDB, vendorIDs []int, storeIDs []int, isAsync bool) (hint string, err error) { +func (v *VendorSync) SyncStoresCategory(ctx *jxcontext.Context, db *dao.DaoDB, vendorIDs []int, storeIDs []int, isForce, isAsync, isContinueWhenError bool) (hint string, err error) { globals.SugarLogger.Debug("SyncStoresCategory") isManageIt := len(storeIDs) != 1 - return v.LoopStoresMap(ctx, db, fmt.Sprintf("同步门店分类信息:%v", storeIDs), isAsync, isManageIt, vendorIDs, storeIDs, + hint, err = v.LoopStoresMap(ctx, db, fmt.Sprintf("同步门店分类信息:%v", storeIDs), isAsync, isManageIt, vendorIDs, storeIDs, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (interface{}, error) { loopMapInfo := batchItemList[0].(*LoopStoreMapInfo) if handler := v.GetSingleStoreHandler(loopMapInfo.VendorID); handler != nil { + if isForce { + dao.SetStoreCategorySyncStatus(db, loopMapInfo.VendorID, storeIDs, nil, model.SyncFlagModifiedMask) + } if len(loopMapInfo.StoreMapList) > 1 { loopStoreTask := tasksch.NewSeqTask(fmt.Sprintf("处理平台%s", model.VendorChineseNames[loopMapInfo.VendorID]), ctx, func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { @@ -367,73 +382,120 @@ func (v *VendorSync) SyncStoresCategory(ctx *jxcontext.Context, db *dao.DaoDB, v }, len(loopMapInfo.StoreMapList)) t.AddChild(loopStoreTask).Run() _, err = loopStoreTask.GetResult(0) - return nil, err + } else { + _, err = handler.SyncStoreCategory(ctx, t, loopMapInfo.StoreMapList[0].StoreID, false) } - _, err = handler.SyncStoreCategory(ctx, t, loopMapInfo.StoreMapList[0].StoreID, false) - err = jxutils.AddVendorInfo2Err(err, loopMapInfo.VendorID) } - return nil, err - }) + return nil, partner.AddVendorInfo2Err(err, loopMapInfo.VendorID) + }, isContinueWhenError) + return hint, makeSyncError(err) } // -func (v *VendorSync) SyncStoresSkus(ctx *jxcontext.Context, db *dao.DaoDB, vendorIDs []int, storeIDs []int, skuIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) { +func (v *VendorSync) SyncStoresSkus(ctx *jxcontext.Context, db *dao.DaoDB, vendorIDs []int, storeIDs []int, skuIDs []int, isForce, isAsync, isContinueWhenError bool) (hint string, err error) { globals.SugarLogger.Debug("SyncStoresSkus") isManageIt := len(storeIDs) != 1 || len(skuIDs) == 0 || len(skuIDs) > 8 - return v.LoopStoresMap(ctx, db, fmt.Sprintf("同步门店商品信息:%v", storeIDs), isAsync, isManageIt, vendorIDs, storeIDs, + task, hint, err := v.LoopStoresMap2(ctx, db, fmt.Sprintf("同步门店商品信息:%v", storeIDs), isAsync, isManageIt, vendorIDs, storeIDs, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (interface{}, error) { loopMapInfo := batchItemList[0].(*LoopStoreMapInfo) if handler := v.GetStoreHandler(loopMapInfo.VendorID); handler != nil { + if isForce { + dao.SetStoreSkuSyncStatus(db, loopMapInfo.VendorID, storeIDs, skuIDs, model.SyncFlagModifiedMask) + } if len(loopMapInfo.StoreMapList) > 1 { - loopStoreTask := tasksch.NewSeqTask(fmt.Sprintf("处理平台%s", model.VendorChineseNames[loopMapInfo.VendorID]), ctx, - func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { - storeID := loopMapInfo.StoreMapList[step].StoreID - if _, err = handler.SyncStoreSkus(ctx, task, storeID, skuIDs, false, isContinueWhenError); err != nil { - globals.SugarLogger.Debugf("SyncStoresSkus failed1 store:%d failed with error:%v", storeID, err) - if isContinueWhenError { - err = nil + var loopStoreTask tasksch.ITask + if model.MultiStoresVendorMap[loopMapInfo.VendorID] == 1 { + loopStoreTask = tasksch.NewSeqTask(fmt.Sprintf("处理平台%s", model.VendorChineseNames[loopMapInfo.VendorID]), ctx, + func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { + storeID := loopMapInfo.StoreMapList[step].StoreID + if _, err = handler.SyncStoreSkus(ctx, task, storeID, skuIDs, false, isContinueWhenError); err != nil { + globals.SugarLogger.Debugf("SyncStoresSkus failed1 store:%d failed with error:%v", storeID, err) + if isContinueWhenError { + err = nil + } } - } - return nil, err - }, len(loopMapInfo.StoreMapList)) + return nil, err + }, len(loopMapInfo.StoreMapList)) + } else { + loopStoreTask = tasksch.NewParallelTask(fmt.Sprintf("处理平台%s", model.VendorChineseNames[loopMapInfo.VendorID]), tasksch.NewParallelConfig().SetIsContinueWhenError(isContinueWhenError), ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + storeMap := batchItemList[0].(*model.StoreMap) + if _, err = handler.SyncStoreSkus(ctx, task, storeMap.StoreID, skuIDs, false, isContinueWhenError); err != nil { + globals.SugarLogger.Debugf("SyncStoresSkus failed2 store:%d failed with error:%v", storeMap.StoreID, err) + } + return nil, err + }, loopMapInfo.StoreMapList) + } t.AddChild(loopStoreTask).Run() _, err = loopStoreTask.GetResult(0) - return nil, err + } else { + _, err = handler.SyncStoreSkus(ctx, t, loopMapInfo.StoreMapList[0].StoreID, skuIDs, false, isContinueWhenError) } - _, err = handler.SyncStoreSkus(ctx, t, loopMapInfo.StoreMapList[0].StoreID, skuIDs, false, isContinueWhenError) - err = jxutils.AddVendorInfo2Err(err, loopMapInfo.VendorID) } - return nil, err - }) + return nil, partner.AddVendorInfo2Err(err, loopMapInfo.VendorID) + }, isContinueWhenError) + if task != nil { + if vendorErr := partner.IsErrChangePriceFailed(task.GetOriginalErr()); vendorErr != nil { + platformList := make([]string, len(task.GetDetailErrList())) + for k, v := range task.GetDetailErrList() { + if vendorErr := partner.IsErrVendorError(v); vendorErr != nil { + platformList[k] = model.VendorChineseNames[vendorErr.VendorID()] + } else { + platformList[k] = "未知" + } + } + err = fmt.Errorf("同步价格失败\n失败平台:%s", strings.Join(platformList, ",")) + } else { + err = makeSyncError(err) + } + } + return hint, err } func (v *VendorSync) FullSyncStoresSkus(ctx *jxcontext.Context, db *dao.DaoDB, vendorIDs []int, storeIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) { globals.SugarLogger.Debug("FullSyncStoresSkus") - return v.LoopStoresMap(ctx, db, fmt.Sprintf("初始化门店商品信息:%v", storeIDs), isAsync, true, vendorIDs, storeIDs, + hint, err = v.LoopStoresMap(ctx, db, fmt.Sprintf("初始化门店商品信息:%v", storeIDs), isAsync, true, vendorIDs, storeIDs, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (interface{}, error) { loopMapInfo := batchItemList[0].(*LoopStoreMapInfo) if handler := v.GetStoreHandler(loopMapInfo.VendorID); handler != nil { if len(loopMapInfo.StoreMapList) > 1 { - loopStoreTask := tasksch.NewSeqTask(fmt.Sprintf("处理平台%s", model.VendorChineseNames[loopMapInfo.VendorID]), ctx, - func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { - storeID := loopMapInfo.StoreMapList[step].StoreID - _, err = handler.FullSyncStoreSkus(ctx, task, storeID, false, isContinueWhenError) - return nil, err - }, len(loopMapInfo.StoreMapList)) + var loopStoreTask tasksch.ITask + if model.MultiStoresVendorMap[loopMapInfo.VendorID] == 1 { + loopStoreTask = tasksch.NewSeqTask(fmt.Sprintf("处理平台%s", model.VendorChineseNames[loopMapInfo.VendorID]), ctx, + func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { + storeID := loopMapInfo.StoreMapList[step].StoreID + if _, err = handler.FullSyncStoreSkus(ctx, task, storeID, false, isContinueWhenError); err != nil { + globals.SugarLogger.Debugf("FullSyncStoresSkus failed1 store:%d failed with error:%v", storeID, err) + if isContinueWhenError { + err = nil + } + } + return nil, err + }, len(loopMapInfo.StoreMapList)) + } else { + loopStoreTask = tasksch.NewParallelTask(fmt.Sprintf("处理平台%s", model.VendorChineseNames[loopMapInfo.VendorID]), tasksch.NewParallelConfig().SetIsContinueWhenError(isContinueWhenError), ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + storeMap := batchItemList[0].(*model.StoreMap) + if _, err = handler.FullSyncStoreSkus(ctx, task, storeMap.StoreID, false, isContinueWhenError); err != nil { + globals.SugarLogger.Debugf("FullSyncStoresSkus failed2 store:%d failed with error:%v", storeMap.StoreID, err) + } + return nil, err + }, loopMapInfo.StoreMapList) + } t.AddChild(loopStoreTask).Run() _, err = loopStoreTask.GetResult(0) - return nil, err + } else { + _, err = handler.FullSyncStoreSkus(ctx, t, loopMapInfo.StoreMapList[0].StoreID, false, isContinueWhenError) } - _, err = handler.FullSyncStoreSkus(ctx, t, loopMapInfo.StoreMapList[0].StoreID, false, isContinueWhenError) - err = jxutils.AddVendorInfo2Err(err, loopMapInfo.VendorID) } - return nil, err - }) + return nil, partner.AddVendorInfo2Err(err, loopMapInfo.VendorID) + }, isContinueWhenError) + return hint, makeSyncError(err) } func (v *VendorSync) DeleteRemoteStoreSkus(ctx *jxcontext.Context, db *dao.DaoDB, vendorIDs []int, storeIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) { globals.SugarLogger.Debug("DeleteRemoteStoreSkus") - return v.LoopStoresMap(ctx, db, fmt.Sprintf("删除远程门店商品信息:%v", storeIDs), isAsync, true, vendorIDs, storeIDs, + hint, err = v.LoopStoresMap(ctx, db, fmt.Sprintf("删除远程门店商品信息:%v", storeIDs), isAsync, true, vendorIDs, storeIDs, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (interface{}, error) { loopMapInfo := batchItemList[0].(*LoopStoreMapInfo) if handler := v.GetStoreHandler(loopMapInfo.VendorID); handler != nil { @@ -446,16 +508,16 @@ func (v *VendorSync) DeleteRemoteStoreSkus(ctx *jxcontext.Context, db *dao.DaoDB }, len(loopMapInfo.StoreMapList)) t.AddChild(loopStoreTask).Run() _, err = loopStoreTask.GetResult(0) - return nil, err + } else { + _, err = handler.DeleteRemoteStoreSkus(ctx, t, loopMapInfo.StoreMapList[0].StoreID, false, isContinueWhenError) } - _, err = handler.DeleteRemoteStoreSkus(ctx, t, loopMapInfo.StoreMapList[0].StoreID, false, isContinueWhenError) - err = jxutils.AddVendorInfo2Err(err, loopMapInfo.VendorID) } - return nil, err - }) + return nil, partner.AddVendorInfo2Err(err, loopMapInfo.VendorID) + }, isContinueWhenError) + return hint, makeSyncError(err) } -func (v *VendorSync) LoopStoresMap(ctx *jxcontext.Context, db *dao.DaoDB, taskName string, isAsync, isManageIt bool, vendorIDs []int, storeIDs []int, handler tasksch.WorkFunc) (hint string, err error) { +func (v *VendorSync) LoopStoresMap2(ctx *jxcontext.Context, db *dao.DaoDB, taskName string, isAsync, isManageIt bool, vendorIDs []int, storeIDs []int, handler tasksch.WorkFunc, isContinueWhenError bool) (task tasksch.ITask, hint string, err error) { sql := ` SELECT t1.* FROM store_map t1 @@ -475,11 +537,11 @@ func (v *VendorSync) LoopStoresMap(ctx *jxcontext.Context, db *dao.DaoDB, taskNa sql += " ORDER BY t1.store_id, t1.vendor_id" var storeMapList []*model.StoreMap if err = dao.GetRows(db, &storeMapList, sql, sqlParams...); err != nil { - return "", err + return nil, "", err } if len(storeMapList) == 0 { - return "", nil + return nil, "", nil } vendorStoreMap := make(map[int][]*model.StoreMap) for _, v := range storeMapList { @@ -497,21 +559,40 @@ func (v *VendorSync) LoopStoresMap(ctx *jxcontext.Context, db *dao.DaoDB, taskNa if len(loopInfoList) == 1 { taskName = fmt.Sprintf("%s,处理平台%s", taskName, model.VendorChineseNames[loopInfoList[0].VendorID]) } - task := tasksch.NewParallelTask(taskName, tasksch.NewParallelConfig().SetIsContinueWhenError(true), ctx, handler, loopInfoList) + task = tasksch.NewParallelTask(taskName, tasksch.NewParallelConfig().SetIsContinueWhenError(true), ctx, handler, loopInfoList) tasksch.HandleTask(task, nil, isManageIt).Run() if !isAsync { - _, err = task.GetResult(0) + resultList, err2 := task.GetResult(0) + if err = err2; err == nil { + if len(resultList) == 0 { + hint = "1" // todo 暂时这样 + } else { + hint = jxutils.TaskResult2Hint(resultList) + } + } + } else { + hint = task.GetID() } - return task.ID, makeSyncError(err) + return task, hint, err +} + +func (v *VendorSync) LoopStoresMap(ctx *jxcontext.Context, db *dao.DaoDB, taskName string, isAsync, isManageIt bool, vendorIDs []int, storeIDs []int, handler tasksch.WorkFunc, isContinueWhenError bool) (hint string, err error) { + _, hint, err = v.LoopStoresMap2(ctx, db, taskName, isAsync, isManageIt, vendorIDs, storeIDs, handler, isContinueWhenError) + return hint, err } func (v *VendorSync) LoopMultiStoresVendors(ctx *jxcontext.Context, db *dao.DaoDB, taskName string, isAsync bool, userName string, handler tasksch.WorkFunc) (hint string, err error) { task := tasksch.NewParallelTask(taskName, tasksch.NewParallelConfig().SetIsContinueWhenError(true), ctx, handler, v.MultiStoreVendorIDs) tasksch.HandleTask(task, nil, true).Run() if !isAsync { - _, err = task.GetResult(0) + result, err2 := task.GetResult(0) + if err = err2; err == nil { + hint = utils.Int2Str(len(result)) + } + } else { + hint = task.ID } - return task.ID, makeSyncError(err) + return hint, makeSyncError(err) } func (v *VendorSync) RefreshAllSkusID(ctx *jxcontext.Context, isAsync bool, vendorIDs []int, storeIDs []int) (hint string, err error) { @@ -552,8 +633,10 @@ func (v *VendorSync) RefreshAllStoresID(ctx *jxcontext.Context, isAsync bool, ve func makeSyncError(err error) (newErr error) { if err != nil { - return &SyncError{ - Original: err, + if _, ok := err.(*SyncError); !ok { + return &SyncError{ + Original: err, + } } } return err diff --git a/business/jxstore/financial/financial.go b/business/jxstore/financial/financial.go index 8a20b7aab..e4a4a3fa2 100644 --- a/business/jxstore/financial/financial.go +++ b/business/jxstore/financial/financial.go @@ -61,7 +61,7 @@ func SendFilesToStores(ctx *jxcontext.Context, files []*multipart.FileHeader, ti globals.SugarLogger.Debugf("SendFilesToStores upload file:%s", fileHeader.Filename) if err == nil { ret := storage.PutRet{} - key := "storeBill_" + utils.Int2Str(storeID) + "_" + strings.ToLower(utils.GetUUID()) + path.Ext(fileHeader.Filename) + key := "storeBill/" + utils.Int2Str(storeID) + "_" + strings.ToLower(utils.GetUUID()) + path.Ext(fileHeader.Filename) formUploader := storage.NewFormUploader(cfg) for i := 0; i < 3; i++ { if err = formUploader.Put(context.Background(), &ret, upToken, key, file, fileHeader.Size, &storage.PutExtra{}); err == nil { @@ -73,7 +73,7 @@ func SendFilesToStores(ctx *jxcontext.Context, files []*multipart.FileHeader, ti db := dao.GetDB() billRec := &legacymodel.StoreBill{ Date: time.Now(), - Url: jxutils.ComposeQiniuResURL(strings.Replace(ret.Key, "http://", "https://", -1)), + Url: strings.Replace(jxutils.ComposeQiniuResURL(ret.Key), "http://", "https://", -1), StoreId: storeID, BillName: fileHeader.Filename, ShopName: shopName, diff --git a/business/jxstore/initdata/initdata.go b/business/jxstore/initdata/initdata.go index 0036ec031..e7bb63cc9 100644 --- a/business/jxstore/initdata/initdata.go +++ b/business/jxstore/initdata/initdata.go @@ -57,86 +57,135 @@ func insertPlace(ctx *jxcontext.Context, db *dao.DaoDB, parent *autonavi.Distric } func InitPlace(ctx *jxcontext.Context) (err error) { + placeList, err2 := api.AutonaviAPI.GetDistricts(autonavi.DistrictLevelDistrict, "") + if err = err2; err != nil { + return err + } + placeList = placeList[0].Districts db := dao.GetDB() - if err = TruncateTable(db, "place"); err == nil { - placeList, err2 := api.AutonaviAPI.GetDistricts(autonavi.DistrictLevelDistrict, "") - if err = err2; err != nil { - return err - } - placeList = placeList[0].Districts - dao.Begin(db) - defer func() { + dao.Begin(db) + defer func() { + if r := recover(); r != nil || err != nil { dao.Rollback(db) - }() - if err = insertPlace(ctx, db, nil, placeList); err != nil { - return err - } - updateSqls := []string{ - ` - UPDATE place t1 - JOIN jde_city t2 ON t1.code = t2.col_tencentAddressCode - SET t1.jd_code = t2.col_areaCode; - `, - ` - UPDATE place t1 - JOIN place t2 ON t1.parent_code = t2.code AND t2.jd_code != 0 - JOIN jde_district t3 ON t1.name = t3.col_areaName AND t2.jd_code = t3.col_cityCode - SET t1.jd_code = t3.col_areaCode - WHERE t1.level = 3; - `, - ` - UPDATE - place t1 - JOIN ebde_places t2 ON t1.name = t2.col_city_name - SET t1.ebai_code = t2.col_city_id - WHERE t1.level = 1 OR t1.level = 2; - `, - ` - UPDATE - place t1 - JOIN place t1p ON t1.parent_code = t1p.code - JOIN ebde_places t2 ON t1.name = t2.col_city_name - JOIN ebde_places t2p ON t2.col_parent_id = t2p.col_city_id AND t1p.ebai_code = t2p.col_city_id - SET t1.ebai_code = t2.col_city_id - WHERE t1.level = 3; - `, - ` - UPDATE - place t1 - JOIN mtpsdeliveryprice t2 ON t1.code = t2.citycode - SET t1.mtps_price = t2.price; - `, - ` - UPDATE - place t1 - JOIN mtpsdeliveryprice t2 ON t1.name LIKE CONCAT(t2.cityname, '%') - SET t1.mtps_price = t2.price - WHERE t1.level = 2 AND t1.mtps_price = 0; - `, - ` - UPDATE place t1 - LEFT JOIN ( - SELECT DISTINCT city_code - FROM store - UNION DISTINCT - SELECT DISTINCT place_code city_code - FROM sku_name_place_bind - ) t2 ON t1.code = t2.city_code - SET t1.enabled = 0 - WHERE t1.level = 2 AND t2.city_code IS NULL; - `, - } - for _, v := range updateSqls { - if _, err = dao.ExecuteSQL(db, v); err != nil { - return err + if r != nil { + panic(r) } } - dao.Commit(db) + }() + if _, err = dao.ExecuteSQL(db, ` + DELETE t1 + FROM place t1 + WHERE code < 9000000; + `); err != nil { + return err } + if err = insertPlace(ctx, db, nil, placeList); err != nil { + return err + } + updateSqls := []string{ + ` + UPDATE place t1 + JOIN jde_city t2 ON t1.code = t2.col_tencentAddressCode + SET t1.jd_code = t2.col_areaCode; + `, + ` + UPDATE place t1 + JOIN place t2 ON t1.parent_code = t2.code AND t2.jd_code != 0 + JOIN jde_district t3 ON t1.name = t3.col_areaName AND t2.jd_code = t3.col_cityCode + SET t1.jd_code = t3.col_areaCode + WHERE t1.level = 3; + `, + ` + UPDATE + place t1 + JOIN ebde_places t2 ON t1.name = t2.col_city_name + SET t1.ebai_code = t2.col_city_id + WHERE t1.level = 1 OR t1.level = 2; + `, + ` + UPDATE + place t1 + JOIN place t1p ON t1.parent_code = t1p.code + JOIN ebde_places t2 ON t1.name = t2.col_city_name + JOIN ebde_places t2p ON t2.col_parent_id = t2p.col_city_id AND t1p.ebai_code = t2p.col_city_id + SET t1.ebai_code = t2.col_city_id + WHERE t1.level = 3; + `, + ` + UPDATE + place t1 + JOIN mtpsdeliveryprice t2 ON t1.code = t2.citycode + SET t1.mtps_price = t2.price; + `, + ` + UPDATE + place t1 + JOIN mtpsdeliveryprice t2 ON t1.name LIKE CONCAT(t2.cityname, '%') + SET t1.mtps_price = t2.price + WHERE t1.level = 2 AND t1.mtps_price = 0; + `, + ` + UPDATE place t1 + LEFT JOIN ( + SELECT DISTINCT city_code + FROM store + UNION DISTINCT + SELECT DISTINCT place_code city_code + FROM sku_name_place_bind + ) t2 ON t1.code = t2.city_code + SET t1.enabled = 0 + WHERE t1.level = 2 AND t2.city_code IS NULL; + `, + } + for _, v := range updateSqls { + if _, err = dao.ExecuteSQL(db, v); err != nil { + return err + } + } + dao.Commit(db) return err } -func InitSkuName(ctx *jxcontext.Context, isForce, isAsync, isContinueWhenError bool) (hint string, err error) { +func RefreshSkuNameImg(ctx *jxcontext.Context, parentTask tasksch.ITask, isForce, isAsync, isContinueWhenError bool) (hint string, err error) { + db := dao.GetDB() + var skuNameList []*model.SkuName + if err = dao.GetRows(db, &skuNameList, ` + SELECT t1.id, t1.img, MAX(t2.jd_id) jd_id + FROM sku_name t1 + JOIN sku t2 ON t2.name_id = t1.id AND t2.deleted_at = ? + WHERE t1.deleted_at = ? + GROUP BY 1,2 + ORDER BY t1.id + `, utils.DefaultTimeValue, utils.DefaultTimeValue); err != nil { + return "", err + } + + task := tasksch.NewParallelTask("RefreshSkuNameImg", tasksch.NewParallelConfig().SetIsContinueWhenError(isContinueWhenError), ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + skuName := batchItemList[0].(*model.SkuName) + if !jxutils.IsEmptyID(skuName.JdID) { + if skuName.Img == "" || isForce { + var imgList []*jdapi.SkuPageImg + if imgList, err = api.JdAPI.GetSkuPageImageInfo(skuName.JdID); err == nil { + if len(imgList) > 0 { + skuName.Img = imgList[0].Big + _, err = dao.UpdateEntity(db, skuName, "Img") + } + } + } + } + return nil, err + }, skuNameList) + tasksch.HandleTask(task, parentTask, true).Run() + if !isAsync { + _, err = task.GetResult(0) + } else { + hint = task.ID + } + return hint, err +} + +func RefreshImgMd5(ctx *jxcontext.Context, parentTask tasksch.ITask, isForce, isAsync, isContinueWhenError bool) (hint string, err error) { db := dao.GetDB() var skuNameList []*model.SkuName if err = dao.GetRows(db, &skuNameList, ` @@ -169,7 +218,7 @@ func InitSkuName(ctx *jxcontext.Context, isForce, isAsync, isContinueWhenError b } return nil, err }, skuNameList) - tasksch.ManageTask(task).Run() + tasksch.HandleTask(task, parentTask, true).Run() if !isAsync { _, err = task.GetResult(0) } else { @@ -178,6 +227,26 @@ func InitSkuName(ctx *jxcontext.Context, isForce, isAsync, isContinueWhenError b return hint, err } +func InitSkuName(ctx *jxcontext.Context, isForce, isAsync, isContinueWhenError bool) (hint string, err error) { + rootTask := tasksch.NewSeqTask("InitSkuName", ctx, + func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { + switch step { + case 0: + _, err = RefreshSkuNameImg(ctx, task, isForce, false, isContinueWhenError) + case 1: + _, err = RefreshImgMd5(ctx, task, isForce, false, isContinueWhenError) + } + return nil, err + }, 2) + tasksch.ManageTask(rootTask).Run() + if !isAsync { + _, err = rootTask.GetResult(0) + } else { + hint = rootTask.ID + } + return hint, err +} + func InitVendorCategory(ctx *jxcontext.Context, vendorID int) (num int64, err error) { if handler := partner.GetPurchasePlatformFromVendorID(vendorID); handler != nil { cats, err2 := handler.GetVendorCategories(ctx) @@ -437,7 +506,7 @@ func BuildSkuFromEbaiStore(ctx *jxcontext.Context, baiduShopID int64, isAsync, i } price := sku.LinkID sku.LinkID = 0 - skuName := jxutils.ComposeSkuName(skuNameExt.Prefix, skuNameExt.Name, sku.Comment, skuNameExt.Unit, sku.SpecQuality, sku.SpecUnit, jdapi.MaxSkuNameLen) + skuName := jxutils.ComposeSkuName(skuNameExt.Prefix, skuNameExt.Name, sku.Comment, skuNameExt.Unit, sku.SpecQuality, sku.SpecUnit, jdapi.MaxSkuNameCharCount) fixedStatus := 1 if sku.Status != model.SkuStatusNormal { fixedStatus = 2 diff --git a/business/jxstore/misc/misc.go b/business/jxstore/misc/misc.go index 689509ad5..52821ce0d 100644 --- a/business/jxstore/misc/misc.go +++ b/business/jxstore/misc/misc.go @@ -8,6 +8,7 @@ import ( "time" "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-callback/business/jxstore/cms" "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/tasksch" @@ -47,11 +48,10 @@ func RefreshRealMobile(ctx *jxcontext.Context, vendorID int, fromTime, toTime ti sql := ` SELECT * FROM goods_order - WHERE vendor_id = ? AND consignee_mobile2 = '' AND order_created_at <= ? + WHERE vendor_id = ? AND consignee_mobile2 = '' ` sqlParams := []interface{}{ vendorID, - time.Now().Add(-4 * time.Hour), // 最近的刷新意义不大 } if !utils.IsTimeZero(fromTime) { sql += " AND order_created_at >= ?" @@ -97,29 +97,6 @@ func StartGetCityStoreInfo() { } cityCenters = append(cityCenters, guiyang) GetCityStoreInfo(cityCenters) - // countries, err := api.AutonaviAPI.GetDistricts(2, "") - // if err == nil { - // cityCenters := make([]*CityCenter, 0) - // country := countries[0] - // districts := country.Districts - // for _, province := range districts { // 省 - // for _, city := range province.Districts { // 市 - // // globals.SugarLogger.Debug(utils.Format4Output(city.Name, false)) - // cityCenter := &CityCenter{ - // Lng: city.Lng, - // Lat: city.Lat, - // CityName: city.Name, - // } - // // globals.SugarLogger.Debug(utils.Format4Output(cityCenter, false)) - // cityCenters = append(cityCenters, cityCenter) - // } - // } - // // globals.SugarLogger.Debug(utils.Format4Output(cityCenters, false)) - // GetCityStoreInfo(cityCenters) - // } - // utils.AfterFuncWithRecover(12*time.Hour, func() { - // StartGetCityStoreInfo() - // }) } func GetCityStoreInfo(cityCenters []*CityCenter) { @@ -233,63 +210,6 @@ func SaveEbaiStoreInfo(storeId, cityName string) { } } -// func GetCityStoreInfo(cityCenters []*CityCenter, i int) { -// cityCenter := cityCenters[i] -// cityPoints := GetCityPoints(cityCenter.lng, cityCenter.lat) -// GetJdCityPointsStores(jxcontext.AdminCtx, cityPoints, true, true) -// utils.AfterFuncWithRecover(10*time.Minute, func() { -// GetEbaiCityPointsStores(jxcontext.AdminCtx, cityPoints, true, true) -// utils.AfterFuncWithRecover(10*time.Minute, func() { -// i++ -// if i < len(cityCenters) { -// GetCityStoreInfo(cityCenters, i) -// } -// }) -// }) -// } - -// func GetJdCityPointsStores(ctx *jxcontext.Context, cityPoints [][]string, isAsync, isContinueWhenError bool) (hint string, err error) { -// if len(cityPoints) > 0 { -// task := tasksch.NewParallelTask("misc GetJdCityPointsStores", tasksch.NewParallelConfig().SetIsContinueWhenError(isContinueWhenError), ctx, -// func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { -// // globals.SugarLogger.Debug(batchItemList) -// point := batchItemList[0].([]string) -// err2 := jd.OnSaveStoreListInfo(point[0], point[1]) -// if err = err2; err != nil { -// globals.SugarLogger.Infof("GetJdCityPointsStores point:%s,%s failed with error:%v", point[0], point[1], err) -// } -// return nil, err -// }, cityPoints) -// // globals.SugarLogger.Debug(utils.Format4Output(task, false)) -// tasksch.HandleTask(task, nil, true).Run() -// hint = task.ID -// if !isAsync { -// _, err = task.GetResult(0) -// } -// } -// return hint, err -// } - -// func GetEbaiCityPointsStores(ctx *jxcontext.Context, cityPoints [][]string, isAsync, isContinueWhenError bool) (hint string, err error) { -// if len(cityPoints) > 0 { -// task := tasksch.NewParallelTask("misc GetEbaiCityPointsStores", tasksch.NewParallelConfig().SetIsContinueWhenError(isContinueWhenError), ctx, -// func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { -// point := batchItemList[0].([]interface{}) -// err2 := ebai.OnSaveStoreListInfo(point[0].(string), point[1].(string)) -// if err = err2; err != nil { -// globals.SugarLogger.Infof("GetEbaiCityPointsStores point:%s,%s failed with error:%v", point[0].(string), point[1].(string), err) -// } -// return nil, err -// }, cityPoints) -// tasksch.HandleTask(task, nil, true).Run() -// hint = task.ID -// if !isAsync { -// _, err = task.GetResult(0) -// } -// } -// return hint, err -// } - func GetCityPoints(lng float64, lat float64, cityName string) (cityPoints [][]string) { oneDu := 111319.55 for a := 0; a <= 80; a++ { @@ -309,3 +229,25 @@ func GetCityPoints(lng float64, lat float64, cityName string) (cityPoints [][]st } return cityPoints } +func StartDailyWork() { + if globals.ReallyCallPlatformAPI { + now := time.Now() + runTime := time.Date(now.Year(), now.Month(), now.Day(), 21, 0, 0, 0, time.Local) // 凌晨00:25点开始执行 + waitDuration := runTime.Sub(now) + if waitDuration < 5*time.Second { + waitDuration += 24 * time.Hour + } + globals.SugarLogger.Debugf("dailyWork waitDuration:%d minutes", waitDuration/time.Minute) + utils.AfterFuncWithRecover(waitDuration, func() { + doDailyWork() + StartDailyWork() + }) + } +} + +func doDailyWork() { + globals.SugarLogger.Debug("doDailyWork") + cms.EnableHaveRestStores(jxcontext.AdminCtx, true, true) + // cms.CurVendorSync.FullSyncStoresSkus(jxcontext.AdminCtx, dao.GetDB(), []int{model.VendorIDJD}, nil, true, true) + cms.CurVendorSync.SyncStoresSkus(jxcontext.AdminCtx, dao.GetDB(), []int{model.VendorIDJD, model.VendorIDEBAI, model.VendorIDMTWM}, nil, nil, false, true, true) +} diff --git a/business/jxstore/promotion/jd_promotion.go b/business/jxstore/promotion/jd_promotion.go index 11f41b020..2b78d6fda 100644 --- a/business/jxstore/promotion/jd_promotion.go +++ b/business/jxstore/promotion/jd_promotion.go @@ -72,6 +72,8 @@ type SkuPrice struct { Price int `json:"price"` // 分,这个不是单价,是这个sku的活动价 LimitSkuCount int `json:"limitSkuCount"` IsLock int8 `json:"isLock"` + + EarningPrice int `json:"earningPrice"` // 活动商品设置,结算给门店老板的钱 } type tPromotionItemInfo struct { @@ -131,7 +133,7 @@ var ( type JdPromotionHandler interface { CreatePromotionInfos(name string, beginDate, endDate time.Time, outInfoId, advertising string) (infoId int64, err error) CreatePromotionRules(infoId int64, outInfoId string, limitDevice, limitPin, limitCount, limitDaily int) (err error) - CreatePromotionSku(infoId int64, outInfoId string, skus []map[string]interface{}) (skusResult []map[string]interface{}, err error) + CreatePromotionSku(infoId int64, outInfoId string, skus []*jdapi.PromotionSku) (skusResult []*jdapi.PromotionSku, err error) ConfirmPromotion(infoId int64, outInfoId string) (err error) CancelPromotion(infoId int64, outInfoId string) (err error) } @@ -140,38 +142,38 @@ type JdDirectDownHandler struct { } func (p *JdDirectDownHandler) CreatePromotionInfos(name string, beginDate, endDate time.Time, outInfoId, advertising string) (infoId int64, err error) { - return api.JdAPI.CreatePromotionInfosSingle(name, beginDate, endDate, outInfoId, advertising) + return api.JdAPI.CreatePromotionInfosSingle(name, beginDate, endDate, outInfoId, advertising, "") } func (p *JdDirectDownHandler) CreatePromotionRules(infoId int64, outInfoId string, limitDevice, limitPin, limitCount, limitDaily int) (err error) { - return api.JdAPI.CreatePromotionRules(infoId, outInfoId, limitDevice, limitPin, limitCount, limitDaily) + return api.JdAPI.CreatePromotionRulesSingle(infoId, outInfoId, limitDevice, limitPin, limitCount, limitDaily, "") } -func (p *JdDirectDownHandler) CreatePromotionSku(infoId int64, outInfoId string, skus []map[string]interface{}) (skusResult []map[string]interface{}, err error) { - return api.JdAPI.CreatePromotionSkuSingle(infoId, outInfoId, skus) +func (p *JdDirectDownHandler) CreatePromotionSku(infoId int64, outInfoId string, skus []*jdapi.PromotionSku) (skusResult []*jdapi.PromotionSku, err error) { + return api.JdAPI.CreatePromotionSkuSingle(infoId, outInfoId, skus, "") } func (p *JdDirectDownHandler) ConfirmPromotion(infoId int64, outInfoId string) (err error) { - return api.JdAPI.ConfirmPromotionSingle(infoId, outInfoId) + return api.JdAPI.ConfirmPromotionSingle(infoId, outInfoId, "") } func (p *JdDirectDownHandler) CancelPromotion(infoId int64, outInfoId string) (err error) { - return api.JdAPI.CancelPromotionSingle(infoId, outInfoId) + return api.JdAPI.CancelPromotionSingle(infoId, outInfoId, "") } type JdLimitedTimeHandler struct { } func (p *JdLimitedTimeHandler) CreatePromotionInfos(name string, beginDate, endDate time.Time, outInfoId, advertising string) (infoId int64, err error) { - return api.JdAPI.CreatePromotionInfosLimitTime(name, beginDate, endDate, outInfoId, advertising) + return api.JdAPI.CreatePromotionInfosLimitTime(name, beginDate, endDate, outInfoId, advertising, "") } func (p *JdLimitedTimeHandler) CreatePromotionRules(infoId int64, outInfoId string, limitDevice, limitPin, limitCount, limitDaily int) (err error) { - return api.JdAPI.CreatePromotionRules(infoId, outInfoId, limitDevice, limitPin, limitCount, limitDaily) + return api.JdAPI.CreatePromotionRulesLimitTime(infoId, outInfoId, limitDevice, limitPin, limitCount, limitDaily, "") } -func (p *JdLimitedTimeHandler) CreatePromotionSku(infoId int64, outInfoId string, skus []map[string]interface{}) (skusResult []map[string]interface{}, err error) { - return api.JdAPI.CreatePromotionSkuLimitTime(infoId, outInfoId, skus) +func (p *JdLimitedTimeHandler) CreatePromotionSku(infoId int64, outInfoId string, skus []*jdapi.PromotionSku) (skusResult []*jdapi.PromotionSku, err error) { + return api.JdAPI.CreatePromotionSkuLimitTime(infoId, outInfoId, skus, "") } func (p *JdLimitedTimeHandler) ConfirmPromotion(infoId int64, outInfoId string) (err error) { - return api.JdAPI.ConfirmPromotionLimitTime(infoId, outInfoId) + return api.JdAPI.ConfirmPromotionLimitTime(infoId, outInfoId, "") } func (p *JdLimitedTimeHandler) CancelPromotion(infoId int64, outInfoId string) (err error) { - return api.JdAPI.CancelPromotionLimitTime(infoId, outInfoId) + return api.JdAPI.CancelPromotionLimitTime(infoId, outInfoId, "") } type JdNullHandler struct { @@ -183,7 +185,7 @@ func (p *JdNullHandler) CreatePromotionInfos(name string, beginDate, endDate tim func (p *JdNullHandler) CreatePromotionRules(infoId int64, outInfoId string, limitDevice, limitPin, limitCount, limitDaily int) (err error) { return nil } -func (p *JdNullHandler) CreatePromotionSku(infoId int64, outInfoId string, skus []map[string]interface{}) (skusResult []map[string]interface{}, err error) { +func (p *JdNullHandler) CreatePromotionSku(infoId int64, outInfoId string, skus []*jdapi.PromotionSku) (skusResult []*jdapi.PromotionSku, err error) { return nil, nil } func (p *JdNullHandler) ConfirmPromotion(infoId int64, outInfoId string) (err error) { @@ -230,7 +232,10 @@ func Init() { // scheduleRoutine(true) } -func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueWhenError bool, vendorPromotionID string, params *PromotionParams, mapData map[string]interface{}) (hint string, err error) { +func CreateJdPromotion(ctx *jxcontext.Context, vendorID int, isIDJd bool, isAsync, isContinueWhenError bool, vendorPromotionID string, params *PromotionParams, mapData map[string]interface{}) (hint string, err error) { + if vendorID != model.VendorIDJD && vendorID != model.VendorIDJX { + return "", fmt.Errorf("当前只支持京西与京东活动") + } if vendorPromotionID != "" && len(vendorPromotionID) != len("14863853") { return "", fmt.Errorf("%s看起来不像是一个有效的京东活动ID,请仔细检查一下", vendorPromotionID) } @@ -260,12 +265,12 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueW userName := ctx.GetUserName() db := dao.GetDB() modifyPricesList := make(map[int][]*jdapi.SkuPriceInfo) - promotionPrices := make([]map[string]interface{}, len(params.StoreIDs)*len(params.SkuPrices)) + promotionPrices := make([]*jdapi.PromotionSku, len(params.StoreIDs)*len(params.SkuPrices)) var jxStoreIDs []int promotion := &model.Promotion{ Name: params.Name, Advertising: params.Advertising, - VendorID: model.VendorIDJD, + VendorID: vendorID, Type: params.Type, Status: model.PromotionStatusLocalCreated, LimitDevice: int8(limitDevice), @@ -278,7 +283,7 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueW Source: PromotionSourceOpenPlatform, } - if vendorPromotionID == "" { + if vendorPromotionID == "" && vendorID == model.VendorIDJD { skuIDs := make([]int, len(params.SkuPrices)) skuPriceMap := make(map[int64]*SkuPrice) for k, v := range params.SkuPrices { @@ -323,7 +328,7 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueW if promotionSkuPrice.PriceType == PriceTypePercentage { promotionSkuPrice.Price = skuBind.Price * promotionSkuPrice.Price / 100 } - if promotionSkuPrice.Price >= skuBind.Price { + if vendorID != model.VendorIDJX && promotionSkuPrice.Price >= skuBind.Price { errMsg += fmt.Sprintf("活动价大于等于原价,storeID:%d, skuID:%d\n", skuBind.StoreID, skuBind.SkuID) } if promotionSkuPrice.LimitSkuCount <= 0 { @@ -338,13 +343,11 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueW }) } } - promotionPrices[index] = map[string]interface{}{ - jdapi.KeyStationNo: utils.Str2Int64(skuBind.VendorStoreID), - jdapi.KeySkuId: skuBind.JdSkuID, - // jdapi.KeyOutStationNo: utils.Int2Str(skuBind.StoreID), - // jdapi.KeyOutSkuId: utils.Int2Str(skuBind.SkuID), - jdapi.KeyPromotionPrice: promotionSkuPrice.Price, - jdapi.KeyLimitSkuCount: promotionSkuPrice.LimitSkuCount, + promotionPrices[index] = &jdapi.PromotionSku{ + StationNo: utils.Str2Int64(skuBind.VendorStoreID), + SkuID: skuBind.JdSkuID, + PromotionPrice: int64(promotionSkuPrice.Price), + LimitSkuCount: promotionSkuPrice.LimitSkuCount, } index++ } @@ -402,6 +405,7 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueW Price: skuPrice.Price, LimitSkuCount: skuPrice.LimitSkuCount, IsLock: skuPrice.IsLock, + EarningPrice: skuPrice.EarningPrice, } dao.WrapAddIDCULDEntity(promotionSku, ctx.GetUserName()) if err = dao.CreateEntity(db, promotionSku); err != nil { @@ -409,7 +413,7 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueW } } - if vendorPromotionID == "" { + if vendorID != model.VendorIDJX && vendorPromotionID == "" { promotionHandler := getPromotionHander(params.Type) if promotionHandler == nil { return "", errors.New("非法的活动类型") @@ -436,7 +440,7 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueW for k, v := range modifyPrices { modifyPrices2[k] = v.(*jdapi.SkuPriceInfo) } - if globals.EnableStoreWrite { + if globals.EnableJdStoreWrite { if _, err = api.JdAPI.UpdateVendorStationPrice(utils.Int2Str(storeID), "", modifyPrices2); err != nil { return nil, err } @@ -451,9 +455,9 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync, isContinueW } else if step == 2 { task2 := tasksch.NewParallelTask("CreateJdPromotion CreatePromotionSku", tasksch.NewParallelConfig().SetBatchSize(MaxPromotionSkuCount).SetIsContinueWhenError(isContinueWhenError), ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params2 ...interface{}) (retVal interface{}, err error) { - skus := make([]map[string]interface{}, len(batchItemList)) + skus := make([]*jdapi.PromotionSku, len(batchItemList)) for k, v := range batchItemList { - skus[k] = v.(map[string]interface{}) + skus[k] = v.(*jdapi.PromotionSku) } _, err = promotionHandler.CreatePromotionSku(infoId, "", skus) return nil, err @@ -510,7 +514,8 @@ func GetJdPromotions(ctx *jxcontext.Context, keyword string, params map[string]i t1.end_at, t1.advertising, CONCAT("[", GROUP_CONCAT(DISTINCT CONCAT('{"storeID":', t2.store_id, ', "name":"', REPLACE(REPLACE(t22.name, '\t', ''), '"', ''), '"}')), "]") store_str, - CONCAT("[", GROUP_CONCAT(DISTINCT CONCAT('{"skuID":', t3.sku_id, ', "priceType":', t3.price_type, ', "price":', t3.price, ', "limitSkuCount":', t3.limit_sku_count, ', "isLock":', t3.is_lock, '}')), "]") sku_price_str + CONCAT("[", GROUP_CONCAT(DISTINCT CONCAT('{"skuID":', t3.sku_id, ', "priceType":', t3.price_type, ', "price":', t3.price, + ', "limitSkuCount":', t3.limit_sku_count, ', "isLock":', t3.is_lock, ', "earningPrice":', t3.earning_price, '}')), "]") sku_price_str FROM promotion t1 JOIN promotion_store t2 ON t1.id = t2.promotion_id JOIN store t22 ON t2.store_id = t22.id @@ -881,6 +886,34 @@ func LockPromotionSkus(ctx *jxcontext.Context, promotionID int, isLock int, skuI return num, err } +func UpdatePromotionSkusEarningPrice(ctx *jxcontext.Context, promotionID int, skuPriceList []*SkuPrice) (num int64, err error) { + db := dao.GetDB() + dao.Begin(db) + defer func() { + if r := recover(); r != nil || err != nil { + dao.Rollback(db) + if r != nil { + panic(r) + } + } + }() + for _, v := range skuPriceList { + var tmpNum int64 + if tmpNum, err = dao.UpdateEntityLogically(db, &model.PromotionSku{}, map[string]interface{}{ + "EarningPrice": v.EarningPrice, + }, ctx.GetUserName(), map[string]interface{}{ + "PromotionID": promotionID, + model.FieldSkuID: v.SkuID, + model.FieldDeletedAt: utils.DefaultTimeValue, + }); err != nil { + return 0, err + } + num += tmpNum + } + dao.Commit(db) + return num, err +} + func OnStoreStockMsg(msg *jdapi.CallbackStoreStockMsg) (retVal *jdapi.CallbackResponse) { var err error // globals.SugarLogger.Debugf("OnStoreStockMsg IsJdStoreSkuLocked:%t", storeskulock.IsJdStoreSkuLocked(msg.StationNo, msg.SkuId)) @@ -930,12 +963,12 @@ func createLocalPromotionFromRemote(promotionInfoId int64) (retVal *jdapi.Callba if err = dao.GetEntity(db, promotion, "VendorPromotionID"); dao.IsNoRowsError(err) { storeIDMap := make(map[int64]int) skuIDMap := make(map[int64]int) - skuMap := make(map[int64]*jdapi.PromotionSkuResult) + skuMap := make(map[int64]*jdapi.PromotionLspQuerySkuResult) // 注意,这样处理可能是有问题,我们假定的是门店信息与SKU信息的叉乘 for _, v := range result.SkuResultList { storeIDMap[v.StationNo] = 1 - skuIDMap[v.SkuId] = 1 - skuMap[v.SkuId] = v + skuIDMap[v.SkuID] = 1 + skuMap[v.SkuID] = v } jdStoreIDs := make([]string, len(storeIDMap)) index := 0 @@ -970,7 +1003,7 @@ func createLocalPromotionFromRemote(promotionInfoId int64) (retVal *jdapi.Callba jxStoreIDs[k] = v.StoreID } priceList := make([]*SkuPrice, len(skuList)) - var skuResult *jdapi.PromotionSkuResult + var skuResult *jdapi.PromotionLspQuerySkuResult for k, v := range skuList { skuResult = skuMap[v.JdID] priceList[k] = &SkuPrice{ @@ -985,11 +1018,11 @@ func createLocalPromotionFromRemote(promotionInfoId int64) (retVal *jdapi.Callba // globals.SugarLogger.Debugf("priceList:%s", utils.Format4Output(priceList, false)) source := strings.Trim(result.Source, "来源") promotionParams := &PromotionParams{ - Name: source + "-" + utils.Int64ToStr(result.PromotionInfoId), + Name: source + "-" + utils.Int64ToStr(result.PromotionInfoID), Advertising: "", Type: result.PromotionType, - BeginAt: result.BeginTime, - EndAt: result.EndTime, + BeginAt: result.BeginTime.GoTime(), + EndAt: result.EndTime.GoTime(), StoreIDs: jxStoreIDs, SkuPrices: priceList, } @@ -1002,7 +1035,7 @@ func createLocalPromotionFromRemote(promotionInfoId int64) (retVal *jdapi.Callba mapData[keyLimitDevice] = skuResult.LimitDevice mapData[keyLimitPin] = skuResult.LimitPin } - _, err = CreateJdPromotion(jxcontext.AdminCtx, false, true, false, utils.Int64ToStr(promotionInfoId), promotionParams, mapData) + _, err = CreateJdPromotion(jxcontext.AdminCtx, model.VendorIDJD, false, true, false, utils.Int64ToStr(promotionInfoId), promotionParams, mapData) if dao.IsDuplicateError(err) || err == ErrLimitDeviceIsInvalid { err = nil } @@ -1048,7 +1081,7 @@ func getPromotionHander(promotionType int) JdPromotionHandler { // panic(fmt.Sprintf("unknown promotion type:%d", promotionType)) return nil } - if !globals.EnableStoreWrite { + if !globals.EnableJdStoreWrite { promotionHandler = &JdNullHandler{} } return promotionHandler diff --git a/business/jxstore/tempop/tempop.go b/business/jxstore/tempop/tempop.go index cb4ffdcc8..c9c9d7324 100644 --- a/business/jxstore/tempop/tempop.go +++ b/business/jxstore/tempop/tempop.go @@ -8,6 +8,8 @@ import ( "sync" "time" + "git.rosy.net.cn/jx-callback/business/partner/delivery" + "git.rosy.net.cn/baseapi/platformapi/jdapi" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxcallback/orderman" @@ -102,7 +104,7 @@ func Convert2JDSPU(ctx *jxcontext.Context, count int, isAsync, isContinueWhenErr skuNew2 := *sku skuNew := &skuNew2 dao.WrapAddIDCULEntity(skuNew, ctx.GetUserName()) - skuNew.JdID = 0 //jxutils.GenFakeID() + skuNew.JdID = 0 skuNew.LinkID = sku.ID skuNew.NameID = skuNameNew.ID skuNew.JdSyncStatus = model.SyncFlagNewMask @@ -277,7 +279,7 @@ func Change2JDSPU4Store(ctx *jxcontext.Context, storeIDs []int, step int, isAsyn if err = dao.GetRows(db, &skuIDs, sql, sqlParams...); err != nil { return "", err } - hint, err = cms.CurVendorSync.SyncStoresSkus(ctx, db, []int{model.VendorIDJD}, storeIDs, skuIDs, isAsync, isContinueWhenError) + hint, err = cms.CurVendorSync.SyncStoresSkus(ctx, db, []int{model.VendorIDJD}, storeIDs, skuIDs, false, isAsync, isContinueWhenError) return hint, err } @@ -706,7 +708,7 @@ func TransformJdSpu2Sku(ctx *jxcontext.Context, skuNameIDs []int, count int, isA subTask := tasksch.NewParallelTask(fmt.Sprintf("TransformJdSpu2Sku:%d", step), tasksch.NewParallelConfig().SetIsContinueWhenError(isContinueWhenError), ctx, func(subTask *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { skuName := batchItemList[0].(*model.SkuName) - if !jxutils.IsFakeID(skuName.JdID) { + if !jxutils.IsEmptyID(skuName.JdID) { sql = ` SELECT * FROM sku @@ -727,8 +729,8 @@ func TransformJdSpu2Sku(ctx *jxcontext.Context, skuNameIDs []int, count int, isA locker.Lock() skuIDs = append(skuIDs, sku.ID) locker.Unlock() - if !jxutils.IsFakeID(sku.JdID) { - if globals.EnableStoreWrite { + if !jxutils.IsEmptyID(sku.JdID) { + if globals.EnableJdStoreWrite { if err = api.JdAPI.UpdateSkuBaseInfo(utils.Int2Str(skuName.ID), utils.Int2Str(sku.ID), utils.Params2Map(jdapi.KeyFixedStatus, jdapi.SkuFixedStatusDeleted)); err != nil { if errExt, ok := err.(*utils.ErrorWithCode); ok && errExt.IntCode() == 11004 { err = nil @@ -740,7 +742,7 @@ func TransformJdSpu2Sku(ctx *jxcontext.Context, skuNameIDs []int, count int, isA } } } - if err == nil && globals.EnableStoreWrite { + if err == nil && globals.EnableJdStoreWrite { if err = api.JdAPI.UpdateSpu(utils.Int2Str(skuName.ID), utils.Params2Map(jdapi.KeyFixedStatus, jdapi.SkuFixedStatusOffline)); err == nil { err = api.JdAPI.UpdateSpu(utils.Int2Str(skuName.ID), utils.Params2Map(jdapi.KeyFixedStatus, jdapi.SkuFixedStatusDeleted)) } else if errExt, ok := err.(*utils.ErrorWithCode); ok && errExt.IntCode() == 11035 { @@ -777,7 +779,7 @@ func TransformJdSpu2Sku(ctx *jxcontext.Context, skuNameIDs []int, count int, isA rootTask.AddChild(subTask).Run() if _, err = subTask.GetResult(0); err == nil { if len(skuIDs) > 0 { - if _, err = dao.SetStoreSkuSyncStatus(db, model.VendorIDJD, -1, skuIDs, model.SyncFlagModifiedMask|model.SyncFlagPriceMask|model.SyncFlagSaleMask); err == nil { + if _, err = dao.SetStoreSkuSyncStatus(db, model.VendorIDJD, nil, skuIDs, model.SyncFlagStoreSkuModifiedMask); err == nil { // time.Sleep(20 * time.Second) // _, err = cms.CurVendorSync.SyncStoresSkus(ctx, db, []int{model.VendorIDJD}, nil, skuIDs, false, isContinueWhenError) } @@ -960,3 +962,29 @@ func RetrieveEbaiShopLicence(ctx *jxcontext.Context, isAsync, isContinueWhenErro } return hint, err } + +func RefreshMtpsWaybillFee(ctx *jxcontext.Context, isAsync, isContinueWhenError bool) (hint string, err error) { + var waybillList []*model.Waybill + db := dao.GetDB() + if err = dao.GetRows(db, &waybillList, ` + SELECT * + FROM waybill + WHERE status_time > '2019-04-01' AND waybill_vendor_id = 102 AND desired_fee = 0 + `); err == nil { + globals.SugarLogger.Debugf("RefreshMtpsWaybillFee, count:%d", len(waybillList)) + rootTask := tasksch.NewParallelTask("RefreshMtpsWaybillFee", tasksch.NewParallelConfig().SetIsContinueWhenError(isContinueWhenError), ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + bill := batchItemList[0].(*model.Waybill) + bill.DesiredFee, _ = delivery.CalculateBillDeliveryFee(bill) + _, err = dao.UpdateEntity(db, bill, "DesiredFee") + return nil, err + }, waybillList) + tasksch.ManageTask(rootTask).Run() + if !isAsync { + _, err = rootTask.GetResult(0) + } else { + hint = rootTask.ID + } + } + return hint, err +} diff --git a/business/jxutils/excel/excel.go b/business/jxutils/excel/excel.go index 3e867a9d6..217a4493f 100644 --- a/business/jxutils/excel/excel.go +++ b/business/jxutils/excel/excel.go @@ -45,19 +45,44 @@ func Obj2Excel(sheetList []*Obj2ExcelSheetConfig) []byte { } else { excelFile.NewSheet(sheetConfig.Title) } - for index, name := range sheetConfig.CaptionList { - excelFile.SetCellStr(sheetConfig.Title, genAxis(0, index), name) - } - for i := 0; i < valueInfo.Len(); i++ { - var mapData map[string]interface{} - if typeInfo.Kind() == reflect.Struct { - mapData = utils.FlatMap(utils.Struct2MapByJson(valueInfo.Index(i).Interface())) - } else { - mapData = valueInfo.Index(i).Interface().(map[string]interface{}) + isMemberStruct := typeInfo.Kind() == reflect.Struct + if isMemberStruct { + var indexSlice [][]int + name2IndexMap := utils.GetStructNameIndex(typeInfo, "json") + for col, name := range sheetConfig.CaptionList { + if _, ok := name2IndexMap[name]; !ok { + panic(fmt.Sprintf("col:%s不能找到相应的数据", name)) + } + indexSlice = append(indexSlice, name2IndexMap[name]) + excelFile.SetCellStr(sheetConfig.Title, genAxis(0, col), name) } - for index, name := range sheetConfig.CaptionList { - // globals.SugarLogger.Debug(sheetConfig.Title, " ", genAxis(i+1, index), " ", fmt.Sprintf("%v", mapData[name])) - excelFile.SetCellStr(sheetConfig.Title, genAxis(i+1, index), fmt.Sprintf("%v", mapData[name])) + for i := 0; i < valueInfo.Len(); i++ { + for col, index := range indexSlice { + excelFile.SetCellStr(sheetConfig.Title, genAxis(i+1, col), fmt.Sprint(reflect.Indirect(valueInfo.Index(i)).FieldByIndex(index).Interface())) + } + } + } else { + var name2IndexMap map[string]int + if valueInfo.Len() > 0 { + oneData := valueInfo.Index(0).Interface().(map[string]interface{}) + name2IndexMap = make(map[string]int) + for k := range oneData { + name2IndexMap[k] = 1 + } + } + for col, name := range sheetConfig.CaptionList { + if name2IndexMap != nil { + if _, ok := name2IndexMap[name]; !ok { + panic(fmt.Sprintf("col:%s不能找到相应的数据", name)) + } + } + excelFile.SetCellStr(sheetConfig.Title, genAxis(0, col), name) + } + for i := 0; i < valueInfo.Len(); i++ { + mapData := valueInfo.Index(i).Interface().(map[string]interface{}) + for col, name := range sheetConfig.CaptionList { + excelFile.SetCellStr(sheetConfig.Title, genAxis(i+1, col), fmt.Sprint(mapData[name])) + } } } } @@ -77,6 +102,10 @@ func Excel2Slice(reader io.Reader) (contents map[string][][]string) { return contents } -func genAxis(row, col int) string { - return fmt.Sprintf("%c%d", col+65, row+1) +func genAxis(row, col int) (pos string) { + pos, err := excelize.CoordinatesToCellName(col+1, row+1) + if err != nil { + globals.SugarLogger.Debugf("err:%v", err) + } + return pos } diff --git a/business/jxutils/excel/excel_test.go b/business/jxutils/excel/excel_test.go index 4faec4626..659797a51 100644 --- a/business/jxutils/excel/excel_test.go +++ b/business/jxutils/excel/excel_test.go @@ -1,9 +1,47 @@ package excel import ( + "reflect" "testing" ) +type XXXX struct { + ID int64 `orm:"column(id)" json:"id"` + VendorOrderID string `orm:"column(vendor_order_id);size(48)" json:"vendorOrderID"` + VendorOrderID2 string `orm:"column(vendor_order_id2);size(48);index" json:"vendorOrderID2"` + VendorID int `orm:"column(vendor_id)" json:"vendorID"` + VendorStoreID string `orm:"column(vendor_store_id);size(48)" json:"vendorStoreID"` + StoreID int `orm:"column(store_id)" json:"storeID"` // 外部系统里记录的 jxstoreid + JxStoreID int `orm:"column(jx_store_id)" json:"jxStoreID"` // 根据VendorStoreID在本地系统里查询出来的 jxstoreid + StoreName string `orm:"size(64)" json:"storeName"` + ShopPrice int64 `json:"shopPrice"` // 单位为分 门店标价 + SalePrice int64 `json:"salePrice"` // 单位为分 售卖价 + ActualPayPrice int64 `json:"actualPayPrice"` // 单位为分 顾客实际支付 + Weight int `json:"weight"` // 单位为克 + ConsigneeName string `orm:"size(32)" json:"consigneeName"` + ConsigneeMobile string `orm:"size(32)" json:"consigneeMobile"` + ConsigneeMobile2 string `orm:"size(32)" json:"consigneeMobile2"` + ConsigneeAddress string `orm:"size(255)" json:"consigneeAddress"` + CoordinateType int `json:"-"` + ConsigneeLng int `json:"-"` // 坐标 * (10的六次方) + ConsigneeLat int `json:"-"` // 坐标 * (10的六次方) + SkuCount int `json:"skuCount"` // 商品类别数量,即有多少种商品(注意在某些情况下,相同SKU的商品由于售价不同,也会当成不同商品在这个值里) + GoodsCount int `json:"goodsCount"` // 商品个数 + Status int `json:"status"` // 参见OrderStatus*相关的常量定义 + VendorStatus string `orm:"size(255)" json:"vendorStatus"` + LockStatus int `json:"lockStatus"` + OrderSeq int `json:"orderSeq"` // 门店订单序号 + BuyerComment string `orm:"size(255)" json:"buyerComment"` + BusinessType int `json:"businessType"` + CancelApplyReason string `orm:"size(255)" json:"-"` // ""表示没有申请,不为null表示用户正在取消申请 + VendorWaybillID string `orm:"column(vendor_waybill_id);size(48)" json:"vendorWaybillID"` + WaybillVendorID int `orm:"column(waybill_vendor_id)" json:"waybillVendorID"` // 表示当前承运商,-1表示还没有安排 + DeliveryFlag int8 `json:"deliveryFlag"` // 第1位为1表示禁止调度器调度三方配送 + DuplicatedCount int `json:"-"` // 重复新订单消息数,这个一般不是由于消息重发造成的(消息重发由OrderStatus过滤),一般是业务逻辑造成的 + OriginalData string `orm:"-" json:"-"` // 只是用于传递数据 + Flag int8 `json:"flag"` //非运单调整相关的其它状态 +} + func TestObj2Excel(t *testing.T) { // kk := make([]*model.SkuName, 1) // kk[0] = &model.SkuName{ @@ -24,3 +62,49 @@ func TestObj2Excel(t *testing.T) { } Obj2Excel([]*Obj2ExcelSheetConfig{cc}) } + +func BenchmarkObj2Excel(b *testing.B) { + const sliceLen = 1000 + oneData := &XXXX{} + cc := &Obj2ExcelSheetConfig{ + Title: "Title", + CaptionList: nil, + } + elmType := reflect.TypeOf(oneData) + if elmType.Kind() == reflect.Ptr { + elmType = elmType.Elem() + } + for i := 0; i < elmType.NumField(); i++ { + if jsonTag := elmType.Field(i).Tag.Get("json"); jsonTag != "" && jsonTag != "-" { + cc.CaptionList = append(cc.CaptionList, jsonTag) + } + } + value := reflect.Indirect(reflect.ValueOf(oneData)) + for i := 0; i < elmType.NumField(); i++ { + value2 := value.Field(i) + if value2.Kind() == reflect.String { + value2.SetString(elmType.Field(i).Name) + } + } + + /* + data := make([]map[string]interface{}, sliceLen) + for k := range data { + data[k] = utils.Struct2MapByJson(oneData) + } + //*/ + + //* + data := make([]*XXXX, 1000) + for k := range data { + copied := *oneData + data[k] = &copied + } + //*/ + + cc.Data = data + for i := 0; i < b.N; i++ { + Obj2Excel([]*Obj2ExcelSheetConfig{cc}) + } + // b.Log(utils.Format4Output(data, false)) +} diff --git a/business/jxutils/jxcontext/jxcontext.go b/business/jxutils/jxcontext/jxcontext.go index 25daa9223..37ba57326 100644 --- a/business/jxutils/jxcontext/jxcontext.go +++ b/business/jxutils/jxcontext/jxcontext.go @@ -9,7 +9,6 @@ import ( "git.rosy.net.cn/jx-callback/business/jxcallback/auth" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/globals" - "github.com/astaxie/beego" ) type IAuther interface { @@ -71,7 +70,7 @@ func New(notUsed interface{}, token string, w http.ResponseWriter, r *http.Reque } } if err == model.ErrTokenIsInvalid { - if beego.BConfig.RunMode != "prod" { + if !globals.IsProductEnv() { err = nil } else { errCode = model.ErrCodeTokenIsInvalid diff --git a/business/jxutils/jxutils.go b/business/jxutils/jxutils.go index bec25de5f..3b87c736d 100644 --- a/business/jxutils/jxutils.go +++ b/business/jxutils/jxutils.go @@ -1,10 +1,13 @@ package jxutils import ( + "bytes" + "context" "fmt" "math" "math/rand" "regexp" + "sort" "strings" "sync" "time" @@ -14,7 +17,9 @@ import ( "git.rosy.net.cn/baseapi/utils/routinepool" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model/dao" + "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/globals/api" + "github.com/qiniu/api.v7/storage" ) var ( @@ -27,6 +32,25 @@ type SyncMapWithTimeout struct { timers sync.Map } +type OrderSkuList []*model.OrderSku + +func (l OrderSkuList) Len() int { + return len(l) +} + +// Less reports whether the element with +// index i should sort before the element with index j. +func (l OrderSkuList) Less(i, j int) bool { + return l[i].SalePrice < l[j].SalePrice +} + +// Swap swaps the elements with indexes i and j. +func (l OrderSkuList) Swap(i, j int) { + tmp := l[i] + l[i] = l[j] + l[j] = tmp +} + func init() { rand.Seed(time.Now().Unix()) routinePool = routinepool.New(1000, 1000) @@ -82,6 +106,13 @@ func GetSkuIDFromOrderSku(sku *model.OrderSku) (skuID int) { return sku.SkuID } +func GetSaleStoreIDFromAfsOrder(order *model.AfsOrder) (retVal int) { + if order.JxStoreID > 0 { + return order.JxStoreID + } + return order.StoreID +} + func SplitUniversalOrderID(universalOrderID string) (orderID string, vendorID int) { index := strings.Index(universalOrderID, "|") if index != -1 { @@ -122,6 +153,19 @@ func GetPossibleVendorIDFromVendorOrderID(vendorOrderID string) (vendorID int) { return vendorID } +func GetPossibleVendorIDFromAfsOrderID(afsOrderID string) (vendorID int) { + vendorID = model.VendorIDUnknown + if afsOrderIDInt64 := utils.Str2Int64WithDefault(afsOrderID, 0); afsOrderIDInt64 > 0 { + orderIDLen := len(afsOrderID) + if orderIDLen == len("22586438") { + vendorID = model.VendorIDJD + } else if orderIDLen == len("579557034") { + vendorID = model.VendorIDEBAI + } + } + return vendorID +} + func ComposeUniversalOrderID(orderID string, vendorID int) string { // return fmt.Sprintf("%s|%d", orderID, vendorID) return orderID // 当前用长度就能区分,先不加上vendorID @@ -276,10 +320,7 @@ func ComposeSkuName(prefix, name, comment, unit string, spec_quality float32, sp skuName += "(" + comment + ")" } if maxLen > 0 { - runeList := []rune(skuName) - if len(runeList) > maxLen { - skuName = string(runeList[:maxLen]) - } + skuName = utils.LimitUTF8StringLen(skuName, maxLen) } return skuName } @@ -421,3 +462,122 @@ func HandleUserWXRemark(db *dao.DaoDB, mobile string) (err error) { } return err } + +func RefreshOrderSkuRelated(order *model.GoodsOrder) *model.GoodsOrder { + order.SkuCount = 0 + order.GoodsCount = 0 + order.SalePrice = 0 + order.VendorPrice = 0 + order.Weight = 0 + order.OrderCreatedAt = order.StatusTime + for _, sku := range order.Skus { + if sku.SkuID > math.MaxInt32 { + sku.SkuID = 0 + } + sku.OrderCreatedAt = order.OrderCreatedAt + order.SkuCount++ + order.GoodsCount += sku.Count + order.SalePrice += sku.SalePrice * int64(sku.Count) + order.VendorPrice += sku.VendorPrice * int64(sku.Count) + order.Weight += sku.Weight * sku.Count + } + return order +} + +func RefreshAfsOrderSkuRelated(afsOrder *model.AfsOrder) *model.AfsOrder { + afsOrder.SkuUserMoney = 0 + afsOrder.PmSkuSubsidyMoney = 0 + for _, orderSku := range afsOrder.Skus { + if orderSku.SkuID > math.MaxInt32 { + orderSku.SkuID = 0 + } + afsOrder.SkuUserMoney += orderSku.UserMoney + afsOrder.PmSkuSubsidyMoney += orderSku.PmSkuSubsidyMoney + } + return afsOrder +} + +func RemoveSkuFromOrder(order *model.GoodsOrder, removedSkuList []*model.OrderSku) *model.GoodsOrder { + removedSkuMap := make(map[int]*model.OrderSku) + removedSkuMap2 := make(map[string]*model.OrderSku) + for _, sku := range removedSkuList { + if skuID := GetSkuIDFromOrderSku(sku); skuID > 0 { + if removedSkuMap[skuID] == nil { + removedSkuMap[skuID] = sku + } else { + removedSkuMap[skuID].Count += sku.Count + } + } + if vendorSkuID := sku.VendorSkuID; vendorSkuID != "" { + if removedSkuMap2[vendorSkuID] == nil { + removedSkuMap2[vendorSkuID] = sku + } else { + removedSkuMap2[vendorSkuID].Count += sku.Count + } + } + } + var skuList []*model.OrderSku + sort.Sort(sort.Reverse(OrderSkuList(order.Skus))) + for _, sku := range order.Skus { + var removedSku *model.OrderSku + if skuID := GetSkuIDFromOrderSku(sku); skuID > 0 { + removedSku = removedSkuMap[skuID] + } + if removedSku == nil { + if vendorSkuID := sku.VendorSkuID; vendorSkuID != "" { + removedSku = removedSkuMap2[vendorSkuID] + } + } + copiedSku := *sku + tmp := &copiedSku + if removedSku != nil { + if removedSku.Count >= sku.Count { + tmp = nil + removedSku.Count -= sku.Count + } else { + tmp.Count -= removedSku.Count + removedSku.Count = 0 + } + } + if tmp != nil { + skuList = append(skuList, tmp) + } + } + order.Skus = skuList + return RefreshOrderSkuRelated(order) +} + +func UploadExportContent(content []byte, key string) (downloadURL string, err error) { + putPolicy := storage.PutPolicy{ + Scope: globals.QiniuBucket, + Expires: 10 * 60, + DeleteAfterDays: 1, + } + upToken := putPolicy.UploadToken(api.QiniuAPI) + cfg := &storage.Config{} + formUploader := storage.NewFormUploader(cfg) + ret := storage.PutRet{} + for i := 0; i < 3; i++ { + if err = formUploader.Put(context.Background(), &ret, upToken, key, bytes.NewReader(content), int64(len(content)), &storage.PutExtra{}); err == nil { + break + } + } + if err == nil { + downloadURL = ComposeQiniuResURL(key) + } + return downloadURL, err +} + +func TaskResult2Hint(resultList []interface{}) (hint string) { + strList := make([]string, len(resultList)) + for k, v := range resultList { + strList[k] = fmt.Sprint(v) + } + hint = strings.Join(strList, ",") + return hint +} + +// 这个函数用于将两个整数合并为一单一int64,不要用于持久化的场景 +func Combine2Int(int1, int2 int) (outInt int64) { + return int64(int1)*100000 + int64(int2) +} diff --git a/business/jxutils/jxutils_cms.go b/business/jxutils/jxutils_cms.go index 8b045aa36..690f75148 100644 --- a/business/jxutils/jxutils_cms.go +++ b/business/jxutils/jxutils_cms.go @@ -9,11 +9,13 @@ import ( "reflect" "regexp" "strings" + "sync" "time" "git.rosy.net.cn/baseapi/platformapi" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/model" + "git.rosy.net.cn/jx-callback/globals" ) var ( @@ -82,9 +84,9 @@ func SplitStoreName(fullName, separator, defaultPrefix string) (prefix, bareName func ComposeStoreName(bareName string, vendorID int) (fullName string) { bareName = TrimDecorationChar(strings.Trim(bareName, "-")) if vendorID == model.VendorIDJD { - fullName = "京西菜市-" + bareName + fullName = globals.StoreName + "-" + bareName } else { - fullName = "京西菜市(" + bareName + ")" + fullName = globals.StoreName + "(" + bareName + ")" } return fullName } @@ -152,19 +154,24 @@ func Int64Map2List(int64Map map[int64]int) []int64 { return retVal } -// 计算SKU价格,unitPrice为一斤的单价,specQuality为质量,单位为克 -func CaculateSkuPrice(unitPrice int, specQuality float32, specUnit string, skuNameUnit string) int { - if skuNameUnit != "份" { - return unitPrice - } +func RegularizeSkuQuality(specQuality float32, specUnit string) (g int) { lowerSpecUnit := strings.ToLower(specUnit) if lowerSpecUnit == "kg" || lowerSpecUnit == "l" { specQuality *= 1000 } - price := int(math.Round(float64(float32(unitPrice) * specQuality / 500))) - if specQuality < 250 { + return int(specQuality) +} + +// 计算SKU价格,unitPrice为一斤的单价,specQuality为质量,单位为克 +func CaculateSkuPrice(unitPrice int, specQuality float32, specUnit string, skuNameUnit string) int { + if skuNameUnit != model.SpecialUnit { + return unitPrice + } + specQuality2 := RegularizeSkuQuality(specQuality, specUnit) + price := int(math.Round(float64(unitPrice * specQuality2 / model.SpecialSpecQuality))) + if specQuality2 < 250 { price = price * 110 / 100 - } else if specQuality < 500 { + } else if specQuality2 < 500 { price = price * 105 / 100 } if price <= 0 { @@ -173,35 +180,100 @@ func CaculateSkuPrice(unitPrice int, specQuality float32, specUnit string, skuNa return price } +// 计算SKU标准价格,CaculateSkuPrice的逆过程 +func CaculateUnitPrice(skuPrice int, specQuality float32, specUnit string, skuNameUnit string) (unitPrice int) { + if skuNameUnit != model.SpecialUnit { + return skuPrice + } + specQuality2 := RegularizeSkuQuality(specQuality, specUnit) + unitPrice = skuPrice * model.SpecialSpecQuality / specQuality2 + if specQuality2 < 250 { + unitPrice = unitPrice * 100 / 110 + } else if specQuality2 < 500 { + unitPrice = unitPrice * 100 / 105 + } + if unitPrice <= 0 { + unitPrice = 1 + } + return unitPrice +} + func GetSliceLen(list interface{}) int { return reflect.ValueOf(list).Len() } -func CaculateSkuVendorPrice(price int, percentage int) int { - storePrice := int(math.Round(float64(price*percentage) / 100)) - if storePrice < 0 { - storePrice = 0 +func CaculateSkuVendorPrice(price, percentage, catPercentage int) int { + if percentage <= 10 || percentage >= 400 { + percentage = 100 } - return storePrice + if catPercentage <= 10 || catPercentage >= 400 { + catPercentage = 100 + } + percentage = percentage * catPercentage / 100 + vendorPrice := int(math.Round(float64(price*percentage) / 100)) + if vendorPrice < 0 { + vendorPrice = 0 + } + return vendorPrice } +func CaculateSkuPriceFromVendor(vendorPrice, percentage, catPercentage int) int { + if percentage <= 10 || percentage >= 400 { + percentage = 100 + } + if catPercentage <= 10 || catPercentage >= 400 { + catPercentage = 100 + } + percentage = percentage * catPercentage / 100 + price := int(math.Round(float64(vendorPrice * 100 / percentage))) + if price < 0 { + price = 0 + } + return price +} + +func IsSkuSpecial(specQuality float32, specUnit string) bool { + return int(specQuality) == model.SpecialSpecQuality && (specUnit == model.SpecialSpecUnit || specUnit == model.SpecialSpecUnit2) +} + +var lastFakeID int64 +var lastFakeIDMutex sync.RWMutex + // 生成一个不重复的临时ID func genFakeID1() int64 { - return time.Now().UnixNano() / 1000000 + for { + fakeID := time.Now().UnixNano() / 1000 + lastFakeIDMutex.RLock() + if fakeID == lastFakeID { + lastFakeIDMutex.RUnlock() + time.Sleep(1 * time.Microsecond) + } else { + lastFakeIDMutex.RUnlock() + lastFakeIDMutex.Lock() + defer lastFakeIDMutex.Unlock() + lastFakeID = fakeID + return fakeID + } + } } +// 这个用于没有打开远程同步时的假同步,生成ID使用 func GenFakeID() int64 { return genFakeID1() * 3 } func IsFakeID(id int64) bool { - if id == 0 { + if IsEmptyID(id) { return true } multiple := id / genFakeID1() return multiple >= 2 && multiple <= 4 } +func IsEmptyID(id int64) bool { + return id == 0 +} + func FormalizePageSize(pageSize int) int { if pageSize == 0 { return model.DefPageSize @@ -238,11 +310,7 @@ func IsLegalStoreID(id int) bool { // 将规格转为重量 func FormatSkuWeight(specQuality float32, specUnit string) int { - lowerSpecUnit := strings.ToLower(specUnit) - if lowerSpecUnit == "kg" || lowerSpecUnit == "l" { - specQuality *= 1000 - } - return int(specQuality) + return RegularizeSkuQuality(specQuality, specUnit) } type SkuList []*model.Sku @@ -284,7 +352,7 @@ func DownloadFileByURL(fileURL string) (bodyData []byte, fileMD5 string, err err ///// func GenPicFileName(suffix string) string { - return fmt.Sprintf("%x%s", md5.Sum([]byte(utils.GetUUID()+suffix)), suffix) + return fmt.Sprintf("image/%x%s", md5.Sum([]byte(utils.GetUUID()+suffix)), suffix) } func GuessVendorIDFromVendorStoreID(vendorStoreID int64) (vendorID int) { @@ -293,7 +361,7 @@ func GuessVendorIDFromVendorStoreID(vendorStoreID int64) (vendorID int) { vendorID = model.VendorIDJD } else if vendorStoreID > 1234567 && vendorStoreID < 9876543 { // 美团外卖 2461713,7位 vendorID = model.VendorIDMTWM - } else if vendorStoreID > 1234567890 && vendorStoreID < 9987654321 { // 饿百 2167002607,10位 + } else if vendorStoreID > 1234567890 && vendorStoreID < 99876543210 { // 饿百 2167002607,10位,11位 vendorID = model.VendorIDEBAI } else if vendorStoreID > 123456789 && vendorStoreID < 987654321 { // 微盟微商城 132091048,9位 vendorID = model.VendorIDWSC @@ -306,10 +374,3 @@ func GuessVendorIDFromVendorStoreID(vendorStoreID int64) (vendorID int) { func GetVendorName(vendorID int) (vendorName string) { return model.VendorChineseNames[vendorID] } - -func AddVendorInfo2Err(inErr error, vendorID int) (outErr error) { - if inErr != nil { - outErr = fmt.Errorf("处理平台%s, %s", model.VendorChineseNames[vendorID], inErr.Error()) - } - return outErr -} diff --git a/business/jxutils/msg/msg.go b/business/jxutils/msg/msg.go index dc6d861cc..e0e11e44d 100644 --- a/business/jxutils/msg/msg.go +++ b/business/jxutils/msg/msg.go @@ -21,7 +21,9 @@ func SendUserMessage(userID, title, content string) (err error) { if len(content) > dingdingapi.MaxWorkContentLen { content = content[:dingdingapi.MaxWorkContentLen-4] + "..." } - err = api.DingDingAPI.CorpAsyncSendSimple(auth.AuthID, content) + if globals.IsProductEnv() { + err = api.DingDingAPI.CorpAsyncSendSimple(auth.AuthID, content) + } break } } diff --git a/business/jxutils/netprinter/netprinter.go b/business/jxutils/netprinter/netprinter.go index b0d5b892f..bc20ae3e1 100644 --- a/business/jxutils/netprinter/netprinter.go +++ b/business/jxutils/netprinter/netprinter.go @@ -14,7 +14,7 @@ import ( const ( testVendorOrderID = "test" - realTestVendorOrderID = "817102016000041" + realTestVendorOrderID = "901234567890123" realTestOrderVendorID = model.VendorIDJD ) @@ -27,14 +27,6 @@ func PrintOrder(ctx *jxcontext.Context, vendorOrderID string, vendorID int) (pri order, err := partner.CurOrderManager.LoadOrder(vendorOrderID, vendorID) if err == nil { if vendorOrderID == realTestVendorOrderID { - order.BuyerComment = "用户备注" - order.ConsigneeAddress = "四川省成都市某个地方" - order.ConsigneeLat = 30695171 - order.ConsigneeLng = 104056984 - order.ConsigneeName = "用户姓名" - order.ConsigneeMobile = "13812345678" - order.ConsigneeMobile2 = "13812345678" - order.StoreName = "京西菜市-测试门店" order.StoreID = storeID order.JxStoreID = storeID } diff --git a/business/jxutils/tasks/configrefresh.go b/business/jxutils/tasks/configrefresh.go index 6270b951d..c5a791e75 100644 --- a/business/jxutils/tasks/configrefresh.go +++ b/business/jxutils/tasks/configrefresh.go @@ -80,7 +80,7 @@ func RefreshConfig(configKey string, expiresTime time.Duration, configGetter fun } if handleType != 0 { if curConfig.Token, curConfig.Date = configGetter(); curConfig.Token == "" { - if beego.BConfig.RunMode == "prod" { + if globals.IsProductEnv() { globals.SugarLogger.Errorf("RefreshConfig %s get empty token", configKey) sleepDuration = errRefreshGap } else { @@ -126,7 +126,7 @@ func RefreshWeixinToken() (err error) { if api.WeixinAPI != nil { err = RefreshConfig("wechat", weixinTokenExpires, func() (token string, expireTimeStr string) { globals.SugarLogger.Debugf("RefreshWeixinToken RunMode:%s", beego.BConfig.RunMode) - if globals.IsProductEnv() { + if globals.IsProductEnv() || beego.BConfig.RunMode == "alpha" { if globals.IsMainProductEnv() { if tokenInfo, err := api.WeixinAPI.CBRetrieveToken(); err == nil { globals.SugarLogger.Debugf("RefreshWeixinToken tokenInfo:%s", utils.Format4Output(tokenInfo, true)) @@ -154,7 +154,7 @@ func RefreshWeixinToken() (err error) { func RefreshElmToken() (err error) { if api.ElmAPI != nil { err = RefreshConfig("eleme", elmTokenExpires, func() (string, string) { - if beego.BConfig.RunMode == "prod" { + if globals.IsProductEnv() { if tokenInfo, err := api.ElmAPI.RefreshTokenIndividual(); err == nil { tokenInfo2 := &ElmTokenForCompatible{ Error: "", @@ -183,7 +183,7 @@ func RefreshElmToken() (err error) { func RefreshWeimobToken() (err error) { if api.WeimobAPI != nil { err = RefreshConfig("weimob", weimobTokenExpires, func() (string, string) { - if beego.BConfig.RunMode == "prod" { + if globals.IsProductEnv() { if tokenInfo, err := api.WeimobAPI.RefreshTokenByRefreshToken(); err == nil { return string(utils.MustMarshal(tokenInfo)), utils.Time2Str(time.Now().Add((time.Duration(tokenInfo.ExpiresIn) - weimobTokenExpires/time.Second) * time.Second)) } @@ -204,7 +204,7 @@ func RefreshDingDingToken() (err error) { api.DingDingAPI.RetrieveToken() return RefreshConfig("dingding", dingdingTokenExpires, func() (string, string) { globals.SugarLogger.Debugf("RefreshDingDingToken RunMode:%s", beego.BConfig.RunMode) - if true { //beego.BConfig.RunMode == "prod" { + if true { //globals.IsProductEnv() { if token, err := api.DingDingAPI.RetrieveToken(); err == nil { globals.SugarLogger.Debugf("RefreshDingDingToken tokenInfo:%s", token) return token, "" @@ -232,12 +232,15 @@ func SaveWeimobToken(token *weimobapi.TokenInfo) (err error) { func RefreshYilianyunToken() (err error) { return RefreshConfig("yilianyun", yilianyunTokenExpires, func() (string, string) { globals.SugarLogger.Debugf("RefreshYilianyunToken RunMode:%s", beego.BConfig.RunMode) - if beego.BConfig.RunMode == "prod" { - if tokenInfo, err := api.YilianyunAPI.RetrieveToken(); err == nil { - return string(utils.MustMarshal(tokenInfo)), "" - } else { - globals.SugarLogger.Errorf("RefreshYilianyunToken RefreshToken failed with error:%v", err) + if globals.IsProductEnv() { + if globals.IsMainProductEnv() { // 只有京西菜市刷新易联云key + if tokenInfo, err := api.YilianyunAPI.RetrieveToken(); err == nil { + return string(utils.MustMarshal(tokenInfo)), "" + } else { + globals.SugarLogger.Errorf("RefreshYilianyunToken RefreshToken failed with error:%v", err) + } } + return api.YilianyunAPI.GetToken(), "" } return "", "" }, func(value string) { diff --git a/business/jxutils/tasksch/parallel_task.go b/business/jxutils/tasksch/parallel_task.go index 375688916..626df2e3e 100644 --- a/business/jxutils/tasksch/parallel_task.go +++ b/business/jxutils/tasksch/parallel_task.go @@ -134,13 +134,13 @@ func (task *ParallelTask) Run() { } } else { globals.SugarLogger.Infof("ParallelTask.Run %s, subtask(job:%s, params:%s) result:%v, failed with error:%v", task.Name, utils.Format4Output(job, true), utils.Format4Output(task.params, true), result, err) + task.locker.Lock() + task.detailErrList = append(task.detailErrList, err) + task.locker.Unlock() if !task.IsContinueWhenError { // 出错 chanRetVal = err goto end } - task.locker.Lock() - task.detailErrMsgList = append(task.detailErrMsgList, err.Error()) - task.locker.Unlock() } } } @@ -188,12 +188,17 @@ func (task *ParallelTask) Run() { } } if taskErr != nil { + task.OriginalErr = taskErr task.Err = NewTaskError(task.Name, taskErr) } else { + if len(task.detailErrList) > 0 { + task.OriginalErr = task.detailErrList[0] + } task.Err = task.buildTaskErrFromDetail() } task.Result = taskResult task.TerminatedAt = time.Now() + task.jobList = nil // 如果不释放,任务被管理的话,会导致内存不能释放 task.locker.Unlock() globals.SugarLogger.Debugf("ParallelTask.Run %s, err:%v", task.Name, task.Err) close(task.subFinishChan) diff --git a/business/jxutils/tasksch/sequence_task.go b/business/jxutils/tasksch/sequence_task.go index 74f719f2a..5ec16d942 100644 --- a/business/jxutils/tasksch/sequence_task.go +++ b/business/jxutils/tasksch/sequence_task.go @@ -44,13 +44,13 @@ func (task *SeqTask) Run() { }) task.finishedOneJob(1, err) if taskErr = err; taskErr != nil { + task.locker.Lock() + task.detailErrList = append(task.detailErrList, err) + task.locker.Unlock() globals.SugarLogger.Infof("SeqTask.Run %s step:%d failed with error:%v", task.Name, i, err) if !task.IsContinueWhenError { break } - task.locker.Lock() - task.detailErrMsgList = append(task.detailErrMsgList, err.Error()) - task.locker.Unlock() } else if result != nil { taskResult = append(taskResult, utils.Interface2Slice(result)...) } @@ -68,8 +68,12 @@ func (task *SeqTask) Run() { } } if taskErr != nil { + task.OriginalErr = taskErr task.Err = NewTaskError(task.Name, taskErr) } else { + if len(task.detailErrList) > 0 { + task.OriginalErr = task.detailErrList[0] + } task.Err = task.buildTaskErrFromDetail() } task.Result = taskResult diff --git a/business/jxutils/tasksch/task.go b/business/jxutils/tasksch/task.go index 17f3b9507..b9a915dcd 100644 --- a/business/jxutils/tasksch/task.go +++ b/business/jxutils/tasksch/task.go @@ -57,6 +57,8 @@ type ITask interface { AddChild(task ITask) ITask GetChildren() TaskList SetParent(parentTask ITask) + GetOriginalErr() error + GetDetailErrList() []error json.Marshaler } @@ -103,15 +105,19 @@ type BaseTask struct { FailedJobCount int `json:"failedJobCount"` Status int `json:"status"` - Result []interface{} `json:"-"` - Children TaskList `json:"children"` - Err error `json:"err"` + NoticeMsg string `json:"noticeMsg"` - detailErrMsgList []string - finishChan chan struct{} - C <-chan struct{} `json:"-"` - params []interface{} - quitChan chan int + Result []interface{} `json:"-"` + Children TaskList `json:"children"` + Err error `json:"err"` + OriginalErr error `json:"-"` + + detailErrList []error + + finishChan chan struct{} + C <-chan struct{} `json:"-"` + params []interface{} + quitChan chan int locker sync.RWMutex parent ITask @@ -161,7 +167,7 @@ func (t *BaseTask) GetID() string { func (t *BaseTask) GetResult(duration time.Duration) (retVal []interface{}, err error) { if t.GetStatus() >= TaskStatusEndBegin { - return t.Result, t.Err + return t.Result, t.OriginalErr } if duration == 0 { duration = time.Hour * 10000 // duration为0表示无限等待 @@ -171,7 +177,7 @@ func (t *BaseTask) GetResult(duration time.Duration) (retVal []interface{}, err case <-t.finishChan: t.isGetResultCalled = true timer.Stop() - return t.Result, t.Err + return t.Result, t.OriginalErr case <-timer.C: } return nil, ErrTaskNotFinished @@ -257,6 +263,30 @@ func (t *BaseTask) SetParent(parentTask ITask) { t.parent = parentTask } +func (t *BaseTask) SetNoticeMsg(noticeMsg string) { + t.locker.Lock() + defer t.locker.Unlock() + t.NoticeMsg = noticeMsg +} + +func (t *BaseTask) GetNoticeMsg() string { + t.locker.RLock() + defer t.locker.RUnlock() + return t.NoticeMsg +} + +func (t *BaseTask) GetOriginalErr() error { + t.locker.RLock() + defer t.locker.RUnlock() + return t.OriginalErr +} + +func (t *BaseTask) GetDetailErrList() []error { + t.locker.RLock() + defer t.locker.RUnlock() + return t.detailErrList +} + func AddChild(parentTask ITask, task ITask) ITask { if parentTask != nil { return parentTask.AddChild(task) @@ -311,6 +341,10 @@ func (t *BaseTask) run(taskHandler func()) { taskDesc := fmt.Sprintf("你的异步任务[%s],ID[%s],开始于:%s,结束于:%s,", t.Name, t.ID, utils.Time2Str(t.CreatedAt), utils.Time2Str(t.TerminatedAt)) if t.Err == nil { content = fmt.Sprintf("%s执行%s", taskDesc, TaskStatusName[t.Status]) + noticeMsg := t.GetNoticeMsg() + if noticeMsg != "" { + content += ",通知消息:" + noticeMsg + } } else { if t.Status == TaskStatusFinished { content = fmt.Sprintf("%s执行部分失败,%s", taskDesc, t.Err.Error()) @@ -347,8 +381,12 @@ func (t *BaseTask) setStatus(status int) { } func (t *BaseTask) buildTaskErrFromDetail() (err error) { - if len(t.detailErrMsgList) > 0 { - return NewTaskError(t.Name, fmt.Errorf("总共:%d, 失败:%d, 详情:\n%s", t.TotalItemCount, t.FailedItemCount, strings.Join(t.detailErrMsgList, "\n"))) + if len(t.detailErrList) > 0 { + strList := make([]string, len(t.detailErrList)) + for k, v := range t.detailErrList { + strList[k] = v.Error() + } + return NewTaskError(t.Name, fmt.Errorf("总共:%d, 失败:%d, 详情:\n%s", t.TotalItemCount, t.FailedItemCount, strings.Join(strList, "\n"))) } return nil } diff --git a/business/jxutils/weixinmsg/weixinmsg.go b/business/jxutils/weixinmsg/weixinmsg.go index 4e2917afa..12f057f5d 100644 --- a/business/jxutils/weixinmsg/weixinmsg.go +++ b/business/jxutils/weixinmsg/weixinmsg.go @@ -5,6 +5,8 @@ import ( "strings" "time" + "git.rosy.net.cn/jx-callback/business/partner" + "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/model" @@ -49,7 +51,10 @@ const ( WX_NORMAL_STORE_MSG_TEMPLATE_ID = "7ngcTFYiUFw66BMzIYntM1tpy-xZkJwlcCT5pVtXwtw" WX_CHANGE_APPROVED_TEMPLATE_ID = "gIG2olBZtQbjXmp6doNB_dESu60By5xuXYOGxksLv3Y" WX_CHANGE_REJECTED_TEMPLATE_ID = "tn2QXWi4HtSIwaztmtN6Bb2uzNL-jBxWltCZTDNJuYE" - WS_ORDER_CANCLED_TEMPLATE_ID = "iFozwiCsQdMs7VTiPXoBne45jKIQkoyxdGHSeAExP9U" + WX_ORDER_CANCLED_TEMPLATE_ID = "iFozwiCsQdMs7VTiPXoBne45jKIQkoyxdGHSeAExP9U" + + WX_AFS_ORDER_WAIT4APPROVE_TEMPLATE_ID = "X29udtANvhX6x1Lyh-T40NGNjRXBbUj5oSBTfDhZAqU" + WX_AFS_ORDER_STATUS_CHANGED_TEMPLATE_ID = "99T33rrXX0VboO1hljs4x8dDoLiSj3QX_rOikPHIXkg" ) var ( @@ -305,7 +310,7 @@ func NotifyUserApplyCancel(order *model.GoodsOrder, cancelReason string) (err er }, } storeID := jxutils.GetSaleStoreIDFromOrder(order) - return SendMsgToStore(storeID, WS_ORDER_CANCLED_TEMPLATE_ID, "", "", data) + return SendMsgToStore(storeID, WX_ORDER_CANCLED_TEMPLATE_ID, "", "", data) } @@ -347,7 +352,7 @@ func NotifySaleBill(storeID int, title, shopName, fileURL string) (err error) { title = "当期账单" } if shopName == "" { - shopName = "京西菜市" + shopName = globals.StoreName } data := map[string]interface{}{ "first": map[string]interface{}{ @@ -449,6 +454,54 @@ func NotifyStoreMessage(storeID, msgID, msgStatusID int, title, content string) return SendMsgToStore(storeID, templateID, fileURL, fmt.Sprintf(WX_MINI_TO_SHOW_MSG, msgID, msgStatusID), data) } +func NotifyAfsOrderStatus(afsOrder *model.AfsOrder) (err error) { + globals.SugarLogger.Debugf("NotifyAfsOrderStatus orderID:%s", afsOrder.VendorOrderID) + if afsOrder.VendorID == model.VendorIDELM { + return nil + } + + var templateID, comment string + if afsOrder.Status == model.AfsOrderStatusWait4Approve { + templateID = WX_AFS_ORDER_WAIT4APPROVE_TEMPLATE_ID + comment = "您有新售后单,请尽快处理" + } else if afsOrder.Status == model.AfsOrderStatusWait4ReceiveGoods { + templateID = WX_AFS_ORDER_STATUS_CHANGED_TEMPLATE_ID + comment = "商家您好!如顾客商品已成功退回,请点击确认收货" + } else { + return err + } + order, err := partner.CurOrderManager.LoadOrder(afsOrder.VendorOrderID, afsOrder.VendorID) + if err != nil { + return err + } + + data := map[string]interface{}{ + "first": map[string]interface{}{ + "value": fmt.Sprintf("%s 第%d号订单,订单编号:%s", model.VendorChineseNames[afsOrder.VendorID], order.OrderSeq, afsOrder.VendorOrderID), + "color": WX_HIGHLEVEL_TEMPLATE_COLOR2, + }, + "keyword1": map[string]interface{}{ + "value": afsOrder.AfsOrderID, + "color": WX_TEMPLATE_VENDERCOLOR_JDDJ, + }, + "keyword2": map[string]interface{}{ + "value": model.OrderStatusName[afsOrder.Status], + "color": WX_HIGHLEVEL_TEMPLATE_COLOR, + }, + "keyword3": map[string]interface{}{ + "value": utils.Time2Str(afsOrder.CreatedAt), + "color": venderColors[order.VendorID], + }, + "remark": map[string]interface{}{ + "value": comment, + "color": WX_NEW_ORDER_TEMPLATE_COLOR, + }, + } + storeID := jxutils.GetSaleStoreIDFromAfsOrder(afsOrder) + err = SendMsgToStore(storeID, templateID, globals.WxBackstageHost+fmt.Sprintf("%s%d", WX_TO_ORDER_PAGE_URL, storeID), WX_MINI_TO_ORDER_PAGE_URL, data) + return err +} + func FormatDeliveryTime(order *model.GoodsOrder) string { var tmpTime time.Time if order.ExpectedDeliveredTime == utils.DefaultTimeValue { diff --git a/business/model/act.go b/business/model/act.go new file mode 100644 index 000000000..188729be6 --- /dev/null +++ b/business/model/act.go @@ -0,0 +1,135 @@ +package model + +import "time" + +const ( + ActSkuDirectDown = 1 + ActSkuSecKill = 2 + + ActOrderBegin = 10 + ActOrderMoneyOff = 11 + ActOrderMoneyOffCoupon = 12 + ActOrderReduceFreight = 13 + ActOrderReduceFreightCoupon = 14 +) + +const ( + ActStatusCreated = 1 // 需同步 + ActStatusCanceled = 2 // 需同步 + ActStatusEnded = 3 // 不需要同步,根据活动时间自动刷新的 +) + +type Act struct { + ModelIDCULD + + Name string `orm:"size(64)" json:"name"` + Advertising string `orm:"size(255)" json:"advertising"` + Type int `json:"type"` + Status int `json:"status"` + LimitDevice int `json:"limitDevice"` + LimitPin int `json:"limitPin"` + LimitDaily int `json:"limitDaily"` + LimitCount int `json:"limitCount"` + Source string `orm:"size(255)" json:"source"` + CreateType int `json:"createType"` + PricePercentage int `json:"pricePercentage"` // 单品级活动才有效 + BeginAt time.Time `orm:"type(datetime);index;null" json:"beginAt"` + EndAt time.Time `orm:"type(datetime);index;null" json:"endAt"` +} + +type ActMap struct { + ModelIDCULD + + ActID int `orm:"column(act_id)" json:"actID"` + VendorID int `orm:"column(vendor_id)" json:"vendorID"` + + VendorActID string `orm:"column(vendor_act_id);size(48)" json:"vendorActID"` + SyncStatus int `orm:"default(2)" json:"syncStatus"` +} + +type Act2 struct { + MapID int `orm:"column(map_id)"` + + Act + VendorID int `orm:"column(vendor_id)" json:"vendorID"` + + VendorActID string `orm:"column(vendor_act_id);size(48)" json:"vendorActID"` + SyncStatus int `orm:"default(2)" json:"syncStatus"` +} + +type ActOrderRule struct { + ModelIDCULD + + ActID int `orm:"column(act_id)" json:"actID"` + SalePrice int64 `orm:"" json:"salePrice"` // 满的价格 + DeductPrice int64 `orm:"" json:"deductPrice"` // 减的价格 +} + +// type ActStore struct { +// ModelIDCULD + +// ActID int `orm:"column(act_id)" json:"actID"` +// StoreID int `orm:"column(store_id)" json:"storeID"` +// } + +type ActStoreMap struct { + ModelIDCULD + + ActID int `orm:"column(act_id)" json:"actID"` + StoreID int `orm:"column(store_id)" json:"storeID"` + VendorID int `orm:"column(vendor_id)" json:"vendorID"` + + VendorActID string `orm:"column(vendor_act_id);size(48)" json:"vendorActID"` + SyncStatus int `orm:"default(2)" json:"syncStatus"` +} + +type ActStore2 struct { + MapID int `orm:"column(map_id)"` + + ActStoreMap + + VendorStoreID string `orm:"column(vendor_store_id)" json:"vendorStoreID"` +} + +type ActStoreSku struct { + ModelIDCULD + + ActID int `orm:"column(act_id)" json:"actID"` + StoreID int `orm:"column(store_id)" json:"storeID"` + SkuID int `orm:"column(sku_id)" json:"skuID"` + + // LocalStatus int // 这个状态是多个平台的 + // RemoteStatus int // 这个状态是多个平台的 + OriginalPrice int64 `orm:"" json:"originalPrice"` // 单品级活动用,创建活动时商品的原始京西价 + PricePercentage int `orm:"" json:"pricePercentage"` // 单品级活动用,SKU级的价格比例,非0覆盖Act中的PricePercentage + ActPrice int64 `orm:"" json:"actPrice"` // 单品级活动用,SKU级指定的价格,非0覆盖CustomPricePercentage与Act中的PricePercentage + + Stock int `orm:"" json:"stock"` // 订单级活动用 +} + +type ActStoreSkuMap struct { + ModelIDCULD + + ActID int `orm:"column(act_id)" json:"actID"` + StoreID int `orm:"column(store_id)" json:"storeID"` + SkuID int `orm:"column(sku_id)" json:"skuID"` + VendorID int `orm:"column(vendor_id)" json:"vendorID"` + + VendorActID string `orm:"column(vendor_act_id);size(48)" json:"vendorActID"` + SyncStatus int `orm:"default(2)" json:"syncStatus"` + ActualActPrice int64 `orm:"" json:"actualActPrice"` // 单品级活动用,创建活动时商品的活动价格 +} + +type ActStoreSku2 struct { + MapID int `orm:"column(map_id)"` + + ActStoreSku + VendorID int `orm:"column(vendor_id)" json:"vendorID"` + + VendorActID string `orm:"column(vendor_act_id);size(48)" json:"vendorActID"` + SyncStatus int `orm:"default(2)" json:"syncStatus"` + ActualActPrice int64 `orm:"" json:"actualActPrice"` // 单品级活动用,创建活动时商品的活动价格 + + VendorStoreID string `orm:"column(vendor_store_id)" json:"vendorStoreID"` + VendorSkuID string `orm:"column(vendor_sku_id)" json:"vendorSkuID"` +} diff --git a/business/model/api.go b/business/model/api.go index 01bbba320..6838910e2 100644 --- a/business/model/api.go +++ b/business/model/api.go @@ -12,15 +12,25 @@ const ( type GoodsOrderExt struct { GoodsOrder + EarningPrice int64 `json:"earningPrice"` // 预估结算给门店老板的钱 + WaybillStatus int `json:"waybillStatus"` CourierName string `orm:"size(32)" json:"courierName"` CourierMobile string `orm:"size(32)" json:"courierMobile"` CurrentConsigneeMobile string `orm:"-" json:"currentConsigneeMobile"` + CourierVendorName string `json:"courierVendorName"` + Status2 string `json:"status2"` ActualFee int64 `json:"actualFee"` // 实际要支付给快递公司的费用 DesiredFee int64 `json:"desiredFee"` // 运单总费用 WaybillCreatedAt time.Time `orm:"type(datetime);index" json:"waybillCreatedAt"` WaybillFinishedAt time.Time `orm:"type(datetime)" json:"waybillFinishedAt"` + + SkuID int `orm:"column(sku_id)" json:"skuID,omitempty"` + SkuShopPrice int `json:"skuShopPrice,omitempty"` + SkuSalePrice int `json:"skuSalePrice,omitempty"` + SkuCount2 int `json:"skuCount2,omitempty"` + SkuInfo string `json:"skuInfo,omitempty"` } type OrderSkuExt struct { @@ -30,11 +40,6 @@ type OrderSkuExt struct { } type GoodsOrderCountInfo struct { - Status int `json:"status"` - Count int `json:"count"` -} - -type GoodsOrderCountInfo2 struct { LockStatus int `json:"lockStatus"` Status int `json:"status"` Count int `json:"count"` @@ -61,3 +66,8 @@ type OrderFinancialExt struct { Skus []*OrderSkuFinancial `orm:"-" json:"skus"` // 正向订单购买商品列表 Discounts []*OrderDiscountFinancial `orm:"-" json:"discounts"` // 正向订单享受优惠列表 } + +type OrderFinancialSkuExt struct { + OrderSkuFinancial + Image string `json:"image"` +} diff --git a/business/model/const.go b/business/model/const.go index e42b6f55d..b46de96e6 100644 --- a/business/model/const.go +++ b/business/model/const.go @@ -14,9 +14,9 @@ const ( VendorIDMTWM = 1 VendorIDELM = 2 VendorIDEBAI = 3 - VendorIDJX = 9 // 这是一个假的京西VendorID VendorIDWSC = 11 // 微盟微商城 VendorIDPurchaseEnd = 11 + VendorIDJX = 99 // 这是一个假的京西VendorID VendorIDDeliveryBegin = 101 VendorIDDada = 101 @@ -112,22 +112,34 @@ var ( OrderStatusDeliverFailed: "投递失败", OrderStatusFinished: "完成", OrderStatusCanceled: "取消", + + AfsOrderStatusWait4Approve: "待审核", + AfsOrderStatusNew: "已审核", + AfsOrderStatusWait4ReceiveGoods: "退货待确认", + AfsOrderStatusReceivedGoods: "退货已收到", + AfsOrderStatusFinished: "售后成功", + AfsOrderStatusFailed: "售后失败", } WaybillStatusName = map[int]string{ WaybillStatusUnknown: "一般事件", - WaybillStatusNew: "新运单", - WaybillStatusAcceptCanceled: "取消接受", - WaybillStatusAccepted: "已接单", - WaybillStatusCourierArrived: "已到店", - WaybillStatusDelivering: "配送中", - WaybillStatusDelivered: "送达", - WaybillStatusCanceled: "取消", - WaybillStatusFailed: "失败", + WaybillStatusNew: "新运单", + WaybillStatusPending: "压单", + WaybillStatusAcceptCanceled: "取消接受", + WaybillStatusAccepted: "已接单", + WaybillStatusCourierArrived: "已到店", + WaybillStatusApplyFailedGetGoods: "取货失败待审核", + WaybillStatusAgreeFailedGetGoods: "取货失败", + WaybillStatusDelivering: "配送中", + WaybillStatusDeliverFailed: "投递失败", + WaybillStatusDelivered: "送达", + WaybillStatusCanceled: "取消", + WaybillStatusFailed: "失败", } OrderTypeName = map[int]string{ - OrderTypeOrder: "订单", - OrderTypeWaybill: "运单", + OrderTypeOrder: "订单", + OrderTypeWaybill: "运单", + OrderTypeAfsOrder: "售后单", } MultiStoresVendorMap = map[int]int{ @@ -159,11 +171,30 @@ var ( "打印机密钥", }, } + AfsReasonTypeName = map[int]string{ + AfsReasonTypeGoodsQuality: "商品质量", + AfsReasonTypeWrongGoods: "错误的商品", + AfsReasonTypeMissingGoods: "缺少部分商品", + AfsReasonTypeNoGoods: "全部商品未收到", + AfsReasonTypeDamagedGoods: "商品有损伤", + AfsReasonTypeGoodsQuantity: "缺斤少两", + AfsReasonTypeAgreedByMerchant: "与商家协商一致", + AfsReasonTypeGoodsNoSame: "商品与描述不符", + AfsReasonWrongPurchase: "误购", + AfsReasonNotReceivedIntime: "未在时效内送达", + AfsReasonNotOthers: "其它", + } + AfsAppealTypeName = map[int]string{ + AfsAppealTypeRefund: "仅退款", + AfsAppealTypeReturnAndRefund: "退货退款", + AfsAppealTypeNewGoods: "重发商品", + } ) const ( - OrderTypeOrder = 1 - OrderTypeWaybill = 2 + OrderTypeOrder = 1 + OrderTypeWaybill = 2 + OrderTypeAfsOrder = 3 ) // https://blog.csdn.net/a13570320979/article/details/51366355 @@ -212,6 +243,14 @@ const ( OrderStatusEndBegin = 100 // 以下的状态就是结束状态 OrderStatusFinished = 110 // 订单已完成 OrderStatusCanceled = 115 // 订单已取消 + OrderStatusEndEnd = 120 + + AfsOrderStatusWait4Approve = 155 // 待审核售后单 + AfsOrderStatusNew = 160 // 已审核或不需要审核售后单 + AfsOrderStatusWait4ReceiveGoods = 165 // 退款退货的,需要商家确认收到货 + AfsOrderStatusReceivedGoods = 167 // 已确认收到货 + AfsOrderStatusFinished = 180 // 售后单成功完成 + AfsOrderStatusFailed = 190 // 售后单失败 ) const ( @@ -224,6 +263,7 @@ const ( WaybillStatusUnknown = 0 WaybillStatusNew = 5 + WaybillStatusPending = 7 WaybillStatusAcceptCanceled = 8 WaybillStatusAccepted = 10 WaybillStatusCourierArrived = 15 // 此状态是可选的,明确写出来是因为还是较重要的状态,但业务逻辑不应依赖此状态 @@ -238,7 +278,6 @@ const ( WaybillStatusDelivered = 105 // todo 这个应该改为110,与订单对应 WaybillStatusCanceled = 115 WaybillStatusFailed = 120 // 这个状态存在的意义是区分于WaybillStatusCanceled,比如达达平台在这种状态下再次创建运单的方式不一样 - WaybillStatusNeverSend = 125 // 这个状态指的是平台方不愿意配送,门店自己想办法。与WaybillStatusAcceptCanceled不一样,WaybillStatusAcceptCanceled可能之后还会尝试配送 ) const ( @@ -261,6 +300,13 @@ const ( const ( OrderDeliveryFlagMaskScheduleDisabled = 1 // 禁止三方配送调度 OrderDeliveryFlagMaskPurcahseDisabled = 2 // 购物平台已不配送(一般为门店配送类型本身为自配送,或已经转自配送) + + OrderDeliveryFlagMaskDada = 16 // 创建达达运单中 + OrderDeliveryFlagMaskMtps = 32 // 创建美团配送运单中 +) + +const ( + WaybillDeliveryFlagMaskActiveCancel = 1 // 主动取消 ) const ( @@ -275,7 +321,42 @@ const ( OrderFlagRefuseFailedGetGoods = 24 OrderFlagMaskFailedDeliver = 32 - OrderFlagMaskCallPMCourier = 64 // 取货失败后召唤平台配送 + OrderFlagMaskCallPMCourier = 64 // 取货失败后召唤平台配送 + OrderFlagMaskSetDelivered = 128 // 设置送达 +) + +const ( + AfsOrderFlagMaskUserRefund = 3 // 门店处理售后单申请 + AfsOrderFlagAgreeUserRefund = 1 // 门店同意售后单申请 + AfsOrderFlagRefuseUserRefund = 3 // 门店拒绝售后单申请 + + AfsOrderFlagMaskReturnGoods = 4 // 门店确认收到退货 +) + +const ( + AfsAppealTypeRefund = 1 // 仅退款 + AfsAppealTypeReturnAndRefund = 2 // 退货退款 + AfsAppealTypeNewGoods = 3 // 重发新商品(即京东到家的直赔) +) + +const ( + AfsReasonTypeGoodsQuality = 1 // 商品质量 + AfsReasonTypeWrongGoods = 2 // 错误的商品 + AfsReasonTypeMissingGoods = 3 // 缺少部分商品 + AfsReasonTypeNoGoods = 4 // 全部商品未收到 + AfsReasonTypeDamagedGoods = 5 // 商品有损伤 + AfsReasonTypeGoodsQuantity = 6 // 缺斤少两 + AfsReasonTypeAgreedByMerchant = 7 // 与商家协商一致 + AfsReasonTypeGoodsNoSame = 8 // 商品与描述不符 + AfsReasonWrongPurchase = 9 // 误购 + AfsReasonNotReceivedIntime = 10 // 未在时效内送达 + AfsReasonNotOthers = 0 // 其它 +) + +const ( + AfsTypeUnknown = 0 // 未知 + AfsTypePartRefund = 1 // 部分退款 + AfsTypeFullRefund = 2 // 全额退款 ) func IsPurchaseVendorExist(vendorID int) bool { @@ -306,9 +387,22 @@ func IsOrderMainStatus(status int) bool { } func IsOrderFinalStatus(status int) bool { - return status >= OrderStatusEndBegin + return status >= OrderStatusEndBegin && status <= OrderStatusEndEnd } func IsOrderImportantStatus(status int) bool { return IsOrderMainStatus(status) || IsOrderLockStatus(status) || IsOrderUnlockStatus(status) } + +func WaybillVendorID2Mask(vendorID int) (mask int8) { + if vendorID == VendorIDDada { + mask = OrderDeliveryFlagMaskDada + } else if vendorID == VendorIDMTPS { + mask = OrderDeliveryFlagMaskMtps + } + return mask +} + +func IsAfsOrderFinalStatus(status int) bool { + return status >= AfsOrderStatusFinished && status <= AfsOrderStatusFailed +} diff --git a/business/model/dao/act.go b/business/model/dao/act.go new file mode 100644 index 000000000..97b000667 --- /dev/null +++ b/business/model/dao/act.go @@ -0,0 +1,117 @@ +package dao + +import ( + "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-callback/business/model" +) + +func GetActVendorInfo(db *DaoDB, actID int, vendorIDs []int) (actMap map[int]*model.Act2, err error) { + sql := ` + SELECT t1.*, + t2.id map_id, t2.vendor_id, t2.vendor_act_id, t2.sync_status + FROM act t1 + JOIN act_map t2 ON t2.act_id = t1.id AND t2.deleted_at = ? + WHERE t1.deleted_at = ? AND t1.id = ? + ` + sqlParams := []interface{}{ + utils.DefaultTimeValue, + utils.DefaultTimeValue, + actID, + } + if len(vendorIDs) > 0 { + sql += " AND t2.vendor_id IN (" + GenQuestionMarks(len(vendorIDs)) + ")" + sqlParams = append(sqlParams, vendorIDs) + } + var actList []*model.Act2 + if err = GetRows(db, &actList, sql, sqlParams...); err == nil { + actMap = make(map[int]*model.Act2) + for _, v := range actList { + actMap[v.VendorID] = v + } + } + return actMap, err +} + +func GetActStoreVendorInfo(db *DaoDB, actID int, vendorIDs, storeIDs []int) (actStoreMap map[int][]*model.ActStore2, err error) { + sql := ` + SELECT t1.*, + t1.id map_id, + t2.vendor_store_id + FROM act_store_map t1 + JOIN store_map t2 ON t2.store_id = t1.store_id AND t2.vendor_id = t1.vendor_id AND t2.deleted_at = ? + WHERE t1.deleted_at = ? AND t1.act_id = ? + ` + sqlParams := []interface{}{ + utils.DefaultTimeValue, + utils.DefaultTimeValue, + actID, + } + if len(vendorIDs) > 0 { + sql += " AND t1.vendor_id IN (" + GenQuestionMarks(len(vendorIDs)) + ")" + sqlParams = append(sqlParams, vendorIDs) + } + if len(storeIDs) > 0 { + sql += " AND t1.store_id IN (" + GenQuestionMarks(len(storeIDs)) + ")" + sqlParams = append(sqlParams, storeIDs) + } + var actStoreList []*model.ActStore2 + if err = GetRows(db, &actStoreList, sql, sqlParams...); err == nil { + actStoreMap = make(map[int][]*model.ActStore2) + for _, v := range actStoreList { + actStoreMap[v.VendorID] = append(actStoreMap[v.VendorID], v) + } + } + return actStoreMap, err +} + +func GetActStoreSkuVendorInfo(db *DaoDB, actID int, vendorIDs, storeIDs, skuIDs []int) (actStoreSkuMap map[int][]*model.ActStoreSku2, err error) { + sql := ` + SELECT t1.*, + t2.id map_id, t2.vendor_id, t2.vendor_act_id, t2.sync_status, + t3.vendor_store_id, + CASE t2.vendor_id + WHEN 0 THEN + t4.jd_id + WHEN 1 THEN + t5.mtwm_id + WHEN 3 THEN + t5.ebai_id + ELSE + '' + END vendor_sku_id + FROM act_store_sku t1 + JOIN act_store_sku_map t2 ON t2.act_id = t1.act_id AND t2.sku_id = t1.sku_id AND t2.store_id = t1.store_id AND t2.deleted_at = ? + JOIN store_map t3 ON t3.store_id = t1.store_id AND t3.vendor_id = t2.vendor_id AND t3.deleted_at = ? + JOIN sku t4 ON t4.id = t1.sku_id AND t4.deleted_at = ? + JOIN store_sku_bind t5 ON t5.sku_id = t1.sku_id AND t5.store_id = t1.store_id AND t5.deleted_at = ? + WHERE t1.deleted_at = ? AND t1.act_id = ? + ` + sqlParams := []interface{}{ + utils.DefaultTimeValue, + utils.DefaultTimeValue, + utils.DefaultTimeValue, + utils.DefaultTimeValue, + utils.DefaultTimeValue, + actID, + } + if len(vendorIDs) > 0 { + sql += " AND t2.vendor_id IN (" + GenQuestionMarks(len(vendorIDs)) + ")" + sqlParams = append(sqlParams, vendorIDs) + } + if len(storeIDs) > 0 { + sql += " AND t1.store_id IN (" + GenQuestionMarks(len(storeIDs)) + ")" + sqlParams = append(sqlParams, storeIDs) + } + if len(skuIDs) > 0 { + sql += " AND t1.sku_id IN (" + GenQuestionMarks(len(skuIDs)) + ")" + sqlParams = append(sqlParams, skuIDs) + } + var actStoreSkuList []*model.ActStoreSku2 + if err = GetRows(db, &actStoreSkuList, sql, sqlParams...); err == nil { + actStoreSkuMap = make(map[int][]*model.ActStoreSku2) + for _, v := range actStoreSkuList { + actStoreSkuMap[v.VendorID] = append(actStoreSkuMap[v.VendorID], v) + } + } + return actStoreSkuMap, err +} diff --git a/business/model/dao/dao_bz.go b/business/model/dao/dao_bz.go index 18fb2d78d..e2ddbab3a 100644 --- a/business/model/dao/dao_bz.go +++ b/business/model/dao/dao_bz.go @@ -58,7 +58,7 @@ func UpdateEntityByKV(db *DaoDB, item interface{}, kvs map[string]interface{}, c } func UpdateEntityLogically(db *DaoDB, item interface{}, kvs map[string]interface{}, userName string, conditions map[string]interface{}) (num int64, err error) { - if conditions != nil { + if conditions != nil && refutil.IsFieldExist(item, model.FieldDeletedAt) { conditions = utils.MergeMaps(conditions, map[string]interface{}{ model.FieldDeletedAt: utils.DefaultTimeValue, }) @@ -71,7 +71,7 @@ func UpdateEntityLogically(db *DaoDB, item interface{}, kvs map[string]interface // 此函数会更新同步标志 func UpdateEntityLogicallyAndUpdateSyncStatus(db *DaoDB, item interface{}, kvs map[string]interface{}, userName string, conditions map[string]interface{}, syncStatusFieldName string, valueMask int) (num int64, err error) { - if conditions != nil { + if conditions != nil && refutil.IsFieldExist(item, model.FieldDeletedAt) { conditions = utils.MergeMaps(conditions, map[string]interface{}{ model.FieldDeletedAt: utils.DefaultTimeValue, }) @@ -115,6 +115,7 @@ func AddStoreCategoryMap(db *DaoDB, storeID, categoryID int, vendorID int, vendo StoreID: storeID, CategoryID: categoryID, MtwmSyncStatus: model.SyncFlagNewMask, + EbaiSyncStatus: model.SyncFlagNewMask, WscSyncStatus: model.SyncFlagNewMask, } storeCat.DeletedAt = utils.DefaultTimeValue diff --git a/business/model/dao/dao_order.go b/business/model/dao/dao_order.go index 2c3c82909..9d3855fc9 100644 --- a/business/model/dao/dao_order.go +++ b/business/model/dao/dao_order.go @@ -29,12 +29,12 @@ func SetOrderPrintFlag(db *DaoDB, userName string, vendorOrderID string, vendorI if isPrinted { err = SetOrderFlag(db, userName, vendorOrderID, vendorID, model.OrderFlagMaskPrinted) } else { - err = SetOrderFlag(db, userName, vendorOrderID, vendorID, ^int8(model.OrderFlagMaskPrinted)) + err = SetOrderFlag(db, userName, vendorOrderID, vendorID, ^model.OrderFlagMaskPrinted) } return err } -func SetOrderFlag(db *DaoDB, userName string, vendorOrderID string, vendorID int, flag int8) (err error) { +func SetOrderFlag(db *DaoDB, userName string, vendorOrderID string, vendorID int, flag int) (err error) { _, err = ExecuteSQL(db, ` UPDATE goods_order SET flag = flag | ? @@ -43,7 +43,7 @@ func SetOrderFlag(db *DaoDB, userName string, vendorOrderID string, vendorID int return err } -func ClearOrderFlag(db *DaoDB, userName string, vendorOrderID string, vendorID int, flag int8) (err error) { +func ClearOrderFlag(db *DaoDB, userName string, vendorOrderID string, vendorID int, flag int) (err error) { _, err = ExecuteSQL(db, ` UPDATE goods_order SET flag = flag & ? @@ -51,3 +51,34 @@ func ClearOrderFlag(db *DaoDB, userName string, vendorOrderID string, vendorID i `, flag, vendorOrderID, vendorID) return err } + +func SetAfsOrderFlag(db *DaoDB, userName string, afsOrderID string, vendorID int, flag int) (err error) { + _, err = ExecuteSQL(db, ` + UPDATE afs_order + SET flag = flag | ? + WHERE afs_order_id = ? AND vendor_id = ? + `, flag, afsOrderID, vendorID) + return err +} + +func GetAfsOrders(db *DaoDB, vendorID int, vendorOrderID, afsOrderID string) (afsOrderList []*model.AfsOrder, err error) { + sql := ` + SELECT * + FROM afs_order t1 + WHERE t1.vendor_id = ? + ` + sqlParams := []interface{}{ + vendorID, + } + if vendorOrderID != "" { + sql += " AND t1.vendor_order_id = ?" + sqlParams = append(sqlParams, vendorOrderID) + } + if afsOrderID != "" { + sql += " AND t1.afs_order_id = ?" + sqlParams = append(sqlParams, afsOrderID) + } + sql += " ORDER BY t1.afs_order_id DESC" + err = GetRows(db, &afsOrderList, sql, sqlParams...) + return afsOrderList, err +} diff --git a/business/model/dao/dao_utils.go b/business/model/dao/dao_utils.go index c09777970..8b3c70c7f 100644 --- a/business/model/dao/dao_utils.go +++ b/business/model/dao/dao_utils.go @@ -168,3 +168,7 @@ func value2Value(srcValue, dstValue reflect.Value, copyType int) { // func ObjNull2Normal(src, dst interface{}) { // copyBetweenNoramAndNullObj(src, dst, 2) // } + +func IsVendorThingIDEmpty(vendorThingID string) bool { + return vendorThingID == "" || vendorThingID == "0" +} diff --git a/business/model/dao/sku.go b/business/model/dao/sku.go index 599a6f134..bedf9d785 100644 --- a/business/model/dao/sku.go +++ b/business/model/dao/sku.go @@ -1,8 +1,11 @@ package dao import ( + "fmt" + "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/model" + "git.rosy.net.cn/jx-callback/globals" ) func GetSellCities(db *DaoDB, nameID int, vendorID int) (cities []*model.Place, err error) { @@ -52,6 +55,47 @@ func GetSkuNameByHashCode(db *DaoDB, hashCode string) (skuName *model.SkuName, e return nil, err } +func GetSkus(db *DaoDB, skuIDs, nameIDs, statuss, catIDs []int) (skuList []*model.SkuAndName, err error) { + sql := ` + SELECT t1.*, t2.name, t2.unit + FROM sku t1 + JOIN sku_name t2 ON t2.id = t1.name_id AND t2.deleted_at = ? + ` + sqlWhere := ` + WHERE t1.deleted_at = ? + ` + sqlParams := []interface{}{ + utils.DefaultTimeValue, + utils.DefaultTimeValue, + } + if len(skuIDs) > 0 { + sqlWhere += " AND t1.id IN (" + GenQuestionMarks(len(skuIDs)) + ")" + sqlParams = append(sqlParams, skuIDs) + } + if len(nameIDs) > 0 { + sqlWhere += " AND t1.name_id IN (" + GenQuestionMarks(len(nameIDs)) + ")" + sqlParams = append(sqlParams, nameIDs) + } + if len(statuss) > 0 { + sqlWhere += " AND t1.status IN (" + GenQuestionMarks(len(statuss)) + ") AND t2.status IN (" + GenQuestionMarks(len(statuss)) + ")" + sqlParams = append(sqlParams, statuss, statuss) + } + if len(catIDs) > 0 { + sql += ` + JOIN sku_category t3 ON t3.id = t2.category_id + LEFT JOIN sku_category t3p ON t3p.id = t3.parent_id + ` + sqlWhere += " AND (t3.id IN (" + GenQuestionMarks(len(catIDs)) + ")" + sqlWhere += " OR t3p.id IN (" + GenQuestionMarks(len(catIDs)) + ") )" + sqlParams = append(sqlParams, catIDs, catIDs) + } + sql += sqlWhere + if err = GetRows(db, &skuList, sql, sqlParams...); err == nil { + return skuList, nil + } + return nil, err +} + func GetSkuNames(db *DaoDB, nameIDs []int) (skuNameList []*model.SkuName, err error) { sql := ` SELECT * @@ -70,3 +114,76 @@ func GetSkuNames(db *DaoDB, nameIDs []int) (skuNameList []*model.SkuName, err er } return nil, err } + +func GetSkuByNames(db *DaoDB, nameIDs []int) (skuList []*model.Sku, err error) { + sql := ` + SELECT * + FROM sku t1 + WHERE t1.deleted_at = ? + ` + sqlParams := []interface{}{ + utils.DefaultTimeValue, + } + if len(nameIDs) > 0 { + sql += " AND t1.name_id IN (" + GenQuestionMarks(len(nameIDs)) + ")" + sqlParams = append(sqlParams, nameIDs) + } + if err = GetRows(db, &skuList, sql, sqlParams...); err == nil { + return skuList, nil + } + return nil, err +} + +func GetSkuIDByNames(db *DaoDB, nameIDs []int) (skuIDs []int, err error) { + skuList, err := GetSkuByNames(db, nameIDs) + if err == nil { + for _, sku := range skuList { + skuIDs = append(skuIDs, sku.ID) + } + } + return skuIDs, err +} + +func GetSkuByCats(db *DaoDB, catIDs []int) (skuList []*model.Sku, err error) { + sql := ` + SELECT t1.* + FROM sku t1 + JOIN sku_name t2 ON t2.id = t1.name_id + WHERE t1.deleted_at = ? + ` + sqlParams := []interface{}{ + utils.DefaultTimeValue, + } + if len(catIDs) > 0 { + sql += " AND t2.category_id IN (" + GenQuestionMarks(len(catIDs)) + ")" + sqlParams = append(sqlParams, catIDs) + } + err = GetRows(db, &skuList, sql, sqlParams...) + globals.SugarLogger.Debugf("GetSkuByCats err:%v", err) + return skuList, err +} + +func SetSkuSyncStatus(db *DaoDB, vendorID int, skuIDs []int, syncStatus int) (num int64, err error) { + globals.SugarLogger.Debugf("SetSkuSyncStatus, vendorID:%d", vendorID) + + fieldPrefix := ConvertDBFieldPrefix(model.VendorNames[vendorID]) + sql := fmt.Sprintf(` + UPDATE sku t1 + SET t1.%s_sync_status = IF(t1.deleted_at = ?, t1.%s_sync_status | ?, 0) + `, fieldPrefix, fieldPrefix) + sqlParams := []interface{}{ + utils.DefaultTimeValue, + syncStatus, + } + if (syncStatus & model.SyncFlagNewMask) != 0 { + sql += fmt.Sprintf(`, + t1.%s_id = 0 + `, fieldPrefix) + } + sql += " WHERE 1 = 1" + if len(skuIDs) > 0 { + sql += " AND t1.id IN (" + GenQuestionMarks(len(skuIDs)) + ")" + sqlParams = append(sqlParams, skuIDs) + } + return ExecuteSQL(db, sql, sqlParams...) +} diff --git a/business/model/dao/store.go b/business/model/dao/store.go index a228e97e9..2031c4713 100644 --- a/business/model/dao/store.go +++ b/business/model/dao/store.go @@ -24,8 +24,8 @@ type StoreDetail struct { type StoreDetail2 struct { model.Store - VendorStoreID string `orm:"column(vendor_store_id)` // 这个在GetMissingDadaStores返回中指的是到家的vendorStoreID - DadaStoreID string `orm:"column(dada_store_id)` + VendorStoreID string `orm:"column(vendor_store_id)"` // 这个在GetMissingDadaStores返回中指的是到家的vendorStoreID + DadaStoreID string `orm:"column(dada_store_id)"` DistrictName string CityName string } @@ -136,8 +136,8 @@ func GetMissingDadaStores(db *DaoDB, storeID int, isMustHaveJdStore bool) (store t3.vendor_store_id dada_store_id FROM store t1 LEFT JOIN store_map t2 ON t1.id = t2.store_id AND t2.vendor_id = ? AND t2.deleted_at = ? - JOIN place city ON city.code = t1.city_code - JOIN place district ON district.code = t1.district_code + LEFT JOIN place city ON city.code = t1.city_code + LEFT JOIN place district ON district.code = t1.district_code LEFT JOIN store_courier_map t3 ON t3.store_id = t1.id AND t3.vendor_id = ? AND t3.deleted_at = ? WHERE t1.deleted_at = ? ` @@ -181,6 +181,33 @@ func GetStoreCourierList(db *DaoDB, storeID, status int) (courierStoreList []*mo return nil, err } +func GetStoresMapList(db *DaoDB, vendorIDs, storeIDs []int, status int) (storeMapList []*model.StoreMap, err error) { + sql := ` + SELECT t1.* + FROM store_map t1 + WHERE t1.deleted_at = ? + ` + sqlParams := []interface{}{ + utils.DefaultTimeValue, + } + if len(vendorIDs) > 0 { + sql += " AND t1.vendor_id IN (" + GenQuestionMarks(len(vendorIDs)) + ")" + sqlParams = append(sqlParams, vendorIDs) + } + if len(storeIDs) > 0 { + sql += " AND t1.store_id IN (" + GenQuestionMarks(len(storeIDs)) + ")" + sqlParams = append(sqlParams, storeIDs) + } + if status != model.StoreStatusAll { + sql += " AND t1.status = ?" + sqlParams = append(sqlParams, status) + } + if err = GetRows(db, &storeMapList, sql, sqlParams...); err == nil { + return storeMapList, nil + } + return nil, err +} + // 此函数在检测到一个门店的所有平台状态一样,且不为StoreStatusOpened时, // 将平台门店状态全部改为StoreStatusOpened,则把京西门店状态改为之前那个统一的平台门店状态 func FormalizeStoreStatus(db *DaoDB, storeID, storeStatus int) (err error) { diff --git a/business/model/dao/store_sku.go b/business/model/dao/store_sku.go index 334c5ffce..3d72a58a0 100644 --- a/business/model/dao/store_sku.go +++ b/business/model/dao/store_sku.go @@ -2,6 +2,7 @@ package dao import ( "fmt" + "time" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/model" @@ -13,13 +14,16 @@ var ( model.VendorIDWSC: "img_weimob", model.VendorIDEBAI: "img_ebai", } + descImgFieldMap = map[int]string{ + model.VendorIDEBAI: "desc_img_ebai", + } ) type SkuStoreCatInfo struct { model.SkuCategory - MapID int `orm:"column(map_id)"` // 这个主要用于判断是否有store_sku_category_map - VendorCatID string `orm:"column(vendor_cat_id)"` - CatSyncStatus int8 + MapID int `orm:"column(map_id)"` // 这个主要用于判断是否有store_sku_category_map + VendorCatID string `orm:"column(vendor_cat_id)"` + StoreCatSyncStatus int8 ParentCatName string ParentMapID int `orm:"column(parent_map_id)"` // 这个主要用于判断是否有父store_sku_category_map @@ -27,47 +31,63 @@ type SkuStoreCatInfo struct { ParentCatSyncStatus int8 } -type StoreCatSyncInfo struct { - CatName string - Seq int `json:"seq"` - model.StoreSkuCategoryMap - - ParentCatName string - ParentCatID int `orm:"column(parent_cat_id)"` // 这个主要用于判断是否有父store_sku_category_map - ParentVendorCatID string `orm:"column(parent_vendor_cat_id)"` - ParentCatSyncStatus int8 -} - type StoreSkuSyncInfo struct { - BindID int `orm:"column(bind_id)"` - Price int64 - UnitPrice int64 - StoreSkuStatus int - SkuSyncStatus int8 + // 平台无关的store sku信息 + BindID int `orm:"column(bind_id)"` // 换名的原因是与Sku.ID同名区别 + StoreID int `orm:"column(store_id)"` + SkuID int `orm:"column(sku_id)"` // 这个与Sku.ID的区别是SkuID是必然存在的 + Price int64 + UnitPrice int64 + + // 平台相关的store sku信息 + StoreSkuStatus int + StoreSkuSyncStatus int8 + VendorSkuID string `orm:"column(vendor_sku_id)"` + model.Sku - VendorSkuID string `orm:"column(vendor_sku_id)"` + + // sku_name Prefix string NameID int `orm:"column(name_id)"` VendorNameID string `orm:"column(vendor_name_id)"` Name string Unit string - Img string Upc string - Seq int - VendorVendorCatID int64 `orm:"column(vendor_vendor_cat_id)"` + // 平台相关的图片信息 + Img string + DescImg string - CatSyncStatus int8 - VendorCatID string `orm:"column(vendor_cat_id)"` + VendorVendorCatID int64 `orm:"column(vendor_vendor_cat_id)"` // 平台商品分类(叶子结点) + VendorVendorCatID2 int64 `orm:"column(vendor_vendor_cat_id2)"` // 平台商品分类上一级 + VendorVendorCatID3 int64 `orm:"column(vendor_vendor_cat_id3)"` // 平台商品分类再上一级 - SkuCatSyncStatus int8 - SkuVendorCatID string `orm:"column(sku_vendor_cat_id)"` + // sku的商家分类信息 + SkuStoreCatSyncStatus int8 + SkuVendorCatID string `orm:"column(sku_vendor_cat_id)"` + + // sku_name的商家分类信息 + StoreCatSyncStatus int8 + VendorCatID string `orm:"column(vendor_cat_id)"` + + CatPricePercentage int +} + +type MissingStoreSkuInfo struct { + StoreID int `orm:"column(store_id)"` + NameID int `orm:"column(name_id)"` + SkuID int `orm:"column(sku_id)"` + SpecQuality float32 + SpecUnit string + Unit string + RefPrice int } // 单门店模式厂商适用 +// 从store_sku_bind中,得到所有依赖的商家分类信息 func GetSkusCategories(db *DaoDB, vendorID, storeID int, skuIDs []int, level int) (cats []*SkuStoreCatInfo, err error) { sql := ` - SELECT DISTINCT t4.*, t5.id map_id, t5.%s_id vendor_cat_id, t5.%s_sync_status cat_sync_status, t4p.name parent_cat_name, t5p.id parent_map_id, t5p.%s_id parent_vendor_cat_id, t5p.%s_sync_status parent_cat_sync_status + SELECT DISTINCT t4.*, t5.id map_id, t5.%s_id vendor_cat_id, t5.%s_sync_status store_cat_sync_status, t4p.name parent_cat_name, t5p.id parent_map_id, t5p.%s_id parent_vendor_cat_id, t5p.%s_sync_status parent_cat_sync_status FROM store_sku_bind t1 JOIN sku t2 ON t1.sku_id = t2.id AND t2.deleted_at = ? AND t2.status = ? JOIN sku_name t3 ON t2.name_id = t3.id AND t3.deleted_at = ? AND t3.status = ? @@ -86,7 +106,7 @@ func GetSkusCategories(db *DaoDB, vendorID, storeID int, skuIDs []int, level int LEFT JOIN store_sku_category_map t5 ON t4.id = t5.category_id AND t5.store_id = t1.store_id AND t5.deleted_at = ? LEFT JOIN sku_category t4p ON t4.parent_id = t4p.id LEFT JOIN store_sku_category_map t5p ON t4p.id = t5p.category_id AND t5p.store_id = t1.store_id AND t5p.deleted_at = ? - WHERE t1.deleted_at = ? AND t1.store_id = ? + WHERE t1.deleted_at = ? AND t1.store_id = ? AND t1.status = ? ` sqlParams := []interface{}{ utils.DefaultTimeValue, @@ -98,6 +118,7 @@ func GetSkusCategories(db *DaoDB, vendorID, storeID int, skuIDs []int, level int utils.DefaultTimeValue, utils.DefaultTimeValue, storeID, + model.SkuStatusNormal, } if len(skuIDs) > 0 { sql += " AND t1.sku_id IN (" + GenQuestionMarks(len(skuIDs)) + ")" @@ -112,93 +133,132 @@ func GetSkusCategories(db *DaoDB, vendorID, storeID int, skuIDs []int, level int } // 单门店模式厂商适用 -func GetStoreCategories(db *DaoDB, vendorID, storeID int, level int) (cats []*StoreCatSyncInfo, err error) { +// 单纯的从已经创建的store_sku_category_map中,得到相关的同步信息 +func GetStoreCategories(db *DaoDB, vendorID, storeID int, level int) (cats []*SkuStoreCatInfo, err error) { fieldPrefix := ConvertDBFieldPrefix(model.VendorNames[vendorID]) sql := fmt.Sprintf(` - SELECT t5.*, t4.name cat_name, t4.seq, t4p.name parent_cat_name, t5p.category_id parent_cat_id, t5p.%s_id parent_vendor_cat_id, t5p.%s_sync_status parent_cat_sync_status + SELECT t4.*, + t5.id map_id, t5.%s_id vendor_cat_id, t5.%s_sync_status store_cat_sync_status, + t4p.name parent_cat_name, + t5p.id parent_map_id, t5p.%s_id parent_vendor_cat_id, t5p.%s_sync_status parent_cat_sync_status FROM store_sku_category_map t5 JOIN sku_category t4 ON t5.category_id = t4.id AND t4.deleted_at = ? LEFT JOIN sku_category t4p ON t4.parent_id = t4p.id LEFT JOIN store_sku_category_map t5p ON t4p.id = t5p.category_id AND t5.store_id = t5p.store_id AND t5p.deleted_at = ? WHERE t5.store_id = ? AND t4.level = ? AND t5.%s_sync_status <> 0 AND t5.deleted_at = ? - `, fieldPrefix, fieldPrefix, fieldPrefix) + `, fieldPrefix, fieldPrefix, fieldPrefix, fieldPrefix, fieldPrefix) if err = GetRows(db, &cats, sql, utils.DefaultTimeValue, utils.DefaultTimeValue, storeID, level, utils.DefaultTimeValue); err != nil { return nil, err } return cats, err } -// 单门店模式厂商适用 -func GetStoreSkus(db *DaoDB, vendorID, storeID int, skuIDs []int) (skus []*StoreSkuSyncInfo, err error) { +// 以store_sku_bind为基础来做同步,正常情况下使用 +// 单多门店模式厂商通用 +func GetStoreSkus2(db *DaoDB, vendorID, storeID int, skuIDs []int, isDirty bool) (skus []*StoreSkuSyncInfo, err error) { + isSingleStorePF := model.MultiStoresVendorMap[vendorID] != 1 tableName := "t1" - if model.MultiStoresVendorMap[vendorID] == 1 { // 多店模式平台 + if !isSingleStorePF { tableName = "t2" } vendorSkuNameField := "0" if vendorID == model.VendorIDWSC { vendorSkuNameField = "t1.wsc_id2" } + fieldPrefix := ConvertDBFieldPrefix(model.VendorNames[vendorID]) sql := ` - SELECT t1.id bind_id, t1.price, t1.unit_price, t1.status store_sku_status, %s.%s_id vendor_sku_id, t1.%s_sync_status sku_sync_status, %s vendor_name_id, + SELECT t1.id bind_id, t1.sku_id, t1.price, t1.unit_price, t1.status store_sku_status, %s.%s_id vendor_sku_id, t1.%s_sync_status store_sku_sync_status, %s vendor_name_id, t1.store_id, t2.*, - t3.id name_id, t3.prefix, t3.name, t3.unit, t3.%s img, t3.upc, - t4.%s_category_id vendor_vendor_cat_id, - t5.%s_sync_status cat_sync_status, t5.%s_id vendor_cat_id, - t5sku.%s_sync_status sku_cat_sync_status, t5sku.%s_id sku_vendor_cat_id + t3.id name_id, t3.prefix, t3.name, t3.unit, IF(t3.%s <> '', t3.%s, t3.img) img, t3.upc, t3.%s desc_img, + t4.%s_category_id vendor_vendor_cat_id, t4.%s_price_percentage cat_price_percentage + ` + fmtParams := []interface{}{ + tableName, fieldPrefix, fieldPrefix, vendorSkuNameField, GetImgFieldName(vendorID), GetImgFieldName(vendorID), GetDescImgFieldName(vendorID), + fieldPrefix, fieldPrefix, + } + if vendorID == model.VendorIDEBAI { + sql += `, + t4vp.vendor_category_id vendor_vendor_cat_id2, t4vp.parent_id vendor_vendor_cat_id3` + } + if isSingleStorePF { + sql += `, + t5.%s_sync_status store_cat_sync_status, t5.%s_id vendor_cat_id, + t5sku.%s_sync_status sku_store_cat_sync_status, t5sku.%s_id sku_vendor_cat_id` + fmtParams = append(fmtParams, fieldPrefix, fieldPrefix, fieldPrefix, fieldPrefix) + } + sql += ` FROM store_sku_bind t1 LEFT JOIN sku t2 ON t1.sku_id = t2.id AND t2.deleted_at = ? AND t2.status = ? LEFT JOIN sku_name t3 ON t2.name_id = t3.id AND t3.deleted_at = ? AND t3.status = ? - JOIN sku_category t4 ON t3.category_id = t4.id AND t4.deleted_at = ? - JOIN store_sku_category_map t5 ON t4.id = t5.category_id AND t5.store_id = t1.store_id AND t5.deleted_at = ? - LEFT JOIN store_sku_category_map t5sku ON t2.category_id = t5sku.category_id AND t5sku.store_id = t1.store_id AND t5sku.deleted_at = ? - WHERE t1.store_id = ? AND t1.%s_sync_status <> 0 - ` + LEFT JOIN sku_category t4 ON t3.category_id = t4.id AND t4.deleted_at = ?` sqlParams := []interface{}{ utils.DefaultTimeValue, model.SkuStatusNormal, utils.DefaultTimeValue, model.SkuStatusNormal, utils.DefaultTimeValue, - utils.DefaultTimeValue, - utils.DefaultTimeValue, - storeID, + } + if vendorID == model.VendorIDEBAI { + sql += ` + LEFT JOIN sku_vendor_category t4v ON t4v.vendor_category_id = t4.%s_category_id AND t4v.vendor_id = ? + LEFT JOIN sku_vendor_category t4vp ON t4vp.vendor_category_id = t4v.parent_id AND t4v.vendor_id = ?` + fmtParams = append(fmtParams, fieldPrefix) + sqlParams = append(sqlParams, vendorID, vendorID) + } + if isSingleStorePF { + sql += ` + LEFT JOIN store_sku_category_map t5 ON t4.id = t5.category_id AND t5.store_id = t1.store_id AND t5.deleted_at = ? + LEFT JOIN store_sku_category_map t5sku ON t2.category_id = t5sku.category_id AND t5sku.store_id = t1.store_id AND t5sku.deleted_at = ?` + sqlParams = append(sqlParams, utils.DefaultTimeValue, utils.DefaultTimeValue) + } + sql += " WHERE t1.store_id = ?" + sqlParams = append(sqlParams, storeID) + if isDirty { + sql += " AND (t1.%s_sync_status <> 0 OR (%s.%s_id <> 0 AND t3.id IS NULL))" + fmtParams = append(fmtParams, fieldPrefix, tableName, fieldPrefix) + } else { + sql += " AND t1.deleted_at = ?" + sqlParams = append(sqlParams, utils.DefaultTimeValue) } if len(skuIDs) > 0 { sql += " AND t1.sku_id IN (" + GenQuestionMarks(len(skuIDs)) + ")" sqlParams = append(sqlParams, skuIDs) } - fieldPrefix := ConvertDBFieldPrefix(model.VendorNames[vendorID]) - sql = fmt.Sprintf(sql, tableName, fieldPrefix, fieldPrefix, vendorSkuNameField, GetImgFieldName(vendorID), fieldPrefix, fieldPrefix, fieldPrefix, fieldPrefix, fieldPrefix, fieldPrefix) + if !isSingleStorePF { + sql += " AND t2.%s_id <> 0" + fmtParams = append(fmtParams, fieldPrefix) + } + sql = fmt.Sprintf(sql, fmtParams...) sql += " ORDER BY t1.price" // globals.SugarLogger.Debug(sql) if err = GetRows(db, &skus, sql, sqlParams...); err != nil { return nil, err } - index := 1 - for _, sku := range skus { - sku.Seq = index - index++ - } return skus, err } +func GetStoreSkus(db *DaoDB, vendorID, storeID int, skuIDs []int) (skus []*StoreSkuSyncInfo, err error) { + return GetStoreSkus2(db, vendorID, storeID, skuIDs, true) +} + +// 以sku为基础来做全同步, // 多门店模式厂商适用 func GetFullStoreSkus(db *DaoDB, vendorID, storeID int) (skus []*StoreSkuSyncInfo, err error) { globals.SugarLogger.Debugf("GetFullStoreSkus, storeID:%d, vendorID:%d", storeID, vendorID) sql := ` - SELECT t1.id bind_id, t1.price, t1.unit_price, t1.status store_sku_status, t2.%s_id vendor_sku_id, t1.%s_sync_status sku_sync_status, - t2.*, - t3.id name_id, t3.prefix, t3.name, t3.unit, t3.%s img, + SELECT t1.id bind_id, t1.price, t1.unit_price, t1.status store_sku_status, t2.%s_id vendor_sku_id, t1.%s_sync_status store_sku_sync_status, t1.store_id, + t2.*, t2.id sku_id, + t3.id name_id, t3.prefix, t3.name, t3.unit, IF(t3.%s <> '', t3.%s, t3.img) img, t4.%s_category_id vendor_vendor_cat_id, - t4.%s_sync_status cat_sync_status, t4.%s_id vendor_cat_id, - t5sku.%s_sync_status sku_cat_sync_status, t5sku.%s_id sku_vendor_cat_id + t4.%s_sync_status store_cat_sync_status, t4.%s_id vendor_cat_id, + t5sku.%s_sync_status sku_store_cat_sync_status, t5sku.%s_id sku_vendor_cat_id FROM sku t2 LEFT JOIN store_sku_bind t1 ON t1.sku_id = t2.id AND t1.store_id = ? AND t1.deleted_at = ? JOIN sku_name t3 ON t2.name_id = t3.id AND t3.deleted_at = ? AND t3.status = ? JOIN sku_category t4 ON t3.category_id = t4.id AND t4.deleted_at = ? LEFT JOIN sku_category t5sku ON t2.category_id = t5sku.id - WHERE t2.deleted_at = ? AND t2.status = ? + WHERE t2.deleted_at = ? AND t2.status = ? AND t2.%s_id <> 0 ` sqlParams := []interface{}{ storeID, @@ -210,7 +270,8 @@ func GetFullStoreSkus(db *DaoDB, vendorID, storeID int) (skus []*StoreSkuSyncInf model.SkuStatusNormal, } fieldPrefix := ConvertDBFieldPrefix(model.VendorNames[vendorID]) - sql = fmt.Sprintf(sql, fieldPrefix, fieldPrefix, GetImgFieldName(vendorID), fieldPrefix, fieldPrefix, fieldPrefix, fieldPrefix, fieldPrefix) + sql = fmt.Sprintf(sql, fieldPrefix, fieldPrefix, GetImgFieldName(vendorID), GetImgFieldName(vendorID), + fieldPrefix, fieldPrefix, fieldPrefix, fieldPrefix, fieldPrefix, fieldPrefix) // globals.SugarLogger.Debug(sql) // globals.SugarLogger.Debug(utils.Format4Output(sqlParams, false)) if err = GetRows(db, &skus, sql, sqlParams...); err != nil { @@ -219,53 +280,79 @@ func GetFullStoreSkus(db *DaoDB, vendorID, storeID int) (skus []*StoreSkuSyncInf return skus, err } -func SetStoreSkuSyncStatus(db *DaoDB, vendorID, storeID int, skuIDs []int, syncStatus int) (num int64, err error) { - globals.SugarLogger.Debugf("SetStoreSkuSyncStatus, storeID:%d, vendorID:%d", storeID, vendorID) +// 这个函数之前是要设置没有删除或同步标志不为0的,会导致将同步标志不为0且删除了的把标志去掉,现在改为只设置没有删除的 +func SetStoreSkuSyncStatus(db *DaoDB, vendorID int, storeIDs []int, skuIDs []int, syncStatus int) (num int64, err error) { + globals.SugarLogger.Debugf("SetStoreSkuSyncStatus, storeIDs:%v, vendorID:%d", storeIDs, vendorID) + isSingleStorePF := model.MultiStoresVendorMap[vendorID] != 1 fieldPrefix := ConvertDBFieldPrefix(model.VendorNames[vendorID]) - sql := fmt.Sprintf(` - UPDATE store_sku_bind - SET %s_sync_status = IF(deleted_at = ?, %s_sync_status | ?, 0) - `, fieldPrefix, fieldPrefix) + sql := ` + UPDATE store_sku_bind t1 + SET t1.%s_sync_status = t1.%s_sync_status | ? + ` + fmtParams := []interface{}{ + fieldPrefix, + fieldPrefix, + } sqlParams := []interface{}{ - utils.DefaultTimeValue, syncStatus, } - if (syncStatus & model.SyncFlagNewMask) != 0 { - sql += fmt.Sprintf(`, - %s_id = 0 - `, fieldPrefix) + if isSingleStorePF && (syncStatus&model.SyncFlagNewMask) != 0 { + sql += `, + t1.%s_id = 0 + ` + fmtParams = append(fmtParams, fieldPrefix) } - sql += " WHERE 1 = 1" - if storeID > 0 { - sql += " AND store_id = ?" - sqlParams = append(sqlParams, storeID) + sql += " WHERE (t1.deleted_at = ?)" + // fmtParams = append(fmtParams, fieldPrefix) + sqlParams = append(sqlParams, utils.DefaultTimeValue) + if len(storeIDs) > 0 { + sql += " AND t1.store_id IN (" + GenQuestionMarks(len(storeIDs)) + ")" + sqlParams = append(sqlParams, storeIDs) } if len(skuIDs) > 0 { - sql += " AND sku_id IN (" + GenQuestionMarks(len(skuIDs)) + ")" + sql += " AND t1.sku_id IN (" + GenQuestionMarks(len(skuIDs)) + ")" sqlParams = append(sqlParams, skuIDs) } + sql = fmt.Sprintf(sql, fmtParams...) return ExecuteSQL(db, sql, sqlParams...) } -func SetStoreCategorySyncStatus(db *DaoDB, vendorID, storeID int, catIDs []int, syncStatus int) (num int64, err error) { - globals.SugarLogger.Debugf("SetStoreCategorySyncStatus, storeID:%d, vendorID:%d", storeID, vendorID) +func SetStoreCategorySyncStatus(db *DaoDB, vendorID int, storeIDs []int, catIDs []int, syncStatus int) (num int64, err error) { + globals.SugarLogger.Debugf("SetStoreCategorySyncStatus, storeIDs:%v, vendorID:%d", storeIDs, vendorID) + isSingleStorePF := model.MultiStoresVendorMap[vendorID] != 1 fieldPrefix := ConvertDBFieldPrefix(model.VendorNames[vendorID]) - sql := fmt.Sprintf(` - UPDATE store_sku_category_map - SET %s_sync_status = %s_sync_status | ? - WHERE deleted_at = ? AND store_id = ? - `, fieldPrefix, fieldPrefix) + sql := ` + UPDATE store_sku_category_map t1 + SET t1.%s_sync_status = IF(t1.deleted_at = ?, t1.%s_sync_status | ?, 0) + ` + fmtParams := []interface{}{ + fieldPrefix, + fieldPrefix, + } sqlParams := []interface{}{ - syncStatus, utils.DefaultTimeValue, - storeID, + syncStatus, + } + if isSingleStorePF && (syncStatus&model.SyncFlagNewMask) != 0 { + sql += `, + t1.%s_id = 0 + ` + fmtParams = append(fmtParams, fieldPrefix) + } + sql += " WHERE (t1.deleted_at = ? OR t1.%s_sync_status <> 0)" + fmtParams = append(fmtParams, fieldPrefix) + sqlParams = append(sqlParams, utils.DefaultTimeValue) + if len(storeIDs) > 0 { + sql += " AND t1.store_id IN (" + GenQuestionMarks(len(storeIDs)) + ")" + sqlParams = append(sqlParams, storeIDs) } if len(catIDs) > 0 { - sql += " AND category_id IN (" + GenQuestionMarks(len(catIDs)) + ")" + sql += " AND t1.category_id IN (" + GenQuestionMarks(len(catIDs)) + ")" sqlParams = append(sqlParams, catIDs) } + sql = fmt.Sprintf(sql, fmtParams...) return ExecuteSQL(db, sql, sqlParams...) } @@ -276,3 +363,66 @@ func GetImgFieldName(vendorID int) (fieldName string) { } return fieldName } + +func GetDescImgFieldName(vendorID int) (fieldName string) { + fieldName = descImgFieldMap[vendorID] + if fieldName == "" { + fieldName = "desc_img" + } + return fieldName +} + +func GetStoresSkusInfo(db *DaoDB, storeIDs, skuIDs []int) (storeSkuList []*model.StoreSkuBind, err error) { + sql := ` + SELECT * + FROM store_sku_bind t1 + WHERE t1.deleted_at = ? + ` + sqlParams := []interface{}{ + utils.DefaultTimeValue, + } + if len(storeIDs) > 0 { + sql += " AND t1.store_id IN (" + GenQuestionMarks(len(storeIDs)) + ")" + sqlParams = append(sqlParams, storeIDs) + } + if len(skuIDs) > 0 { + sql += " AND t1.sku_id IN (" + GenQuestionMarks(len(skuIDs)) + ")" + sqlParams = append(sqlParams, skuIDs) + } + err = GetRows(db, &storeSkuList, sql, sqlParams...) + return storeSkuList, err +} + +func GetMissingStoreSkuFromOrder(db *DaoDB, storeIDs []int, fromTime time.Time) (storeSkuList []*MissingStoreSkuInfo, err error) { + if time.Now().Sub(fromTime) > 24*time.Hour*60 { + return nil, fmt.Errorf("GetMissingStoreSkuFromOrder,时间超过60天") + } + sql := ` + SELECT IF(t2.jx_store_id <> 0, t2.jx_store_id, t2.store_id) store_id, t4.name_id, t1.sku_id, + t4.spec_quality, t4.spec_unit, t5.unit, + COUNT(*) ct, CAST(AVG(IF(t1.vendor_price <> 0, t1.vendor_price, t1.sale_price)) AS SIGNED) ref_price + FROM order_sku t1 + JOIN goods_order t2 ON t2.vendor_order_id = t1.vendor_order_id + LEFT JOIN store_sku_bind t3 ON t3.store_id = IF(t2.jx_store_id <> 0, t2.jx_store_id, t2.store_id) AND + t3.sku_id = t1.sku_id AND t3.deleted_at = ? + JOIN sku t4 ON t4.id = t1.sku_id + JOIN sku_name t5 ON t5.id = t4.name_id + WHERE t2.status = ? AND IF(t2.jx_store_id <> 0, t2.jx_store_id, t2.store_id) > 0 AND t1.sku_id > 0 AND t1.shop_price = 0 AND + t1.order_created_at > ? AND t3.id IS NULL + ` + sqlParams := []interface{}{ + utils.DefaultTimeValue, + model.OrderStatusFinished, + fromTime, + } + if len(storeIDs) > 0 { + sql += " AND IF(t2.jx_store_id <> 0, t2.jx_store_id, t2.store_id) IN (" + GenQuestionMarks(len(storeIDs)) + ")" + sqlParams = append(sqlParams, storeIDs) + } + sql += ` + GROUP BY 1,2,3,4,5,6 + ORDER BY 1,2,3,4,5,6 + ` + err = GetRows(db, &storeSkuList, sql, sqlParams...) + return storeSkuList, err +} diff --git a/business/model/model.go b/business/model/model.go index c21b271ef..706643982 100644 --- a/business/model/model.go +++ b/business/model/model.go @@ -45,6 +45,9 @@ const ( FieldVendorOrderID = "VendorOrderID" FieldVendorOrderID2 = "VendorOrderID2" + + FieldActID = "ActID" + FieldVendorActID = "VendorActID" ) type ModelIDCUL struct { @@ -67,16 +70,43 @@ const ( SyncFlagModifiedMask = 1 SyncFlagNewMask = 2 SyncFlagDeletedMask = 4 - SyncFlagChangedMask = SyncFlagModifiedMask | SyncFlagNewMask | SyncFlagDeletedMask - SyncFlagSaleMask = 8 - SyncFlagPriceMask = 16 + SyncFlagSaleMask = 8 // 改了门店商品可售状态必须设置此标志 + SyncFlagPriceMask = 16 // 改了门店商品价格必须设置此标志 SyncFlagSpecMask = 32 + SyncFlagStoreSkuOnlyMask = SyncFlagSaleMask | SyncFlagPriceMask + SyncFlagStoreSkuModifiedMask = SyncFlagStoreSkuOnlyMask | SyncFlagModifiedMask + SyncFlagChangedMask = SyncFlagSpecMask | SyncFlagNewMask | SyncFlagDeletedMask | SyncFlagStoreSkuModifiedMask + SyncFlagStoreName = 8 SyncFlagStoreAddress = 16 ) +func IsSyncStatusNew(syncStatus int) bool { + return (syncStatus & SyncFlagNewMask) != 0 +} + +func IsSyncStatusDelete(syncStatus int) bool { + return (syncStatus & SyncFlagDeletedMask) != 0 +} + +func IsSyncStatusUpdate(syncStatus int) bool { + return (syncStatus & SyncFlagModifiedMask) != 0 +} + +func IsSyncStatusNeedCreate(syncStatus int) bool { + return IsSyncStatusNew(syncStatus) && !IsSyncStatusDelete(syncStatus) +} + +func IsSyncStatusNeedDelete(syncStatus int) bool { + return !IsSyncStatusNew(syncStatus) && IsSyncStatusDelete(syncStatus) +} + +func IsSyncStatusNeedUpdate(syncStatus int) bool { + return !IsSyncStatusNew(syncStatus) && !IsSyncStatusDelete(syncStatus) && IsSyncStatusUpdate(syncStatus) +} + // const ( // KeyJdFlag = "jdFlag" // KeyJdSyncedAt = "jdSyncedAt" diff --git a/business/model/order.go b/business/model/order.go index a0a2a9629..bbde7a557 100644 --- a/business/model/order.go +++ b/business/model/order.go @@ -16,9 +16,12 @@ type GoodsOrder struct { StoreID int `orm:"column(store_id)" json:"storeID"` // 外部系统里记录的 jxstoreid JxStoreID int `orm:"column(jx_store_id)" json:"jxStoreID"` // 根据VendorStoreID在本地系统里查询出来的 jxstoreid StoreName string `orm:"size(64)" json:"storeName"` - ShopPrice int64 `json:"shopPrice"` // 单位为分 门店标价 - SalePrice int64 `json:"salePrice"` // 单位为分 售卖价 + ShopPrice int64 `json:"shopPrice"` // 京西价 + VendorPrice int64 `json:"vendorPrice"` // 平台价 + SalePrice int64 `json:"salePrice"` // 售卖价 ActualPayPrice int64 `json:"actualPayPrice"` // 单位为分 顾客实际支付 + TotalShopMoney int64 `json:"shopMoney"` // 应结金额-第三方平台结算给京西的金额(包括了所有的补贴,扣除) + PmSubsidyMoney int64 `json:"pmSubsidyMoney"` // 平台活动补贴(订单主体活动补贴+订单单条sku补贴)1+ Weight int `json:"weight"` // 单位为克 ConsigneeName string `orm:"size(32)" json:"consigneeName"` ConsigneeMobile string `orm:"size(32)" json:"consigneeMobile"` @@ -30,10 +33,10 @@ type GoodsOrder struct { SkuCount int `json:"skuCount"` // 商品类别数量,即有多少种商品(注意在某些情况下,相同SKU的商品由于售价不同,也会当成不同商品在这个值里) GoodsCount int `json:"goodsCount"` // 商品个数 Status int `json:"status"` // 参见OrderStatus*相关的常量定义 - VendorStatus string `orm:"size(255)" json:"-"` + VendorStatus string `orm:"size(255)" json:"vendorStatus"` LockStatus int `json:"lockStatus"` - LockStatusTime time.Time `orm:"type(datetime);null" json:"-"` // last lock status time - OrderSeq int `json:"orderSeq"` // 门店订单序号 + LockStatusTime time.Time `orm:"type(datetime);null" json:"lockStatusTime"` // last lock status time + OrderSeq int `json:"orderSeq"` // 门店订单序号 BuyerComment string `orm:"size(255)" json:"buyerComment"` BusinessType int `json:"businessType"` ExpectedDeliveredTime time.Time `orm:"type(datetime)" json:"expectedDeliveredTime"` // 预期送达时间 @@ -44,19 +47,12 @@ type GoodsOrder struct { DuplicatedCount int `json:"-"` // 重复新订单消息数,这个一般不是由于消息重发造成的(消息重发由OrderStatus过滤),一般是业务逻辑造成的 OrderCreatedAt time.Time `orm:"type(datetime);index" json:"orderCreatedAt"` // 这里记录的是订单生效时间,即用户支付完成(货到付款即为下单时间) OrderFinishedAt time.Time `orm:"type(datetime)" json:"orderFinishedAt"` - StatusTime time.Time `orm:"type(datetime)" json:"-"` // last status time + StatusTime time.Time `orm:"type(datetime)" json:"statusTime"` // last status time PickDeadline time.Time `orm:"type(datetime)" json:"pickDeadline"` ModelTimeInfo `json:"-"` OriginalData string `orm:"-" json:"-"` // 只是用于传递数据 Skus []*OrderSku `orm:"-" json:"-"` - Flag int8 `json:"flag"` //非运单调整相关的其它状态 - SkuPmFee int64 `json:"-"` //门店商品活动总支出 - OrderPmFee int64 `json:"-"` //门店订单活动支出 - SkuPmSubsidy int64 `json:"-"` //平台商品活动总补贴 - OrderPmSubsidy int64 `json:"-"` //平台订单活动补贴 - BoxFee int64 `json:"-"` //餐盒费 - PlatformFeeRate int16 `json:"-"` //平台费 - BillStoreFreightFee int64 `json:"-"` //需要回调,门店所承担的运费 + Flag int `json:"flag"` //非运单调整相关的其它状态 } func (o *GoodsOrder) TableUnique() [][]string { @@ -88,18 +84,18 @@ type OrderSku struct { StoreSubID int `orm:"column(store_sub_id)" json:"storeSubID"` StoreSubName string `orm:"size(64)" json:"storeSubName"` Count int `json:"count"` - VendorSkuID string `orm:"column(vendor_sku_id);size(48)" json:"-"` + VendorSkuID string `orm:"column(vendor_sku_id);size(48)" json:"vendorSkuID"` SkuID int `orm:"column(sku_id)" json:"skuID"` // 外部系统里记录的 jxskuid JxSkuID int `orm:"column(jx_sku_id)" json:"jxSkuID"` // 根据VendorSkuID在本地系统里查询出来的 jxskuid SkuName string `orm:"size(255)" json:"skuName"` - ShopPrice int64 `json:"shopPrice"` // 门店标价 + ShopPrice int64 `json:"shopPrice"` // 京西价 + VendorPrice int64 `json:"vendorPrice"` // 平台价 SalePrice int64 `json:"salePrice"` // 售卖价 - Weight int `json:"-"` // 单位为克 - SkuType int `json:"-"` // 当前如果为gift就为1,否则缺省为0 - PromotionType int `json:"-"` // todo 当前是用于记录京东的PromotionType(生成jxorder用),没有做转换 + EarningPrice int64 `json:"earningPrice"` // 活动商品设置,结算给门店老板的钱 + Weight int `json:"weight"` // 单位为克 + SkuType int `json:"skuType"` // 当前如果为gift就为1,否则缺省为0 + PromotionType int `json:"promotionType"` // todo 当前是用于记录京东的PromotionType(生成jxorder用),没有做转换 OrderCreatedAt time.Time `orm:"type(datetime);index" json:"-"` // 分区考虑 - SkuPmSubsidy int64 `json:"-"` //平台商品活动补贴 - SkuPmFee int64 `json:"-"` //门店商品活动支出 } // 同样商品在一个订单中可能重复出现(比如搞活动时,相同商品价格不一样,第一个有优惠) @@ -124,6 +120,7 @@ type Waybill struct { ActualFee int64 `json:"actualFee"` // 实际要支付给快递公司的费用 DesiredFee int64 `json:"desiredFee"` // 运单总费用 DuplicatedCount int `json:"-"` // 重复新订单消息数,这个一般不是由于消息重发造成的(消息重发由OrderStatus过滤),一般是业务逻辑造成的 + DeliveryFlag int8 `json:"deliveryFlag"` WaybillCreatedAt time.Time `orm:"type(datetime);index" json:"waybillCreatedAt"` WaybillFinishedAt time.Time `orm:"type(datetime)" json:"waybillFinishedAt"` StatusTime time.Time `orm:"type(datetime)" json:"-"` // last status time @@ -240,3 +237,24 @@ type OrderComment struct { CommentUpdatedAt time.Time UpdatedOriginalMsg string `orm:"type(text)" json:"-"` } + +// 判断是否是购买平台自有物流 +// 对于京东,饿百来说,就是其自有的物流,对于微商城来说,是达达 +func IsWaybillPlatformOwn(bill *Waybill) bool { + return bill.OrderVendorID == bill.WaybillVendorID || IsSpecialOrderPlatformWaybill(bill) +} + +// 是否是特殊物流 +func IsSpecialOrderPlatformWaybill(bill *Waybill) bool { + return (bill.OrderVendorID == VendorIDWSC && bill.WaybillVendorID == VendorIDDada) +} + +// 订单是否已经有了有效运单 +func IsOrderHaveWaybill(order *GoodsOrder) bool { + return order.WaybillVendorID != VendorIDUnknown && order.VendorWaybillID != "" +} + +// 订单是否有自己平台的有效运单 +func IsOrderHaveOwnWaybill(order *GoodsOrder) bool { + return order.VendorID == order.WaybillVendorID && order.VendorWaybillID != "" +} diff --git a/business/model/order_financial.go b/business/model/order_financial.go index 28ab408c9..3cb6eeba3 100644 --- a/business/model/order_financial.go +++ b/business/model/order_financial.go @@ -70,25 +70,40 @@ func (o *OrderDiscountFinancial) TableUnique() [][]string { type AfsOrder struct { ModelIDCUL - VendorID int `orm:"column(vendor_id)" json:"vendorID"` - VendorOrderID string `orm:"column(vendor_order_id);size(48)" json:"vendorOrderID"` // 关联原始订单ID - VendorOrderID2 string `orm:"column(vendor_order_id2);size(48);index" json:"vendorOrderID2"` // 关联原始订单ID2,饿百独有 - AfsOrderID string `orm:"column(afs_order_id);size(48)" json:"afsOrderID"` // 售后订单ID - AfsCreateAt time.Time `orm:"type(datetime);index" json:"afsCreateAt"` // 订单生成时间 - VendorStoreID string `orm:"column(vendor_store_id);size(48)" json:"vendorStoreID"` // 外部系统里记录的storeid - StoreID int `orm:"column(store_id)" json:"storeID"` // 接口返回的京西门店ID - JxStoreID int `orm:"column(jx_store_id)" json:"jxStoreID"` // 根据VendorStoreID在本地系统里查询出来的 jxstoreid - SkuUserMoney int64 `json:"skuUserMoney"` // 用户支付菜品金额 - FreightUserMoney int64 `json:"freightUserMoney"` // 用户支付运费金额 - AfsFreightMoney int64 `json:"afsFreightMoney"` // 退货取件费 - BoxMoney int64 `json:"boxMoney"` // 应退包装费金额 - TongchengFreightMoney int64 `json:"tongchengFreightMoney"` // 退货单产生的同城送费用 - SkuBoxMoney int64 `json:"skuBoxMoney"` // 应退订单餐盒费 - PmSubsidyMoney int64 `json:"pmSubsidyMoney"` // 平台总补贴金额 - PmSkuSubsidyMoney int64 `json:"pmSkuSubsidyMoney"` // 平台sku补贴金额 - PmRefundMoney int64 `json:"pmRefundMoney"` // 订单取消后可能存在佣金减少,平台退回部分金额的情况 - RefundMoney int64 `json:"refundMoney"` // 平台扣款总额 1 - RefundMoneyByCal int64 `json:"refundMoneyByCal"` // 平台扣款总额-通过公式计算平台扣除京西的金额M + VendorID int `orm:"column(vendor_id)" json:"vendorID"` + VendorOrderID string `orm:"column(vendor_order_id);size(48)" json:"vendorOrderID"` // 关联原始订单ID + VendorOrderID2 string `orm:"column(vendor_order_id2);size(48);index" json:"vendorOrderID2"` // 关联原始订单ID2,饿百独有 + AfsOrderID string `orm:"column(afs_order_id);size(48)" json:"afsOrderID"` // 售后订单ID + AfsCreatedAt time.Time `orm:"type(datetime);null;index" json:"afsCreatedAt"` // 售后单生成时间 + AfsFinishedAt time.Time `orm:"type(datetime);null;index" json:"afsFinishedAt"` // 售后单结束时间 + VendorStoreID string `orm:"column(vendor_store_id);size(48)" json:"vendorStoreID"` // 外部系统里记录的storeid + StoreID int `orm:"column(store_id)" json:"storeID"` // 接口返回的京西门店ID + JxStoreID int `orm:"column(jx_store_id)" json:"jxStoreID"` // 根据VendorStoreID在本地系统里查询出来的 jxstoreid + + // IsNeedApprove int8 `json:"isNeedApprove"` // 售后单是否需要商家审核 + Status int `json:"status"` + VendorStatus string `orm:"size(255)" json:"vendorStatus"` + ReasonType int8 `json:"reasonType"` // 售后原因 + VendorReasonType string `orm:"size(255)" json:"vendorReasonType"` + ReasonDesc string `orm:"size(1024)" json:"reasonDesc"` // 售后原因描述 + ReasonImgList string `orm:"size(1024)" json:"reasonImgList"` // 售后描述图片 + AppealType int8 `json:"appealType"` // 售后方式 + VendorAppealType string `orm:"size(255)" json:"vendorAppealType"` + Flag int `json:"flag"` + RefundType int8 `json:"refundType"` + RefuseReason string `orm:"size(1024)" json:"refuseReason"` + + SkuUserMoney int64 `json:"skuUserMoney"` // 用户支付菜品金额 + FreightUserMoney int64 `json:"freightUserMoney"` // 用户支付运费金额 + AfsFreightMoney int64 `json:"afsFreightMoney"` // 退货取件费 + BoxMoney int64 `json:"boxMoney"` // 应退包装费金额 + TongchengFreightMoney int64 `json:"tongchengFreightMoney"` // 退货单产生的同城送费用 + SkuBoxMoney int64 `json:"skuBoxMoney"` // 应退订单餐盒费 + PmSubsidyMoney int64 `json:"pmSubsidyMoney"` // 平台总补贴金额 + PmSkuSubsidyMoney int64 `json:"pmSkuSubsidyMoney"` // 平台sku补贴金额 + PmRefundMoney int64 `json:"pmRefundMoney"` // 订单取消后可能存在佣金减少,平台退回部分金额的情况 + RefundMoney int64 `json:"refundMoney"` // 平台扣款总额 1 + RefundMoneyByCal int64 `json:"refundMoneyByCal"` // 平台扣款总额-通过公式计算平台扣除京西的金额M // JxSkuMoney int64 `json:"jxSkuMoney"` // 京西补贴金额,现阶段是平台扣京西多少钱,京西扣商家多少钱,暂不考虑撤回京西补贴 Skus []*OrderSkuFinancial `orm:"-" json:"skus"` } @@ -102,11 +117,10 @@ func (o *AfsOrder) TableUnique() [][]string { type OrderSkuFinancial struct { ModelIDCUL - VendorID int `orm:"column(vendor_id)" json:"vendorID"` // 平台id - VendorOrderID string `orm:"column(vendor_order_id);size(48)" json:"vendorOrderID"` // 关联原始订单ID - VendorOrderID2 string `orm:"column(vendor_order_id2);size(48);index" json:"vendorOrderID2"` // 关联原始订单ID2,饿百独有 - AfsOrderID string `orm:"column(order_financial_id);size(48)" json:"afsOrderID"` // 订单结账ID - IsAfsOrder int8 `json:"isAfsOrder"` // 0--正向单, 1--售后单 + VendorID int `orm:"column(vendor_id)" json:"vendorID"` // 平台id + VendorOrderID string `orm:"column(vendor_order_id);size(48)" json:"vendorOrderID"` // 关联原始订单ID + AfsOrderID string `orm:"column(afs_order_id);size(48)" json:"afsOrderID"` // 售后单ID + IsAfsOrder int8 `json:"isAfsOrder"` // 0--正向单, 1--售后单 // ConfirmTime time.Time `orm:"type(datetime)" json:"confirmTime"` // 订单生成/完成时间 VendorStoreID string `orm:"column(vendor_store_id);size(48)" json:"vendorStoreID"` // 外部系统里记录的storeid @@ -141,7 +155,8 @@ type OrderSkuFinancial struct { func (o *OrderSkuFinancial) TableIndex() [][]string { return [][]string{ - []string{"VendorOrderID", "VendorSkuID", "PromotionType", "IsAfsOrder", "VendorID"}, + []string{"VendorOrderID", "VendorSkuID"}, + []string{"AfsOrderID", "VendorSkuID"}, } } diff --git a/business/model/promotion.go b/business/model/promotion.go index 2d33f2101..ca4037cc3 100644 --- a/business/model/promotion.go +++ b/business/model/promotion.go @@ -30,7 +30,7 @@ var ( type Promotion struct { ModelIDCULD - VendorID int `orm:"column(vendor_id)"` + VendorID int `orm:"column(vendor_id)" json:"vendorID"` Name string `orm:"size(64)" json:"name"` Advertising string `orm:"size(255)" json:"advertising"` Type int `json:"type"` @@ -75,6 +75,8 @@ type PromotionSku struct { Price int `json:"price"` // 分,活动价,这个不是单价 LimitSkuCount int `json:"limitSkuCount"` IsLock int8 `json:"isLock"` // 是否锁定门店商品信息 + + EarningPrice int `json:"earningPrice"` // 活动商品设置,结算给门店老板的钱 } func (*PromotionSku) TableUnique() [][]string { diff --git a/business/model/sku.go b/business/model/sku.go index 487d4c3b8..2a4fd330e 100644 --- a/business/model/sku.go +++ b/business/model/sku.go @@ -89,6 +89,7 @@ var ( SpecialUnit = "份" SpecialSpecQuality = 500 SpecialSpecUnit = "g" + SpecialSpecUnit2 = "ml" ) var ( @@ -131,7 +132,12 @@ type SkuCategory struct { Type int8 `json:"type"` // 类别类型,即是普通类别还是特殊用于做活动的类别 Seq int `json:"seq"` - JdCategoryID int `orm:"column(jd_category_id)" json:"jdCategoryID"` // 这个是指对应的京东商品类别 + JdPricePercentage int16 `orm:"default(100)" json:"jdPricePercentage"` + EbaiPricePercentage int16 `orm:"default(100)" json:"ebaiPricePercentage"` + MtwmPricePercentage int16 `orm:"default(100)" json:"mtwmPricePercentage"` + WscPricePercentage int16 `orm:"default(100)" json:"wscPricePercentage"` + + JdCategoryID int64 `orm:"column(jd_category_id)" json:"jdCategoryID"` // 这个是指对应的京东商品类别 ElmCategoryID int64 `orm:"column(elm_category_id)" json:"elmCategoryID"` // 这个是指对应的饿了么商品类别 EbaiCategoryID int64 `orm:"column(ebai_category_id)" json:"ebaiCategoryID"` // 这个是指对应的饿百商品类别 MtwmCategoryID int64 `orm:"column(mtwm_category_id)" json:"mtwmCategoryID"` // 这个是指对应的美团外卖商品类别 @@ -159,8 +165,9 @@ type SkuName struct { Prefix string `orm:"size(255)" json:"prefix"` Name string `orm:"size(255);index" json:"name"` - BrandID int `orm:"column(brand_id);default(0)" json:"brandID"` // todo,此属性暂时没有使用,且有问题,应该是不同平台都有一个brandid - CategoryID int `orm:"column(category_id);index" json:"categoryID"` // 标准类别 + BrandID int `orm:"column(brand_id);default(0)" json:"brandID"` // todo,此属性暂时没有使用,且有问题,应该是不同平台都有一个brandid + CategoryID int `orm:"column(category_id);index" json:"categoryID"` // 标准类别 + JdCategoryID int64 `orm:"column(jd_category_id)" json:"jdCategoryID"` // 这个是指对应的京东商品类别 IsGlobal int8 `orm:"default(1)" json:"isGlobal"` // 是否是全部(全国)可见,如果否的话,可见性由SkuPlace决定 Unit string `orm:"size(8)" json:"unit"` @@ -176,6 +183,9 @@ type SkuName struct { Status int `orm:"default(1)" json:"status"` // skuname状态,取值同sku.Status IsSpu int8 `orm:"column(is_spu)" json:"isSpu"` // 用于指明是否SKUNAME当成SPU + DescImg string `orm:"size(255)" json:"descImg"` // 商品详情图片描述 + DescImgEbai string `orm:"size(255)" json:"descImgEbai"` // 饿百的商品详情图片描述RTF + JdID int64 `orm:"column(jd_id);null;index" json:"jdID"` JdSyncStatus int8 `orm:"default(2)" json:"jdSyncStatus"` @@ -206,6 +216,12 @@ type Sku struct { LinkID int `orm:"column(link_id);null;index" json:"linkID"` } +type SkuAndName struct { + Sku + Name string + Unit string +} + // func (*Sku) TableUnique() [][]string { // return [][]string{ // []string{"JdID", "DeletedAt"}, diff --git a/business/model/store.go b/business/model/store.go index 9800ab26a..b535e9e4f 100644 --- a/business/model/store.go +++ b/business/model/store.go @@ -2,8 +2,9 @@ package model const ( StoreStatusAll = -9 - StoreStatusDisabled = -1 - StoreStatusClosed = 0 + StoreStatusDisabled = -2 + StoreStatusClosed = -1 + StoreStatusHaveRest = 0 StoreStatusOpened = 1 ) @@ -32,7 +33,8 @@ const ( var ( StoreStatusName = map[int]string{ StoreStatusDisabled: "禁用", - StoreStatusClosed: "休息", + StoreStatusClosed: "长期休息", + StoreStatusHaveRest: "临时休息", StoreStatusOpened: "营业中", } DeliveryRangeTypeName = map[int]string{ @@ -62,6 +64,7 @@ var ( "NBB": "宁波银行", "SPDB": "浦发银行", "GDB": "广发银行", + "BOCOM": "交通银行", "SPAB": "平安银行", "BSB": "包商银行", "CSCB": "长沙银行", @@ -131,6 +134,8 @@ type Store struct { Status int `json:"status"` ChangePriceType int8 `json:"changePriceType"` // 修改价格类型,即是否需要审核 + DeliveryType int8 `orm:"-" json:"deliveryType"` // 仅用于传值 + PrinterVendorID int `orm:"column(printer_vendor_id);" json:"printerVendorID"` PrinterSN string `orm:"size(32);column(printer_sn);index" json:"printerSN"` PrinterKey string `orm:"size(32)" json:"printerKey"` @@ -138,10 +143,39 @@ type Store struct { IDCardFront string `orm:"size(255);column(id_card_front)" json:"idCardFront"` IDCardBack string `orm:"size(255);column(id_card_back)" json:"idCardBack"` IDCardHand string `orm:"size(255);column(id_card_hand)" json:"idCardHand"` - Licence string `orm:"size(255)" json:"licence"` + Licence string `orm:"size(255)" json:"licence"` // 营业执照图片 LicenceCode string `orm:"size(32)" json:"licenceCode"` - DeliveryType int8 `orm:"-" json:"deliveryType"` // 仅用于传值 + LicenceType int8 `json:"licenceType"` // 营业执照类型,0:个人,1:公司 + LicenceCorpName string `orm:"size(64)" json:"licenceCorpName"` // 营业执照公司名称 + LicenceOwnerName string `orm:"size(8)" json:"licenceOwnerName"` // 法人姓名 + LicenceAddress string `orm:"size(255)" json:"licenceAddress"` // 地址 + LicenceValid string `orm:"size(32)" json:"licenceValid"` // 有效期开始 + LicenceExpire string `orm:"size(32)" json:"licenceExpire"` // 有效期结束 + + IDName string `orm:"size(8);column(id_name)" json:"idName"` // 身份证姓名 + IDCode string `orm:"size(32);column(id_code)" json:"idCode"` // 身份证号 + IDValid string `orm:"column(id_valid);size(32)" json:"idValid"` // 有效期开始 + IDExpire string `orm:"column(id_expire);size(32)" json:"idExpire"` // 有效期结束 + + Licence2Image string `orm:"size(255)" json:"licence2Image"` // 食品经营许可证 + Licence2Code string `orm:"size(32)" json:"licence2Code"` // 食品经营许可证编号 + Licence2Valid string `orm:"size(32)" json:"licence2Valid"` // 有效期开始 + Licence2Expire string `orm:"size(32)" json:"licence2Expire"` // 有效期结束 + + MarketManName string `orm:"size(8)" json:"marketManName"` // 市场负责人姓名 + MarketManPhone string `orm:"size(16)" json:"marketManPhone"` // 市场负责人电话 + JxBrandFeeFactor int `json:"jxBrandFeeFactor"` // 京西品牌费因子 + MarketAddFeeFactor int `json:"marketAddFeeFactor"` // 市场附加费因子 + + PayeeName string `orm:"size(8)" json:"payeeName"` // 收款人姓名 + PayeeAccountNo string `orm:"size(255)" json:"payeeAccountNo"` // 收款账号 + PayeeBankBranchName string `orm:"size(255)" json:"payeeBankBranchName"` // 开户银行 + PayeeBankName string `orm:"size(255)" json:"payeeBankName"` // 开户支行 + PayPercentage int `json:"payPercentage"` + + OperatorName string `orm:"size(8)" json:"operatorName"` // 运营人姓名 + OperatorPhone string `orm:"size(16)" json:"operatorPhone"` // 运营人电话 } func (*Store) TableUnique() [][]string { diff --git a/business/model/store_sku.go b/business/model/store_sku.go index f803b6a9c..be6942bde 100644 --- a/business/model/store_sku.go +++ b/business/model/store_sku.go @@ -58,6 +58,29 @@ func (*StoreSkuCategoryMap) TableUnique() [][]string { } } +func (*StoreSkuCategoryMap) TableIndex() [][]string { + return [][]string{ + []string{"CategoryID", "StoreID", "DeletedAt"}, + } +} + +// type StoreSkuCategoryMap2 struct { +// ModelIDCULD + +// StoreID int `orm:"column(store_id)"` +// VendorID int `orm:"column(vendor_id)"` +// CategoryID int `orm:"column(category_id)"` + +// VendorCatID string `orm:"column(vendor_cat_id);size(48)" json:"vendorCatID"` +// SyncStatus int8 `orm:"default(2)"` +// } + +// func (*StoreSkuCategoryMap2) TableUnique() [][]string { +// return [][]string{ +// []string{"StoreID", "VendorID", "CategoryID", "DeletedAt"}, +// } +// } + type StoreSkuBind struct { ModelIDCULD @@ -88,6 +111,12 @@ func (*StoreSkuBind) TableUnique() [][]string { } } +func (*StoreSkuBind) TableIndex() [][]string { + return [][]string{ + []string{"SkuID", "StoreID", "DeletedAt"}, + } +} + type StoreOpRequest struct { ModelIDCULD // DeletedAt用于表示请求操作结束,而并不一定是删除 diff --git a/business/msghub/msghub.go b/business/msghub/msghub.go index 39144e0f3..ded5374c4 100644 --- a/business/msghub/msghub.go +++ b/business/msghub/msghub.go @@ -18,7 +18,11 @@ const ( ServerMsgPing = "ping" ServerMsgNewOrder = "newOrder" + ServerMsgFinishedPickup = "finishedPickup" ServerMsgKeyOrderStatusChanged = "keyOrderStatusChanged" // 重要订单状态变化 + + ServerMsgNewWait4ApproveAfsOrder = "newWait4ApproveAfsOrder" + ServerMsgKeyAfsOrderStatusChanged = "keyAfsOrderStatusChanged" // 重要售后单状态变化 ) const ( @@ -67,8 +71,8 @@ func routinueFunc() { registerMsg := msg.MsgData.(*MsgOp) delete(channelMap[registerMsg.StoreID], registerMsg.Chan2Listen) close(registerMsg.Chan2Close) - case ServerMsgNewOrder, ServerMsgKeyOrderStatusChanged: - globals.SugarLogger.Debugf("msghub routinueFunc, msg:%s", utils.Format4Output(msg, false)) + case ServerMsgNewOrder, ServerMsgFinishedPickup, ServerMsgKeyOrderStatusChanged, ServerMsgNewWait4ApproveAfsOrder, ServerMsgKeyAfsOrderStatusChanged: + globals.SugarLogger.Debugf("msghub routinueFunc, msg:%s", utils.Format4Output(msg, true)) utils.CallFuncAsync(func() { for chan2Send := range channelMap[msg.StoreID] { chan2Send <- msg @@ -138,7 +142,7 @@ func GetMsg(ctx *jxcontext.Context, storeID int, lastOrderTime time.Time, lastOr case msg2, ok := <-chan2Listen: timer.Stop() if ok { - msg.MsgData = msg2.MsgData + msg = msg2 } case <-timer.C: unregisterChan(storeID, chan2Listen) @@ -152,7 +156,7 @@ func GetMsg(ctx *jxcontext.Context, storeID int, lastOrderTime time.Time, lastOr } func OnNewOrder(order *model.GoodsOrder) { - globals.SugarLogger.Debugf("msghub OnNewOrder, order:%s", utils.Format4Output(order, false)) + globals.SugarLogger.Debugf("msghub OnNewOrder, order:%s", utils.Format4Output(order, true)) utils.CallFuncAsync(func() { msgChan <- &ServerMsg{ Type: ServerMsgNewOrder, @@ -167,13 +171,13 @@ func OnNewOrder(order *model.GoodsOrder) { }) } -func OnKeyOrderStatusChanged(order *model.GoodsOrder) { - globals.SugarLogger.Debugf("msghub OnKeyOrderStatusChanged, order:%s", utils.Format4Output(order, false)) +func OnFinishedPickup(order *model.GoodsOrder) { + globals.SugarLogger.Debugf("msghub OnFinishedPickup, order:%s", utils.Format4Output(order, true)) utils.CallFuncAsync(func() { msgChan <- &ServerMsg{ - Type: ServerMsgKeyOrderStatusChanged, + Type: ServerMsgFinishedPickup, StoreID: jxutils.GetSaleStoreIDFromOrder(order), - MsgData: order, + MsgData: 1, // MsgData: []*model.GoodsOrderExt{ // &model.GoodsOrderExt{ // GoodsOrder: *order, @@ -182,3 +186,36 @@ func OnKeyOrderStatusChanged(order *model.GoodsOrder) { } }) } + +func OnKeyOrderStatusChanged(order *model.GoodsOrder) { + globals.SugarLogger.Debugf("msghub OnKeyOrderStatusChanged, order:%s", utils.Format4Output(order, true)) + utils.CallFuncAsync(func() { + msgChan <- &ServerMsg{ + Type: ServerMsgKeyOrderStatusChanged, + StoreID: jxutils.GetSaleStoreIDFromOrder(order), + MsgData: order, + } + }) +} + +func OnNewWait4ApproveAfsOrder(order *model.AfsOrder) { + globals.SugarLogger.Debugf("msghub OnNewWait4ApproveAfsOrder, order:%s", utils.Format4Output(order, true)) + utils.CallFuncAsync(func() { + msgChan <- &ServerMsg{ + Type: ServerMsgNewWait4ApproveAfsOrder, + StoreID: jxutils.GetSaleStoreIDFromAfsOrder(order), + MsgData: order, + } + }) +} + +func OnKeyAfsOrderStatusChanged(order *model.AfsOrder) { + globals.SugarLogger.Debugf("msghub OnKeyAfsOrderStatusChanged, order:%s", utils.Format4Output(order, true)) + utils.CallFuncAsync(func() { + msgChan <- &ServerMsg{ + Type: ServerMsgKeyAfsOrderStatusChanged, + StoreID: jxutils.GetSaleStoreIDFromAfsOrder(order), + MsgData: order, + } + }) +} diff --git a/business/partner/delivery/dada/waybill.go b/business/partner/delivery/dada/waybill.go index 1061337b8..f98e6fb36 100644 --- a/business/partner/delivery/dada/waybill.go +++ b/business/partner/delivery/dada/waybill.go @@ -50,7 +50,7 @@ func (c *DeliveryHandler) GetVendorID() int { func (c *DeliveryHandler) OnWaybillMsg(msg *dadaapi.CallbackMsg) (retVal *dadaapi.CallbackResponse) { jxutils.CallMsgHandler(func() { retVal = c.onWaybillMsg(msg) - }, msg.OrderID) + }, jxutils.ComposeUniversalOrderID(msg.OrderID, model.VendorIDDada)) return retVal } @@ -58,12 +58,16 @@ func (c *DeliveryHandler) onWaybillMsg(msg *dadaapi.CallbackMsg) (retVal *dadaap order := c.callbackMsg2Waybill(msg) switch msg.OrderStatus { case dadaapi.OrderStatusWaitingForAccept: - order.Status = model.WaybillStatusNew - case dadaapi.OrderStatusAccepted: if result, err := api.DadaAPI.QueryOrderInfo(msg.OrderID); err == nil { order.ActualFee = jxutils.StandardPrice2Int(utils.Interface2Float64WithDefault(result["actualFee"], 0.0)) order.DesiredFee = jxutils.StandardPrice2Int(utils.Interface2Float64WithDefault(result["deliveryFee"], 0.0)) } + order.Status = model.WaybillStatusNew + case dadaapi.OrderStatusAccepted: + // if result, err := api.DadaAPI.QueryOrderInfo(msg.OrderID); err == nil { + // order.ActualFee = jxutils.StandardPrice2Int(utils.Interface2Float64WithDefault(result["actualFee"], 0.0)) + // order.DesiredFee = jxutils.StandardPrice2Int(utils.Interface2Float64WithDefault(result["deliveryFee"], 0.0)) + // } order.Status = model.WaybillStatusAccepted case dadaapi.OrderStatusDelivering: order.Status = model.WaybillStatusDelivering @@ -99,17 +103,23 @@ func (c *DeliveryHandler) callbackMsg2Waybill(msg *dadaapi.CallbackMsg) (retVal return retVal } -// IDeliveryPlatformHandler -func (c *DeliveryHandler) CreateWaybill(order *model.GoodsOrder, policy partner.CreateWaybillPolicy) (bill *model.Waybill, err error) { +func (c *DeliveryHandler) GetWaybillFee(order *model.GoodsOrder) (deliveryFeeInfo *partner.WaybillFeeInfo, err error) { db := dao.GetDB() - deliveryFee, addFee, err := delivery.CalculateOrderDeliveryFee(order, time.Now(), db) - if err != nil { - return nil, err + deliveryFeeInfo = &partner.WaybillFeeInfo{} + deliveryFeeInfo.RefDeliveryFee, deliveryFeeInfo.RefAddFee, err = delivery.CalculateOrderDeliveryFee(order, time.Now(), db) + billParams, addParams, err := c.getBillParams(db, order) + if err == nil { + var result *dadaapi.CreateOrderResponse + if result, err = api.DadaAPI.QueryDeliverFee(billParams, addParams); err != nil { + return nil, err + } + deliveryFeeInfo.DeliveryFee = jxutils.StandardPrice2Int(result.Fee) } - if err = delivery.CallCreateWaybillPolicy(policy, deliveryFee, addFee, deliveryFee, order, model.VendorIDDada); err != nil { - return nil, err - } - billParams := &dadaapi.OperateOrderRequiredParams{ + return deliveryFeeInfo, err +} + +func (c *DeliveryHandler) getBillParams(db *dao.DaoDB, order *model.GoodsOrder) (billParams *dadaapi.OperateOrderRequiredParams, addParams map[string]interface{}, err error) { + billParams = &dadaapi.OperateOrderRequiredParams{ // ShopNo: utils.Int2Str(order.StoreID), // 当前达达的门店号与京西是一样的 OriginID: jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID), CargoPrice: jxutils.IntPrice2Standard(limitOrderPrice(order.ActualPayPrice)), @@ -121,7 +131,7 @@ func (c *DeliveryHandler) CreateWaybill(order *model.GoodsOrder, policy partner. if billParams.ShopNo, err = c.getDadaShopID(order, db); err == nil { if billParams.CityCode, err = c.getDataCityCodeFromOrder(order, db); err == nil { billParams.ReceiverLng, billParams.ReceiverLat, _ = jxutils.IntCoordinate2MarsStandard(order.ConsigneeLng, order.ConsigneeLat, order.CoordinateType) - addParams := map[string]interface{}{ + addParams = map[string]interface{}{ "info": fmt.Sprintf("%s第%d号订单, %s", model.VendorChineseNames[order.VendorID], order.OrderSeq, utils.FilterMb4(order.BuyerComment)), // "origin_mark": model.VendorNames[order.VendorID], // 订单来源标示(该字段可以显示在达达app订单详情页面,只支持字母,最大长度为10) // "origin_mark_no": fmt.Sprintf("%d", order.OrderSeq), // 订单来源编号(该字段可以显示在达达app订单详情页面,支持字母和数字,最大长度为30) @@ -129,11 +139,27 @@ func (c *DeliveryHandler) CreateWaybill(order *model.GoodsOrder, policy partner. "cargo_weight": jxutils.IntWeight2Float(limitOrderWeight(order.Weight)), "cargo_num": order.GoodsCount, } + } + } + return billParams, addParams, err +} - if globals.EnableStoreWrite { - // 达达要求第二次创建运单,调用函数不同。所以查找两天内有无相同订单号的运单 - var waybillList []*model.Waybill - err2 := dao.GetRows(db, &waybillList, ` +// IDeliveryPlatformHandler +func (c *DeliveryHandler) CreateWaybill(order *model.GoodsOrder, policy partner.CreateWaybillPolicy) (bill *model.Waybill, err error) { + db := dao.GetDB() + deliveryFee, addFee, err := delivery.CalculateOrderDeliveryFee(order, time.Now(), db) + if err != nil { + return nil, err + } + if err = delivery.CallCreateWaybillPolicy(policy, deliveryFee, addFee, deliveryFee, order, model.VendorIDDada); err != nil { + return nil, err + } + billParams, addParams, err := c.getBillParams(db, order) + if err == nil { + if globals.EnableStoreWrite { + // 达达要求第二次创建运单,调用函数不同。所以查找两天内有无相同订单号的运单 + var waybillList []*model.Waybill + err2 := dao.GetRows(db, &waybillList, ` SELECT * FROM waybill WHERE waybill_created_at > DATE_ADD(NOW(), interval -2 day) @@ -141,39 +167,38 @@ func (c *DeliveryHandler) CreateWaybill(order *model.GoodsOrder, policy partner. AND waybill_vendor_id = ? ORDER BY id DESC `, jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID), model.VendorIDDada) - var result *dadaapi.CreateOrderResponse - if err = err2; err == nil && len(waybillList) > 0 && waybillList[0].Status != model.WaybillStatusFailed { - globals.SugarLogger.Debugf("CreateWaybill orderID:%s len(waybillList)=%d use ReaddOrder", order.VendorOrderID, len(waybillList)) - result, err = api.DadaAPI.ReaddOrder(billParams, addParams) - } else { - if err != nil { - globals.SugarLogger.Warnf("CreateWaybill orderID:%s error:%v", order.VendorOrderID, err) - } - if false { - result, err = api.DadaAPI.AddOrder(billParams, addParams) - } else { - if result, err = api.DadaAPI.QueryDeliverFee(billParams, addParams); err != nil { - return nil, err - } - dadaFee := jxutils.StandardPrice2Int(result.Fee) - if err = delivery.CallCreateWaybillPolicy(policy, deliveryFee, addFee, dadaFee, order, model.VendorIDDada); err != nil { - return nil, err - } - err = api.DadaAPI.AddOrderAfterQuery(result.DeliveryNo) - } - } - if err == nil && result != nil { - bill = &model.Waybill{ - VendorOrderID: order.VendorOrderID, - OrderVendorID: order.VendorID, - WaybillVendorID: model.VendorIDDada, - DesiredFee: deliveryFee, - ActualFee: jxutils.StandardPrice2Int(result.Fee), - } - } + var result *dadaapi.CreateOrderResponse + if err = err2; err == nil && len(waybillList) > 0 && waybillList[0].Status != model.WaybillStatusFailed { + globals.SugarLogger.Debugf("CreateWaybill orderID:%s len(waybillList)=%d use ReaddOrder", order.VendorOrderID, len(waybillList)) + result, err = api.DadaAPI.ReaddOrder(billParams, addParams) } else { - err = fmt.Errorf("测试环境不能真正创建运单") + if err != nil { + globals.SugarLogger.Warnf("CreateWaybill orderID:%s error:%v", order.VendorOrderID, err) + } + if false { + result, err = api.DadaAPI.AddOrder(billParams, addParams) + } else { + if result, err = api.DadaAPI.QueryDeliverFee(billParams, addParams); err != nil { + return nil, err + } + dadaFee := jxutils.StandardPrice2Int(result.Fee) + if err = delivery.CallCreateWaybillPolicy(policy, deliveryFee, addFee, dadaFee, order, model.VendorIDDada); err != nil { + return nil, err + } + err = api.DadaAPI.AddOrderAfterQuery(result.DeliveryNo) + } } + if err == nil && result != nil { + bill = &model.Waybill{ + VendorOrderID: order.VendorOrderID, + OrderVendorID: order.VendorID, + WaybillVendorID: model.VendorIDDada, + DesiredFee: deliveryFee, + ActualFee: jxutils.StandardPrice2Int(result.Fee), + } + } + } else { + err = fmt.Errorf("测试环境不能真正创建运单") } } return bill, err diff --git a/business/partner/delivery/delivery.go b/business/partner/delivery/delivery.go index 9fb2e5492..7c366da13 100644 --- a/business/partner/delivery/delivery.go +++ b/business/partner/delivery/delivery.go @@ -17,21 +17,33 @@ import ( const ( warningDistance = 10 // 公里 warningWeight = 50 * 1000 // 克 - maxDiffFee2Mtps = 150 // 与美团配送最多差价 - maxAddFee = 300 // 最大增加费用,单位为分,超过不发三方配送了 + maxDiffFee2Mtps = 500 // 与美团配送最多差价 + maxAddFee = 200 // 最大增加费用,单位为分,超过不发三方配送了 +) + +var ( + DefCreateWaybillPolicy = CreateWaybillPolicy(maxDiffFee2Mtps, maxAddFee) ) func NullCreateWaybillPolicy(refDeliveryFee, refAddFee, deliveryFee int64) (errStr string) { return "" } -func DefCreateWaybillPolicy(refDeliveryFee, refAddFee, deliveryFee int64) (errStr string) { - if refDeliveryFee-deliveryFee > maxDiffFee2Mtps { - errStr = fmt.Sprintf("超参考价太多, 费用:%d,参考价:%d, 最高超价:%d", deliveryFee, refDeliveryFee, maxDiffFee2Mtps) - } else if refAddFee > maxAddFee { - errStr = fmt.Sprintf("超基础价太多, 当前加价:%d, 最高加价:%d", refAddFee, maxAddFee) +func CreateWaybillPolicy(maxDiffFee2Mtps2, maxAddFee2 int64) func(refDeliveryFee, refAddFee, deliveryFee int64) (errStr string) { + if maxDiffFee2Mtps2 == 0 { + maxDiffFee2Mtps2 = maxDiffFee2Mtps + } + if maxAddFee2 == 0 { + maxAddFee2 = maxAddFee + } + return func(refDeliveryFee, refAddFee, deliveryFee int64) (errStr string) { + if deliveryFee-refDeliveryFee > maxDiffFee2Mtps2 { + errStr = fmt.Sprintf("超参考价太多, 费用:%d,参考价:%d, 最高超价:%d", deliveryFee, refDeliveryFee, maxDiffFee2Mtps2) + } else if refAddFee > maxAddFee2 { + errStr = fmt.Sprintf("超基础价太多, 当前加价:%d, 最高加价:%d", refAddFee, maxAddFee2) + } + return errStr } - return errStr } func AddPolicy(prevPolicy, newPolicy partner.CreateWaybillPolicy) (outPolicy partner.CreateWaybillPolicy) { @@ -43,11 +55,11 @@ func AddPolicy(prevPolicy, newPolicy partner.CreateWaybillPolicy) (outPolicy par } } -func CallCreateWaybillPolicy(policy partner.CreateWaybillPolicy, refDeliveryFee, deliveryFee, addFee int64, order *model.GoodsOrder, waybillVendorID int) (err error) { +func CallCreateWaybillPolicy(policy partner.CreateWaybillPolicy, refDeliveryFee, refAddFee, deliveryFee int64, order *model.GoodsOrder, waybillVendorID int) (err error) { if policy == nil { policy = NullCreateWaybillPolicy } - if errStr := policy(deliveryFee, deliveryFee, addFee); errStr != "" { + if errStr := policy(refDeliveryFee, refAddFee, deliveryFee); errStr != "" { waybillVendorName := jxutils.GetVendorName(waybillVendorID) errStr = fmt.Sprintf("oderID:%s创建运单出错:%s", order.VendorOrderID, errStr) globals.SugarLogger.Debugf("%s CallCreateWaybillPolicy failed with %s", waybillVendorName, errStr) diff --git a/business/partner/delivery/mtps/waybill.go b/business/partner/delivery/mtps/waybill.go index 41a78e228..a56969543 100644 --- a/business/partner/delivery/mtps/waybill.go +++ b/business/partner/delivery/mtps/waybill.go @@ -55,7 +55,7 @@ func OnWaybillExcept(msg *mtpsapi.CallbackOrderExceptionMsg) (retVal *mtpsapi.Ca func (c *DeliveryHandler) OnWaybillMsg(msg *mtpsapi.CallbackOrderMsg) (retVal *mtpsapi.CallbackResponse) { jxutils.CallMsgHandler(func() { retVal = c.onWaybillMsg(msg) - }, msg.OrderID) + }, jxutils.ComposeUniversalOrderID(msg.OrderID, model.VendorIDMTPS)) return retVal } @@ -73,7 +73,7 @@ func (c *DeliveryHandler) OnWaybillExcept(msg *mtpsapi.CallbackOrderExceptionMsg } order.VendorOrderID, order.OrderVendorID = jxutils.SplitUniversalOrderID(msg.OrderID) retVal = mtpsapi.Err2CallbackResponse(partner.CurOrderManager.OnWaybillStatusChanged(order), "mtps OnWaybillExcept") - }, msg.OrderID) + }, jxutils.ComposeUniversalOrderID(msg.OrderID, model.VendorIDDada)) return retVal } @@ -81,9 +81,10 @@ func (c *DeliveryHandler) onWaybillMsg(msg *mtpsapi.CallbackOrderMsg) (retVal *m order := c.callbackMsg2Waybill(msg) switch msg.Status { case mtpsapi.OrderStatusWaitingForSchedule: + order.DesiredFee, _ = delivery.CalculateBillDeliveryFee(order) order.Status = model.WaybillStatusNew case mtpsapi.OrderStatusAccepted: - order.DesiredFee, _ = delivery.CalculateBillDeliveryFee(order) + order.DesiredFee, _ = delivery.CalculateBillDeliveryFee(order) // 美团外卖可能会丢失新运单事件,这里补一下 order.Status = model.WaybillStatusAccepted case mtpsapi.OrderStatusPickedUp: order.Status = model.WaybillStatusDelivering @@ -113,6 +114,18 @@ func (c *DeliveryHandler) callbackMsg2Waybill(msg *mtpsapi.CallbackOrderMsg) (re return retVal } +func (c *DeliveryHandler) GetWaybillFee(order *model.GoodsOrder) (deliveryFeeInfo *partner.WaybillFeeInfo, err error) { + db := dao.GetDB() + deliveryFeeInfo = &partner.WaybillFeeInfo{} + deliveryFeeInfo.RefDeliveryFee, deliveryFeeInfo.RefAddFee, err = delivery.CalculateOrderDeliveryFee(order, time.Now(), db) + if err == nil { + if _, err = c.getMTPSShopID(order, db); err == nil { + deliveryFeeInfo.DeliveryFee = deliveryFeeInfo.RefDeliveryFee + } + } + return deliveryFeeInfo, err +} + // IDeliveryPlatformHandler func (c *DeliveryHandler) CreateWaybill(order *model.GoodsOrder, policy partner.CreateWaybillPolicy) (bill *model.Waybill, err error) { db := dao.GetDB() @@ -167,7 +180,6 @@ func (c *DeliveryHandler) CreateWaybill(order *model.GoodsOrder, policy partner. if globals.EnableStoreWrite { result, err2 := api.MtpsAPI.CreateOrderByShop(billParams, addParams) if err = err2; err == nil { - globals.SugarLogger.Debugf("CreateWaybill failed, orderID:%s, billParams:%v, addParams:%v, error:%v", order.VendorOrderID, billParams, addParams, err) bill = &model.Waybill{ VendorOrderID: order.VendorOrderID, OrderVendorID: order.VendorID, @@ -176,6 +188,8 @@ func (c *DeliveryHandler) CreateWaybill(order *model.GoodsOrder, policy partner. WaybillVendorID: model.VendorIDMTPS, DesiredFee: deliveryFee, } + } else { + globals.SugarLogger.Debugf("CreateWaybill failed, orderID:%s, billParams:%v, addParams:%v, error:%v", order.VendorOrderID, billParams, addParams, err) } } else { err = fmt.Errorf("测试环境不能真正创建运单") diff --git a/business/partner/partner.go b/business/partner/partner.go index 36490ed41..6e88e4211 100644 --- a/business/partner/partner.go +++ b/business/partner/partner.go @@ -33,6 +33,12 @@ const ( PrinterStatusOnlineAbnormal = 3 ) +const ( + AfsApproveTypeRefund = 1 // 退款 + AfsApproveTypeReturnGoods = 2 // 退货 + AfsApproveTypeRefused = 3 // 驳回 +) + const ( PrintResultSuccess = 0 PrintResultNoPrinter = 1 @@ -51,10 +57,11 @@ type PrinterStatus struct { } const ( - TimerTypeNoOverride = 0 // GetStatusActionConfig 返回表示不修改缺省配置 - TimerTypeByPass = 1 - TimerTypeBaseNow = 2 - TimerTypeBaseStatusTime = 3 + TimerTypeNoOverride = 0 // GetStatusActionConfig 返回表示不修改缺省配置 + TimerTypeByPass = 1 + TimerTypeBaseNow = 2 + TimerTypeBaseStatusTime = 3 + TimerTypeBaseOrderCreatedAt = 4 ) type StatusActionParams struct { @@ -63,15 +70,37 @@ type StatusActionParams struct { TimeoutGap int // 以秒为单位的随机时间,0在GetStatusActionConfig返回时表示不修改缺省 } -func (s *StatusActionParams) GetRefTimeout(statusTime time.Time) (timeout time.Duration) { +const ( + WaybillFeeErrCodeCourierNotOpen = 1 //配送门店没有启用 + WaybillFeeErrCodeCourierNotSupported = 2 //配送门店不被系统支持 + WaybillFeeErrCodeCourierForbidden = 3 //配送门店内部禁用 + WaybillFeeErrCodeCourierOthers = 10 //其它错误 +) + +type WaybillFeeInfo struct { + ErrCode int `json:"errCode"` + ErrStr string `json:"errStr"` + RefDeliveryFee int64 `json:"refDeliveryFee"` + RefAddFee int64 `json:"refAddFee"` + DeliveryFee int64 `json:"deliveryFee"` + TimeoutSecond int `json:"timeoutSecond"` // 系统会自动发运单的倒计时 + Waybill *model.Waybill `json:"waybill"` +} + +func (s *StatusActionParams) GetRefTimeout(statusTime time.Time, orderCreatedAt time.Time) (timeout time.Duration) { switch s.TimerType { case TimerTypeBaseNow: timeout = s.Timeout case TimerTypeBaseStatusTime: timeout = statusTime.Sub(time.Now()) + s.Timeout + case TimerTypeBaseOrderCreatedAt: + timeout = orderCreatedAt.Sub(time.Now()) + s.Timeout default: timeout = 0 } + if timeout < 0 { + timeout = 0 + } return timeout } @@ -100,8 +129,8 @@ var ( type IOrderManager interface { SaveOrder(order *model.GoodsOrder, isAdjust bool, db *dao.DaoDB) (isDuplicated bool, err error) - OnOrderNew(order *model.GoodsOrder, msgVendorStatus string) (err error) - OnOrderAdjust(order *model.GoodsOrder, msgVendorStatus string) (err error) + OnOrderNew(order *model.GoodsOrder, orderStatus *model.OrderStatus) (err error) + OnOrderAdjust(order *model.GoodsOrder, orderStatus *model.OrderStatus) (err error) OnOrderStatusChanged(orderStatus *model.OrderStatus) (err error) OnOrderMsg(order *model.GoodsOrder, vendorStatus, remark string) (err error) @@ -113,14 +142,25 @@ type IOrderManager interface { LoadOrderFinancial(vendorOrderID string, vendorID int) (order *model.OrderFinancial, err error) LoadOrderFinancial2(vendorOrderID2 string, vendorID int) (order *model.OrderFinancial, err error) - UpdateWaybillVendorID(bill *model.Waybill, revertStatus bool) (err error) - UpdateOrderStatusAndFlag(order *model.GoodsOrder) (err error) + UpdateOrderStatusAndDeliveryFlag(order *model.GoodsOrder) (err error) + UpdateOrderFields(order *model.GoodsOrder, fieldList []string) (err error) LoadWaybill(vendorWaybillID string, waybillVendorID int) (bill *model.Waybill, err error) OnOrderComments(orderCommentList []*model.OrderComment) (err error) SaveOrderFinancialInfo(order *model.OrderFinancial, operation string) (err error) SaveAfsOrderFinancialInfo(afsOrder *model.AfsOrder) (err error) + + GetOrderWaybillInfo(ctx *jxcontext.Context, vendorOrderID string, vendorID int, isNotEnded bool) (bills []*model.Waybill, err error) + + // afs order + OnAfsOrderAdjust(afsOrder *model.AfsOrder, orderStatus *model.OrderStatus) (err error) + OnAfsOrderNew(afsOrder *model.AfsOrder, orderStatus *model.OrderStatus) (err error) + OnAfsOrderStatusChanged(orderStatus *model.OrderStatus) (err error) + LoadAfsOrder(vendorAfsOrderID string, vendorID int) (afsOrder *model.AfsOrder, err error) + UpdateAfsOrderFields(afsOrder *model.AfsOrder, fieldList []string) (err error) + + GetStatusDuplicatedCount(status *model.OrderStatus) (duplicatedCount int) } type IStoreManager interface { @@ -132,6 +172,7 @@ type IStoreManager interface { // 所有非以Sync,Refresh开头的函数不用自己清理sync_status标记(VendorSync统一处理) type IPurchasePlatformHandler interface { + IPurchasePlatformPromotionHandler GetVendorID() int GetStatusFromVendorStatus(vendorStatus string) int @@ -167,6 +208,16 @@ type IPurchasePlatformHandler interface { // order.Skus要包含原始订单中的Sku信息,removedSkuList中是要移除的Sku信息 AdjustOrder(ctx *jxcontext.Context, order *model.GoodsOrder, removedSkuList []*model.OrderSku, reason string) (err error) + // 售后 + // // 发起全款退款 + // RefundOrder(ctx *jxcontext.Context, order *model.GoodsOrder, reason string) (err error) + // // 发起部分退款 + // PartRefundOrder(ctx *jxcontext.Context, order *model.GoodsOrder, refundSkuList []*model.OrderSku, reason string) (err error) + // 审核售后单申请 + AgreeOrRefuseRefund(ctx *jxcontext.Context, order *model.AfsOrder, approveType int, reason string) (err error) + // // 确认收到退货 + ConfirmReceivedReturnGoods(ctx *jxcontext.Context, order *model.AfsOrder) (err error) + //////// // Store ReadStore(vendorStoreID string) (store *model.Store, err error) @@ -184,6 +235,10 @@ type IPurchasePlatformHandler interface { UploadImg(ctx *jxcontext.Context, imgURL string, imgData []byte, imgName string) (imgHint string, err error) GetStoreStatus(ctx *jxcontext.Context, vendorStoreID string) (storeStatus int, err error) GetVendorCategories(ctx *jxcontext.Context) (vendorCats []*model.SkuVendorCategory, err error) + + // todo 如下两个函数需要合并一下s + GetStoresSku(ctx *jxcontext.Context, parentTask tasksch.ITask, storeIDs []int) (storeSkuList []*model.StoreSkuBind, err error) + GetStoreSkusInfo(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, vendorStoreID string, inStoreSkuList []*BareStoreSkuInfo) (outStoreSkuList []*BareStoreSkuInfo, err error) } // db *dao.DaoDB, @@ -219,6 +274,7 @@ type IDeliveryPlatformHandler interface { CancelWaybill(bill *model.Waybill, cancelReasonID int, cancelReason string) (err error) GetVendorID() int + GetWaybillFee(order *model.GoodsOrder) (deliveryFeeInfo *WaybillFeeInfo, err error) } type IPrinterHandler interface { diff --git a/business/partner/partner_act.go b/business/partner/partner_act.go new file mode 100644 index 000000000..458dd183b --- /dev/null +++ b/business/partner/partner_act.go @@ -0,0 +1,24 @@ +package partner + +import ( + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" + "git.rosy.net.cn/jx-callback/business/model" +) + +type IPurchasePlatformPromotionHandler interface { + // 如果是单品级活动,actOrderRules为空 + // 如果是订单级活动,actStoreSku可以为空(表示不限制SKU) + CreateAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actOrderRules []*model.ActOrderRule, actStoreMap []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) + UpdateAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actOrderRules []*model.ActOrderRule, actStoreMap2Remove, actStoreMap2Add, actStoreMap2Update []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) + // 取消整个京西活动 + CancelAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actStoreMap []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) +} + +func ActStoreSku2Map(actStoreSku []*model.ActStoreSku2) (actStoreSkuMap map[int][]*model.ActStoreSku2) { + actStoreSkuMap = make(map[int][]*model.ActStoreSku2) + for _, storeSku := range actStoreSku { + actStoreSkuMap[storeSku.StoreID] = append(actStoreSkuMap[storeSku.StoreID], storeSku) + } + return actStoreSkuMap +} diff --git a/business/partner/partner_err.go b/business/partner/partner_err.go new file mode 100644 index 000000000..e4c1e6504 --- /dev/null +++ b/business/partner/partner_err.go @@ -0,0 +1,89 @@ +package partner + +import ( + "fmt" + + "git.rosy.net.cn/jx-callback/business/model" +) + +const ( + ErrCodeUnknown = 1 + ErrCodeChangePriceFailed = 100 +) + +type ErrorWithCode struct { + errMsg string + intCode int + vendorID int + storeID int + skuID int +} + +func NewErrorCode(errMsg string, code, vendorID int) *ErrorWithCode { + retVal := &ErrorWithCode{ + errMsg: errMsg, + intCode: code, + vendorID: vendorID, + } + return retVal +} + +func (e *ErrorWithCode) SetStoreID(storeID int) { + e.storeID = storeID +} + +func (e *ErrorWithCode) SetSkuID(skuID int) { + e.skuID = skuID +} + +func (e *ErrorWithCode) Error() string { + return fmt.Sprintf("平台:%s, code:%d, %s", model.VendorChineseNames[e.VendorID()], e.intCode, e.errMsg) +} + +func (e *ErrorWithCode) String() string { + return e.Error() +} + +func (e *ErrorWithCode) Code() int { + return e.intCode +} + +func (e *ErrorWithCode) ErrMsg() string { + return e.errMsg +} + +func (e *ErrorWithCode) VendorID() int { + return e.vendorID +} + +func (e *ErrorWithCode) StoreID() int { + return e.storeID +} + +func (e *ErrorWithCode) SkuID() int { + return e.skuID +} + +func IsErrChangePriceFailed(err error) *ErrorWithCode { + if vendorErr, ok := err.(*ErrorWithCode); ok && vendorErr.Code() == ErrCodeChangePriceFailed { + return vendorErr + } + return nil +} + +func IsErrVendorError(err error) *ErrorWithCode { + if vendorErr, ok := err.(*ErrorWithCode); ok { + return vendorErr + } + return nil +} + +func AddVendorInfo2Err(inErr error, vendorID int) (outErr error) { + outErr = inErr + if inErr != nil { + if IsErrVendorError(inErr) == nil { + outErr = NewErrorCode(inErr.Error(), ErrCodeUnknown, vendorID) + } + } + return outErr +} diff --git a/business/partner/partner_store_sku.go b/business/partner/partner_store_sku.go new file mode 100644 index 000000000..cad008e9e --- /dev/null +++ b/business/partner/partner_store_sku.go @@ -0,0 +1,82 @@ +package partner + +import ( + "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "git.rosy.net.cn/jx-callback/business/model/dao" +) + +const ( + FuncCreateStoreSkus = 1 + FuncDeleteStoreSkus = 2 + FuncUpdateStoreSkusStatus = 3 + FuncUpdateStoreSkusPrice = 4 + FuncUpdateStoreSkus = 5 +) + +type BareStoreSkuInfo struct { + SkuID int `json:"skuID,omitempty"` + VendorSkuID string `json:"vendorSkuID,omitempty"` + NameID int `json:"nameID,omitempty"` + VendorNameID string `json:"vendorNameID,omitempty"` + + Price int64 `json:"price,omitempty"` + Status int `json:"status,omitempty"` +} + +type BareStoreSkuInfoList []*BareStoreSkuInfo + +func (l BareStoreSkuInfoList) GetVendorSkuIDList() (vendorSkuIDList []string) { + for _, v := range l { + if !dao.IsVendorThingIDEmpty(v.VendorSkuID) { + vendorSkuIDList = append(vendorSkuIDList, v.VendorSkuID) + } + } + return vendorSkuIDList +} + +func (l BareStoreSkuInfoList) GetVendorSkuIDIntList() (vendorSkuIDIntList []int64) { + for _, v := range l { + if !dao.IsVendorThingIDEmpty(v.VendorSkuID) { + vendorSkuIDIntList = append(vendorSkuIDIntList, utils.Str2Int64(v.VendorSkuID)) + } + } + return vendorSkuIDIntList +} + +func (l BareStoreSkuInfoList) GetSkuIDList() (skuIDList []int) { + for k, v := range l { + if v.SkuID > 0 { + skuIDList[k] = v.SkuID + } + } + return skuIDList +} + +type BareCategoryInfo struct { + VendorCatID string `json:"vendorCatID"` + + Level int `json:"level"` + Name string `json:"name"` + Seq int `json:"seq,omitempty"` + Children []*BareCategoryInfo `json:"children,omitempty"` +} + +type IPurchasePlatformStoreSkuHandler interface { + GetStoreSkusBatchSize(funcID int) int + + CreateStoreSkus(ctx *jxcontext.Context, vendorStoreID string, storeSkuList []*dao.StoreSkuSyncInfo) (err error) + DeleteStoreSkus(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*BareStoreSkuInfo) (err error) + UpdateStoreSkusStatus(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*BareStoreSkuInfo) (err error) + UpdateStoreSkusPrice(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*BareStoreSkuInfo) (err error) +} + +type ISingleStoreStoreSkuHandler interface { + IPurchasePlatformStoreSkuHandler + + ReadStoreCategory(ctx *jxcontext.Context, vendorStoreID string) (cats []*BareCategoryInfo, err error) + CreateStoreSkuCategory(ctx *jxcontext.Context, vendorStoreID string, storeCat *dao.SkuStoreCatInfo) (err error) + UpdateStoreSkuCategory(ctx *jxcontext.Context, vendorStoreID string, storeCat *dao.SkuStoreCatInfo) (err error) + DeleteStoreSkuCategory(ctx *jxcontext.Context, vendorStoreID, vendorCatID string) (err error) + UpdateStoreSkus(ctx *jxcontext.Context, vendorStoreID string, storeSkuList []*dao.StoreSkuSyncInfo) (err error) +} diff --git a/business/partner/printer/feie/feie.go b/business/partner/printer/feie/feie.go index 3764bd373..1fe7cfd55 100644 --- a/business/partner/printer/feie/feie.go +++ b/business/partner/printer/feie/feie.go @@ -33,7 +33,7 @@ func (c *PrinterHandler) getOrderContent(order *model.GoodsOrder, storeTel strin expectedDeliveryTime = order.OrderCreatedAt.Add(1 * time.Hour) } orderFmt := ` -京西菜市

+%s

手机买菜上京西
极速到家送惊喜
--------------------------------
@@ -59,6 +59,7 @@ func (c *PrinterHandler) getOrderContent(order *model.GoodsOrder, storeTel strin 品名 数量 单价 小计
--------------------------------
` orderParams := []interface{}{ + globals.StoreName, utils.Time2Str(order.OrderCreatedAt), utils.Time2Str(expectedDeliveryTime), order.VendorOrderID, @@ -84,7 +85,7 @@ func (c *PrinterHandler) getOrderContent(order *model.GoodsOrder, storeTel strin 商品质量问题请联系:
%s:%s


-更多信息请关注官方微信: 京西菜市
+更多信息请关注官方微信: %s



--------------------------------
@@ -92,7 +93,7 @@ func (c *PrinterHandler) getOrderContent(order *model.GoodsOrder, storeTel strin

` // http://weixin.qq.com/r/tkkDGzTERmk5rXB49xyk - orderParams = append(orderParams, order.SkuCount, order.GoodsCount, order.StoreName, storeTel) + orderParams = append(orderParams, order.SkuCount, order.GoodsCount, order.StoreName, storeTel, globals.StoreName) return fmt.Sprintf(strings.Replace(orderFmt, "\n", "", -1), orderParams...) } diff --git a/business/partner/printer/xiaowm/xiaowm.go b/business/partner/printer/xiaowm/xiaowm.go index 8bf3becd5..06c7c71c2 100644 --- a/business/partner/printer/xiaowm/xiaowm.go +++ b/business/partner/printer/xiaowm/xiaowm.go @@ -34,7 +34,7 @@ func (c *PrinterHandler) getOrderContent(order *model.GoodsOrder, storeTel strin expectedDeliveryTime = order.OrderCreatedAt.Add(1 * time.Hour) } orderFmt := ` - 京西菜市** + %s** 手机买菜上京西* 极速到家送惊喜* ------------------------------* @@ -58,6 +58,7 @@ func (c *PrinterHandler) getOrderContent(order *model.GoodsOrder, storeTel strin --------------------------------* ` orderParams := []interface{}{ + globals.StoreName, utils.Time2Str(order.OrderCreatedAt), utils.Time2Str(expectedDeliveryTime), order.VendorOrderID, @@ -82,16 +83,80 @@ func (c *PrinterHandler) getOrderContent(order *model.GoodsOrder, storeTel strin 商品质量问题请联系:* %s:%s* * -更多信息请关注官方微信: 京西菜市* +更多信息请关注官方微信: %s* -------------------------------- -------------------------------- ** ` // http://weixin.qq.com/r/tkkDGzTERmk5rXB49xyk - orderParams = append(orderParams, order.SkuCount, order.GoodsCount, order.StoreName, storeTel) + orderParams = append(orderParams, order.SkuCount, order.GoodsCount, order.StoreName, storeTel, globals.StoreName) return fmt.Sprintf(strings.Replace(orderFmt, "\n", "", -1), escapeString4Printer(orderParams)...) } +func (c *PrinterHandler) getOrderContent2(order *model.GoodsOrder, storeTel string) (content string) { + expectedDeliveryTime := order.ExpectedDeliveredTime + if utils.IsTimeZero(expectedDeliveryTime) { + expectedDeliveryTime = order.OrderCreatedAt.Add(1 * time.Hour) + } + orderFmt := ` +|7 %s +|5 手机买菜上京西 +|5 极速到家送惊喜 +|5-------------------------------- +|5下单时间: %s +|5预计送达: %s +|5订单编号: %s +|5 +|7%s\#%d +|5 +|2%s +|5客户: %s +|5电话: %s +|5地址: %s +|5 +|5客户备注: +|7%s +|5 +|6实际支付: %s +|5 +|5商品明细: +|5品名 数量 单价 小计 +|5-------------------------------- +` + orderParams := []interface{}{ + globals.StoreName, + utils.Time2Str(order.OrderCreatedAt), + utils.Time2Str(expectedDeliveryTime), + order.VendorOrderID, + jxutils.GetVendorName(order.VendorID), + order.OrderSeq, + order.VendorOrderID, + order.ConsigneeName, + order.ConsigneeMobile, + order.ConsigneeAddress, + order.BuyerComment, + jxutils.IntPrice2StandardCurrencyString(order.ActualPayPrice), + } + for _, sku := range order.Skus { + orderFmt += `|5%s` + orderFmt += `|5%8s%10s%10s` + orderParams = append(orderParams, sku.SkuName, "x"+utils.Int2Str(sku.Count), jxutils.IntPrice2StandardCurrencyString(sku.SalePrice), jxutils.IntPrice2StandardCurrencyString(sku.SalePrice*int64(sku.Count))) + } + orderFmt += ` +|5 +|6共%d种%d件商品 +|5-------------------------------- +|5商品质量问题请联系: +|5%s:%s +|5 +|5更多信息请关注官方微信: %s +|5-------------------------------- +|5-------------------------------- +` + orderParams = append(orderParams, order.SkuCount, order.GoodsCount, order.StoreName, storeTel, globals.StoreName) + return fmt.Sprintf(orderFmt, escapeString4Printer(orderParams)...) +} + func (c *PrinterHandler) GetVendorID() int { return model.VendorIDXiaoWM } @@ -131,10 +196,20 @@ func (c *PrinterHandler) GetPrinterStatus(ctx *jxcontext.Context, printerNumber, func (c *PrinterHandler) PrintOrder(ctx *jxcontext.Context, store *model.Store, order *model.GoodsOrder) (printerStatus *partner.PrinterStatus, err error) { globals.SugarLogger.Debugf("xiaowm PrintOrderByOrder orderID:%s", order.VendorOrderID) - content := c.getOrderContent(order, store.Tel1) + var content string + if isV500(store.PrinterSN) { + content = c.getOrderContent2(order, store.Tel1) + } else { + content = c.getOrderContent(order, store.Tel1) + } return c.PrintMsg(ctx, store.PrinterSN, store.PrinterKey, order.VendorOrderID, content) } +func isV500(printerNo string) bool { + printerNoNum := utils.Str2Int64WithDefault("1"+printerNo, 0) + return printerNoNum > 1000000000 +} + func (c *PrinterHandler) RegisterPrinter(ctx *jxcontext.Context, printerNumber, notUsed, printerName string) (newID1, printerToken string, err error) { globals.SugarLogger.Debugf("xiaowm RegisterPrinter printerNumber:%s", printerNumber) if printerNumber == "" { //len(printerNumber) != len("7JizmSyiXNzkggaqU") { diff --git a/business/partner/printer/yilianyun/yilianyun.go b/business/partner/printer/yilianyun/yilianyun.go index b0c6b0db7..17d76ecf5 100644 --- a/business/partner/printer/yilianyun/yilianyun.go +++ b/business/partner/printer/yilianyun/yilianyun.go @@ -33,7 +33,7 @@ func (c *PrinterHandler) getOrderContent(order *model.GoodsOrder, storeTel strin expectedDeliveryTime = order.OrderCreatedAt.Add(1 * time.Hour) } orderFmt := ` -
京西菜市
\n\n +
%s
\n\n
手机买菜上京西
极速到家送惊喜
\n -------------------------------- @@ -57,6 +57,7 @@ func (c *PrinterHandler) getOrderContent(order *model.GoodsOrder, storeTel strin 品名 数量 单价 小计\n --------------------------------\n` orderParams := []interface{}{ + globals.StoreName, utils.Time2Str(order.OrderCreatedAt), utils.Time2Str(expectedDeliveryTime), order.VendorOrderID, @@ -81,12 +82,12 @@ func (c *PrinterHandler) getOrderContent(order *model.GoodsOrder, storeTel strin --------------------------------\n
商品质量问题请联系:
%s:%s
\n -更多信息请关注官方微信: 京西菜市\n +更多信息请关注官方微信: %s\n --------------------------------\n --------------------------------\n ` // http://weixin.qq.com/r/tkkDGzTERmk5rXB49xyk - orderParams = append(orderParams, order.SkuCount, order.GoodsCount, order.StoreName, storeTel) + orderParams = append(orderParams, order.SkuCount, order.GoodsCount, order.StoreName, storeTel, globals.StoreName) return strings.Replace(fmt.Sprintf(strings.Replace(orderFmt, "\n", "", -1), orderParams...), "\\n", "\r\n", -1) } diff --git a/business/partner/printer/zhongwu/zhongwu.go b/business/partner/printer/zhongwu/zhongwu.go index 612bc077d..4558bcbaa 100644 --- a/business/partner/printer/zhongwu/zhongwu.go +++ b/business/partner/printer/zhongwu/zhongwu.go @@ -34,7 +34,7 @@ func (c *PrinterHandler) getOrderContent(order *model.GoodsOrder, storeTel strin expectedDeliveryTime = order.OrderCreatedAt.Add(1 * time.Hour) } orderFmt := ` -京西菜市 +%s 手机买菜上京西 极速到家送惊喜 ******************************** @@ -58,6 +58,7 @@ func (c *PrinterHandler) getOrderContent(order *model.GoodsOrder, storeTel strin 品名 数量 单价 小计 ********************************` orderParams := []interface{}{ + globals.StoreName, utils.Time2Str(order.OrderCreatedAt), utils.Time2Str(expectedDeliveryTime), order.VendorOrderID, @@ -82,12 +83,12 @@ func (c *PrinterHandler) getOrderContent(order *model.GoodsOrder, storeTel strin ********************************

商品质量问题请联系:

%s:%s

-更多信息请关注官方微信: 京西菜市 +更多信息请关注官方微信: %s ******************************** ******************************** ` // http://weixin.qq.com/r/tkkDGzTERmk5rXB49xyk - orderParams = append(orderParams, order.SkuCount, order.GoodsCount, order.StoreName, storeTel) + orderParams = append(orderParams, order.SkuCount, order.GoodsCount, order.StoreName, storeTel, globals.StoreName) return fmt.Sprintf(strings.Replace(orderFmt, "\n", "", -1), orderParams...) } diff --git a/business/partner/purchase/ebai/act.go b/business/partner/purchase/ebai/act.go new file mode 100644 index 000000000..f0c89bd08 --- /dev/null +++ b/business/partner/purchase/ebai/act.go @@ -0,0 +1,186 @@ +package ebai + +import ( + "git.rosy.net.cn/baseapi/platformapi/ebaiapi" + "git.rosy.net.cn/baseapi/utils" + "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/tasksch" + "git.rosy.net.cn/jx-callback/business/model" + "git.rosy.net.cn/jx-callback/business/partner" + "git.rosy.net.cn/jx-callback/globals" + "git.rosy.net.cn/jx-callback/globals/api" +) + +func actType2Ebai(actType int) int { + if actType < model.ActOrderBegin { + return ebaiapi.ActivityTypeDirectDown + } + return ebaiapi.ActivityTypeFullDiscount +} + +func actOrderRules2Ebai(actOrderRules []*model.ActOrderRule) (ebaiRules []*ebaiapi.ActivityRule) { + for _, v := range actOrderRules { + ebaiRules = append(ebaiRules, &ebaiapi.ActivityRule{ + Accords: int(jxutils.IntPrice2Standard(v.SalePrice)), + Sale: int(jxutils.IntPrice2Standard(v.DeductPrice)), + }) + } + return ebaiRules +} + +func actStoreSu2Ebai4Add(oneStoreActSku []*model.ActStoreSku2) (skus []*ebaiapi.ActivitySkuInfo4Add) { + for _, v := range oneStoreActSku { + if model.IsSyncStatusNeedCreate(v.SyncStatus) { + skus = append(skus, &ebaiapi.ActivitySkuInfo4Add{ + SkuID: v.VendorSkuID, + SpecialPrice: v.ActPrice, + }) + } + } + return skus +} + +func actStoreSu2Ebai4Update(oneStoreActSku []*model.ActStoreSku2) (skus []*ebaiapi.ActivitySkuInfo4Update) { + for _, v := range oneStoreActSku { + if model.IsSyncStatusNeedUpdate(v.SyncStatus) { + skus = append(skus, &ebaiapi.ActivitySkuInfo4Update{ + ShopID: utils.Int2Str(v.StoreID), + SkuID: v.VendorSkuID, + SpecialPrice: v.ActPrice, + }) + } + } + return skus +} + +func actStoreSu2Ebai4Delete(oneStoreActSku []*model.ActStoreSku2) (skus []string) { + for _, v := range oneStoreActSku { + if model.IsSyncStatusNeedDelete(v.SyncStatus) { + skus = append(skus, v.VendorSkuID) + } + } + return skus +} + +func act2EbaiActivity(act *model.Act2, actOrderRules []*model.ActOrderRule) (activity *ebaiapi.ActivityInfo) { + activity = &ebaiapi.ActivityInfo{ + ActivityName: act.Name, + ActivityType: actType2Ebai(act.Type), + StartTime: act.BeginAt.Unix(), + EndTime: act.EndAt.Unix(), + ActivityDesc: act.Advertising, + ShowCategory: act.Name, + PromotionSkuDesc: act.Name, + Rule: actOrderRules2Ebai(actOrderRules), + } + return activity +} + +func createOneShopAct(shopID string, activity *ebaiapi.ActivityInfo, oneStoreActSku []*model.ActStoreSku2) (ebaiActIDStr string, err error) { + if globals.EnableEbaiStoreWrite { + ebaiActID, err := api.EbaiAPI.ActivityCreate(shopID, 0, 0, activity) + if err == nil { + ebaiActIDStr = utils.Int64ToStr(ebaiActID) + _, err = ActivitySkuAddBatch(ebaiActID, shopID, 0, activity.ActivityType, actStoreSu2Ebai4Add(oneStoreActSku), false) + } + } else { + ebaiActIDStr = utils.Int64ToStr(jxutils.GenFakeID()) + } + return ebaiActIDStr, err +} + +func ActivitySkuAddBatch(activityID int64, shopID string, baiduShopID int64, activityType int, skuList []*ebaiapi.ActivitySkuInfo4Add, isSkuIDCustom bool) (successIDs []string, err error) { + return api.EbaiAPI.ActivitySkuAddBatch(activityID, shopID, baiduShopID, activityType, skuList, isSkuIDCustom) +} + +func ActivitySkuDeleteBatch(activityID int64, shopID string, baiduShopID int64, skuIDs []string, isSkuIDCustom bool) (successIDs []string, err error) { + return api.EbaiAPI.ActivitySkuDeleteBatch(activityID, shopID, baiduShopID, skuIDs, isSkuIDCustom) +} + +func ActivitySkuUpdateBatch(activityID int64, actSkuInfoList []*ebaiapi.ActivitySkuInfo4Update) (faildInfoList []string, err error) { + return api.EbaiAPI.ActivitySkuUpdateBatch(activityID, actSkuInfoList) +} + +func (c *PurchaseHandler) CreateAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actOrderRules []*model.ActOrderRule, actStoreMap []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + activity := act2EbaiActivity(act, actOrderRules) + if act.Type < model.ActOrderBegin { + actStoreSkuMap := partner.ActStoreSku2Map(actStoreSku) + task := tasksch.NewParallelTask("ebai CreateAct", nil, ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + store := batchItemList[0].(*model.ActStore2) + store.VendorActID, err = createOneShopAct(utils.Int2Str(store.StoreID), activity, actStoreSkuMap[store.StoreID]) + return nil, err + }, actStoreMap) + tasksch.HandleTask(task, parentTask, true).Run() + _, err = task.GetResult(0) + } else { + + } + return err +} + +func (c *PurchaseHandler) UpdateAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actOrderRules []*model.ActOrderRule, actStoreMap2Remove, actStoreMap2Add, actStoreMap2Update []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + if act.Type < model.ActOrderBegin { + actStoreSkuMap := partner.ActStoreSku2Map(actStoreSku) + if len(actStoreMap2Remove) > 0 { + if err = c.CancelAct(ctx, parentTask, act, actStoreMap2Remove, nil); err != nil { + return err + } + for _, v := range actStoreMap2Remove { + delete(actStoreSkuMap, v.StoreID) + } + } + if len(actStoreMap2Add) > 0 { + if err = c.CreateAct(ctx, parentTask, act, actOrderRules, actStoreMap2Add, actStoreSku); err != nil { + return err + } + for _, v := range actStoreMap2Add { + delete(actStoreSkuMap, v.StoreID) + } + } + task := tasksch.NewParallelTask("ebai UpdateAct", nil, ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + v := batchItemList[0].(*model.ActStore2) + if storeSkus := actStoreSkuMap[v.StoreID]; storeSkus != nil { + if list := actStoreSu2Ebai4Delete(storeSkus); len(list) > 0 { + if _, err = ActivitySkuDeleteBatch(utils.Str2Int64(v.VendorActID), utils.Int2Str(v.StoreID), 0, list, false); err != nil { + return nil, err + } + } + if list := actStoreSu2Ebai4Add(storeSkus); len(list) > 0 { + if _, err = ActivitySkuAddBatch(utils.Str2Int64(v.VendorActID), utils.Int2Str(v.StoreID), 0, actType2Ebai(act.Type), list, false); err != nil { + return nil, err + } + } + if list := actStoreSu2Ebai4Update(storeSkus); len(list) > 0 { + if _, err = ActivitySkuUpdateBatch(utils.Str2Int64(v.VendorActID), list); err != nil { + return nil, err + } + } + } + return nil, err + }, actStoreMap2Update) + tasksch.HandleTask(task, parentTask, true).Run() + _, err = task.GetResult(0) + } else { + + } + return err +} + +func (c *PurchaseHandler) CancelAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actStoreMap []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + if act.Type < model.ActOrderBegin { + task := tasksch.NewParallelTask("ebai DeleteAct", nil, ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + v := batchItemList[0].(*model.ActStore2) + if globals.EnableEbaiStoreWrite { + err = api.EbaiAPI.ActivityDisable(utils.Str2Int64(v.VendorActID), utils.Int2Str(v.StoreID), 0, 0) + } + return nil, err + }, actStoreMap) + tasksch.HandleTask(task, parentTask, true).Run() + _, err = task.GetResult(0) + } + return err +} diff --git a/business/partner/purchase/ebai/callback.go b/business/partner/purchase/ebai/callback.go index aa283022d..b041d5165 100644 --- a/business/partner/purchase/ebai/callback.go +++ b/business/partner/purchase/ebai/callback.go @@ -19,10 +19,8 @@ func OnCallbackMsg(msg *ebaiapi.CallbackMsg) (response *ebaiapi.CallbackResponse } }, jxutils.ComposeUniversalOrderID(orderID, model.VendorIDEBAI)) } - if msg.Cmd == ebaiapi.CmdOrderPartRefund || msg.Cmd == ebaiapi.CmdOrderUserCancel || msg.Cmd == ebaiapi.CmdOrderDeliveryStatus { - utils.CallFuncAsync(func() { - OnFinancialMsg(msg) - }) + if /*msg.Cmd == ebaiapi.CmdOrderPartRefund || msg.Cmd == ebaiapi.CmdOrderUserCancel || */ msg.Cmd == ebaiapi.CmdOrderDeliveryStatus { + response = CurPurchaseHandler.OnFinancialMsg(msg) } else if msg.Cmd == ebaiapi.CmdShopMsgPush { response = CurPurchaseHandler.onShopMsgPush(msg) } @@ -31,7 +29,11 @@ func OnCallbackMsg(msg *ebaiapi.CallbackMsg) (response *ebaiapi.CallbackResponse } func GetOrderIDFromMsg(msg *ebaiapi.CallbackMsg) string { - if orderID := msg.Body["order_id"]; orderID != nil { + return GetOrderIDFromMap(msg.Body) +} + +func GetOrderIDFromMap(orderMap map[string]interface{}) string { + if orderID := orderMap["order_id"]; orderID != nil { if tryOrderID, ok := orderID.(string); ok { return tryOrderID } diff --git a/business/partner/purchase/ebai/ebai.go b/business/partner/purchase/ebai/ebai.go index 6dabb5db1..4990e962c 100644 --- a/business/partner/purchase/ebai/ebai.go +++ b/business/partner/purchase/ebai/ebai.go @@ -27,7 +27,7 @@ func init() { func EbaiBusStatus2JxStatus(ebaiStatus int) int { if ebaiStatus == ebaiapi.ShopBusStatusHaveRest || ebaiStatus == ebaiapi.ShopBusStatusSuspended { - return model.StoreStatusClosed + return model.StoreStatusHaveRest } return model.StoreStatusOpened } diff --git a/business/partner/purchase/ebai/financial.go b/business/partner/purchase/ebai/financial.go index 12a986ddc..66c13c6d6 100644 --- a/business/partner/purchase/ebai/financial.go +++ b/business/partner/purchase/ebai/financial.go @@ -9,25 +9,33 @@ import ( "git.rosy.net.cn/jx-callback/globals/api" ) +func (p *PurchaseHandler) OnFinancialMsg(msg *ebaiapi.CallbackMsg) (response *ebaiapi.CallbackResponse) { + utils.CallFuncAsync(func() { + response = p.onFinancialMsg(msg) + }) + return response +} + // 存储饿百退款订单结账信息 -func OnFinancialMsg(msg *ebaiapi.CallbackMsg) (err error) { +func (p *PurchaseHandler) onFinancialMsg(msg *ebaiapi.CallbackMsg) (response *ebaiapi.CallbackResponse) { + var err error if msg.Cmd == ebaiapi.CmdOrderPartRefund { // 部分退款处理 - if utils.Int64ToStr(utils.MustInterface2Int64(msg.Body["status"])) == ebaiapi.OrderPartRefundSuccess { + if int(utils.MustInterface2Int64(msg.Body["status"])) == ebaiapi.OrderPartRefundSuccess { // 获取到部分退款订单id - afsOrderID := utils.Interface2String(msg.Body["order_id"]) + afsOrderID := GetOrderIDFromMsg(msg) // 处理部分退款信息 - orderData, err2 := api.EbaiAPI.OrderPartrefundGet(afsOrderID) + orderData, err2 := api.EbaiAPI.OrderPartRefundGet(afsOrderID) if err = err2; err == nil { afsOrder := CurPurchaseHandler.AfsOrderDetail2Financial(orderData) err = partner.CurOrderManager.SaveAfsOrderFinancialInfo(afsOrder) } } } else if msg.Cmd == ebaiapi.CmdOrderUserCancel { // 全额退款处理 - messageType := utils.Int64ToStr(utils.MustInterface2Int64(msg.Body["type"])) - if utils.Int64ToStr(utils.MustInterface2Int64(msg.Body["cancel_type"])) == ebaiapi.OrderUserCancelTypeAfterSale && + messageType := int(utils.MustInterface2Int64(msg.Body["type"])) + if int(utils.MustInterface2Int64(msg.Body["cancel_type"])) == ebaiapi.OrderUserCancelTypeAfterSale && (messageType == ebaiapi.OrderUserCancelCSAgreed || messageType == ebaiapi.OrderUserCancelMerchantAgreed) { - globals.SugarLogger.Debug(utils.Interface2String(msg.Body["order_id"])) // 获得退款订单ID,去本地数据库拿?饿百消息推送只给了订单号,但是没有查询全额退款的接口,只有部分退款才可以查询 - afsOrderID := utils.Interface2String(msg.Body["order_id"]) + afsOrderID := GetOrderIDFromMsg(msg) + // 获得退款订单ID,去本地数据库拿?饿百消息推送只给了订单号,但是没有查询全额退款的接口,只有部分退款才可以查询 orderFinancial, err := partner.CurOrderManager.LoadOrderFinancial(afsOrderID, model.VendorIDEBAI) if err == nil { globals.SugarLogger.Debug(utils.Format4Output(orderFinancial, false)) @@ -45,15 +53,15 @@ func OnFinancialMsg(msg *ebaiapi.CallbackMsg) (err error) { } } } - return err + return api.EbaiAPI.Err2CallbackResponse(msg.Cmd, err, msg.Cmd) } func (p *PurchaseHandler) OrderFinancialDetail2Refund(orderFinancial *model.OrderFinancial, msg *ebaiapi.CallbackMsg) (afsOrder *model.AfsOrder) { afsOrder = &model.AfsOrder{ VendorID: model.VendorIDEBAI, - AfsOrderID: utils.Interface2String(msg.Body["order_id"]), - VendorOrderID: utils.Interface2String(msg.Body["order_id"]), - AfsCreateAt: utils.Timestamp2Time(msg.Timestamp), + AfsOrderID: GetOrderIDFromMsg(msg), + VendorOrderID: GetOrderIDFromMsg(msg), + AfsCreatedAt: utils.Timestamp2Time(msg.Timestamp), // BoxMoney: orderFinancial.BoxMoney, // 饿百的餐盒费已经拆分到单条Sku里面,退款时直接计算用户支付sku金额就好了 // SkuBoxMoney: orderFinancial.SkuBoxMoney, FreightUserMoney: orderFinancial.FreightMoney, @@ -78,10 +86,9 @@ func (p *PurchaseHandler) OrderFinancialDetail2Refund(orderFinancial *model.Orde } for _, sku := range orderFinancial.Skus { orderSkuFinancial := &model.OrderSkuFinancial{ - VendorID: sku.VendorID, - VendorOrderID: sku.VendorOrderID, - VendorOrderID2: sku.VendorOrderID2, - AfsOrderID: sku.VendorOrderID, + VendorID: sku.VendorID, + VendorOrderID: sku.VendorOrderID, + AfsOrderID: sku.VendorOrderID, // ConfirmTime: afsOrder.AfsCreateAt, VendorStoreID: afsOrder.VendorStoreID, StoreID: afsOrder.StoreID, @@ -106,8 +113,8 @@ func (p *PurchaseHandler) OrderFinancialDetail2Refund(orderFinancial *model.Orde func (p *PurchaseHandler) AfsOrderDetail2Financial(orderData map[string]interface{}) (afsOrder *model.AfsOrder) { afsOrder = &model.AfsOrder{ VendorID: model.VendorIDEBAI, - AfsOrderID: utils.Interface2String(orderData["order_id"]), - VendorOrderID: utils.Interface2String(orderData["order_id"]), + AfsOrderID: GetOrderIDFromMap(orderData), + VendorOrderID: GetOrderIDFromMap(orderData), } order, err := partner.CurOrderManager.LoadOrder(afsOrder.VendorOrderID, afsOrder.VendorID) if err == nil { @@ -125,7 +132,7 @@ func (p *PurchaseHandler) AfsOrderDetail2Financial(orderData map[string]interfac afsOrder.PmRefundMoney = orderFinancial.PmMoney - utils.MustInterface2Int64(orderData["commission"]) } else { // 此处应该报错 - globals.SugarLogger.Warnf("ebai AfsOrderDetail2Financial, afsOrderID:%s is not found from partner.CurOrderManager.LoadOrderFinancial", afsOrder.VendorOrderID) + // globals.SugarLogger.Warnf("ebai AfsOrderDetail2Financial, afsOrderID:%s is not found from partner.CurOrderManager.LoadOrderFinancial", afsOrder.VendorOrderID) err = nil } if orderData["refund_detail"] != nil { @@ -149,7 +156,7 @@ func (p *PurchaseHandler) AfsOrderDetail2Financial(orderData map[string]interfac afsOrder.PmSubsidyMoney += orderSkuFinancial.PmSubsidyMoney } if len(refundDetail) > 0 { - afsOrder.AfsCreateAt = getTimeFromInterface(refundDetail[0].(map[string]interface{})["apply_time"]) + afsOrder.AfsCreatedAt = getTimeFromInterface(refundDetail[0].(map[string]interface{})["apply_time"]) } else { globals.SugarLogger.Warnf("ebai AfsOrderDetail2Financial, orderID:%s have no refund_detail", afsOrder.VendorOrderID) } @@ -168,7 +175,7 @@ func (p *PurchaseHandler) OnOrderDetail(result map[string]interface{}, operation // func (p *PurchaseHandler) GetTrueEbaiOrder(result1 map[string]interface{}) (result2 map[string]interface{}) { // order := result1["order"].(map[string]interface{}) // if utils.MustInterface2Int64(order["down_flag"]) == 1 { -// result, err := api.EbaiAPI.OrderGet(utils.Interface2String(order["order_id"])) +// result, err := api.EbaiAPI.OrderGet(GetOrderIDFromMap(order)) // if err == nil { // return p.GetTrueEbaiOrder(result) // } @@ -181,7 +188,7 @@ func (p *PurchaseHandler) OrderDetail2Financial(result map[string]interface{}) ( VendorID: model.VendorIDEBAI, } order1 := result["order"].(map[string]interface{}) - orderFinancial.VendorOrderID = utils.Interface2String(order1["order_id"]) + orderFinancial.VendorOrderID = GetOrderIDFromMap(order1) orderFinancial.VendorOrderID2 = utils.Interface2String(order1["eleme_order_id"]) // orderFinancial.DeliveryConfirmTime = getTimeFromInterface(order1["finished_time"]) orderFinancial.TotalDiscountMoney = utils.MustInterface2Int64(order1["discount_fee"]) @@ -224,9 +231,8 @@ func (p *PurchaseHandler) OrderDetail2Financial(result map[string]interface{}) ( for _, y := range x.([]interface{}) { product := y.(map[string]interface{}) orderSkuFinancial := &model.OrderSkuFinancial{ - VendorID: orderFinancial.VendorID, - VendorOrderID: orderFinancial.VendorOrderID, - VendorOrderID2: orderFinancial.VendorOrderID2, + VendorID: orderFinancial.VendorID, + VendorOrderID: orderFinancial.VendorOrderID, // OrderFinancialID: orderFinancial.VendorOrderID, // ConfirmTime: getTimeFromInterface(order1["create_time"]), VendorStoreID: utils.Interface2String(shop["baidu_shop_id"]), diff --git a/business/partner/purchase/ebai/financial_test.go b/business/partner/purchase/ebai/financial_test.go index 723ec6e75..dd8b99cd5 100644 --- a/business/partner/purchase/ebai/financial_test.go +++ b/business/partner/purchase/ebai/financial_test.go @@ -27,7 +27,7 @@ func TestOnFinancialMsg(t *testing.T) { // msg.Body["type"] = json.Number("40") // msg.Body["cancel_type"] = json.Number("2") - res := OnFinancialMsg(msg) + res := CurPurchaseHandler.onAfsOrderMsg(msg) fmt.Println(res) } diff --git a/business/partner/purchase/ebai/order.go b/business/partner/purchase/ebai/order.go index cbfae3ebe..fb327a5de 100644 --- a/business/partner/purchase/ebai/order.go +++ b/business/partner/purchase/ebai/order.go @@ -4,6 +4,8 @@ import ( "math" "time" + "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" + "git.rosy.net.cn/baseapi/platformapi/autonavi" "git.rosy.net.cn/baseapi/platformapi/ebaiapi" "git.rosy.net.cn/baseapi/utils" @@ -19,7 +21,8 @@ import ( const ( // acceptOrderDelay = 180 * time.Second - pickupOrderDelay = 260 * time.Second + // pickupOrderDelay = 260 * time.Second + pickupOrderDelay = 1 * time.Second callDeliveryDelay = 10 * time.Minute callDeliveryDelayGap = 30 @@ -34,6 +37,7 @@ const ( var ( VendorStatus2StatusMap = map[string]int{ + ebaiapi.CmdOrderCreate: model.OrderStatusNew, ebaiapi.OrderStatusNew: model.OrderStatusNew, fakeAcceptOrder: model.OrderStatusAccepted, ebaiapi.OrderStatusAccepted: model.OrderStatusFinishedPickup, @@ -46,6 +50,11 @@ var ( fakeUserApplyCancel: model.OrderStatusApplyCancel, fakeUserUndoApplyCancel: model.OrderStatusUndoApplyCancel, } + + skuActTypeMap = map[string]int{ + ebaiapi.OrderSkuDiscountTypeZhe: 1, + ebaiapi.OrderSkuDiscountTypeReduce: 1, + } ) func (p *PurchaseHandler) GetStatusFromVendorStatus(vendorStatus string) int { @@ -68,6 +77,65 @@ func (p *PurchaseHandler) getOrder(vendorOrderID string) (order *model.GoodsOrde return order, result, err } +func (p *PurchaseHandler) GetOrder4PartRefund(vendorOrderID string) (order *model.GoodsOrder, err error) { + taskIDs := []int{1, 2} + var ( + err1, err2 error + result1, result2 map[string]interface{} + ) + task := tasksch.NewParallelTask("GetOrder4PartRefund", nil, jxcontext.AdminCtx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + taskID := batchItemList[0].(int) + if taskID == 1 { + result1, err1 = api.EbaiAPI.OrderGet(vendorOrderID) + } else if taskID == 2 { + result2, err2 = api.EbaiAPI.OrderPartRefundGet(vendorOrderID) + } + return nil, nil + }, taskIDs) + task.Run() + task.GetResult(0) + if err1 == nil { + order = p.Map2Order(result1) + if err2 == nil { + order.Skus = p.partRefund2OrderDetailSkuList(utils.Interface2String(result2["order_id"]), result2["order_detail"]) + order.ActualPayPrice = utils.MustInterface2Int64(result2["user_fee"]) + jxutils.RefreshOrderSkuRelated(order) + } else if err2Ext, ok := err2.(*utils.ErrorWithCode); !ok || err2Ext.IntCode() != ebaiapi.ErrOrderIsNotPartRefund { + err = err2 + } + } else { + err = err1 + } + return order, err +} + +func (p *PurchaseHandler) partRefund2OrderDetailSkuList(orderID string, orderDetail2 interface{}) (skuList []*model.OrderSku) { + orderDetail := orderDetail2.([]interface{}) + for _, product2 := range orderDetail { + product := product2.(map[string]interface{}) + skuName := product["name"].(string) + _, _, _, specUnit, _, specQuality := jxutils.SplitSkuName(skuName) + number := int(utils.MustInterface2Int64(product["number"])) + sku := &model.OrderSku{ + VendorOrderID: orderID, + VendorID: model.VendorIDEBAI, + Count: number, + SkuID: int(utils.Str2Int64WithDefault(utils.Interface2String(product[ebaiapi.KeyCustomSkuID]), 0)), + VendorSkuID: utils.Interface2String(product[ebaiapi.KeySkuID]), + SkuName: skuName, + // Weight: int(utils.Interface2Int64WithDefault(product["total_weight"], 0)) / number, // 退单这里的total_weight有BUG,这里的total_weight还是没有退单时的值 + VendorPrice: utils.MustInterface2Int64(product["product_price"]), + } + sku.SalePrice, _, sku.StoreSubName = getSkuSalePrice(product) + if sku.Weight == 0 { + sku.Weight = jxutils.FormatSkuWeight(specQuality, specUnit) // 订单信息里没有重量,只有名字里尝试找 + } + skuList = append(skuList, sku) + } + return skuList +} + func (p *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *model.GoodsOrder) { result := orderData shopMap := result["shop"].(map[string]interface{}) @@ -93,7 +161,7 @@ func (p *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *mo StatusTime: getTimeFromInterface(orderMap["create_time"]), OriginalData: string(utils.MustMarshal(result)), ActualPayPrice: utils.MustInterface2Int64(orderMap["user_fee"]), - Skus: []*model.OrderSku{}, + TotalShopMoney: utils.MustInterface2Int64(orderMap["shop_fee"]), } if utils.IsTimeZero(order.PickDeadline) && !utils.IsTimeZero(order.StatusTime) { order.PickDeadline = order.StatusTime.Add(pickupOrderDelay) // 饿百要求在5分钟内拣货,不然订单会被取消 @@ -134,33 +202,58 @@ func (p *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *mo product := product2.(map[string]interface{}) skuName := product["product_name"].(string) _, _, _, specUnit, _, specQuality := jxutils.SplitSkuName(skuName) + productAmount := int(utils.MustInterface2Int64(product["product_amount"])) sku := &model.OrderSku{ VendorOrderID: order.VendorOrderID, VendorID: model.VendorIDEBAI, - Count: int(utils.MustInterface2Int64(product["product_amount"])), + Count: productAmount, SkuID: int(utils.Str2Int64WithDefault(utils.Interface2String(product[ebaiapi.KeyCustomSkuID]), 0)), VendorSkuID: utils.Interface2String(product["baidu_product_id"]), SkuName: skuName, - Weight: jxutils.FormatSkuWeight(specQuality, specUnit), // 订单信息里没有重量,只有名字里尝试找 - SalePrice: utils.MustInterface2Int64(product["product_price"]), - // PromotionType: int(utils.MustInterface2Int64(product["promotionType"])), + Weight: int(utils.Interface2Int64WithDefault(product["total_weight"], 0)) / productAmount, + VendorPrice: utils.MustInterface2Int64(product["product_price"]), } + var baiduRate int64 + sku.SalePrice, baiduRate, sku.StoreSubName = getSkuSalePrice(product) + order.PmSubsidyMoney += baiduRate if sku.Weight == 0 { - sku.Weight = 222 // 如果名字里找不到缺省给半斤左右的一个特别值 + sku.Weight = jxutils.FormatSkuWeight(specQuality, specUnit) // 订单信息里没有重量,只有名字里尝试找 } // if product["isGift"].(bool) { // sku.SkuType = 1 // } order.Skus = append(order.Skus, sku) - order.SkuCount++ - order.GoodsCount += sku.Count - order.SalePrice += sku.SalePrice * int64(sku.Count) - order.Weight += sku.Weight * sku.Count } - // setOrederDetailFee(result, order) + jxutils.RefreshOrderSkuRelated(order) return order } +func getSkuSalePrice(product map[string]interface{}) (salePrice, baiduRate int64, vendorActType string) { + var product2 *ebaiapi.OrderProductInfo + if err := utils.Map2StructByJson(product, &product2, true); err != nil { + return utils.MustInterface2Int64(product["product_price"]), 0, "" + } + return getSkuSalePrice2(product2) +} + +func getSkuSalePrice2(product *ebaiapi.OrderProductInfo) (salePrice, baiduRate int64, vendorActType string) { + salePrice = int64(product.ProductPrice) + if product.ProductSubsidy != nil { + for _, v := range product.ProductSubsidy.DiscountDetail { + if skuActTypeMap[v.Type] == 1 { + skuCount := product.ProductAmount + if skuCount == 0 { + skuCount = product.Number + } + salePrice -= int64(math.Round(float64(v.BaiduRate+v.ShopRate) / float64(skuCount))) // 饿百同一SKU的优惠与非优惠没有拆开,平均摊销处理 + vendorActType = v.Type + } + baiduRate += int64(v.BaiduRate) + } + } + return salePrice, baiduRate, vendorActType +} + func (p *PurchaseHandler) AcceptOrRefuseOrder(order *model.GoodsOrder, isAcceptIt bool, userName string) (err error) { globals.SugarLogger.Debugf("ebai AcceptOrRefuseOrder orderID:%s, isAcceptIt:%t", order.VendorOrderID, isAcceptIt) if isAcceptIt { @@ -239,24 +332,38 @@ func (p *PurchaseHandler) SelfDeliverDelivered(order *model.GoodsOrder, userName // func (c *PurchaseHandler) onOrderMsg(msg *ebaiapi.CallbackMsg) (retVal *ebaiapi.CallbackResponse) { - if ebaiapi.CmdOrderCreate == msg.Cmd { - retVal = c.onOrderNew(msg) + if c.isAfsMsg(msg) { + retVal = c.OnAfsOrderMsg(msg) } else { status := c.callbackMsg2Status(msg) - var err error - if status != nil { - err = partner.CurOrderManager.OnOrderStatusChanged(status) + if partner.CurOrderManager.GetStatusDuplicatedCount(status) > 0 { + return nil + } + if ebaiapi.CmdOrderCreate == msg.Cmd { + retVal = c.onOrderNew(msg, status) + } else { + var err error + if status != nil { + if status.Status == model.OrderStatusAdjust { + var order *model.GoodsOrder + if order, err = c.GetOrder4PartRefund(GetOrderIDFromMsg(msg)); err == nil { + err = partner.CurOrderManager.OnOrderAdjust(order, status) + } + } else { + err = partner.CurOrderManager.OnOrderStatusChanged(status) + } + } + retVal = api.EbaiAPI.Err2CallbackResponse(msg.Cmd, err, nil) } - retVal = api.EbaiAPI.Err2CallbackResponse(msg.Cmd, err, nil) } return retVal } -func (c *PurchaseHandler) onOrderNew(msg *ebaiapi.CallbackMsg) (response *ebaiapi.CallbackResponse) { +func (c *PurchaseHandler) onOrderNew(msg *ebaiapi.CallbackMsg, orderStatus *model.OrderStatus) (response *ebaiapi.CallbackResponse) { vendorOrderID := GetOrderIDFromMsg(msg) order, orderMap, err := c.getOrder(vendorOrderID) if err == nil { - if err = partner.CurOrderManager.OnOrderNew(order, order.VendorStatus); err == nil { + if err = partner.CurOrderManager.OnOrderNew(order, orderStatus); err == nil { utils.CallFuncAsync(func() { c.OnOrderDetail(orderMap, partner.CreatedPeration) }) @@ -279,27 +386,24 @@ func (c *PurchaseHandler) callbackMsg2Status(msg *ebaiapi.CallbackMsg) (orderSta VendorStatus: msg.Cmd, } if msg.Cmd == ebaiapi.CmdOrderUserCancel { - msgType := utils.Int64ToStr(utils.MustInterface2Int64(msg.Body["type"])) - cancelType := utils.Int64ToStr(utils.MustInterface2Int64(msg.Body["cancel_type"])) - orderStatus.Remark = utils.Interface2String(msg.Body["cancel_reason"]) - orderStatus.VendorStatus = msg.Cmd + "-" + msgType - if additionReason := utils.Interface2String(msg.Body["addition_reason"]); additionReason != "" { - orderStatus.Remark += ",额外原因:" + additionReason - } + msgType := int(utils.MustInterface2Int64(msg.Body["type"])) + cancelType := int(utils.MustInterface2Int64(msg.Body["cancel_type"])) + orderStatus.Remark = buildFullReason(utils.Interface2String(msg.Body["cancel_reason"]), utils.Interface2String(msg.Body["addition_reason"])) + orderStatus.VendorStatus = msg.Cmd + "-" + utils.Int2Str(msgType) if cancelType == ebaiapi.OrderUserCancelTypeBeforeSale { - if msgType == ebaiapi.OrderUserCancelApply { + if msgType == ebaiapi.OrderUserCancelApply || + msgType == ebaiapi.OrderUserCancelCSIntervene { orderStatus.VendorStatus = fakeUserApplyCancel - } else if msgType == ebaiapi.OrderUserCancelInvalid { + } else if msgType == ebaiapi.OrderUserCancelInvalid || + msgType == ebaiapi.OrderUserCancelMerchantRefused || + msgType == ebaiapi.OrderUserCancelCSRefused { orderStatus.VendorStatus = fakeUserUndoApplyCancel } } } else if msg.Cmd == ebaiapi.CmdOrderPartRefund { - msgType := utils.Int64ToStr(utils.MustInterface2Int64(msg.Body["type"])) - status := utils.Int64ToStr(utils.MustInterface2Int64(msg.Body["status"])) - orderStatus.Remark = utils.Interface2String(msg.Body["reason"]) - if additionReason := utils.Interface2String(msg.Body["addition_reason"]); additionReason != "" { - orderStatus.Remark += ",额外原因:" + additionReason - } + msgType := int(utils.MustInterface2Int64(msg.Body["type"])) + status := int(utils.MustInterface2Int64(msg.Body["status"])) + orderStatus.Remark = buildFullReason(utils.Interface2String(msg.Body["reason"]), utils.Interface2String(msg.Body["addition_reason"])) if msgType == ebaiapi.OrderPartRefuncTypeMerchant && status == ebaiapi.OrderPartRefundSuccess { orderStatus.VendorStatus = fakeOrderAdjustFinished } @@ -315,6 +419,14 @@ func (c *PurchaseHandler) callbackMsg2Status(msg *ebaiapi.CallbackMsg) (orderSta return orderStatus } +func buildFullReason(reason, addReason string) (fullReason string) { + fullReason = reason + if addReason != "" { + fullReason += ",额外原因:" + addReason + } + return fullReason +} + func (c *PurchaseHandler) GetStatusActionTimeout(order *model.GoodsOrder, statusType, status int) (params *partner.StatusActionParams) { if statusType == scheduler.TimerStatusTypeOrder && status == model.OrderStatusAccepted { params = &partner.StatusActionParams{ // PickDeadline没有设置时才有效,饿百要求在5分钟内拣货,不然订单会被取消 @@ -398,15 +510,21 @@ func (c *PurchaseHandler) CancelOrder(ctx *jxcontext.Context, order *model.Goods } func (c *PurchaseHandler) AdjustOrder(ctx *jxcontext.Context, order *model.GoodsOrder, removedSkuList []*model.OrderSku, reason string) (err error) { - var skuList []*ebaiapi.RefundSku - for _, sku := range removedSkuList { - skuList = append(skuList, &ebaiapi.RefundSku{ - CustomeSkuID: utils.Int2Str(jxutils.GetSkuIDFromOrderSku(sku)), - Number: utils.Int2Str(sku.Count), - }) + // 饿百必须要确认订单后才能调整单 + if order.Status < model.OrderStatusFinishedPickup { + err = c.PickupGoods(order, false, ctx.GetUserName()) } - if globals.EnableEbaiStoreWrite { - err = api.EbaiAPI.OrderPartRefund(order.VendorOrderID, skuList) + if err == nil { + var skuList []*ebaiapi.RefundSku + for _, sku := range removedSkuList { + skuList = append(skuList, &ebaiapi.RefundSku{ + CustomeSkuID: utils.Int2Str(jxutils.GetSkuIDFromOrderSku(sku)), + Number: utils.Int2Str(sku.Count), + }) + } + if globals.EnableEbaiStoreWrite { + err = api.EbaiAPI.OrderPartRefund(order.VendorOrderID, skuList) + } } return err } diff --git a/business/partner/purchase/ebai/order_afs.go b/business/partner/purchase/ebai/order_afs.go new file mode 100644 index 000000000..5d13a3256 --- /dev/null +++ b/business/partner/purchase/ebai/order_afs.go @@ -0,0 +1,189 @@ +package ebai + +import ( + "fmt" + "strings" + + "git.rosy.net.cn/baseapi/platformapi/ebaiapi" + "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-callback/business/jxutils" + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "git.rosy.net.cn/jx-callback/business/model" + "git.rosy.net.cn/jx-callback/business/partner" + "git.rosy.net.cn/jx-callback/globals" + "git.rosy.net.cn/jx-callback/globals/api" +) + +var ( + AfsVendorStatus2Status4PartRefundMap = map[int]int{ + ebaiapi.OrderPartRefundApply: model.AfsOrderStatusWait4Approve, + ebaiapi.OrderPartRefundSuccess: model.AfsOrderStatusFinished, + ebaiapi.OrderPartRefundUserApplyArbitration: model.OrderStatusUnknown, // 是否是中间状态 + ebaiapi.OrderPartRefundFailed: model.AfsOrderStatusFailed, + ebaiapi.OrderPartRefundMerchantRefused: model.AfsOrderStatusFailed, // 是否是中间状态 + } + AfsVendorStatus2Status4UserCancel = map[int]int{ + ebaiapi.OrderUserCancelApply: model.AfsOrderStatusWait4Approve, + ebaiapi.OrderUserCancelCSIntervene: model.OrderStatusUnknown, + ebaiapi.OrderUserCancelCSRefused: model.AfsOrderStatusFailed, + ebaiapi.OrderUserCancelCSAgreed: model.AfsOrderStatusFinished, + ebaiapi.OrderUserCancelMerchantRefused: model.AfsOrderStatusFailed, + ebaiapi.OrderUserCancelMerchantAgreed: model.AfsOrderStatusFinished, + ebaiapi.OrderUserCancelInvalid: model.AfsOrderStatusFailed, + } +) + +func (c *PurchaseHandler) isAfsMsg(msg *ebaiapi.CallbackMsg) bool { + if msg.Cmd == ebaiapi.CmdOrderPartRefund { + msgType := int(utils.MustInterface2Int64(msg.Body["type"])) + return msgType == ebaiapi.OrderPartRefuncTypeCustomer + } else if msg.Cmd == ebaiapi.CmdOrderUserCancel { + cancelType := int(utils.MustInterface2Int64(msg.Body["cancel_type"])) + return cancelType == ebaiapi.OrderUserCancelTypeAfterSale + } + return false +} + +func (c *PurchaseHandler) OnAfsOrderMsg(msg *ebaiapi.CallbackMsg) (retVal *ebaiapi.CallbackResponse) { + jxutils.CallMsgHandlerAsync(func() { + retVal = c.onAfsOrderMsg(msg) + }, jxutils.ComposeUniversalOrderID(GetOrderIDFromMsg(msg), model.VendorIDEBAI)) + return retVal +} + +func (c *PurchaseHandler) onAfsOrderMsg(msg *ebaiapi.CallbackMsg) (retVal *ebaiapi.CallbackResponse) { + if orderStatus := c.callbackAfsMsg2Status(msg); orderStatus != nil { + var err error + if orderStatus.Status == model.AfsOrderStatusWait4Approve || orderStatus.Status == model.AfsOrderStatusNew { + var afsOrder *model.AfsOrder + if msg.Cmd == ebaiapi.CmdOrderPartRefund { + partRefundData := msg.Data.(*ebaiapi.CBPartRefundInfo) + afsOrder = &model.AfsOrder{ + VendorID: model.VendorIDEBAI, + AfsOrderID: orderStatus.VendorOrderID, + VendorOrderID: orderStatus.RefVendorOrderID, + VendorStoreID: "", + StoreID: 0, + AfsCreatedAt: utils.Timestamp2Time(msg.Timestamp), + VendorAppealType: "", + AppealType: model.AfsAppealTypeRefund, + VendorReasonType: partRefundData.ReasonType, + ReasonType: c.convertAfsReasonType(partRefundData.ReasonType), + ReasonDesc: utils.LimitUTF8StringLen(buildFullReason(partRefundData.Reason, partRefundData.AdditionReason), 1024), + ReasonImgList: utils.LimitUTF8StringLen(strings.Join(partRefundData.Photos, ","), 1024), + RefundType: model.AfsTypePartRefund, + + // FreightUserMoney: afsInfo.OrderFreightMoney, + // AfsFreightMoney: afsInfo.AfsFreight, + // BoxMoney: afsInfo.PackagingMoney, + // TongchengFreightMoney: afsInfo.TongchengFreightMoney, + // SkuBoxMoney: afsInfo.MealBoxMoney, + } + for _, sku := range partRefundData.RefundProducts { + orderSku := &model.OrderSkuFinancial{ + // VendorID: model.VendorIDEBAI, + // AfsOrderID: afsOrder.AfsOrderID, + // VendorOrderID: afsOrder.VendorOrderID, + // VendorStoreID: afsOrder.VendorStoreID, + // StoreID: afsOrder.StoreID, + // IsAfsOrder: 1, + + Count: sku.Number, + // ConfirmTime: afsOrder.AfsCreateAt, + VendorSkuID: sku.SkuID, + SkuID: int(utils.Str2Int64WithDefault(sku.CustomSkuID, 0)), + Name: sku.Name, + UserMoney: sku.TotalRefund, + PmSkuSubsidyMoney: sku.ShopEleRefund, + } + afsOrder.SkuUserMoney += orderSku.UserMoney + afsOrder.PmSubsidyMoney += orderSku.PmSubsidyMoney + afsOrder.Skus = append(afsOrder.Skus, orderSku) + } + } else if msg.Cmd == ebaiapi.CmdOrderUserCancel { + if orderFinancial, err2 := partner.CurOrderManager.LoadOrderFinancial(orderStatus.RefVendorOrderID, model.VendorIDEBAI); err2 == nil { + afsOrder = c.OrderFinancialDetail2Refund(orderFinancial, msg) + cancelData := msg.Data.(*ebaiapi.CBUserCancelInfo) + afsOrder.AfsOrderID = orderStatus.VendorOrderID + afsOrder.RefundType = model.AfsTypeFullRefund + afsOrder.AppealType = model.AfsAppealTypeRefund + afsOrder.VendorReasonType = "" + afsOrder.ReasonType = model.AfsReasonNotOthers + afsOrder.ReasonDesc = utils.LimitUTF8StringLen(buildFullReason(cancelData.CancelReason, cancelData.AdditionReason), 1024) + afsOrder.ReasonImgList = utils.LimitUTF8StringLen(strings.Join(cancelData.Pictures, ","), 1024) + } + } + if afsOrder != nil { + err = partner.CurOrderManager.OnAfsOrderNew(afsOrder, orderStatus) + } + } else { + err = partner.CurOrderManager.OnAfsOrderStatusChanged(orderStatus) + } + retVal = api.EbaiAPI.Err2CallbackResponse(msg.Cmd, err, nil) + } + return retVal +} + +func (c *PurchaseHandler) convertAfsReasonType(vendorReasonType string) int8 { + return model.AfsReasonNotOthers +} + +func (c *PurchaseHandler) GetAfsStatusFromVendorStatus4PartRefund(vendorStatus int) int { + return AfsVendorStatus2Status4PartRefundMap[vendorStatus] +} + +func (c *PurchaseHandler) GetAfsStatusFromVendorStatus4UserCancel(vendorStatus int) int { + return AfsVendorStatus2Status4UserCancel[vendorStatus] +} + +func (c *PurchaseHandler) callbackAfsMsg2Status(msg *ebaiapi.CallbackMsg) (orderStatus *model.OrderStatus) { + if msg.Cmd == ebaiapi.CmdOrderPartRefund { + partRefundData := msg.Data.(*ebaiapi.CBPartRefundInfo) + orderStatus = &model.OrderStatus{ + VendorOrderID: partRefundData.RefundID, // 是售后单ID,不是订单ID,订单ID在RefVendorOrderID中 + VendorID: model.VendorIDEBAI, + OrderType: model.OrderTypeAfsOrder, + RefVendorOrderID: utils.Int64ToStr(partRefundData.OrderID), + RefVendorID: model.VendorIDEBAI, + VendorStatus: utils.Int2Str(partRefundData.Status), + Status: c.GetAfsStatusFromVendorStatus4PartRefund(partRefundData.Status), + StatusTime: utils.Timestamp2Time(msg.Timestamp), + Remark: buildFullReason(partRefundData.Reason, partRefundData.AdditionReason), + } + if orderStatus.Status == model.AfsOrderStatusWait4Approve && partRefundData.Type != ebaiapi.OrderPartRefuncTypeCustomer { + orderStatus.Status = model.AfsOrderStatusNew + } + } else if msg.Cmd == ebaiapi.CmdOrderUserCancel { + cancelData := msg.Data.(*ebaiapi.CBUserCancelInfo) + orderStatus = &model.OrderStatus{ + VendorOrderID: utils.Int64ToStr(cancelData.OrderID), // 是售后单ID,不是订单ID,订单ID在RefVendorOrderID中 + VendorID: model.VendorIDEBAI, + OrderType: model.OrderTypeAfsOrder, + RefVendorOrderID: utils.Int64ToStr(cancelData.OrderID), + RefVendorID: model.VendorIDEBAI, + VendorStatus: utils.Int2Str(cancelData.Type), + Status: c.GetAfsStatusFromVendorStatus4UserCancel(cancelData.Type), + StatusTime: utils.Timestamp2Time(msg.Timestamp), + Remark: buildFullReason(cancelData.CancelReason, cancelData.AdditionReason), + } + } + return orderStatus +} + +// 审核售后单申请 +func (c *PurchaseHandler) AgreeOrRefuseRefund(ctx *jxcontext.Context, order *model.AfsOrder, approveType int, reason string) (err error) { + if globals.EnableEbaiStoreWrite { + if approveType == partner.AfsApproveTypeRefused { + err = api.EbaiAPI.OrderDisagreeRefund(order.VendorOrderID, reason) + } else { + err = api.EbaiAPI.OrderAgreeRefund(order.VendorOrderID) + } + } + return err +} + +// 确认收到退货 +func (c *PurchaseHandler) ConfirmReceivedReturnGoods(ctx *jxcontext.Context, order *model.AfsOrder) (err error) { + err = fmt.Errorf("内部错误,饿百平台不支持确认收到退货操作") + return err +} diff --git a/business/partner/purchase/ebai/order_test.go b/business/partner/purchase/ebai/order_test.go new file mode 100644 index 000000000..d39be9df4 --- /dev/null +++ b/business/partner/purchase/ebai/order_test.go @@ -0,0 +1,16 @@ +package ebai + +import ( + "testing" + + "git.rosy.net.cn/baseapi/utils" +) + +func TestGetOrder4PartRefund(t *testing.T) { + order, err := new(PurchaseHandler).GetOrder4PartRefund("1556529608021993938") + if err != nil { + t.Fatal(err.Error()) + } else { + t.Log(utils.Format4Output(order, false)) + } +} diff --git a/business/partner/purchase/ebai/store.go b/business/partner/purchase/ebai/store.go index 778ee025c..d00406139 100644 --- a/business/partner/purchase/ebai/store.go +++ b/business/partner/purchase/ebai/store.go @@ -59,7 +59,7 @@ func (p *PurchaseHandler) CreateStore(db *dao.DaoDB, storeID int, userName strin params["category1"] = "" params["category2"] = "" params["category3"] = "" - if globals.EnableStoreWrite && globals.EnableEbaiStoreWrite { + if globals.EnableEbaiStoreWrite { intVendorStoreID, err2 := api.EbaiAPI.ShopCreate(params) if err = err2; err == nil { return utils.Int64ToStr(intVendorStoreID), err @@ -160,7 +160,7 @@ func (p *PurchaseHandler) UpdateStore(db *dao.DaoDB, storeID int, userName strin if err = dao.GetRows(db, &stores, sql, model.VendorIDEBAI, utils.DefaultTimeValue, storeID); err == nil { for _, store := range stores { // globals.SugarLogger.Debug(utils.Format4Output(params, false)) - if globals.EnableStoreWrite && globals.EnableEbaiStoreWrite { + if globals.EnableEbaiStoreWrite { shopID := 0 if store.SyncStatus&model.SyncFlagDeletedMask == 0 { shopID = store.ID @@ -181,10 +181,10 @@ func (p *PurchaseHandler) UpdateStore(db *dao.DaoDB, storeID int, userName strin } if err == nil { mergeStatus := jxutils.MergeStoreStatus(store.Status, store.EbaiStoreStatus) - if store2.Status != mergeStatus { + if !isStoreStatusSame(store2.Status, mergeStatus) { if mergeStatus == model.StoreStatusOpened { err = api.EbaiAPI.ShopOpen("", utils.Str2Int64(store.VendorStoreID)) - } else if mergeStatus == model.StoreStatusClosed { + } else if mergeStatus == model.StoreStatusHaveRest || mergeStatus == model.StoreStatusClosed { err = api.EbaiAPI.ShopClose("", utils.Str2Int64(store.VendorStoreID)) } else if mergeStatus == model.StoreStatusDisabled { err = api.EbaiAPI.ShopOffline("", utils.Str2Int64(store.VendorStoreID)) @@ -204,10 +204,9 @@ func (p *PurchaseHandler) UpdateStore(db *dao.DaoDB, storeID int, userName strin if err != nil { return err } - // todo 饿百 开店审核通过后不允许修改商户信息 - // params := genStoreMapFromStore(store) - // if err = api.EbaiAPI.ShopUpdate(params); err == nil { - // } + params := genStoreMapFromStore(store) + if err = api.EbaiAPI.ShopUpdate(params); err == nil { + } } } } @@ -215,6 +214,16 @@ func (p *PurchaseHandler) UpdateStore(db *dao.DaoDB, storeID int, userName strin return err } +func isStoreStatusSame(status1, status2 int) bool { + if status1 == model.StoreStatusClosed { + status1 = model.StoreStatusHaveRest + } + if status2 == model.StoreStatusClosed { + status2 = model.StoreStatusHaveRest + } + return status1 == status2 +} + func (p *PurchaseHandler) RefreshAllStoresID(ctx *jxcontext.Context, parentTask tasksch.ITask, isAsync bool) (hint string, err error) { globals.SugarLogger.Debugf("ebai RefreshAllStoresID") const batchSize = 50 @@ -248,7 +257,7 @@ func (p *PurchaseHandler) RefreshAllStoresID(ctx *jxcontext.Context, parentTask shopIDs[k] = utils.GetUUID() } } - if globals.EnableStoreWrite && globals.EnableEbaiStoreWrite { + if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.ShopIDBatchUpdate(baiduShopIDs, shopIDs) } return nil, err @@ -311,7 +320,7 @@ func EbaiDeliveryRegion2Jx(deliveryRegion interface{}) string { func JxDeliveryRegion2Ebai(store *model.Store) (deliveryRegion interface{}) { rangeStr := strings.Trim(store.DeliveryRange, ";") if store.DeliveryRangeType == model.DeliveryRangeTypeRadius { - if utils.Str2Int64(store.DeliveryRange) > 100 { // todo 如果小于100米,表示禁用,不更新 + if utils.Str2Int64WithDefault(store.DeliveryRange, 0) > 100 { // todo 如果小于100米,表示禁用,不更新 rangeStr = jxutils.GetPolygonFromCircleStr(jxutils.IntCoordinate2Standard(store.Lng), jxutils.IntCoordinate2Standard(store.Lat), utils.Str2Float64(store.DeliveryRange), 8) } else { rangeStr = "" @@ -363,19 +372,20 @@ func JxBusinessTime2Ebai(store *model.Store) interface{} { func genStoreMapFromStore(store *tEbaiStoreInfo) map[string]interface{} { params := map[string]interface{}{ - "phone": store.Tel1, "business_time": JxBusinessTime2Ebai(&store.Store), } + // if store.Tel2 != "" { + // params["ivr_phone"] = store.Tel2 + // } + // params["phone"] = store.Tel1 if store.VendorStoreID != "" { params["baidu_shop_id"] = store.VendorStoreID } - if store.Tel2 != "" { - params["ivr_phone"] = store.Tel2 + if store.SyncStatus&(model.SyncFlagNewMask /*|model.SyncFlagStoreName*/) != 0 { + params["name"] = jxutils.ComposeStoreName(store.Name, model.VendorIDEBAI) } - if store.SyncStatus&(model.SyncFlagNewMask|model.SyncFlagStoreName) != 0 { - // params["name"] = jxutils.ComposeStoreName(store.Name, model.VendorIDEBAI) - } - if store.SyncStatus&(model.SyncFlagNewMask|model.SyncFlagStoreAddress) != 0 { + // todo 饿百 开店审核通过后不允许修改商户信息 + if store.SyncStatus&(model.SyncFlagNewMask /*|model.SyncFlagStoreAddress*/) != 0 { params["longitude"] = jxutils.IntCoordinate2Standard(store.Lng) params["latitude"] = jxutils.IntCoordinate2Standard(store.Lat) params["address"] = store.Address @@ -415,7 +425,7 @@ func (c *PurchaseHandler) onShopMsgPush(msg *ebaiapi.CallbackMsg) (response *eba if int(utils.ForceInterface2Int64(msg.Body["business_ele"])) == 1 { storeStatus = model.StoreStatusOpened } else { - storeStatus = model.StoreStatusClosed + storeStatus = model.StoreStatusHaveRest } } if err == nil { @@ -423,3 +433,11 @@ func (c *PurchaseHandler) onShopMsgPush(msg *ebaiapi.CallbackMsg) (response *eba } return api.EbaiAPI.Err2CallbackResponse(msg.Cmd, err, nil) } + +func (c *PurchaseHandler) GetShopHealthInfo(vendorShopID string) (shopHealthInfo map[string]interface{}, err error) { + result, err := api.EbaiAPI.GetShopHealthByDetail(utils.Str2Int64(vendorShopID)) + if err == nil { + shopHealthInfo = utils.Struct2FlatMap(result) + } + return shopHealthInfo, err +} diff --git a/business/partner/purchase/ebai/store_sku.go b/business/partner/purchase/ebai/store_sku.go index 96c1848b1..02f625644 100644 --- a/business/partner/purchase/ebai/store_sku.go +++ b/business/partner/purchase/ebai/store_sku.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strings" + "time" "git.rosy.net.cn/baseapi/platformapi/ebaiapi" "git.rosy.net.cn/baseapi/utils" @@ -12,6 +13,7 @@ import ( "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/partner" "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/globals/api" ) @@ -23,7 +25,6 @@ const ( type tStoreSkuFullInfo struct { model.StoreSkuBind - SkuID int `orm:"column(sku_id)"` NameID int `orm:"column(name_id)"` SpecQuality float32 `json:"specQuality"` @@ -31,14 +32,15 @@ type tStoreSkuFullInfo struct { Weight int `json:"weight"` // 重量/质量,单位为克,当相应的SkuName的SpecUnit为g或kg时,必须等于SpecQuality SkuStatus int - Prefix string `orm:"size(255)" json:"prefix"` - Name string `orm:"size(255);index" json:"name"` - Comment string `orm:"size(255)" json:"comment"` - IsGlobal int8 `orm:"default(1)" json:"isGlobal"` // 是否是全部(全国)可见,如果否的话,可见性由SkuPlace决定 - Unit string `orm:"size(8)" json:"unit"` - Img string `orm:"size(255)" json:"img"` - PlaceStr string - Upc string + Prefix string `orm:"size(255)" json:"prefix"` + Name string `orm:"size(255);index" json:"name"` + Comment string `orm:"size(255)" json:"comment"` + IsGlobal int8 `orm:"default(1)" json:"isGlobal"` // 是否是全部(全国)可见,如果否的话,可见性由SkuPlace决定 + Unit string `orm:"size(8)" json:"unit"` + Img string `orm:"size(255)" json:"img"` + PlaceStr string + Upc string + DescImgEbai string CatName string `orm:"size(255)"` @@ -54,7 +56,8 @@ type tStoreSkuFullInfo struct { EbaiCat2ID int64 `orm:"column(ebai_cat2_id)"` EbaiCat3ID int64 `orm:"column(ebai_cat3_id)"` - PricePercentage int + PricePercentage int + CatPricePercentage int } type tStoreCatInfo struct { @@ -80,23 +83,23 @@ var ( func (p *PurchaseHandler) getDirtyStoreSkus(db *dao.DaoDB, storeID int, skuIDs []int) (storeSkuInfoList []*tStoreSkuFullInfo, err error) { sql := ` - SELECT t8.price_percentage, t1.*, t2.id sku_id, t2.spec_quality, t2.spec_unit, t2.weight, t2.status sku_status, - t3.id name_id, t3.prefix, t3.name, t2.comment, t3.is_global, t3.unit, IF(t3.img_ebai <> '', t3.img_ebai, t3.img) img, t3.upc, - t4.name cat_name, + SELECT t8.price_percentage, t1.*, t2.spec_quality, t2.spec_unit, t2.weight, t2.status sku_status, + t3.id name_id, t3.prefix, t3.name, t2.comment, t3.is_global, t3.unit, IF(t3.img_ebai <> '', t3.img_ebai, t3.img) img, t3.upc, t3.desc_img_ebai, + t4.name cat_name, t4.ebai_price_percentage cat_price_percentage, t4.id cat_id, t4.level cat_level, t5.ebai_id cat_ebai_id, t4p.id parent_cat_id, t5p.ebai_id parent_cat_ebai_id, t5p.ebai_sync_status parent_cat_ebai_sync_status, cat1.vendor_category_id ebai_cat3_id, cat2.vendor_category_id ebai_cat2_id, cat2.parent_id ebai_cat1_id FROM store_sku_bind t1 LEFT JOIN sku t2 ON t1.sku_id = t2.id AND t2.deleted_at = ? AND t2.status = ? LEFT JOIN sku_name t3 ON t2.name_id = t3.id AND t3.deleted_at = ? AND t3.status = ? - JOIN sku_category t4 ON t3.category_id = t4.id + LEFT JOIN sku_category t4 ON t3.category_id = t4.id LEFT JOIN sku_category t4p ON t4p.id = t4.parent_id LEFT JOIN store_sku_category_map t5 ON t5.store_id = t1.store_id AND t5.category_id = t4.id AND t5.deleted_at = ? LEFT JOIN store_sku_category_map t5p ON t5p.store_id = t1.store_id AND t5p.category_id = t4p.id AND t5p.deleted_at = ? LEFT JOIN sku_vendor_category cat1 ON t4.ebai_category_id = cat1.vendor_category_id AND cat1.vendor_id = ? LEFT JOIN sku_vendor_category cat2 ON cat1.parent_id = cat2.vendor_category_id AND cat1.vendor_id = ? JOIN store_map t8 ON t8.store_id = t1.store_id AND t8.vendor_id = ? AND t8.deleted_at = ? - WHERE t1.store_id = ? AND (t1.ebai_sync_status <> 0) + WHERE t1.store_id = ? AND (t1.ebai_sync_status <> 0 OR (t1.ebai_id <> 0 AND t3.id IS NULL)) ` sqlParams := []interface{}{ utils.DefaultTimeValue, @@ -115,7 +118,7 @@ func (p *PurchaseHandler) getDirtyStoreSkus(db *dao.DaoDB, storeID int, skuIDs [ sql += " AND t1.sku_id IN (" + dao.GenQuestionMarks(len(skuIDs)) + ")" sqlParams = append(sqlParams, skuIDs) } - sql += " ORDER BY t1.price" + sql += " ORDER BY t1.price DESC" err = dao.GetRows(db, &storeSkuInfoList, sql, sqlParams...) return storeSkuInfoList, err } @@ -123,7 +126,7 @@ func (p *PurchaseHandler) getDirtyStoreSkus(db *dao.DaoDB, storeID int, skuIDs [ func (p *PurchaseHandler) createCatByStoreSkus(ctx *jxcontext.Context, parentTask tasksch.ITask, db *dao.DaoDB, storeID int, storeSkuInfoList []*tStoreSkuFullInfo) (num int64, err error) { catList2Add := make(map[int]int) for _, storeSku := range storeSkuInfoList { - if storeSku.SkuID != 0 && storeSku.EbaiSyncStatus&(model.SyncFlagNewMask|model.SyncFlagModifiedMask) != 0 { + if storeSku.CatID != 0 && storeSku.EbaiSyncStatus&(model.SyncFlagNewMask|model.SyncFlagStoreSkuModifiedMask) != 0 && storeSku.Status == model.SkuStatusNormal { if storeSku.ParentCatEbaiID == 0 && storeSku.ParentCatID != 0 { catList2Add[storeSku.ParentCatID] = 1 } @@ -164,7 +167,7 @@ func (p *PurchaseHandler) FullSyncStoreSkus(ctx *jxcontext.Context, parentTask t _, err = p.setStoreSkuSyncStatus(ctx, db, storeID, nil, model.SyncFlagNewMask) case 2: if err = p.DeleteRemoteCategories(ctx, rootTask, storeID, nil); err == nil { - _, err = dao.SetStoreCategorySyncStatus(db, model.VendorIDEBAI, storeID, nil, model.SyncFlagNewMask) + _, err = dao.SetStoreCategorySyncStatus(db, model.VendorIDEBAI, []int{storeID}, nil, model.SyncFlagNewMask) } case 3: err = p.SyncLocalStoreCategory(db, storeID, userName) @@ -202,7 +205,7 @@ func (p *PurchaseHandler) DeleteRemoteStoreSkus(ctx *jxcontext.Context, parentTa _, err = p.setStoreSkuSyncStatus(ctx, db, storeID, nil, model.SyncFlagNewMask) case 2: if err = p.DeleteRemoteCategories(ctx, rootTask, storeID, nil); err == nil { - _, err = dao.SetStoreCategorySyncStatus(db, model.VendorIDEBAI, storeID, nil, model.SyncFlagNewMask) + _, err = dao.SetStoreCategorySyncStatus(db, model.VendorIDEBAI, []int{storeID}, nil, model.SyncFlagNewMask) } } return nil, err @@ -248,34 +251,65 @@ func (p *PurchaseHandler) SyncStoreSkus(ctx *jxcontext.Context, parentTask tasks func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { storeSku := batchItemList[0].(*tStoreSkuFullInfo) updateFields := []string{model.FieldEbaiSyncStatus} - if globals.EnableStoreWrite && globals.EnableEbaiStoreWrite { - if storeSku.SkuID == 0 || storeSku.EbaiSyncStatus&model.SyncFlagDeletedMask != 0 { - if storeSku.EbaiSyncStatus&model.SyncFlagNewMask == 0 && storeSku.EbaiID != 0 { + if storeSku.NameID == 0 || storeSku.EbaiSyncStatus&model.SyncFlagDeletedMask != 0 { + if storeSku.EbaiSyncStatus&model.SyncFlagNewMask == 0 && !jxutils.IsEmptyID(storeSku.EbaiID) { + if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.SkuDelete(strStoreID, utils.Int64ToStr(storeSku.EbaiID)) + err = ignoreNoSkuErr(err) } - } else if storeSku.EbaiSyncStatus&model.SyncFlagNewMask != 0 { + } + if err == nil { + if utils.IsTimeZero(storeSku.DeletedAt) { + storeSku.DeletedAt = time.Now() + updateFields = append(updateFields, model.FieldDeletedAt) + } + storeSku.EbaiID = 0 + updateFields = append(updateFields, model.FieldEbaiID) + } + } else { + if storeSku.EbaiSyncStatus&model.SyncFlagNewMask != 0 { // globals.SugarLogger.Debug(utils.Format4Output(genSkuParamsFromStoreSkuInfo(storeSku), false)) // todo 适当处理重复(即已经创建)的情况 - if storeSku.EbaiID, err = api.EbaiAPI.SkuCreate(strStoreID, storeSku.SkuID, genSkuParamsFromStoreSkuInfo(storeSku)); err == nil { - updateFields = append(updateFields, model.FieldEbaiID) - } else if storeSku.EbaiID = ebaiapi.GetEbaiSkuIDFromError(err); storeSku.EbaiID > 0 { - // globals.SugarLogger.Debugf("SyncStoreSkus test storeSku.EbaiID:%d, err:%v", storeSku.EbaiID, err) - updateFields = append(updateFields, model.FieldEbaiID) - _, err = api.EbaiAPI.SkuUpdate(strStoreID, storeSku.EbaiID, genSkuParamsFromStoreSkuInfo(storeSku)) + mergedStoreSkuStatus := jxutils.MergeSkuStatus(storeSku.SkuStatus, storeSku.Status) + if mergedStoreSkuStatus == model.SkuStatusNormal { // 待创建且不可售的,暂不新建 + if storeSku.Img != "" { + if globals.EnableEbaiStoreWrite { + storeSku.EbaiID, err = api.EbaiAPI.SkuCreate(strStoreID, storeSku.SkuID, genSkuParamsFromStoreSkuInfo(storeSku)) + } else { + storeSku.EbaiID = jxutils.GenFakeID() + } + if err == nil { + updateFields = append(updateFields, model.FieldEbaiID) + } else if storeSku.EbaiID = ebaiapi.GetEbaiSkuIDFromError(err); storeSku.EbaiID > 0 { + // globals.SugarLogger.Debugf("SyncStoreSkus test storeSku.EbaiID:%d, err:%v", storeSku.EbaiID, err) + updateFields = append(updateFields, model.FieldEbaiID) + err = skuUpdate(strStoreID, storeSku) + } + } else { + err = fmt.Errorf("SKUANME%d:%s没有图片,同步失败", storeSku.NameID, storeSku.Name) + } + } else { + updateFields = nil } - } else if storeSku.EbaiSyncStatus&model.SyncFlagModifiedMask != 0 { - if jxutils.IsFakeID(storeSku.EbaiID) { + } else if storeSku.EbaiSyncStatus&model.SyncFlagStoreSkuModifiedMask != 0 { + if jxutils.IsEmptyID(storeSku.EbaiID) { err = fmt.Errorf("京西数据异常,修改一个没有创建的饿百商品:%d, store:%s", storeSku.SkuID, strStoreID) } else { - if _, err = api.EbaiAPI.SkuUpdate(strStoreID, storeSku.EbaiID, genSkuParamsFromStoreSkuInfo(storeSku)); err == nil { - // err = api.EbaiAPI.SkuShopCategoryMap(strStoreID, storeSku.EbaiID, utils.Int64ToStr(storeSku.CatEbaiID)) + if storeSku.Img != "" { + err = skuUpdate(strStoreID, storeSku) + } else { + err = fmt.Errorf("SKUANME%d:%s没有图片,同步失败", storeSku.NameID, storeSku.Name) } } } } if err == nil { - storeSku.EbaiSyncStatus = 0 - _, err = dao.UpdateEntity(nil, &storeSku.StoreSkuBind, updateFields...) + if len(updateFields) > 0 { + storeSku.EbaiSyncStatus = 0 + _, err = dao.UpdateEntity(nil, &storeSku.StoreSkuBind, updateFields...) + } + } else if isErrModifyPrice(err) { + err = partner.NewErrorCode(err.Error(), partner.ErrCodeChangePriceFailed, model.VendorIDEBAI) } return nil, err }, storeSkuInfoList) @@ -291,9 +325,58 @@ func (p *PurchaseHandler) SyncStoreSkus(ctx *jxcontext.Context, parentTask tasks return rootTask.ID, err } -func (p *PurchaseHandler) GetAllRemoteSkus(ctx *jxcontext.Context, storeID int, parentTask tasksch.ITask) (skus []map[string]interface{}, err error) { +func ignoreNoSkuErr(err error) error { + if err != nil { + if codeErr, ok := err.(*utils.ErrorWithCode); ok { + if codeErr.IntCode() == 1 { + for _, v := range []string{"SKU不存在或者已经被删除", "sku_id与shop_id不匹配"} { + if strings.Index(codeErr.ErrMsg(), v) > 0 { + err = nil + break + } + } + } + } + } + return err +} + +func skuUpdate(strStoreID string, storeSku *tStoreSkuFullInfo) (err error) { + if globals.EnableEbaiStoreWrite { + if _, err = api.EbaiAPI.SkuUpdate(strStoreID, storeSku.EbaiID, genSkuParamsFromStoreSkuInfo(storeSku)); err != nil { + // 如果是改价错误,尝试把价格标志去掉再同步 + if isErrModifyPrice(err) { + storeSku.EbaiSyncStatus = storeSku.EbaiSyncStatus & ^model.SyncFlagPriceMask + if storeSku.EbaiSyncStatus != 0 { + if _, err2 := api.EbaiAPI.SkuUpdate(strStoreID, storeSku.EbaiID, genSkuParamsFromStoreSkuInfo(storeSku)); err2 != nil { + err = err2 + } + } + } + } + } + return err +} + +func isErrModifyPrice(err error) bool { + if errExt, ok := err.(*utils.ErrorWithCode); ok && errExt.IntCode() == 1 { + for _, v := range []string{ + "无法修改价格", + "sku_参加营销活动", + } { + if strings.Index(errExt.ErrMsg(), v) >= 0 { + return true + } + } + } + return false +} + +func (p *PurchaseHandler) GetAllRemoteSkus(ctx *jxcontext.Context, storeID int, parentTask tasksch.ITask) (skus []*ebaiapi.SkuInfo, err error) { globals.SugarLogger.Debugf("ebai GetAllRemoteSkus storeID:%d, userName:%s", storeID, ctx.GetUserName()) - page1, err := api.EbaiAPI.SkuList(utils.Int2Str(storeID), utils.Params2Map("pagesize", MaxPageSize)) + page1, err := api.EbaiAPI.SkuList(utils.Int2Str(storeID), &ebaiapi.SkuListParams{ + PageSize: MaxPageSize, + }) if err == nil { skus = append(skus, page1.List...) if page1.Pages > 1 { @@ -303,9 +386,9 @@ func (p *PurchaseHandler) GetAllRemoteSkus(ctx *jxcontext.Context, storeID int, } task := tasksch.NewParallelTask("GetAllRemoteSkus", nil, ctx, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { - callParams := map[string]interface{}{ - "pagesize": MaxPageSize, - "page": batchItemList[0], + callParams := &ebaiapi.SkuListParams{ + PageSize: MaxPageSize, + Page: batchItemList[0].(int), } pageSku, err2 := api.EbaiAPI.SkuList(utils.Int2Str(storeID), callParams) if err2 == nil { @@ -318,7 +401,7 @@ func (p *PurchaseHandler) GetAllRemoteSkus(ctx *jxcontext.Context, storeID int, result, err2 := task.GetResult(0) if err = err2; err == nil { for _, v := range result { - skus = append(skus, v.(map[string]interface{})) + skus = append(skus, v.(*ebaiapi.SkuInfo)) } } } @@ -334,7 +417,7 @@ func (p *PurchaseHandler) DeleteRemoteSkus(ctx *jxcontext.Context, parentTask ta if err = err2; err == nil { vendorSkuIDs = make([]string, len(result)) for k, v := range result { - vendorSkuIDs[k] = utils.Interface2String(v[ebaiapi.KeySkuID]) + vendorSkuIDs[k] = utils.Int64ToStr(v.SkuID) } } } @@ -344,7 +427,7 @@ func (p *PurchaseHandler) DeleteRemoteSkus(ctx *jxcontext.Context, parentTask ta for k, v := range batchItemList { strList[k] = v.(string) } - if globals.EnableStoreWrite && globals.EnableEbaiStoreWrite { + if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.SkuDelete(utils.Int2Str(storeID), strings.Join(strList, ",")) } return nil, err @@ -369,7 +452,7 @@ func (p *PurchaseHandler) DeleteRemoteCategories(ctx *jxcontext.Context, parentT } task := tasksch.NewParallelTask("DeleteRemoteCategories", tasksch.NewParallelConfig().SetIsContinueWhenError(true), ctx, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { - if globals.EnableStoreWrite && globals.EnableEbaiStoreWrite { + if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.ShopCategoryDelete(strStoreID, batchItemList[0].(int64)) } return nil, err @@ -385,11 +468,12 @@ func (p *PurchaseHandler) RefreshStoresAllSkusID(ctx *jxcontext.Context, parentT /////////// func genSkuParamsFromStoreSkuInfo(storeSku *tStoreSkuFullInfo) (params map[string]interface{}) { - price := jxutils.CaculateSkuVendorPrice(storeSku.Price, storeSku.PricePercentage) + price := jxutils.CaculateSkuVendorPrice(storeSku.Price, storeSku.PricePercentage, storeSku.CatPricePercentage) params = map[string]interface{}{ - "name": jxutils.ComposeSkuName(storeSku.Prefix, storeSku.Name, storeSku.Comment, storeSku.Unit, storeSku.SpecQuality, storeSku.SpecUnit, 0), + "name": utils.LimitMixedStringLen(jxutils.ComposeSkuName(storeSku.Prefix, storeSku.Name, storeSku.Comment, storeSku.Unit, storeSku.SpecQuality, storeSku.SpecUnit, 0), ebaiapi.MaxSkuNameByteCount), "left_num": model.MaxStoreSkuStockQty, "category_id": storeSku.CatEbaiID, + "predict_cat": 0, // 不使用推荐类目 "cat1_id": getEbaiCat(storeSku.EbaiCat1ID, 1), "cat2_id": getEbaiCat(storeSku.EbaiCat2ID, 2), "cat3_id": getEbaiCat(storeSku.EbaiCat3ID, 3), @@ -401,6 +485,9 @@ func genSkuParamsFromStoreSkuInfo(storeSku *tStoreSkuFullInfo) (params map[strin }, }, } + if storeSku.DescImgEbai != "" { + params["rtf"] = storeSku.DescImgEbai + } if storeSku.EbaiSyncStatus&(model.SyncFlagPriceMask|model.SyncFlagNewMask) != 0 { params["sale_price"] = price params["market_price"] = price @@ -516,18 +603,23 @@ func (p *PurchaseHandler) SyncStoreCategory(ctx *jxcontext.Context, parentTask t updateFields := []string{model.FieldEbaiSyncStatus} catInfo := batchItemList[0].(*tStoreCatInfo) // globals.SugarLogger.Debug(utils.Format4Output(catInfo, false)) - if globals.EnableStoreWrite && globals.EnableEbaiStoreWrite { - if catInfo.EbaiSyncStatus&model.SyncFlagDeletedMask != 0 { // 删除 - if catInfo.EbaiSyncStatus&model.SyncFlagNewMask == 0 && catInfo.EbaiID != 0 { + if catInfo.EbaiSyncStatus&model.SyncFlagDeletedMask != 0 { // 删除 + if catInfo.EbaiSyncStatus&model.SyncFlagNewMask == 0 && catInfo.EbaiID != 0 { + if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.ShopCategoryDelete(strStoreID, catInfo.EbaiID) } - } else if catInfo.EbaiSyncStatus&model.SyncFlagNewMask != 0 { // 新增 - ebaiID, err2 := api.EbaiAPI.ShopCategoryCreate(strStoreID, catInfo.ParentEbaiID, formatName(catInfo.Name), jxCatSeq2Ebai(catInfo.Seq)) - if err = err2; err == nil { - catInfo.EbaiID = ebaiID - updateFields = append(updateFields, model.FieldEbaiID) - } - } else if catInfo.EbaiSyncStatus&model.SyncFlagModifiedMask != 0 { // 修改 + } + } else if catInfo.EbaiSyncStatus&model.SyncFlagNewMask != 0 { // 新增 + if globals.EnableEbaiStoreWrite { + catInfo.EbaiID, err = api.EbaiAPI.ShopCategoryCreate(strStoreID, catInfo.ParentEbaiID, formatName(catInfo.Name), jxCatSeq2Ebai(catInfo.Seq)) + } else { + catInfo.EbaiID = jxutils.GenFakeID() + } + if err == nil { + updateFields = append(updateFields, model.FieldEbaiID) + } + } else if catInfo.EbaiSyncStatus&model.SyncFlagModifiedMask != 0 { // 修改 + if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.ShopCategoryUpdate(strStoreID, catInfo.EbaiID, formatName(catInfo.Name), jxCatSeq2Ebai(catInfo.Seq)) } } @@ -603,7 +695,7 @@ func (p *PurchaseHandler) updateLocalCatAsNew(db *dao.DaoDB, localCatMap map[str func (p *PurchaseHandler) setStoreSkuSyncStatus(ctx *jxcontext.Context, db *dao.DaoDB, storeID int, skuIDs []int, syncStatus int) (num int64, err error) { globals.SugarLogger.Debugf("ebai setStoreSkuSyncStatus storeID:%d, userName:%s", storeID, ctx.GetUserName()) - return dao.SetStoreSkuSyncStatus(db, model.VendorIDEBAI, storeID, skuIDs, syncStatus) + return dao.SetStoreSkuSyncStatus(db, model.VendorIDEBAI, []int{storeID}, skuIDs, syncStatus) } func formatName(name string) string { @@ -614,3 +706,7 @@ func formatName(name string) string { func jxCatSeq2Ebai(seq int) int { return 10000 - seq } + +func (p *PurchaseHandler) GetStoresSku(ctx *jxcontext.Context, parentTask tasksch.ITask, storeIDs []int) (storeSkuList []*model.StoreSkuBind, err error) { + return storeSkuList, err +} diff --git a/business/partner/purchase/ebai/store_sku2.go b/business/partner/purchase/ebai/store_sku2.go new file mode 100644 index 000000000..1fd84a97b --- /dev/null +++ b/business/partner/purchase/ebai/store_sku2.go @@ -0,0 +1,248 @@ +package ebai + +import ( + "fmt" + "strings" + + "git.rosy.net.cn/baseapi/platformapi/ebaiapi" + "git.rosy.net.cn/baseapi/utils" + "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/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/partner" + "git.rosy.net.cn/jx-callback/globals" + "git.rosy.net.cn/jx-callback/globals/api" +) + +const ( + minBatchSize = 5 // 使用batch相关的API最少的处理数量,因为饿百批处理API的调用频率限制得更低 +) + +// 门店分类 +func (p *PurchaseHandler) ReadStoreCategory(ctx *jxcontext.Context, vendorStoreID string) (cats []*partner.BareCategoryInfo, err error) { + remoteCats, err := api.EbaiAPI.ShopCategoryGet(vendorStoreID) + if err == nil { + cats = convertVendorCatList(remoteCats) + } + return cats, err +} + +func convertVendorCatList(remoteCats []*ebaiapi.CategoryInfo) (cats []*partner.BareCategoryInfo) { + for _, rCat := range remoteCats { + cat := &partner.BareCategoryInfo{ + VendorCatID: utils.Int64ToStr(rCat.CategoryID), + Name: rCat.Name, + Level: rCat.Level, + Seq: jxCatSeq2Ebai(rCat.Rank), + Children: convertVendorCatList(rCat.Children), + } + cats = append(cats, cat) + } + return cats +} + +func (p *PurchaseHandler) CreateStoreSkuCategory(ctx *jxcontext.Context, vendorStoreID string, storeCat *dao.SkuStoreCatInfo) (err error) { + var vendorCatID int64 + if globals.EnableEbaiStoreWrite { + vendorCatID, err = api.EbaiAPI.ShopCategoryCreate(vendorStoreID, utils.Str2Int64WithDefault(storeCat.ParentVendorCatID, 0), storeCat.Name, jxCatSeq2Ebai(storeCat.Seq)) + } else { + vendorCatID = jxutils.GenFakeID() + } + storeCat.VendorCatID = utils.Int64ToStr(vendorCatID) + return err +} + +func (p *PurchaseHandler) UpdateStoreSkuCategory(ctx *jxcontext.Context, vendorStoreID string, storeCat *dao.SkuStoreCatInfo) (err error) { + if globals.EnableEbaiStoreWrite { + err = api.EbaiAPI.ShopCategoryUpdate(vendorStoreID, utils.Str2Int64WithDefault(storeCat.VendorCatID, 0), storeCat.Name, jxCatSeq2Ebai(storeCat.Seq)) + } + return err +} + +func (p *PurchaseHandler) DeleteStoreSkuCategory(ctx *jxcontext.Context, vendorStoreID, vendorCatID string) (err error) { + if globals.EnableEbaiStoreWrite { + err = api.EbaiAPI.ShopCategoryDelete(vendorStoreID, utils.Str2Int64WithDefault(vendorCatID, 0)) + } + return err +} + +// 门店商品 + +// 多门店平台不需要实现这个接口 +func (p *PurchaseHandler) UpdateStoreSkus(ctx *jxcontext.Context, vendorStoreID string, storeSkuList []*dao.StoreSkuSyncInfo) (err error) { + storeSku := storeSkuList[0] + if globals.EnableEbaiStoreWrite { + _, err = api.EbaiAPI.SkuUpdate(vendorStoreID, utils.Str2Int64(storeSku.VendorSkuID), genSkuParamsFromStoreSkuInfo2(storeSku)) + } + return err +} + +// 通用 + +func (p *PurchaseHandler) GetStoreSkusBatchSize(funcID int) int { + return 1 +} + +// 对于多门店平台来说,storeSkuList中只有SkuID与VendorSkuID有意义 +func (p *PurchaseHandler) CreateStoreSkus(ctx *jxcontext.Context, vendorStoreID string, storeSkuList []*dao.StoreSkuSyncInfo) (err error) { + storeSku := storeSkuList[0] + var vendorSkuID int64 + if globals.EnableEbaiStoreWrite { + vendorSkuID, err = api.EbaiAPI.SkuCreate(vendorStoreID, storeSku.SkuID, genSkuParamsFromStoreSkuInfo2(storeSku)) + } else { + vendorSkuID = jxutils.GenFakeID() + } + storeSku.VendorSkuID = utils.Int64ToStr(vendorSkuID) + return err +} + +func (p *PurchaseHandler) DeleteStoreSkus(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*partner.BareStoreSkuInfo) (err error) { + if globals.EnableEbaiStoreWrite { + err = api.EbaiAPI.SkuDelete(vendorStoreID, strings.Join(partner.BareStoreSkuInfoList(storeSkuList).GetVendorSkuIDList(), ",")) + } + return err +} + +func (p *PurchaseHandler) UpdateStoreSkusStatus(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*partner.BareStoreSkuInfo) (err error) { + var validSkus, invalidSkus []string + for _, storeSku := range storeSkuList { + if storeSku.Status == model.SkuStatusNormal { + validSkus = append(validSkus, storeSku.VendorSkuID) + } else { + invalidSkus = append(invalidSkus, storeSku.VendorSkuID) + } + } + if globals.EnableEbaiStoreWrite { + if len(invalidSkus) > minBatchSize { + err = api.EbaiAPI.SkuOffline(utils.Int2Str(storeID), strings.Join(invalidSkus, ",")) + } else if len(invalidSkus) > 0 { + for _, v := range invalidSkus { + if err = api.EbaiAPI.SkuOfflineOne(utils.Int2Str(storeID), utils.Str2Int64(v), "", ""); err != nil { + break + } + } + } + if err == nil { + if len(validSkus) > minBatchSize { + err = api.EbaiAPI.SkuOnline(utils.Int2Str(storeID), strings.Join(validSkus, ",")) + } else if len(validSkus) > 0 { + for _, v := range invalidSkus { + if err = api.EbaiAPI.SkuOnlineOne(utils.Int2Str(storeID), utils.Str2Int64(v), "", ""); err != nil { + break + } + } + } + } + } + return err +} + +func (p *PurchaseHandler) UpdateStoreSkusPrice(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*partner.BareStoreSkuInfo) (err error) { + skuPriceList := make([]string, len(storeSkuList)) + for k, v := range storeSkuList { + skuPriceList[k] = fmt.Sprintf("%s:%d", v.VendorSkuID, v.Price) + } + if globals.EnableEbaiStoreWrite { + if len(skuPriceList) > minBatchSize { + err = api.EbaiAPI.SkuPriceUpdateBatch(utils.Int2Str(storeID), strings.Join(skuPriceList, ";"), "", "") + } else { + for _, v := range skuPriceList { + if err = api.EbaiAPI.SkuPriceUpdateOne(utils.Int2Str(storeID), v, "", ""); err != nil { + break + } + } + } + } + return err +} + +func genSkuParamsFromStoreSkuInfo2(storeSku *dao.StoreSkuSyncInfo) (params map[string]interface{}) { + params = map[string]interface{}{ + "name": storeSku.Name, + "left_num": model.MaxStoreSkuStockQty, + "category_id": utils.Str2Int64(storeSku.VendorCatID), + "predict_cat": 0, // 不使用推荐类目 + "cat1_id": getEbaiCat(storeSku.VendorVendorCatID3, 1), + "cat2_id": getEbaiCat(storeSku.VendorVendorCatID2, 2), + "cat3_id": getEbaiCat(storeSku.VendorVendorCatID, 3), + "weight": storeSku.Weight, + "photos": []map[string]interface{}{ + map[string]interface{}{ + "is_master": true, + "url": storeSku.Img, + }, + }, + } + if storeSku.DescImg != "" { + params["rtf"] = storeSku.DescImg + } + if storeSku.StoreSkuSyncStatus&(model.SyncFlagPriceMask|model.SyncFlagNewMask) != 0 { + params["sale_price"] = storeSku.Price + params["market_price"] = storeSku.Price + } + if storeSku.StoreSkuSyncStatus&(model.SyncFlagSaleMask|model.SyncFlagNewMask) != 0 { + params["status"] = jxSkuStatus2Ebai(storeSku.StoreSkuStatus) + } + // todo 饿百如果给的UPC是空要报错,但如果我要删除UPC怎么弄? + if storeSku.Upc != "" { + params["upc"] = storeSku.Upc + } + return params +} + +func (p *PurchaseHandler) GetStoreSkusInfo(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, vendorStoreID string, inStoreSkuList []*partner.BareStoreSkuInfo) (outStoreSkuList []*partner.BareStoreSkuInfo, err error) { + vendorSkuIDIntList := partner.BareStoreSkuInfoList(inStoreSkuList).GetVendorSkuIDIntList() + var vendorSkuList []*ebaiapi.SkuInfo + if len(vendorSkuIDIntList) > 1 { + task := tasksch.NewParallelTask("获取饿百平台门店商品信息", nil, ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + vendorSkuID := batchItemList[0].(int64) + skuInfo, err := api.EbaiAPI.SkuList(utils.Int2Str(storeID), &ebaiapi.SkuListParams{ + SkuID: vendorSkuID, + }) + if err == nil { + vendorSkuList = skuInfo.List + return skuInfo.List, nil + } + return nil, err + }, vendorSkuIDIntList) + tasksch.HandleTask(task, parentTask, false).Run() + _, err = task.GetResult(0) + } else if len(vendorSkuIDIntList) == 1 { + skuInfo, err2 := api.EbaiAPI.SkuList(utils.Int2Str(storeID), &ebaiapi.SkuListParams{ + SkuID: vendorSkuIDIntList[0], + }) + if err = err2; err == nil { + vendorSkuList = skuInfo.List + } + } else { + return nil, nil + } + if err == nil { + storeSkuMap := make(map[int64]*partner.BareStoreSkuInfo) + for _, v := range inStoreSkuList { + storeSkuMap[utils.Str2Int64(v.VendorSkuID)] = v + } + for _, skuInfo := range vendorSkuList { + storeSku := storeSkuMap[skuInfo.SkuID] + globals.SugarLogger.Debug(utils.Format4Output(storeSku, false)) + outStoreSkuList = append(outStoreSkuList, storeSku) + storeSku.Price = skuInfo.SalePrice + storeSku.Status = ebaiSkuStatus2Jx(skuInfo.Status) + } + } + return outStoreSkuList, err +} + +func ebaiSkuStatus2Jx(ebaiSkuStatus int) (jxSkuStatus int) { + if ebaiSkuStatus == ebaiapi.SkuStatusOnline { + jxSkuStatus = model.SkuStatusNormal + } else if ebaiSkuStatus == ebaiapi.SkuStatusOffline { + jxSkuStatus = model.SkuStatusDontSale + } else if ebaiSkuStatus == ebaiapi.SkuStatusOnline { + jxSkuStatus = model.SkuStatusDeleted + } + return jxSkuStatus +} diff --git a/business/partner/purchase/ebai/waybill.go b/business/partner/purchase/ebai/waybill.go index 301dbece4..5b81bf2e1 100644 --- a/business/partner/purchase/ebai/waybill.go +++ b/business/partner/purchase/ebai/waybill.go @@ -18,10 +18,10 @@ var ( ebaiapi.WaybillStatusCourierPickedup: model.WaybillStatusDelivering, ebaiapi.WaybillStatusDeliveryCancled: model.WaybillStatusCanceled, ebaiapi.WaybillStatusFinished: model.WaybillStatusDelivered, - ebaiapi.WaybillStatusExceptional: model.WaybillStatusUnknown, + ebaiapi.WaybillStatusExceptional: model.WaybillStatusCanceled, ebaiapi.WaybillStatusSelfDelivery: model.WaybillStatusUnknown, - ebaiapi.WaybillStatusNotInDelivering: model.WaybillStatusUnknown, - ebaiapi.WaybillStatusDeliveryRejected: model.WaybillStatusNeverSend, + ebaiapi.WaybillStatusDontDeliver: model.WaybillStatusCanceled, + ebaiapi.WaybillStatusDeliveryRejected: model.WaybillStatusCanceled, } ) diff --git a/business/partner/purchase/elm/act.go b/business/partner/purchase/elm/act.go new file mode 100644 index 000000000..bcc142ed5 --- /dev/null +++ b/business/partner/purchase/elm/act.go @@ -0,0 +1,19 @@ +package elm + +import ( + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" + "git.rosy.net.cn/jx-callback/business/model" +) + +func (c *PurchaseHandler) CreateAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actOrderRules []*model.ActOrderRule, actStoreMap []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + return err +} + +func (c *PurchaseHandler) UpdateAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actOrderRules []*model.ActOrderRule, actStoreMap2Remove, actStoreMap2Add, actStoreMap2Update []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + return err +} + +func (c *PurchaseHandler) CancelAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actStoreMap []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + return err +} diff --git a/business/partner/purchase/elm/order.go b/business/partner/purchase/elm/order.go index 115965dc2..42765e751 100644 --- a/business/partner/purchase/elm/order.go +++ b/business/partner/purchase/elm/order.go @@ -16,6 +16,7 @@ import ( "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" "git.rosy.net.cn/jx-callback/business/model" + "git.rosy.net.cn/jx-callback/business/model/dao" "git.rosy.net.cn/jx-callback/business/partner" ) @@ -186,47 +187,23 @@ func (c *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *mo SalePrice: jxutils.StandardPrice2Int(utils.MustInterface2Float64(product["price"])), Weight: int(math.Round(utils.Interface2Float64WithDefault(product["weight"], 0.0))), } - if sku.VendorSkuID == "0" { + if dao.IsVendorThingIDEmpty(sku.VendorSkuID) { sku.VendorSkuID = utils.Int64ToStr(utils.MustInterface2Int64(product["id"])) // 2018-09-28日,饿了么迁移到饿百后,这个字段发生了变化 } order.Skus = append(order.Skus, sku) - order.SkuCount++ - order.GoodsCount += sku.Count - order.SalePrice += sku.SalePrice * int64(sku.Count) - order.Weight += sku.Weight * sku.Count } } - setOrederDetailFee(result, order) + jxutils.RefreshOrderSkuRelated(order) return order } -func setOrederDetailFee(result map[string]interface{}, order *model.GoodsOrder) { - orderActivities, ok := result["orderActivities"].([]interface{}) - if ok { - for _, value := range orderActivities { - activity := value.(map[string]interface{}) - categoryId := utils.MustInterface2Int64(activity["categoryId"]) - restaurantPart := -jxutils.StandardPrice2Int(utils.MustInterface2Float64(activity["restaurantPart"])) - elemePart := -jxutils.StandardPrice2Int(utils.MustInterface2Float64(activity["elemePart"])) - if _, ok := model.ElmSkuPromotion[int(categoryId)]; ok { - order.SkuPmFee += restaurantPart - order.SkuPmSubsidy += elemePart - } else { - order.OrderPmFee += restaurantPart - order.OrderPmSubsidy += elemePart - } - } - } - order.PlatformFeeRate = int16(utils.MustInterface2Float64(result["serviceRate"])) -} - // func (c *PurchaseHandler) onOrderNew(msg map[string]interface{}) (response *elmapi.CallbackResponse) { // todo 这里应该可以直接用msg里的内容,而不用再次去查 order, err := c.GetOrder(msg["orderId"].(string)) if err == nil { order.VendorStatus = c.stateAndType2Str(order.VendorStatus, elmapi.MsgTypeOrderValid) - err = partner.CurOrderManager.OnOrderNew(order, c.stateAndType2Str(msg["status"].(string), elmapi.MsgTypeOrderValid)) + err = partner.CurOrderManager.OnOrderNew(order, nil) // if globals.HandleLegacyJxOrder && err == nil { // c.legacyWriteElmOrder(order) // } diff --git a/business/partner/purchase/elm/order_afs.go b/business/partner/purchase/elm/order_afs.go new file mode 100644 index 000000000..0cb9c0aa1 --- /dev/null +++ b/business/partner/purchase/elm/order_afs.go @@ -0,0 +1,16 @@ +package elm + +import ( + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "git.rosy.net.cn/jx-callback/business/model" +) + +// 审核售后单申请 +func (c *PurchaseHandler) AgreeOrRefuseRefund(ctx *jxcontext.Context, order *model.AfsOrder, approveType int, reason string) (err error) { + return err +} + +// 确认收到退货 +func (c *PurchaseHandler) ConfirmReceivedReturnGoods(ctx *jxcontext.Context, order *model.AfsOrder) (err error) { + return err +} diff --git a/business/partner/purchase/elm/store_sku.go b/business/partner/purchase/elm/store_sku.go index 24a194a17..dcb6ede52 100644 --- a/business/partner/purchase/elm/store_sku.go +++ b/business/partner/purchase/elm/store_sku.go @@ -3,6 +3,8 @@ package elm import ( "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" + "git.rosy.net.cn/jx-callback/business/model" + "git.rosy.net.cn/jx-callback/business/partner" ) func (p *PurchaseHandler) SyncStoreCategory(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, isAsync bool) (hint string, err error) { @@ -24,3 +26,11 @@ func (p *PurchaseHandler) FullSyncStoreSkus(ctx *jxcontext.Context, parentTask t func (p *PurchaseHandler) DeleteRemoteStoreSkus(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, isAsync, isContinueWhenError bool) (hint string, err error) { return hint, err } + +func (p *PurchaseHandler) GetStoresSku(ctx *jxcontext.Context, parentTask tasksch.ITask, storeIDs []int) (storeSkuList []*model.StoreSkuBind, err error) { + return storeSkuList, err +} + +func (p *PurchaseHandler) GetStoreSkusInfo(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, vendorStoreID string, inStoreSkuList []*partner.BareStoreSkuInfo) (outStoreSkuList []*partner.BareStoreSkuInfo, err error) { + return outStoreSkuList, err +} diff --git a/business/partner/purchase/jd/act.go b/business/partner/purchase/jd/act.go new file mode 100644 index 000000000..f2dbcedde --- /dev/null +++ b/business/partner/purchase/jd/act.go @@ -0,0 +1,183 @@ +package jd + +import ( + "time" + + "git.rosy.net.cn/jx-callback/business/jxutils" + + "git.rosy.net.cn/jx-callback/globals" + "git.rosy.net.cn/jx-callback/globals/api" + + "git.rosy.net.cn/baseapi/platformapi/jdapi" + "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" + "git.rosy.net.cn/jx-callback/business/model" +) + +func CreatePromotionInfos(promotionType int, name string, beginDate, endDate time.Time, outInfoId, advertising, traceId string) (infoId int64, err error) { + if globals.EnableJdStoreWrite { + if promotionType == model.ActSkuDirectDown { + return api.JdAPI.CreatePromotionInfosSingle(name, beginDate, endDate, outInfoId, advertising, traceId) + } else { + return api.JdAPI.CreatePromotionInfosLimitTime(name, beginDate, endDate, outInfoId, advertising, traceId) + } + } else { + infoId = jxutils.GenFakeID() + } + return infoId, err +} + +func CreatePromotionRules(promotionType int, infoId int64, outInfoId string, limitDevice, limitPin, limitCount, limitDaily int, traceId string) (err error) { + if globals.EnableJdStoreWrite { + if promotionType == model.ActSkuDirectDown { + return api.JdAPI.CreatePromotionRulesSingle(infoId, outInfoId, limitDevice, limitPin, limitCount, limitDaily, traceId) + } else { + return api.JdAPI.CreatePromotionRulesLimitTime(infoId, outInfoId, limitDevice, limitPin, limitCount, limitDaily, traceId) + } + } + return err +} + +func CreatePromotionSku(promotionType int, infoId int64, outInfoId string, skus []*jdapi.PromotionSku, traceId string) (skusResult []*jdapi.PromotionSku, err error) { + if globals.EnableJdStoreWrite { + if promotionType == model.ActSkuDirectDown { + return api.JdAPI.CreatePromotionSkuSingle(infoId, outInfoId, skus, traceId) + } else { + return api.JdAPI.CreatePromotionSkuLimitTime(infoId, outInfoId, skus, traceId) + } + } + return skusResult, err +} + +func CancelPromotionSku(promotionType int, infoId int64, outInfoId string, skus []*jdapi.PromotionSku, traceId string) (err error) { + if globals.EnableJdStoreWrite { + if promotionType == model.ActSkuDirectDown { + return api.JdAPI.CancelPromotionSkuSingle(infoId, outInfoId, skus, traceId) + } else { + return api.JdAPI.CancelPromotionSkuLimitTime(infoId, outInfoId, skus, traceId) + } + } + return err +} + +func ConfirmPromotion(promotionType int, infoId int64, outInfoId, traceId string) (err error) { + if globals.EnableJdStoreWrite { + if promotionType == model.ActSkuDirectDown { + return api.JdAPI.ConfirmPromotionSingle(infoId, outInfoId, traceId) + } else { + return api.JdAPI.ConfirmPromotionLimitTime(infoId, outInfoId, traceId) + } + } + return err +} + +func CancelPromotion(promotionType int, infoId int64, outInfoId, traceId string) (err error) { + if globals.EnableJdStoreWrite { + if promotionType == model.ActSkuDirectDown { + return api.JdAPI.CancelPromotionSingle(infoId, outInfoId, traceId) + } else { + return api.JdAPI.CancelPromotionLimitTime(infoId, outInfoId, traceId) + } + } + return err +} + +func AdjustPromotionTime(promotionType int, infoId int64, outInfoId string, endDate time.Time, traceId string) (err error) { + if globals.EnableJdStoreWrite { + if promotionType == model.ActSkuDirectDown { + return api.JdAPI.AdjustPromotionTimeSingle(infoId, outInfoId, endDate, traceId) + } else { + return api.JdAPI.AdjustPromotionTimeLimitTime(infoId, outInfoId, endDate, traceId) + } + } + return err +} + +func AdjustPromotionSku(promotionType int, infoId int64, outInfoId string, skus []*jdapi.PromotionSku, traceId string) (skusResult []*jdapi.PromotionSku, err error) { + if globals.EnableJdStoreWrite { + if promotionType == model.ActSkuDirectDown { + return api.JdAPI.AdjustPromotionSkuSingle(infoId, outInfoId, skus, traceId) + } else { + return api.JdAPI.AdjustPromotionSkuLimitTime(infoId, outInfoId, skus, traceId) + } + } + return skusResult, err +} + +func getTraceID(ctx *jxcontext.Context) (traceID string) { + traceID = ctx.GetUserName() + utils.GetUUID() + return traceID +} + +func storeSku2Jd(actStoreSku []*model.ActStoreSku2, handler func(syncStatus int) bool) (jdActStoreSku []*jdapi.PromotionSku) { + for _, v := range actStoreSku { + if handler(v.SyncStatus) { + jdActStoreSku = append(jdActStoreSku, &jdapi.PromotionSku{ + StationNo: utils.Str2Int64(v.VendorStoreID), + SkuID: utils.Str2Int64(v.VendorSkuID), + PromotionPrice: v.ActPrice, + // LimitSkuCount:0, + }) + } + } + return jdActStoreSku +} + +func (c *PurchaseHandler) CreateAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actOrderRules []*model.ActOrderRule, actStoreMap []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + traceID := getTraceID(ctx) + if act.Type < model.ActOrderBegin { + outInfoID := utils.Int2Str(act.ID) + infoID, err2 := CreatePromotionInfos(act.Type, act.Name, act.BeginAt, act.EndAt, outInfoID, act.Advertising, traceID) + if err = err2; err == nil { + act.VendorActID = utils.Int64ToStr(infoID) + if err = CreatePromotionRules(act.Type, infoID, "", act.LimitDevice, act.LimitPin, act.LimitCount, act.LimitDaily, traceID); err == nil { + if _, err = CreatePromotionSku(act.Type, infoID, "", storeSku2Jd(actStoreSku, model.IsSyncStatusNeedCreate), traceID); err == nil { + err = ConfirmPromotion(act.Type, infoID, "", traceID) + } + } + } + } else { + + } + return err +} + +func (c *PurchaseHandler) UpdateAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actOrderRules []*model.ActOrderRule, actStoreMap2Remove, actStoreMap2Add, actStoreMap2Update []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + traceID := getTraceID(ctx) + if act.Type < model.ActOrderBegin { + outInfoID := utils.Int2Str(act.ID) + if !utils.IsTimeZero(act.EndAt) { + if err = AdjustPromotionTime(act.Type, 0, outInfoID, act.EndAt, traceID); err != nil { + return err + } + } + if toBeDeleted := storeSku2Jd(actStoreSku, model.IsSyncStatusNeedDelete); len(toBeDeleted) > 0 { + if err = CancelPromotionSku(act.Type, 0, outInfoID, toBeDeleted, traceID); err != nil { + return err + } + } + // if toBeAdded := storeSku2Jd(actStoreSku, model.IsSyncStatusNeedDelete); len(toBeAdded) > 0 { + // if _, err = CreatePromotionSku(act.Type, 0, outInfoID, toBeAdded, traceID); err != nil { + // return err + // } + // } + // if toBeUpdated := storeSku2Jd(actStoreSku, model.IsSyncStatusNeedDelete); len(toBeUpdated) > 0 { + // if _, err = AdjustPromotionSku(act.Type, 0, outInfoID, toBeUpdated, traceID); err != nil { + // return err + // } + // } + } else { + + } + return err +} + +func (c *PurchaseHandler) CancelAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actStoreMap []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + traceID := getTraceID(ctx) + if act.Type < model.ActOrderBegin { + outInfoID := utils.Int2Str(act.ID) + err = CancelPromotion(act.Type, 0, outInfoID, traceID) + } + return err +} diff --git a/business/partner/purchase/jd/callback.go b/business/partner/purchase/jd/callback.go index cf1d8c55f..6b9b91d2f 100644 --- a/business/partner/purchase/jd/callback.go +++ b/business/partner/purchase/jd/callback.go @@ -1,42 +1,26 @@ package jd import ( - "errors" - "git.rosy.net.cn/baseapi/platformapi/jdapi" - "git.rosy.net.cn/baseapi/utils" ) func OnOrderMsg(msg *jdapi.CallbackOrderMsg) (retVal *jdapi.CallbackResponse) { if curPurchaseHandler != nil { - if retVal = curPurchaseHandler.OnOrderMsg(msg); retVal == nil { - retVal = jdapi.Err2CallbackResponse(errors.New("Internal Error"), "") - } + retVal = curPurchaseHandler.OnOrderMsg(msg) } return retVal } func OnWaybillMsg(msg *jdapi.CallbackDeliveryStatusMsg) (retVal *jdapi.CallbackResponse) { if curPurchaseHandler != nil { - if retVal = curPurchaseHandler.OnWaybillMsg(msg); retVal == nil { - retVal = jdapi.Err2CallbackResponse(errors.New("Internal Error"), "") - } + retVal = curPurchaseHandler.OnWaybillMsg(msg) } return retVal } func OnStoreMsg(msg *jdapi.CallbackOrderMsg) (retVal *jdapi.CallbackResponse) { if curPurchaseHandler != nil { - retVal = curPurchaseHandler.onStoreMsg(msg) - } - return retVal -} - -func OnAfterSaleMsg(msg *jdapi.CallbackOrderMsg) (retVal *jdapi.CallbackResponse) { - if curPurchaseHandler != nil { - utils.CallFuncAsync(func() { - OnFinancialMsg(msg) - }) + retVal = curPurchaseHandler.OnStoreMsg(msg) } return retVal } diff --git a/business/partner/purchase/jd/financial.go b/business/partner/purchase/jd/financial.go index 1c4fef6ba..ee015174b 100644 --- a/business/partner/purchase/jd/financial.go +++ b/business/partner/purchase/jd/financial.go @@ -9,8 +9,15 @@ import ( "git.rosy.net.cn/jx-callback/globals/api" ) +func (p *PurchaseHandler) OnFinancialMsg(msg *jdapi.CallbackOrderMsg) (retVal *jdapi.CallbackResponse) { + utils.CallFuncAsync(func() { + retVal = p.onFinancialMsg(msg) + }) + return retVal +} + // 京东正向/退款订单类型处理--存储 -func OnFinancialMsg(msg *jdapi.CallbackOrderMsg) (retVal *jdapi.CallbackResponse) { +func (p *PurchaseHandler) onFinancialMsg(msg *jdapi.CallbackOrderMsg) (retVal *jdapi.CallbackResponse) { var err error // if msg.StatusID == jdapi.OrderStatusPayFinishedSettle || msg.StatusID == jdapi.OrderStatusTipChanged || msg.StatusID == jdapi.OrderStatusSwitch2SelfSettle { // 如果是正向单 if msg.StatusID == jdapi.OrderStatusPayFinishedSettle || msg.StatusID == jdapi.OrderStatusTipChanged || msg.StatusID == jdapi.OrderStatusAdjustSettle || msg.StatusID == jdapi.OrderStatusSwitch2SelfSettle { // 如果是正向单 @@ -30,7 +37,7 @@ func OnFinancialMsg(msg *jdapi.CallbackOrderMsg) (retVal *jdapi.CallbackResponse } else { err = nil } - } else if msg.StatusID == jdapi.SaleBillStatusRefundSuccess || msg.StatusID == jdapi.SaleBillStatusSaleReturnSuccess { // 如果是退款单 + } else if msg.StatusID == jdapi.AfsServiceStateRefundSuccess || msg.StatusID == jdapi.AfsServiceStateReturnGoodsSuccess { // 如果是退款单 orderData, err2 := api.JdAPI.GetAfsService(msg.BillID) if err = err2; err == nil { err = partner.CurOrderManager.SaveAfsOrderFinancialInfo(curPurchaseHandler.AfsOrderDetail2Financial(orderData)) @@ -127,18 +134,20 @@ func (p *PurchaseHandler) OrderDetail2Financial(orderData map[string]interface{} orderFinancial.TotalDiscountMoney += discountPrice if xMap["orderShareRatioData"] != nil { orderShareRatioData, _ := utils.HTTPBody2Values([]byte(utils.Interface2String(xMap["orderShareRatioData"])), false) - activity := &model.OrderDiscountFinancial{ - VendorID: orderFinancial.VendorID, - VendorOrderID: orderFinancial.VendorOrderID, - VendorActivityID: utils.Interface2String(orderShareRatioData["promotionId"][0]), - Type: utils.Int64ToStr(int64(discountType)), - // ActivityName: utils.Interface2String(xMap["discountName"]), - // ActivityMoney: discountPrice, - // Remark: utils.Interface2String(xMap["orderShareRatioData"]), + if promotionID := orderShareRatioData.Get("promotionId"); promotionID != "" { + activity := &model.OrderDiscountFinancial{ + VendorID: orderFinancial.VendorID, + VendorOrderID: orderFinancial.VendorOrderID, + VendorActivityID: promotionID, // utils.Interface2String(orderShareRatioData["promotionId"][0]), + Type: utils.Int64ToStr(int64(discountType)), + // ActivityName: utils.Interface2String(xMap["discountName"]), + // ActivityMoney: discountPrice, + // Remark: utils.Interface2String(xMap["orderShareRatioData"]), + } + orderFinancial.Discounts = append(orderFinancial.Discounts, activity) + // 通过活动Id去取,京西活动补贴 + // orderFinancial.JxSubsidyMoney += } - orderFinancial.Discounts = append(orderFinancial.Discounts, activity) - // 通过活动Id去取,京西活动补贴 - // orderFinancial.JxSubsidyMoney += } } globals.SugarLogger.Debug(utils.Format4Output(orderFinancial.Discounts, false)) @@ -154,7 +163,7 @@ func (p *PurchaseHandler) OrderDetail2Financial(orderData map[string]interface{} orderFinancial.PmSubsidyMoney = utils.Interface2Int64WithDefault(order1["platOrderGoodsDiscountMoney"], 0) + orderFinancial.PmSkuSubsidyMoney } else { if !isFromOrderDetail { - globals.SugarLogger.Warnf("jd OrderDetail2Financial, orderID:%s is not found from api.JdAPI.OrderShoudSettlementService", orderFinancial.VendorOrderID) + globals.SugarLogger.Warnf("jd OrderDetail2Financial, orderID:%s is not found from api.JdAPI.OrderShoudSettlementService, err:%v", orderFinancial.VendorOrderID, err) } } return orderFinancial, err @@ -168,7 +177,7 @@ func (p *PurchaseHandler) AfsOrderDetail2Financial(orderData map[string]interfac VendorOrderID: utils.Interface2String(orderData["orderId"]), VendorStoreID: utils.Interface2String(orderData["stationId"]), StoreID: int(utils.Str2Int64WithDefault(utils.Interface2String(orderData["stationNumOutSystem"]), 0)), - AfsCreateAt: utils.Timestamp2Time(utils.MustInterface2Int64(orderData["updateTime"].(map[string]interface{})["time"]) / 1000), + AfsCreatedAt: utils.Timestamp2Time(utils.MustInterface2Int64(orderData["updateTime"].(map[string]interface{})["time"]) / 1000), FreightUserMoney: utils.MustInterface2Int64(orderData["orderFreightMoney"]), AfsFreightMoney: utils.MustInterface2Int64(orderData["afsFreight"]), BoxMoney: utils.MustInterface2Int64(orderData["packagingMoney"]), diff --git a/business/partner/purchase/jd/financial_test.go b/business/partner/purchase/jd/financial_test.go index 3ce5a73fe..d8a72aab1 100644 --- a/business/partner/purchase/jd/financial_test.go +++ b/business/partner/purchase/jd/financial_test.go @@ -12,6 +12,6 @@ func TestOnFinancialMsg(t *testing.T) { BillID: "907315020000322", StatusID: "330902", } - res := OnFinancialMsg(msg) + res := curPurchaseHandler.onFinancialMsg(msg) fmt.Println(res) } diff --git a/business/partner/purchase/jd/jd.go b/business/partner/purchase/jd/jd.go index 45490f29a..be462ff12 100644 --- a/business/partner/purchase/jd/jd.go +++ b/business/partner/purchase/jd/jd.go @@ -42,7 +42,7 @@ func JdStoreStatus2JxStatus(yn, closeStatus interface{}) int { if yn2 == 1 { return model.StoreStatusDisabled } else if closeStatus2 == 1 { - return model.StoreStatusClosed + return model.StoreStatusHaveRest } return model.StoreStatusOpened } @@ -51,7 +51,7 @@ func JxStoreStatus2JdStatus(status int) (yn, closeStatus int) { switch status { case model.StoreStatusDisabled: return 1, 1 - case model.StoreStatusClosed: + case model.StoreStatusHaveRest, model.StoreStatusClosed: return 0, 1 default: return 0, 0 diff --git a/business/partner/purchase/jd/order.go b/business/partner/purchase/jd/order.go index 5d86f018b..1d887dc6f 100644 --- a/business/partner/purchase/jd/order.go +++ b/business/partner/purchase/jd/order.go @@ -5,6 +5,8 @@ import ( "strings" "time" + "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" + "git.rosy.net.cn/baseapi/platformapi/autonavi" "git.rosy.net.cn/baseapi/platformapi/jdapi" "git.rosy.net.cn/baseapi/utils" @@ -19,8 +21,8 @@ import ( var ( VendorStatus2StatusMap = map[string]int{ jdapi.OrderStatusPurchased: model.OrderStatusNew, - jdapi.OrderStatusNew: model.OrderStatusNew, jdapi.OrderStatusWaitOutStore: model.OrderStatusAccepted, + jdapi.StatusIDWaitOutStore: model.OrderStatusAccepted, jdapi.OrderStatusFinishedPickup: model.OrderStatusFinishedPickup, jdapi.OrderStatusDelivering: model.OrderStatusDelivering, jdapi.OrderStatusDelivered: model.OrderStatusFinished, @@ -42,38 +44,83 @@ func (c *PurchaseHandler) OnOrderMsg(msg *jdapi.CallbackOrderMsg) (retVal *jdapi } func (c *PurchaseHandler) onOrderMsg(msg *jdapi.CallbackOrderMsg) (retVal *jdapi.CallbackResponse) { - if jdapi.OrderStatusNew == msg.StatusID { - retVal = c.onOrderNew(msg) - } else if jdapi.OrderStatusAdjust == msg.StatusID { - retVal = c.onOrderAdjust(msg) + status := c.callbackMsg2Status(msg) + if jdapi.StatusIDNewOrder == msg.StatusID { + status.Status = model.OrderStatusNew // 因为京东将事件32000与状态32000混用,事件32000可能是新订单,也可能是已接单,统一当成新订单处理 + } + if partner.CurOrderManager.GetStatusDuplicatedCount(status) > 0 { + return nil + } + if msg.MsgURL == jdapi.CallbackMsgOrderAccounting { + retVal = c.OnFinancialMsg(msg) + } else if msg.MsgURL == jdapi.CallbackMsgAfterSaleBillStatus { + retVal = c.OnAfsOrderMsg(msg) } else { - status := c.callbackMsg2Status(msg) - if msg.StatusID == jdapi.OrderStatusAddComment || msg.StatusID == jdapi.OrderStatusModifyComment { - utils.CallFuncAsync(func() { - c.onOrderComment2(msg) - }) + // 新订单事件,与订单状态有点冲突 + if jdapi.StatusIDNewOrder == msg.StatusID { + retVal = c.onOrderNew(msg, status) + } else if jdapi.OrderStatusAdjust == msg.StatusID { + retVal = c.onOrderAdjust(msg, status) + } else { + if msg.StatusID == jdapi.OrderStatusAddComment || msg.StatusID == jdapi.OrderStatusModifyComment { + utils.CallFuncAsync(func() { + c.onOrderComment2(msg) + }) + } + err := partner.CurOrderManager.OnOrderStatusChanged(status) + // if globals.HandleLegacyJxOrder && err == nil { + // c.legacyJdOrderStatusChanged(status) + // } + retVal = jdapi.Err2CallbackResponse(err, status.VendorStatus) } - err := partner.CurOrderManager.OnOrderStatusChanged(status) - // if globals.HandleLegacyJxOrder && err == nil { - // c.legacyJdOrderStatusChanged(status) - // } - retVal = jdapi.Err2CallbackResponse(err, status.VendorStatus) } return retVal } func (c *PurchaseHandler) getOrder(orderID string) (order *model.GoodsOrder, orderMap map[string]interface{}, err error) { - globals.SugarLogger.Debugf("jd GetOrder orderID:%s", orderID) - if orderMap, err = api.JdAPI.QuerySingleOrder(orderID); err == nil { - order = c.Map2Order(orderMap) - if jxutils.IsMobileFake(order.ConsigneeMobile) { - if realMobile, err := api.JdAPI.GetRealMobile4Order(orderID, order.VendorStoreID); err == nil { // 故意强制忽略取不到真实手机号错误 - order.ConsigneeMobile2 = jxutils.FormalizeMobile(realMobile) - } else { - // globals.SugarLogger.Warnf("jd GetOrder orderID:%s, GetRealMobile4Order failed with error:%v", orderID, err2) + globals.SugarLogger.Debugf("jd getOrder orderID:%s", orderID) + var ( + realMobile string + orderSettlement *jdapi.OrderSettlementInfo + ) + task := tasksch.NewParallelTask("jd getOrder", tasksch.NewParallelConfig().SetIsContinueWhenError(true), jxcontext.AdminCtx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + taskIndex := batchItemList[0].(int) + switch taskIndex { + case 0: + orderMap, err = api.JdAPI.QuerySingleOrder(orderID) + if err == nil { + order = c.Map2Order(orderMap) + realMobile, err = api.JdAPI.GetRealMobile4Order(orderID, order.VendorStoreID) + if realMobile != "" { + order.ConsigneeMobile2 = jxutils.FormalizeMobile(realMobile) + } + } + case 1: + orderSettlement, err = api.JdAPI.OrderShoudSettlementService2(orderID) } + return nil, err + }, []int{0, 1}) + task.Run() + task.GetResult(0) + if order != nil { + if orderSettlement != nil { + order.TotalShopMoney = orderSettlement.SettlementAmount + order.PmSubsidyMoney = orderSettlement.PlatOrderGoodsDiscountMoney + orderSettlement.PlatSkuGoodsDiscountMoney } } + // if orderMap, err = api.JdAPI.QuerySingleOrder(orderID); err == nil { + // globals.SugarLogger.Debugf("jd getOrder2 orderID:%s", orderID) + // order = c.Map2Order(orderMap) + // if jxutils.IsMobileFake(order.ConsigneeMobile) { + // if realMobile, err := api.JdAPI.GetRealMobile4Order(orderID, order.VendorStoreID); err == nil { // 故意强制忽略取不到真实手机号错误 + // globals.SugarLogger.Debugf("jd getOrder3 orderID:%s", orderID) + // order.ConsigneeMobile2 = jxutils.FormalizeMobile(realMobile) + // } else { + // // globals.SugarLogger.Warnf("jd GetOrder orderID:%s, GetRealMobile4Order failed with error:%v", orderID, err2) + // } + // } + // } return order, orderMap, err } @@ -85,6 +132,8 @@ func (c *PurchaseHandler) GetOrder(orderID string) (order *model.GoodsOrder, err func (c *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *model.GoodsOrder) { result := orderData orderID := utils.Int64ToStr(utils.MustInterface2Int64(result["orderId"])) + globals.SugarLogger.Debugf("jd Map2Order orderID:%s", orderID) + const defaultStatusTimeField = "orderPurchaseTime" statusTimeField := defaultStatusTimeField if result[statusTimeField] == nil { // 814560888003021 orderPurchaseTime为空 @@ -108,7 +157,6 @@ func (c *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *mo StatusTime: utils.Str2Time(result[statusTimeField].(string)), OriginalData: string(utils.MustMarshal(result)), ActualPayPrice: utils.MustInterface2Int64(result["orderBuyerPayableMoney"]), - Skus: []*model.OrderSku{}, } order.Status = c.GetStatusFromVendorStatus(order.VendorStatus) businessTage := utils.Interface2String(result["businessTag"]) @@ -143,8 +191,11 @@ func (c *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *mo VendorSkuID: utils.Int64ToStr(utils.MustInterface2Int64(product["skuId"])), SkuName: product["skuName"].(string), Weight: jxutils.FloatWeight2Int(float32(utils.MustInterface2Float64(product["skuWeight"]))), + VendorPrice: utils.MustInterface2Int64(product["skuStorePrice"]), SalePrice: utils.MustInterface2Int64(product["skuJdPrice"]), - PromotionType: int(utils.MustInterface2Int64(product["promotionType"])), + } + if jdPromotionType := int(utils.MustInterface2Int64(product["promotionType"])); jdPromotionType != jdapi.PromotionTypeNormal { + sku.StoreSubName = utils.Int2Str(jdPromotionType) } if skuCostumeProperty, ok := product["skuCostumeProperty"]; ok { sku.SkuName += skuCostumeProperty.(string) @@ -153,26 +204,18 @@ func (c *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *mo sku.SkuType = 1 } order.Skus = append(order.Skus, sku) - order.SkuCount++ - order.GoodsCount += sku.Count - order.SalePrice += sku.SalePrice * int64(sku.Count) - order.Weight += sku.Weight * sku.Count } - setOrederDetailFee(result, order) + jxutils.RefreshOrderSkuRelated(order) return order } -func setOrederDetailFee(result map[string]interface{}, order *model.GoodsOrder) { - order.BoxFee = utils.Interface2Int64WithDefault(result["packagingMoney"], 0) - order.PlatformFeeRate = model.JdPlatformFeeRate - order.BillStoreFreightFee = utils.Interface2Int64WithDefault(result["merchantPaymentDistanceFreightMoney"], 0) + utils.Interface2Int64WithDefault(result["tips"], 0) -} - // -func (c *PurchaseHandler) onOrderNew(msg *jdapi.CallbackOrderMsg) (response *jdapi.CallbackResponse) { +func (c *PurchaseHandler) onOrderNew(msg *jdapi.CallbackOrderMsg, orderStatus *model.OrderStatus) (response *jdapi.CallbackResponse) { + globals.SugarLogger.Debugf("onOrderNew orderID:%s", msg.BillID) order, orderMap, err := c.getOrder(msg.BillID) if err == nil { - if err = partner.CurOrderManager.OnOrderNew(order, msg.StatusID); err == nil { + globals.SugarLogger.Debugf("onOrderNew2 orderID:%s", msg.BillID) + if err = partner.CurOrderManager.OnOrderNew(order, orderStatus); err == nil { utils.CallFuncAsync(func() { c.OnOrderDetail(orderMap, partner.CreatedPeration) }) @@ -181,10 +224,10 @@ func (c *PurchaseHandler) onOrderNew(msg *jdapi.CallbackOrderMsg) (response *jda return jdapi.Err2CallbackResponse(err, "jd onOrderNew") } -func (c *PurchaseHandler) onOrderAdjust(msg *jdapi.CallbackOrderMsg) *jdapi.CallbackResponse { +func (c *PurchaseHandler) onOrderAdjust(msg *jdapi.CallbackOrderMsg, orderStatus *model.OrderStatus) *jdapi.CallbackResponse { order, orderMap, err := c.getOrder(msg.BillID) if err == nil { - err = partner.CurOrderManager.OnOrderAdjust(order, msg.StatusID) + err = partner.CurOrderManager.OnOrderAdjust(order, orderStatus) if err == nil { utils.CallFuncAsync(func() { c.OnOrderDetail(orderMap, partner.UpdatedPeration) @@ -233,14 +276,14 @@ func (c *PurchaseHandler) AcceptOrRefuseOrder(order *model.GoodsOrder, isAcceptI if globals.EnableStoreWrite { err = api.JdAPI.OrderAcceptOperate(order.VendorOrderID, isAcceptIt, userName) } else { - c.postFakeMsg(order.VendorOrderID, jdapi.OrderStatusWaitOutStore) + c.postFakeMsg(order.VendorOrderID, jdapi.StatusIDWaitOutStore) } return err } func (c *PurchaseHandler) PickupGoods(order *model.GoodsOrder, isSelfDelivery bool, userName string) (err error) { globals.SugarLogger.Debugf("jd PickupGoods orderID:%s, isSelfDelivery:%t", order.VendorOrderID, isSelfDelivery) - if !isSelfDelivery && globals.EnableStoreWrite { + if !isSelfDelivery && globals.EnableJdStoreWrite { _, err = api.JdAPI.OrderJDZBDelivery(order.VendorOrderID, userName) } else { c.postFakeMsg(order.VendorOrderID, jdapi.OrderStatusFinishedPickup) @@ -249,21 +292,21 @@ func (c *PurchaseHandler) PickupGoods(order *model.GoodsOrder, isSelfDelivery bo } func (p *PurchaseHandler) AcceptOrRefuseFailedGetOrder(ctx *jxcontext.Context, order *model.GoodsOrder, isAcceptIt bool) (err error) { - if globals.EnableStoreWrite { + if globals.EnableJdStoreWrite { err = api.JdAPI.ReceiveFailedAudit(order.VendorOrderID, isAcceptIt, ctx.GetUserName(), "") } return err } func (p *PurchaseHandler) CallCourier(ctx *jxcontext.Context, order *model.GoodsOrder) (err error) { // 拣货失败后再次招唤平台配送 - if globals.EnableStoreWrite { + if globals.EnableJdStoreWrite { err = api.JdAPI.UrgeDispatching(order.VendorOrderID, ctx.GetUserName()) } return err } func (p *PurchaseHandler) ConfirmReceiveGoods(ctx *jxcontext.Context, order *model.GoodsOrder) (err error) { // 投递失败后确认收到退货 - if globals.EnableStoreWrite { + if globals.EnableJdStoreWrite { err = api.JdAPI.ConfirmReceiveGoods(order.VendorOrderID) } return err @@ -271,7 +314,7 @@ func (p *PurchaseHandler) ConfirmReceiveGoods(ctx *jxcontext.Context, order *mod func (c *PurchaseHandler) Swtich2SelfDeliver(order *model.GoodsOrder, userName string) (err error) { globals.SugarLogger.Debugf("jd Swtich2SelfDeliver orderID:%s", order.VendorOrderID) - if globals.EnableStoreWrite { + if globals.EnableJdStoreWrite { _, err = api.JdAPI.ModifySellerDelivery(order.VendorOrderID, userName) if err != nil { if errWithCode, ok := err.(*utils.ErrorWithCode); ok && errWithCode.Level() == 1 { @@ -292,7 +335,7 @@ func (c *PurchaseHandler) Swtich2SelfDeliver(order *model.GoodsOrder, userName s func (c *PurchaseHandler) Swtich2SelfDelivered(order *model.GoodsOrder, userName string) (err error) { globals.SugarLogger.Debugf("jd Swtich2SelfDelivered orderID:%s", order.VendorOrderID) - if globals.EnableStoreWrite { + if globals.EnableJdStoreWrite { _, err = api.JdAPI.DeliveryEndOrder(order.VendorOrderID, userName) } return err @@ -300,7 +343,7 @@ func (c *PurchaseHandler) Swtich2SelfDelivered(order *model.GoodsOrder, userName func (c *PurchaseHandler) SelfDeliverDelivering(order *model.GoodsOrder, userName string) (err error) { globals.SugarLogger.Debugf("jd SelfDeliverDelivering orderID:%s", order.VendorOrderID) - if globals.EnableStoreWrite { + if globals.EnableJdStoreWrite { _, err = api.JdAPI.OrderSerllerDelivery(order.VendorOrderID, userName) } return err @@ -309,7 +352,7 @@ func (c *PurchaseHandler) SelfDeliverDelivering(order *model.GoodsOrder, userNam // 京东送达接口都是一样的 func (c *PurchaseHandler) SelfDeliverDelivered(order *model.GoodsOrder, userName string) (err error) { globals.SugarLogger.Debugf("jd SelfDeliverDelivered orderID:%s", order.VendorOrderID) - if globals.EnableStoreWrite { + if globals.EnableJdStoreWrite { err = c.Swtich2SelfDelivered(order, userName) } return err @@ -321,7 +364,7 @@ func (c *PurchaseHandler) GetOrderRealMobile(ctx *jxcontext.Context, order *mode } func (c *PurchaseHandler) AgreeOrRefuseCancel(ctx *jxcontext.Context, order *model.GoodsOrder, isAgree bool, reason string) (err error) { - if globals.EnableStoreWrite { + if globals.EnableJdStoreWrite { err = api.JdAPI.OrderCancelOperate(order.VendorOrderID, isAgree, ctx.GetUserName(), reason) } return err @@ -333,29 +376,15 @@ func (c *PurchaseHandler) CancelOrder(ctx *jxcontext.Context, order *model.Goods } func (c *PurchaseHandler) AdjustOrder(ctx *jxcontext.Context, order *model.GoodsOrder, removedSkuList []*model.OrderSku, reason string) (err error) { - removedSkuMap := make(map[int]*model.OrderSku) - for _, sku := range removedSkuList { - removedSkuMap[jxutils.GetSkuIDFromOrderSku(sku)] = sku - } + order = jxutils.RemoveSkuFromOrder(order, removedSkuList) var oaosAdjustDTOList []*jdapi.OAOSAdjustDTO for _, sku := range order.Skus { - skuID := jxutils.GetSkuIDFromOrderSku(sku) - tmp := &jdapi.OAOSAdjustDTO{ - OutSkuID: utils.Int2Str(skuID), + oaosAdjustDTOList = append(oaosAdjustDTOList, &jdapi.OAOSAdjustDTO{ + OutSkuID: utils.Int2Str(jxutils.GetSkuIDFromOrderSku(sku)), SkuCount: sku.Count, - } - if removedSkuMap[skuID] != nil { - if removedSkuMap[skuID].Count >= sku.Count { - tmp = nil - } else { - tmp.SkuCount -= removedSkuMap[skuID].Count - } - } - if tmp != nil { - oaosAdjustDTOList = append(oaosAdjustDTOList, tmp) - } + }) } - if globals.EnableStoreWrite { + if globals.EnableJdStoreWrite { err = api.JdAPI.AdjustOrder(order.VendorOrderID, ctx.GetUserName(), reason, oaosAdjustDTOList) } return err diff --git a/business/partner/purchase/jd/order_afs.go b/business/partner/purchase/jd/order_afs.go new file mode 100644 index 000000000..c0b73c099 --- /dev/null +++ b/business/partner/purchase/jd/order_afs.go @@ -0,0 +1,190 @@ +package jd + +import ( + "git.rosy.net.cn/baseapi/platformapi/jdapi" + "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-callback/business/jxutils" + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "git.rosy.net.cn/jx-callback/business/model" + "git.rosy.net.cn/jx-callback/business/partner" + "git.rosy.net.cn/jx-callback/globals" + "git.rosy.net.cn/jx-callback/globals/api" +) + +var ( + AfsVendorStatus2StatusMap = map[string]int{ + jdapi.AfsServiceStateWaiting4Audit: model.AfsOrderStatusWait4Approve, // 需要审核 + + jdapi.AfsServiceStateRefundProcessing: model.AfsOrderStatusNew, + jdapi.AfsServiceStateWaiting4DirectCompensate: model.AfsOrderStatusNew, + jdapi.AfsServiceStateWaiting4ReturnGoods: model.AfsOrderStatusNew, + + jdapi.AfsServiceStateWaiting4MerchantReceiveGoods: model.AfsOrderStatusWait4ReceiveGoods, + + jdapi.AfsServiceStateRefundSuccess: model.AfsOrderStatusFinished, + jdapi.AfsServiceStateSolved: model.AfsOrderStatusFinished, + jdapi.AfsServiceStateDirectCompensateSuccess: model.AfsOrderStatusFinished, + jdapi.AfsServiceStateReturnGoodsSuccess: model.AfsOrderStatusFinished, + + jdapi.AfsServiceStateRefundFailed: model.AfsOrderStatusFailed, + jdapi.AfsServiceStateAuditRefused: model.AfsOrderStatusFailed, + jdapi.AfsServiceStateUserCanceled: model.AfsOrderStatusFailed, + jdapi.AfsServiceStateMerchantFailedReceiveGoods: model.AfsOrderStatusFailed, + jdapi.AfsServiceStateDirectCompensateFailed: model.AfsOrderStatusFailed, + jdapi.AfsServiceStateReturnGoodsFailed: model.AfsOrderStatusFailed, + } + + afsReasonTypeMap = map[int]int8{ + jdapi.AfsReasonTypeGoodsQuality: model.AfsReasonTypeGoodsQuality, + jdapi.AfsReasonTypeWrongGoods: model.AfsReasonTypeWrongGoods, + jdapi.AfsReasonTypeMissingGoods: model.AfsReasonTypeMissingGoods, + jdapi.AfsReasonTypeNoGoods: model.AfsReasonTypeNoGoods, + jdapi.AfsReasonTypeDamagedGoods: model.AfsReasonTypeDamagedGoods, + jdapi.AfsReasonTypeGoodsQuantity: model.AfsReasonTypeGoodsQuantity, + jdapi.AfsReasonTypeAgreedByMerchant: model.AfsReasonTypeAgreedByMerchant, + jdapi.AfsReasonTypeGoodsSizeNoSame: model.AfsReasonTypeGoodsNoSame, + jdapi.AfsReasonTypeGoodsColorNoSame: model.AfsReasonTypeGoodsNoSame, + jdapi.AfsReasonWrongPurchase: model.AfsReasonWrongPurchase, + jdapi.AfsReasonNotReceivedIntime: model.AfsReasonNotReceivedIntime, + } + afsAppealTypeMap = map[string]int8{ + jdapi.AfsDealTypeRefund: model.AfsAppealTypeRefund, + jdapi.AfsDealTypeReturnGoodsRefund: model.AfsAppealTypeReturnAndRefund, + jdapi.AfsDealTypeDirectCompensate: model.AfsAppealTypeNewGoods, + } + afsApproveTypeMap = map[int]int{ + partner.AfsApproveTypeRefund: jdapi.AfsApproveTypeRefund, + partner.AfsApproveTypeReturnGoods: jdapi.AfsApproveTypeReturnGoods, + partner.AfsApproveTypeRefused: jdapi.AfsApproveTypeRefused, + } +) + +func (c *PurchaseHandler) OnAfsOrderMsg(msg *jdapi.CallbackOrderMsg) (retVal *jdapi.CallbackResponse) { + jxutils.CallMsgHandlerAsync(func() { + retVal = c.onAfsOrderMsg(msg) + }, jxutils.ComposeUniversalOrderID(msg.BillID, model.VendorIDJD)) + return retVal +} + +func (c *PurchaseHandler) onAfsOrderMsg(msg *jdapi.CallbackOrderMsg) (retVal *jdapi.CallbackResponse) { + afsInfo, err := api.JdAPI.GetAfsService2(msg.BillID) + if err == nil { + status := c.callbackAfsMsg2Status(msg, afsInfo) + if status.Status == model.AfsOrderStatusWait4Approve || status.Status == model.AfsOrderStatusNew { + afsOrder := c.buildAfsOrder(afsInfo) + err = partner.CurOrderManager.OnAfsOrderNew(afsOrder, status) + } else { + err = partner.CurOrderManager.OnAfsOrderStatusChanged(status) + } + retVal = jdapi.Err2CallbackResponse(err, status.VendorStatus) + } + return retVal +} + +func (c *PurchaseHandler) callbackAfsMsg2Status(msg *jdapi.CallbackOrderMsg, afsInfo *jdapi.AfsServiceResponse) *model.OrderStatus { + orderStatus := &model.OrderStatus{ + VendorOrderID: msg.BillID, // 是售后单ID,不是订单ID,订单ID在RefVendorOrderID中 + VendorID: model.VendorIDJD, + OrderType: model.OrderTypeAfsOrder, + RefVendorOrderID: afsInfo.OrderID, + RefVendorID: model.VendorIDJD, + VendorStatus: msg.StatusID, + Status: c.GetAfsStatusFromVendorStatus(msg.StatusID), + StatusTime: utils.Str2Time(msg.Timestamp), + Remark: msg.Remark, + } + return orderStatus +} + +func (c *PurchaseHandler) GetAfsStatusFromVendorStatus(vendorStatus string) int { + if status, ok := AfsVendorStatus2StatusMap[vendorStatus]; ok { + return status + } + return model.OrderStatusUnknown +} + +func (c *PurchaseHandler) convertAfsReasonType(vendorReasonType int) int8 { + if status, ok := afsReasonTypeMap[vendorReasonType]; ok { + return status + } + return model.AfsReasonNotOthers +} + +func (c *PurchaseHandler) convertAfsAppealType(vendorAppealType string) int8 { + if status, ok := afsAppealTypeMap[vendorAppealType]; ok { + return status + } + globals.SugarLogger.Warnf("jd convertAfsAppealType unknown vendorAppealType:%d", vendorAppealType) + return 0 +} + +func (c *PurchaseHandler) buildAfsOrder(afsInfo *jdapi.AfsServiceResponse) (afsOrder *model.AfsOrder) { + afsOrder = &model.AfsOrder{ + VendorID: model.VendorIDJD, + AfsOrderID: afsInfo.AfsServiceOrder, + VendorOrderID: afsInfo.OrderID, + VendorStoreID: afsInfo.StationID, + StoreID: int(utils.Str2Int64WithDefault(afsInfo.StationNumOutSystem, 0)), + AfsCreatedAt: afsInfo.CreateTime.GoTime(), + FreightUserMoney: afsInfo.OrderFreightMoney, + AfsFreightMoney: afsInfo.AfsFreight, + BoxMoney: afsInfo.PackagingMoney, + TongchengFreightMoney: afsInfo.TongchengFreightMoney, + SkuBoxMoney: afsInfo.MealBoxMoney, + + VendorStatus: utils.Int2Str(afsInfo.AfsServiceState), + VendorReasonType: utils.Int2Str(afsInfo.QuestionTypeCid), + ReasonType: c.convertAfsReasonType(afsInfo.QuestionTypeCid), + ReasonDesc: utils.LimitUTF8StringLen(afsInfo.QuestionDesc, 1024), + ReasonImgList: utils.LimitUTF8StringLen(jdapi.ProcessQuestionPic(afsInfo.QuestionPic), 1024), + VendorAppealType: afsInfo.ApplyDeal, + AppealType: c.convertAfsAppealType(afsInfo.ApplyDeal), + } + afsOrder.Status = c.GetAfsStatusFromVendorStatus(afsOrder.VendorStatus) + + for _, x := range afsInfo.AfsDetailList { + orderSku := &model.OrderSkuFinancial{ + // VendorID: model.VendorIDJD, + // AfsOrderID: afsOrder.AfsOrderID, + // VendorOrderID: afsOrder.VendorOrderID, + // VendorStoreID: afsOrder.VendorStoreID, + // StoreID: afsOrder.StoreID, + // IsAfsOrder: 1, + + Count: x.SkuCount, + // ConfirmTime: afsOrder.AfsCreateAt, + VendorSkuID: utils.Int64ToStr(x.WareID), + SkuID: int(utils.Str2Int64WithDefault(x.SkuIDIsv, 0)), + Name: x.WareName, + UserMoney: x.AfsMoney, + PmSkuSubsidyMoney: x.PlatPayMoney, + } + afsOrder.PmSkuSubsidyMoney += orderSku.PmSkuSubsidyMoney + orderSku.PmSubsidyMoney += orderSku.PmSkuSubsidyMoney + + for _, y := range x.AfsSkuDiscountList { + orderSku.PmSubsidyMoney += y.PlatPayMoney + } + + afsOrder.SkuUserMoney += orderSku.UserMoney + afsOrder.PmSubsidyMoney += orderSku.PmSubsidyMoney + afsOrder.Skus = append(afsOrder.Skus, orderSku) + } + return afsOrder +} + +// 审核售后单申请 +func (c *PurchaseHandler) AgreeOrRefuseRefund(ctx *jxcontext.Context, order *model.AfsOrder, approveType int, reason string) (err error) { + if globals.EnableJdStoreWrite { + err = api.JdAPI.AfsOpenApprove(order.AfsOrderID, afsApproveTypeMap[approveType], reason, ctx.GetUserName()) + } + return err +} + +// 确认收到退货 +func (c *PurchaseHandler) ConfirmReceivedReturnGoods(ctx *jxcontext.Context, order *model.AfsOrder) (err error) { + if globals.EnableJdStoreWrite { + err = api.JdAPI.ConfirmReceipt(order.AfsOrderID, ctx.GetUserName()) + } + return err +} diff --git a/business/partner/purchase/jd/sku.go b/business/partner/purchase/jd/sku.go index 9dd7102bf..db977abb8 100644 --- a/business/partner/purchase/jd/sku.go +++ b/business/partner/purchase/jd/sku.go @@ -3,6 +3,7 @@ package jd // 这里函数取得的信息,除了与自身实体相关的ID(比如PARENT ID),都已经转换成了本地ID了 import ( + "fmt" "unicode/utf8" "git.rosy.net.cn/baseapi/platformapi/jdapi" @@ -130,7 +131,7 @@ func (p *PurchaseHandler) ReorderCategories(db *dao.DaoDB, parentCatID int, user func (p *PurchaseHandler) cuSku(db *dao.DaoDB, sku *model.Sku, handler func(skuExt *tSkuInfoExt, price int, skuName string, shopCategories []int64, addParams map[string]interface{}) (string, error)) (err error) { var skuInfoExt tSkuInfoExt err = dao.GetRow(nil, &skuInfoExt, ` - SELECT t2.*, t3.jd_id jd_cat_id, t3.jd_category_id, t4.jd_id sku_cat_id + SELECT t2.*, t3.jd_id jd_cat_id, IF(t2.jd_category_id > 0, t2.jd_category_id, t3.jd_category_id) jd_category_id, t4.jd_id sku_cat_id FROM sku t1 JOIN sku_name t2 ON t1.name_id = t2.id JOIN sku_category t3 ON t2.category_id = t3.id @@ -164,8 +165,14 @@ func (p *PurchaseHandler) cuSku(db *dao.DaoDB, sku *model.Sku, handler func(skuE if addParams["sellCities"] == nil { addParams["sellCities"] = []int{0} } + if skuInfoExt.DescImg != "" { + addParams[jdapi.KeyProductDesc] = fmt.Sprintf(`一张图片`, skuInfoExt.DescImg) + addParams[jdapi.KeyIfViewDesc] = 0 + } else { + addParams[jdapi.KeyIfViewDesc] = 1 + } if err == nil { - skuName := jxutils.ComposeSkuName(skuInfoExt.Prefix, skuInfoExt.Name, sku.Comment, skuInfoExt.Unit, sku.SpecQuality, sku.SpecUnit, 0) + skuName := jxutils.ComposeSkuName(skuInfoExt.Prefix, skuInfoExt.Name, sku.Comment, skuInfoExt.Unit, sku.SpecQuality, sku.SpecUnit, jdapi.MaxSkuNameCharCount) skuPrice := jxutils.CaculateSkuPrice(skuInfoExt.Price, sku.SpecQuality, sku.SpecUnit, skuInfoExt.Unit) if skuInfoExt.Upc != "" { addParams[jdapi.KeyUpcCode] = skuInfoExt.Upc @@ -385,7 +392,7 @@ func (p *PurchaseHandler) syncSkuNameAsSpu(db *dao.DaoDB, sku *model.Sku, skuExt skuNameJdID := skuExt.JdID globals.SugarLogger.Debugf("syncSkuNameAsSpu1 sku.id=%d, bareSkuName:%s, skuName:%s, skuNameJdID:%d", sku.ID, skuExt.Name, skuName, skuNameJdID) spuAddParams, skuAddParams := splitAddParams(addParams) - if !jxutils.IsFakeID(skuNameJdID) && sku.JdSyncStatus&model.SyncFlagDeletedMask != 0 { // 删除SKU + if !jxutils.IsEmptyID(skuNameJdID) && sku.JdSyncStatus&model.SyncFlagDeletedMask != 0 { // 删除SKU if globals.EnableStoreWrite { err = api.JdAPI.UpdateSkuBaseInfo(utils.Int2Str(skuExt.ID), utils.Int2Str(sku.ID), utils.Params2Map(jdapi.KeyFixedStatus, jdapi.SkuFixedStatusDeleted)) } @@ -412,7 +419,7 @@ func (p *PurchaseHandler) syncSkuNameAsSpu(db *dao.DaoDB, sku *model.Sku, skuExt } } } - } else if skuExt.JdSyncStatus&model.SyncFlagNewMask != 0 && jxutils.IsFakeID(skuNameJdID) { + } else if skuExt.JdSyncStatus&model.SyncFlagNewMask != 0 && jxutils.IsEmptyID(skuNameJdID) { if globals.EnableStoreWrite { spuName := jxutils.ComposeSpuName(skuExt.Prefix, skuExt.Name, 0) skus := []map[string]interface{}{ @@ -461,7 +468,7 @@ func (p *PurchaseHandler) syncSkuNameAsSpu(db *dao.DaoDB, sku *model.Sku, skuExt } } } - if err == nil && !jxutils.IsFakeID(skuNameJdID) { + if err == nil && !jxutils.IsEmptyID(skuNameJdID) { if sku.JdSyncStatus&model.SyncFlagNewMask != 0 { // 非首次新增SKU if globals.EnableStoreWrite { vendorSkuID2, err2 := api.JdAPI.AppendSku(utils.Int2Str(skuExt.ID), utils.Int2Str(sku.ID), skuName, price, jxutils.IntWeight2Float(sku.Weight), []string{skuExt.Img}, jxStatus2jdStatus(sku.Status), true, composeSkuSpec(sku.SpecQuality, sku.SpecUnit, skuExt.Unit), skuAddParams) diff --git a/business/partner/purchase/jd/store.go b/business/partner/purchase/jd/store.go index 1fb193740..34603eb9c 100644 --- a/business/partner/purchase/jd/store.go +++ b/business/partner/purchase/jd/store.go @@ -137,7 +137,7 @@ func (p *PurchaseHandler) UpdateStore(db *dao.DaoDB, storeID int, userName strin if store.DeliveryRangeType == model.DeliveryRangeTypePolygon { params["coordinatePoints"] = store.DeliveryRange } else { - params["deliveryRangeRadius"] = utils.Str2Int64(store.DeliveryRange) + params["deliveryRangeRadius"] = utils.Str2Int64WithDefault(store.DeliveryRange, 0) } } openTime2 := JxOperationTime2JdOperationTime(store.OpenTime2) @@ -147,7 +147,7 @@ func (p *PurchaseHandler) UpdateStore(db *dao.DaoDB, storeID int, userName strin } _, params["closeStatus"] = JxStoreStatus2JdStatus(jxutils.MergeStoreStatus(store.Status, store.JdStoreStatus)) globals.SugarLogger.Debug(utils.Format4Output(params, false)) - if globals.EnableStoreWrite { + if globals.EnableJdStoreWrite { if err = api.JdAPI.UpdateStoreInfo4Open(store.VendorStoreID, store.RealLastOperator, params); err != nil { return err } @@ -238,7 +238,7 @@ func (p *PurchaseHandler) RefreshAllStoresID(ctx *jxcontext.Context, parentTask if step != stepCount-1 { storeParams["outSystemId"] = utils.GetUUID() } - if true { //globals.EnableStoreWrite { + if globals.EnableJdStoreWrite { err = api.JdAPI.UpdateStoreInfo4Open(store.VendorStoreID, ctx.GetUserName(), storeParams) } return nil, err @@ -302,7 +302,7 @@ func (p *PurchaseHandler) GetStoreStatus(ctx *jxcontext.Context, vendorStoreID s } // 当前京东的storeCrud消息不会在门店状态改变时发送,所以意义不大,先放在这里 -func (c *PurchaseHandler) onStoreMsg(msg *jdapi.CallbackOrderMsg) (response *jdapi.CallbackResponse) { +func (c *PurchaseHandler) OnStoreMsg(msg *jdapi.CallbackOrderMsg) (response *jdapi.CallbackResponse) { var err error if msg.StatusID == jdapi.StatusIDUpdateStore { var storeStatus int diff --git a/business/partner/purchase/jd/store_sku.go b/business/partner/purchase/jd/store_sku.go index b7418b660..ade2deaf1 100644 --- a/business/partner/purchase/jd/store_sku.go +++ b/business/partner/purchase/jd/store_sku.go @@ -3,6 +3,8 @@ package jd import ( "fmt" + "git.rosy.net.cn/jx-callback/business/partner" + "git.rosy.net.cn/baseapi/platformapi/jdapi" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxutils" @@ -17,140 +19,137 @@ import ( type tStoreSkuBindExt struct { model.StoreSkuBind - PricePercentage int - VendorStoreID string `orm:"column(vendor_store_id)"` - JdID int64 `orm:"column(jd_id)"` + PricePercentage int + CatPricePercentage int + VendorStoreID string `orm:"column(vendor_store_id)"` + JdID int64 `orm:"column(jd_id)"` } // 京东到家,以有库存表示关注(认领) -func (p *PurchaseHandler) SyncStoreSkus(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, skuIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) { - globals.SugarLogger.Debugf("jd SyncStoresSkus, storeID:%d, skuIDs:%v", storeID, skuIDs) - sqlWhere0 := ` - WHERE (t1.jd_sync_status <> 0) AND t1.store_id = ? - ` - sqlWhere := sqlWhere0 - sqlWhereParams := []interface{}{ - storeID, - } - - if len(skuIDs) > 0 { - sqlWhere += " AND t1.sku_id IN (" + dao.GenQuestionMarks(len(skuIDs)) + ")" - sqlWhereParams = append(sqlWhereParams, skuIDs) - } - - sql := ` - SELECT t3.jd_id, t1.*, t2.price_percentage, t2.vendor_store_id - FROM store_sku_bind t1 - JOIN store_map t2 ON t1.store_id = t2.store_id AND t2.vendor_id = ? AND t2.deleted_at = ? - JOIN sku t3 ON t1.sku_id = t3.id AND t3.deleted_at = ? - ` + sqlWhere + " ORDER BY t1.updated_at" - var storeSkus []*tStoreSkuBindExt - sqlParams := []interface{}{ - model.VendorIDJD, - utils.DefaultTimeValue, - utils.DefaultTimeValue, - } - db := dao.GetDB() - if err = dao.GetRows(db, &storeSkus, sql, append(sqlParams, sqlWhereParams...)...); err != nil { +func (p *PurchaseHandler) syncStoreSkus(ctx *jxcontext.Context, parentTask tasksch.ITask, db *dao.DaoDB, storeID int, storeSkus []*dao.StoreSkuSyncInfo, isAsync, isContinueWhenError bool) (hint string, err error) { + globals.SugarLogger.Debugf("jd syncStoreSkus, storeID:%d", storeID) + storeDetail, err := dao.GetStoreDetail(db, storeID, model.VendorIDJD) + if err != nil { return "", err } - task := tasksch.NewParallelTask("SyncStoresSkus京东", tasksch.NewParallelConfig().SetBatchSize(jdapi.MaxStoreSkuBatchSize).SetIsContinueWhenError(isContinueWhenError), ctx, + batchSize := jdapi.MaxStoreSkuBatchSize + // storeSkusLen := len(storeSkus) + // if storeSkusLen < jdapi.MaxStoreSkuBatchSize/2 { + // batchSize = 1 + // } else if storeSkusLen < jdapi.MaxStoreSkuBatchSize { + // batchSize = (storeSkusLen + 1) / 2 + // } else if storeSkusLen < jdapi.MaxStoreSkuBatchSize*2 { + // batchSize = (storeSkusLen + 2) / 3 + // } + task := tasksch.NewParallelTask("syncStoreSkus京东", tasksch.NewParallelConfig().SetBatchSize(batchSize).SetIsContinueWhenError(isContinueWhenError), ctx, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { - var skuPriceInfoList []*jdapi.SkuPriceInfo - var skuVendibilityList []*jdapi.StockVendibility - var skuStockList []*jdapi.SkuStock - stationNo := batchItemList[0].(*tStoreSkuBindExt).VendorStoreID - var batchSkuIDs []int - for _, v := range batchItemList { - storeSku := v.(*tStoreSkuBindExt) - alreadyAddStock := false - if storeSku.JdSyncStatus&model.SyncFlagChangedMask != 0 { - batchSkuIDs = append(batchSkuIDs, storeSku.SkuID) - if storeSku.JdSyncStatus&(model.SyncFlagDeletedMask|model.SyncFlagNewMask) != 0 { // 关注或取消关注 - stock := &jdapi.SkuStock{ - OutSkuId: utils.Int2Str(storeSku.SkuID), - StockQty: model.MaxStoreSkuStockQty, + doWork := func(batchItemList []interface{}) (isPartialFailed bool, err error) { + var skuPriceInfoList []*jdapi.SkuPriceInfo + var skuVendibilityList []*jdapi.StockVendibility + var skuStockList []*jdapi.SkuStock + stationNo := storeDetail.VendorStoreID + var batchBindIDs []int + for _, v := range batchItemList { + storeSku := v.(*dao.StoreSkuSyncInfo) + alreadyAddStock := false + if storeSku.StoreSkuSyncStatus&model.SyncFlagChangedMask != 0 || storeSku.BindID == 0 || storeSku.NameID == 0 { + if storeSku.BindID > 0 { + batchBindIDs = append(batchBindIDs, storeSku.BindID) } - if storeSku.DeletedAt != utils.DefaultTimeValue { - stock.StockQty = 0 - } else { - alreadyAddStock = true - } - if stock.StockQty != 0 || !storeskulock.IsJdStoreSkuLocked(stationNo, storeSku.JdID) { - skuStockList = append(skuStockList, stock) - } - } - if storeSku.JdSyncStatus&(model.SyncFlagPriceMask|model.SyncFlagNewMask) != 0 { - skuPriceInfoList = append(skuPriceInfoList, &jdapi.SkuPriceInfo{ - OutSkuId: utils.Int2Str(storeSku.SkuID), - Price: constrainPrice(jxutils.CaculateSkuVendorPrice(storeSku.Price, storeSku.PricePercentage)), - }) - } - if storeSku.JdSyncStatus&(model.SyncFlagSaleMask|model.SyncFlagNewMask) != 0 { - vendibility := &jdapi.StockVendibility{ - OutSkuId: utils.Int2Str(storeSku.SkuID), - DoSale: true, - } - if storeSku.Status != model.StoreSkuBindStatusNormal { - vendibility.DoSale = false - } else if !alreadyAddStock { // 如果是设置可售则自动将库存加满 + if storeSku.StoreSkuSyncStatus&(model.SyncFlagDeletedMask|model.SyncFlagNewMask) != 0 || storeSku.BindID == 0 || storeSku.NameID == 0 { // 关注或取消关注 stock := &jdapi.SkuStock{ OutSkuId: utils.Int2Str(storeSku.SkuID), StockQty: model.MaxStoreSkuStockQty, } - skuStockList = append(skuStockList, stock) + if storeSku.StoreSkuSyncStatus&model.SyncFlagDeletedMask != 0 || storeSku.DeletedAt != utils.DefaultTimeValue || storeSku.BindID == 0 || storeSku.NameID == 0 { + stock.StockQty = 0 + } else { + alreadyAddStock = true + } + if stock.StockQty != 0 || !storeskulock.IsJdStoreSkuLocked(stationNo, storeSku.JdID) { + skuStockList = append(skuStockList, stock) + } } - if vendibility.DoSale || !storeskulock.IsJdStoreSkuLocked(stationNo, storeSku.JdID) { - skuVendibilityList = append(skuVendibilityList, vendibility) + if storeSku.StoreSkuSyncStatus&(model.SyncFlagPriceMask|model.SyncFlagNewMask) != 0 { + skuPriceInfoList = append(skuPriceInfoList, &jdapi.SkuPriceInfo{ + OutSkuId: utils.Int2Str(storeSku.SkuID), + Price: constrainPrice(jxutils.CaculateSkuVendorPrice(int(storeSku.Price), int(storeDetail.PricePercentage), storeSku.CatPricePercentage)), + }) + } + if storeSku.StoreSkuSyncStatus&(model.SyncFlagSaleMask|model.SyncFlagNewMask) != 0 { + vendibility := &jdapi.StockVendibility{ + OutSkuId: utils.Int2Str(storeSku.SkuID), + DoSale: true, + } + if storeSku.StoreSkuStatus != model.StoreSkuBindStatusNormal { + vendibility.DoSale = false + } else if !alreadyAddStock { // 如果是设置可售则自动将库存加满 + stock := &jdapi.SkuStock{ + OutSkuId: utils.Int2Str(storeSku.SkuID), + StockQty: model.MaxStoreSkuStockQty, + } + skuStockList = append(skuStockList, stock) + } + if vendibility.DoSale || !storeskulock.IsJdStoreSkuLocked(stationNo, storeSku.JdID) { + skuVendibilityList = append(skuVendibilityList, vendibility) + } } } } - } - syncMask := 0 - errList := []error{} - if globals.EnableStoreWrite { - // todo 以下可以优化为并行操作 - globals.SugarLogger.Debug(utils.Format4Output(skuVendibilityList, false), utils.Format4Output(skuPriceInfoList, false), utils.Format4Output(skuStockList, false)) - if len(skuVendibilityList) > 0 { - if _, err = api.JdAPI.BatchUpdateVendibility("", stationNo, skuVendibilityList, ctx.GetUserName()); err == nil { - syncMask |= model.SyncFlagSaleMask - } else { + syncMask := 0 + errList := []error{} + if globals.EnableJdStoreWrite { + // todo 以下可以优化为并行操作 + globals.SugarLogger.Debug(utils.Format4Output(skuVendibilityList, false), utils.Format4Output(skuPriceInfoList, false), utils.Format4Output(skuStockList, false)) + if len(skuVendibilityList) > 0 { + if _, err = api.JdAPI.BatchUpdateVendibility("", stationNo, skuVendibilityList, ctx.GetUserName()); err == nil { + syncMask |= model.SyncFlagSaleMask + } else { + errList = append(errList, err) + } + } + if (err == nil || isContinueWhenError) && len(skuStockList) > 0 { + if _, err = api.JdAPI.BatchUpdateCurrentQtys("", stationNo, skuStockList, ctx.GetUserName()); err == nil { + syncMask |= model.SyncFlagNewMask | model.SyncFlagDeletedMask + } else { + errList = append(errList, err) + } + } + if (err == nil || isContinueWhenError) && len(skuPriceInfoList) > 0 { + if _, err = api.JdAPI.UpdateVendorStationPrice("", stationNo, skuPriceInfoList); err == nil { + syncMask |= model.SyncFlagPriceMask + } else { + isPartialFailed = isErrPartialFailed(err) + errList = append(errList, partner.NewErrorCode(err.Error(), partner.ErrCodeChangePriceFailed, model.VendorIDJD)) + } + } + } + if len(errList) == 0 { + syncMask = -1 + } + if syncMask != 0 && len(batchBindIDs) > 0 { + // db := dao.GetDB() // 多线程问题 + sql := ` + UPDATE store_sku_bind t1 + SET t1.jd_sync_status = t1.jd_sync_status & ? + WHERE t1.id IN (` + dao.GenQuestionMarks(len(batchBindIDs)) + ")" + if _, err = dao.ExecuteSQL(db, sql, ^syncMask, batchBindIDs); err != nil { errList = append(errList, err) } } - if (err == nil || isContinueWhenError) && len(skuStockList) > 0 { - if _, err = api.JdAPI.BatchUpdateCurrentQtys("", stationNo, skuStockList, ctx.GetUserName()); err == nil { - syncMask |= model.SyncFlagNewMask | model.SyncFlagDeletedMask - } else { - errList = append(errList, err) - } + if len(errList) == 1 { + err = errList[0] + } else if len(errList) > 1 { + err = fmt.Errorf("%v", errList) } - if (err == nil || isContinueWhenError) && len(skuPriceInfoList) > 0 { - if _, err = api.JdAPI.UpdateVendorStationPrice("", stationNo, skuPriceInfoList); err == nil { - syncMask |= model.SyncFlagPriceMask - } else { - errList = append(errList, err) - } + return isPartialFailed, err + } + isErrPartialFailed, err := doWork(batchItemList) + if isErrPartialFailed && len(batchItemList) > 1 { + for _, v := range batchItemList { + doWork([]interface{}{v}) } } - if len(errList) == 0 { - syncMask = -1 - } - if syncMask != 0 && len(batchSkuIDs) > 0 { - db := dao.GetDB() // 多线程问题 - sql := ` - UPDATE store_sku_bind t1 - SET t1.jd_sync_status = t1.jd_sync_status & ? - ` + sqlWhere0 + " AND t1.sku_id IN (" + dao.GenQuestionMarks(len(batchSkuIDs)) + ")" - if _, err = dao.ExecuteSQL(db, sql, ^syncMask, storeID, batchSkuIDs); err != nil { - errList = append(errList, err) - } - } - if len(errList) == 1 { - err = errList[0] - } else if len(errList) > 1 { - err = fmt.Errorf("%v", errList) - } return nil, err }, storeSkus) tasksch.HandleTask(task, parentTask, false).Run() @@ -160,112 +159,35 @@ func (p *PurchaseHandler) SyncStoreSkus(ctx *jxcontext.Context, parentTask tasks return task.ID, err } +func isErrPartialFailed(err error) bool { + if errExt, ok := err.(*utils.ErrorWithCode); ok && errExt.Code() == jdapi.ResponseInnerCodePartialFailed { + return true + } + return false +} + +func (p *PurchaseHandler) SyncStoreSkus(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, skuIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) { + globals.SugarLogger.Debugf("jd SyncStoresSkus, storeID:%d, skuIDs:%v", storeID, skuIDs) + db := dao.GetDB() + storeSkus, err := dao.GetStoreSkus(db, model.VendorIDJD, storeID, skuIDs) + if err != nil { + return "", err + } + return p.syncStoreSkus(ctx, parentTask, db, storeID, storeSkus, isAsync, isContinueWhenError) +} + func (p *PurchaseHandler) FullSyncStoreSkus(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, isAsync, isContinueWhenError bool) (hint string, err error) { globals.SugarLogger.Debugf("jd FullSyncStoreSkus, storeID:%d", storeID) db := dao.GetDB() - _, err = dao.SetStoreSkuSyncStatus(db, model.VendorIDJD, storeID, nil, model.SyncFlagModifiedMask|model.SyncFlagPriceMask|model.SyncFlagSaleMask) + _, err = dao.SetStoreSkuSyncStatus(db, model.VendorIDJD, []int{storeID}, nil, model.SyncFlagStoreSkuOnlyMask) if err != nil { return "", err } - skus, err := dao.GetFullStoreSkus(db, model.VendorIDJD, storeID) + storeSkus, err := dao.GetFullStoreSkus(db, model.VendorIDJD, storeID) if err != nil { return "", err } - return p.syncStoreSkus(ctx, parentTask, db, storeID, skus, isAsync, isContinueWhenError) -} - -// todo 之后应该与SyncStoreSkus合并 -func (p *PurchaseHandler) syncStoreSkus(ctx *jxcontext.Context, parentTask tasksch.ITask, db *dao.DaoDB, storeID int, skus []*dao.StoreSkuSyncInfo, isAsync, isContinueWhenError bool) (hint string, err error) { - globals.SugarLogger.Debugf("jd syncStoreSkus, storeID:%d, len(skus):%d", storeID, len(skus)) - if len(skus) == 0 { - return "", nil - } - storeDetail, err := dao.GetStoreDetail(db, storeID, model.VendorIDJD) - if err != nil { - return "", err - } - stationNo := storeDetail.VendorStoreID - task := tasksch.NewParallelTask("SyncStoresSkus京东", tasksch.NewParallelConfig().SetBatchSize(jdapi.MaxStoreSkuBatchSize).SetIsContinueWhenError(isContinueWhenError), ctx, - func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (interface{}, error) { - var skuPriceInfoList []*jdapi.SkuPriceInfo - var skuVendibilityList []*jdapi.StockVendibility - var skuStockList []*jdapi.SkuStock - var batchSkuIDs []int - for _, v := range batchItemList { - storeSku := v.(*dao.StoreSkuSyncInfo) - alreadyAddStock := false - if storeSku.SkuSyncStatus&model.SyncFlagChangedMask != 0 || storeSku.BindID == 0 { - if storeSku.BindID != 0 { - batchSkuIDs = append(batchSkuIDs, storeSku.BindID) - } - if storeSku.SkuSyncStatus&(model.SyncFlagDeletedMask|model.SyncFlagNewMask) != 0 || storeSku.BindID == 0 { // 关注或取消关注 - stock := &jdapi.SkuStock{ - OutSkuId: utils.Int2Str(storeSku.ID), - StockQty: model.MaxStoreSkuStockQty, - } - if storeSku.DeletedAt != utils.DefaultTimeValue || storeSku.BindID == 0 { - stock.StockQty = 0 - } else { - alreadyAddStock = true - } - if stock.StockQty != 0 || !storeskulock.IsJdStoreSkuLocked(stationNo, storeSku.JdID) { - skuStockList = append(skuStockList, stock) - } - } - if storeSku.SkuSyncStatus&(model.SyncFlagPriceMask|model.SyncFlagNewMask) != 0 { - skuPriceInfoList = append(skuPriceInfoList, &jdapi.SkuPriceInfo{ - OutSkuId: utils.Int2Str(storeSku.ID), - Price: constrainPrice(jxutils.CaculateSkuVendorPrice(int(storeSku.Price), int(storeDetail.PricePercentage))), - }) - } - if storeSku.SkuSyncStatus&(model.SyncFlagSaleMask|model.SyncFlagNewMask) != 0 { - vendibility := &jdapi.StockVendibility{ - OutSkuId: utils.Int2Str(storeSku.ID), - DoSale: true, - } - if storeSku.StoreSkuStatus != model.StoreSkuBindStatusNormal { - vendibility.DoSale = false - } else if !alreadyAddStock { // 如果是设置可售则自动将库存加满 - stock := &jdapi.SkuStock{ - OutSkuId: utils.Int2Str(storeSku.ID), - StockQty: model.MaxStoreSkuStockQty, - } - skuStockList = append(skuStockList, stock) - } - if vendibility.DoSale || !storeskulock.IsJdStoreSkuLocked(stationNo, storeSku.JdID) { - skuVendibilityList = append(skuVendibilityList, vendibility) - } - } - } - } - globals.SugarLogger.Debugf("jd syncStoreSkus sync detail, storeID:%d, skuVendibilityList:%s, skuPriceInfoList:%s, skuStockList:%s", storeID, utils.Format4Output(skuVendibilityList, true), utils.Format4Output(skuPriceInfoList, true), utils.Format4Output(skuStockList, true)) - if globals.EnableStoreWrite { - // todo 以下可以优化为并行操作 - if len(skuVendibilityList) > 0 { - _, err = api.JdAPI.BatchUpdateVendibility("", stationNo, skuVendibilityList, ctx.GetUserName()) - } - if err == nil && len(skuStockList) > 0 { - _, err = api.JdAPI.BatchUpdateCurrentQtys("", stationNo, skuStockList, ctx.GetUserName()) - } - if err == nil && len(skuPriceInfoList) > 0 { - _, err = api.JdAPI.UpdateVendorStationPrice("", stationNo, skuPriceInfoList) - } - } - if err == nil && len(batchSkuIDs) > 0 { - db := dao.GetDB() // 多线程问题 - sql := ` - UPDATE store_sku_bind t1 - SET t1.jd_sync_status = 0 - WHERE t1.id IN (` + dao.GenQuestionMarks(len(batchSkuIDs)) + ")" - _, err = dao.ExecuteSQL(db, sql, batchSkuIDs) - } - return nil, err - }, skus) - tasksch.HandleTask(task, parentTask, false).Run() - if !isAsync { - _, err = task.GetResult(0) - } - return task.ID, err + return p.syncStoreSkus(ctx, parentTask, db, storeID, storeSkus, isAsync, isContinueWhenError) } func (p *PurchaseHandler) DeleteRemoteStoreSkus(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, isAsync, isContinueWhenError bool) (hint string, err error) { @@ -278,3 +200,117 @@ func constrainPrice(price int) int { } return price } + +func (p *PurchaseHandler) GetStoresSku(ctx *jxcontext.Context, parentTask tasksch.ITask, storeIDs []int) (storeSkuList []*model.StoreSkuBind, err error) { + db := dao.GetDB() + skuList, err := dao.GetSkus(db, nil, nil, []int{model.SkuStatusNormal}, nil) + if err != nil { + return nil, err + } + var skuInfoList []*jdapi.BaseStockCenterRequest + skuMap := make(map[int64]int) + for _, sku := range skuList { + if !jxutils.IsEmptyID(sku.JdID) { + skuInfoList = append(skuInfoList, &jdapi.BaseStockCenterRequest{ + SkuId: sku.JdID, + }) + skuMap[sku.JdID] = sku.ID + } + } + for _, storeID := range storeIDs { + storeDetail, err := dao.GetStoreDetail(db, storeID, model.VendorIDJD) + if err != nil { + return nil, err + } + for _, sku := range skuInfoList { + sku.StationNo = storeDetail.VendorStoreID + } + task := tasksch.NewParallelTask("jd 获取京东门店商品信息", tasksch.NewParallelConfig().SetBatchSize(jdapi.MaxStoreSkuBatchSize), ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + batchSkuInfoList := make([]*jdapi.BaseStockCenterRequest, len(batchItemList)) + batchSkuList := make([]int64, len(batchItemList)) + for k, v := range batchItemList { + batchSkuInfoList[k] = v.(*jdapi.BaseStockCenterRequest) + batchSkuList[k] = batchSkuInfoList[k].SkuId + } + stockInfo, err := api.JdAPI.QueryOpenUseable(batchSkuInfoList) + if err != nil { + return nil, err + } + priceInfo, err := api.JdAPI.GetStationInfoList(storeDetail.VendorStoreID, batchSkuList) + if err != nil { + return nil, err + } + var batchStoreSkuList []*model.StoreSkuBind + batchStoreSkuMap := make(map[int64]*model.StoreSkuBind) + for _, v := range stockInfo { + if v.UsableQty > 0 { + batchSku := &model.StoreSkuBind{ + StoreID: storeID, + SkuID: skuMap[v.SkuID], + } + if v.Vendibility == 0 { + batchSku.Status = model.SkuStatusNormal + } else { + batchSku.Status = model.SkuStatusDontSale + } + batchStoreSkuMap[v.SkuID] = batchSku + batchStoreSkuList = append(batchStoreSkuList, batchSku) + } + } + for _, v := range priceInfo { + if storeSku := batchStoreSkuMap[v.SkuID]; storeSku != nil { + storeSku.Price = int(v.Price) + } + } + return batchStoreSkuList, err + }, skuInfoList) + tasksch.AddChild(parentTask, task).Run() + result, err := task.GetResult(0) + if err != nil { + return nil, err + } + for _, v := range result { + storeSkuList = append(storeSkuList, v.(*model.StoreSkuBind)) + } + } + return storeSkuList, err +} + +func (p *PurchaseHandler) SyncStoreProducts(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, skuIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) { + globals.SugarLogger.Debugf("jd SyncStoreProducts, storeID:%d", storeID) + db := dao.GetDB() + storeDetail, err := dao.GetStoreDetail(db, storeID, model.VendorIDJD) + if err != nil { + return "", err + } + storeSkuList, err := dao.GetStoreSkus2(db, model.VendorIDJD, storeID, skuIDs, false) + if err != nil { + return "", err + } + task := tasksch.NewParallelTask("SyncStoreProducts京东", tasksch.NewParallelConfig().SetIsContinueWhenError(isContinueWhenError), ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + storeSku := batchItemList[0].(*dao.StoreSkuSyncInfo) + if storeSku.VendorSkuID != "" && storeSku.StoreSkuStatus == model.SkuStatusNormal { + if globals.EnableJdStoreWrite { + synchronized, err2 := api.JdAPI.SyncProduct(storeDetail.VendorStoreID, storeSku.VendorSkuID) + if err = err2; err == nil && synchronized { + retVal = []int{1} + } + } else { + retVal = []int{1} + } + } + return retVal, err + }, storeSkuList) + tasksch.HandleTask(task, parentTask, true).Run() + if !isAsync { + result, err2 := task.GetResult(0) + if err = err2; err == nil { + hint = utils.Int2Str(len(result)) + } + } else { + hint = task.GetID() + } + return hint, err +} diff --git a/business/partner/purchase/jd/store_sku2.go b/business/partner/purchase/jd/store_sku2.go new file mode 100644 index 000000000..73ecfb0a6 --- /dev/null +++ b/business/partner/purchase/jd/store_sku2.go @@ -0,0 +1,66 @@ +package jd + +import ( + "git.rosy.net.cn/baseapi/platformapi/jdapi" + "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "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/partner" + "git.rosy.net.cn/jx-callback/globals" + "git.rosy.net.cn/jx-callback/globals/api" +) + +func (p *PurchaseHandler) GetStoreSkusInfo(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, vendorStoreID string, inStoreSkuList []*partner.BareStoreSkuInfo) (outStoreSkuList []*partner.BareStoreSkuInfo, err error) { + var batchSkuInfoList []*jdapi.BaseStockCenterRequest + batchSkuList := partner.BareStoreSkuInfoList(inStoreSkuList).GetVendorSkuIDIntList() + for _, v := range inStoreSkuList { + if !dao.IsVendorThingIDEmpty(v.VendorSkuID) { + batchSkuInfoList = append(batchSkuInfoList, &jdapi.BaseStockCenterRequest{ + StationNo: vendorStoreID, + SkuId: utils.Str2Int64(v.VendorSkuID), + }) + } + } + if len(batchSkuInfoList) > 0 { + var stockInfo []*jdapi.QueryStockResponse + var priceInfo []*jdapi.StorePriceInfo + task := tasksch.NewParallelTask("获取京东到家平台门店商品信息", tasksch.NewParallelConfig().SetParallelCount(2), ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + subTaskID := batchItemList[0].(int) + if subTaskID == 0 { + stockInfo, err = api.JdAPI.QueryOpenUseable(batchSkuInfoList) + } else { + priceInfo, err = api.JdAPI.GetStationInfoList(vendorStoreID, batchSkuList) + globals.SugarLogger.Debug(utils.Format4Output(priceInfo, false)) + } + return nil, err + }, []int{0, 1}) + tasksch.HandleTask(task, parentTask, false).Run() + _, err = task.GetResult(0) + if err == nil { + storeSkuMap := make(map[int64]*partner.BareStoreSkuInfo) + for _, v := range inStoreSkuList { + storeSkuMap[utils.Str2Int64(v.VendorSkuID)] = v + } + for _, v := range stockInfo { + outStoreSkuList = append(outStoreSkuList, storeSkuMap[v.SkuID]) + storeSkuMap[v.SkuID].Status = jdStoreSkuStatus2Jx(v.Vendibility) + } + for _, v := range priceInfo { + storeSkuMap[v.SkuID].Price = v.Price + } + } + } + return outStoreSkuList, err +} + +func jdStoreSkuStatus2Jx(jdStoreSkuStatus int) (jxSkuStatus int) { + if jdStoreSkuStatus == 0 { + jxSkuStatus = model.SkuStatusNormal + } else { + jxSkuStatus = model.SkuStatusDontSale + } + return jxSkuStatus +} diff --git a/business/partner/purchase/mtwm/act.go b/business/partner/purchase/mtwm/act.go new file mode 100644 index 000000000..144a5b59d --- /dev/null +++ b/business/partner/purchase/mtwm/act.go @@ -0,0 +1,169 @@ +package mtwm + +import ( + "git.rosy.net.cn/baseapi/platformapi/mtwmapi" + "git.rosy.net.cn/baseapi/utils" + "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/tasksch" + "git.rosy.net.cn/jx-callback/business/model" + "git.rosy.net.cn/jx-callback/business/partner" + "git.rosy.net.cn/jx-callback/globals" + "git.rosy.net.cn/jx-callback/globals/api" +) + +func actOrderRules2Mtwm(actOrderRules []*model.ActOrderRule) (actDetails []*mtwmapi.FullDiscountActDetail) { + for _, v := range actOrderRules { + actDetails = append(actDetails, &mtwmapi.FullDiscountActDetail{ + OriginalPrice: jxutils.IntPrice2Standard(v.SalePrice), + ActPrice: jxutils.IntPrice2Standard(v.DeductPrice), + }) + } + return actDetails +} + +func storeSku2ActData(act *model.Act2, actStoreSku []*model.ActStoreSku2, handler func(int) bool) (actData []*mtwmapi.RetailDiscountActData) { + for _, v := range actStoreSku { + if handler == nil || handler(v.SyncStatus) { + actData = append(actData, &mtwmapi.RetailDiscountActData{ + AppFoodCode: utils.Int2Str(v.SkuID), + // UserType: 0, + StartTime: act.BeginAt.Unix(), + EndTime: act.EndAt.Unix(), + OrderLimit: act.LimitCount, + DayLimit: act.LimitDaily, + // Period: "", + // WeeksTime: "", + SettingType: mtwmapi.SettingTypeAsPrice, + ActPrice: jxutils.IntPrice2Standard(v.ActPrice), + // DiscountCoefficient: 0, + Sequence: int(v.ActPrice), + ItemID: utils.Str2Int64WithDefault(v.VendorActID, 0), + }) + } + } + return actData +} + +func storeSku2ActData4Delete(actStoreSku []*model.ActStoreSku2, handler func(int) bool) (actIDList []string) { + for _, v := range actStoreSku { + if handler == nil || handler(v.SyncStatus) { + if v.VendorActID != "" { + actIDList = append(actIDList, v.VendorActID) + } + } + } + return actIDList +} + +func isCreateOrUpdate(syncStatus int) bool { + return model.IsSyncStatusNeedCreate(syncStatus) || model.IsSyncStatusNeedUpdate(syncStatus) +} + +func createOneShopAct(act *model.Act2, storeMap *model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + actData := storeSku2ActData(act, actStoreSku, isCreateOrUpdate) + if len(actData) > 0 { + if globals.EnableMtwmStoreWrite { + actResult, err2 := api.MtwmAPI.RetailDiscountBatchSave(storeMap.VendorStoreID, actData) + if err = err2; err != nil { + return err + } + actResultMap := make(map[string]*mtwmapi.RetailDiscountActResult) + for _, v := range actResult { + actResultMap[v.AppFoodCode] = v + } + for _, v := range actStoreSku { + if result := actResultMap[utils.Int2Str(v.SkuID)]; result != nil { + v.VendorActID = utils.Int64ToStr(result.ActID) + } + } + } else { + for _, v := range actStoreSku { + v.VendorActID = utils.Int64ToStr(jxutils.GenFakeID()) + } + } + } + return err +} + +func (c *PurchaseHandler) CreateAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actOrderRules []*model.ActOrderRule, actStoreMap []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + globals.SugarLogger.Debugf("mtwm CreateAct actID:%d", act.ID) + if act.Type < model.ActOrderBegin { + actStoreSkuMap := partner.ActStoreSku2Map(actStoreSku) + task := tasksch.NewParallelTask("mtwm CreateAct", nil, ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + store := batchItemList[0].(*model.ActStore2) + err = createOneShopAct(act, store, actStoreSkuMap[store.StoreID]) + return nil, err + }, actStoreMap) + tasksch.HandleTask(task, parentTask, true).Run() + _, err = task.GetResult(0) + } else { + + } + return err +} + +func (c *PurchaseHandler) UpdateAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actOrderRules []*model.ActOrderRule, actStoreMap2Remove, actStoreMap2Add, actStoreMap2Update []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + if act.Type < model.ActOrderBegin { + actStoreSkuMap := partner.ActStoreSku2Map(actStoreSku) + if len(actStoreMap2Remove) > 0 { + if err = c.CancelAct(ctx, parentTask, act, actStoreMap2Remove, nil); err != nil { + return err + } + for _, v := range actStoreMap2Remove { + delete(actStoreSkuMap, v.StoreID) + } + } + if len(actStoreMap2Add) > 0 { + if err = c.CreateAct(ctx, parentTask, act, actOrderRules, actStoreMap2Add, actStoreSku); err != nil { + return err + } + for _, v := range actStoreMap2Add { + delete(actStoreSkuMap, v.StoreID) + } + } + task := tasksch.NewParallelTask("mtwm UpdateAct", nil, ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + v := batchItemList[0].(*model.ActStore2) + if storeSkus := actStoreSkuMap[v.StoreID]; storeSkus != nil { + if list := storeSku2ActData4Delete(storeSkus, model.IsSyncStatusNeedDelete); len(list) > 0 { + if err = api.MtwmAPI.RetailDiscountDelete(v.VendorStoreID, list); err != nil { + return nil, err + } + } + if err = createOneShopAct(act, v, actStoreSkuMap[v.StoreID]); err != nil { + return nil, err + } + } + return nil, err + }, actStoreMap2Update) + tasksch.HandleTask(task, parentTask, true).Run() + _, err = task.GetResult(0) + } else { + + } + return err +} + +func (c *PurchaseHandler) CancelAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actStoreMap []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + if act.Type < model.ActOrderBegin { + actStoreSkuMap := partner.ActStoreSku2Map(actStoreSku) + task := tasksch.NewParallelTask("mtwm DeleteAct", nil, ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + v := batchItemList[0].(*model.ActStore2) + actIDList := storeSku2ActData4Delete(actStoreSkuMap[v.StoreID], nil) + if len(actIDList) > 0 { + if globals.EnableMtwmStoreWrite { + err = api.MtwmAPI.RetailDiscountDelete(v.VendorStoreID, actIDList) + } + } + return nil, err + }, actStoreMap) + tasksch.HandleTask(task, parentTask, true).Run() + _, err = task.GetResult(0) + } else { + + } + return err +} diff --git a/business/partner/purchase/mtwm/callback.go b/business/partner/purchase/mtwm/callback.go index 4bb0df4ee..19e6f8021 100644 --- a/business/partner/purchase/mtwm/callback.go +++ b/business/partner/purchase/mtwm/callback.go @@ -2,7 +2,6 @@ package mtwm import ( "git.rosy.net.cn/baseapi/platformapi/mtwmapi" - "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/model" ) @@ -19,11 +18,11 @@ func OnOrderCallbackMsg(msg *mtwmapi.CallbackMsg) (response *mtwmapi.CallbackRes } }, jxutils.ComposeUniversalOrderID(orderID, model.VendorIDMTWM)) } - if msg.Cmd == mtwmapi.MsgTypeOrderRefund || msg.Cmd == mtwmapi.MsgTypeOrderPartialRefund { + /*if msg.Cmd == mtwmapi.MsgTypeOrderRefund || msg.Cmd == mtwmapi.MsgTypeOrderPartialRefund { utils.CallFuncAsync(func() { OnFinancialMsg(msg) }) - } else if msg.Cmd == mtwmapi.MsgTypeStoreStatusChanged { + } else */if msg.Cmd == mtwmapi.MsgTypeStoreStatusChanged { response = curPurchaseHandler.onStoreStatusChanged(msg) } } @@ -31,5 +30,5 @@ func OnOrderCallbackMsg(msg *mtwmapi.CallbackMsg) (response *mtwmapi.CallbackRes } func GetOrderIDFromMsg(msg *mtwmapi.CallbackMsg) string { - return msg.Data.Get(mtwmapi.KeyOrderID) + return msg.FormData.Get(mtwmapi.KeyOrderID) } diff --git a/business/partner/purchase/mtwm/financial.go b/business/partner/purchase/mtwm/financial.go index f32918a40..e4679fade 100644 --- a/business/partner/purchase/mtwm/financial.go +++ b/business/partner/purchase/mtwm/financial.go @@ -18,13 +18,13 @@ const ( // 存储美团退款订单结账信息 func OnFinancialMsg(msg *mtwmapi.CallbackMsg) (err error) { if msg.Cmd == mtwmapi.MsgTypeOrderPartialRefund { // 部分退款处理 - orderData := msg.Data + orderData := msg.FormData if orderData.Get("notify_type") == mtwmapi.NotifyTypeSuccess { err = partner.CurOrderManager.SaveAfsOrderFinancialInfo(curPurchaseHandler.AfsOrderDetail2Financial(orderData)) } } if msg.Cmd == mtwmapi.MsgTypeOrderRefund { // todo 全额退款处理 - orderData := msg.Data + orderData := msg.FormData if orderData.Get("notify_type") == mtwmapi.NotifyTypeSuccess { globals.SugarLogger.Debug(orderData.Get("order_id")) // 获得退款订单ID,去本地数据库拿?饿百消息推送只给了订单号,也没有通过订单号查询退款信息的接口 afsOrderID := orderData.Get("order_id") @@ -45,7 +45,7 @@ func (p *PurchaseHandler) OrderFinancialDetail2Refund(orderFinancial *model.Orde VendorID: model.VendorIDMTWM, AfsOrderID: orderData.Get("order_id"), VendorOrderID: orderData.Get("order_id"), - AfsCreateAt: utils.Timestamp2Time(utils.Str2Int64(orderData.Get("timestamp"))), + AfsCreatedAt: utils.Timestamp2Time(utils.Str2Int64(orderData.Get("timestamp"))), // BoxMoney: orderFinancial.BoxMoney, // SkuBoxMoney: orderFinancial.SkuBoxMoney, // 美团的餐盒费已经拆到单条SKU里面去了,退款时直接计算用户支付sku金额就好了 FreightUserMoney: orderFinancial.FreightMoney, @@ -64,9 +64,8 @@ func (p *PurchaseHandler) OrderFinancialDetail2Refund(orderFinancial *model.Orde } for _, sku := range orderFinancial.Skus { orderSkuFinancial := &model.OrderSkuFinancial{ - VendorID: sku.VendorID, - VendorOrderID: sku.VendorOrderID, - VendorOrderID2: sku.VendorOrderID2, + VendorID: sku.VendorID, + VendorOrderID: sku.VendorOrderID, // OrderFinancialID: sku.VendorOrderID, // ConfirmTime: afsOrder.AfsCreateAt, VendorStoreID: afsOrder.VendorStoreID, @@ -93,7 +92,7 @@ func (p *PurchaseHandler) AfsOrderDetail2Financial(orderData url.Values) (afsOrd VendorID: model.VendorIDMTWM, AfsOrderID: orderData.Get("order_id"), VendorOrderID: orderData.Get("order_id"), - AfsCreateAt: utils.Timestamp2Time(utils.Str2Int64(orderData.Get("timestamp"))), + AfsCreatedAt: utils.Timestamp2Time(utils.Str2Int64(orderData.Get("timestamp"))), RefundMoney: jxutils.StandardPrice2Int(utils.Str2Float64(orderData.Get("money"))), } // if orderData.Get("timestamp") != "" { diff --git a/business/partner/purchase/mtwm/financial_test.go b/business/partner/purchase/mtwm/financial_test.go index 28fbaa1ff..fb0bacc67 100644 --- a/business/partner/purchase/mtwm/financial_test.go +++ b/business/partner/purchase/mtwm/financial_test.go @@ -13,13 +13,13 @@ import ( func TestOnFinancialMsg(t *testing.T) { msg := &mtwmapi.CallbackMsg{ - Cmd: "orderRefund", - Data: url.Values{}, + Cmd: "orderRefund", + FormData: url.Values{}, } - msg.Data.Set("timestamp", utils.Int64ToStr(time.Now().Unix())) - msg.Data.Set("order_id", "33762863167364867") - msg.Data.Set("notify_type", "agree") - msg.Data.Set("money", "23.56") + msg.FormData.Set("timestamp", utils.Int64ToStr(time.Now().Unix())) + msg.FormData.Set("order_id", "33762863167364867") + msg.FormData.Set("notify_type", "agree") + msg.FormData.Set("money", "23.56") food := []map[string]interface{}{ map[string]interface{}{ "app_food_code": "123", @@ -40,8 +40,8 @@ func TestOnFinancialMsg(t *testing.T) { "box_price": 1, }, } - msg.Data.Set("food", string(utils.MustMarshal(food))) - res := OnFinancialMsg(msg) + msg.FormData.Set("food", string(utils.MustMarshal(food))) + res := curPurchaseHandler.onAfsOrderMsg(msg) fmt.Println(res) } diff --git a/business/partner/purchase/mtwm/mtwm.go b/business/partner/purchase/mtwm/mtwm.go index d26bcc6ec..14a80b4d7 100644 --- a/business/partner/purchase/mtwm/mtwm.go +++ b/business/partner/purchase/mtwm/mtwm.go @@ -129,7 +129,7 @@ func bizStatusMtwm2JX(openLevel, online int) int { return model.StoreStatusDisabled } else { if openLevel == mtwmapi.PoiOpenLevelHaveRest { - return model.StoreStatusClosed + return model.StoreStatusHaveRest } } return model.StoreStatusOpened @@ -138,7 +138,7 @@ func bizStatusMtwm2JX(openLevel, online int) int { func bizStatusJX2Mtwm(status int) (openLevel, online int) { if status == model.StoreStatusDisabled { return mtwmapi.PoiOpenLevelHaveRest, mtwmapi.PoiStatusOffline - } else if status == model.StoreStatusClosed { + } else if status == model.StoreStatusHaveRest || status == model.StoreStatusClosed { return mtwmapi.PoiOpenLevelHaveRest, mtwmapi.PoiStatusOnline } return mtwmapi.PoiOpenLevelNormal, mtwmapi.PoiStatusOnline diff --git a/business/partner/purchase/mtwm/order.go b/business/partner/purchase/mtwm/order.go index 3d886b888..ef93ca337 100644 --- a/business/partner/purchase/mtwm/order.go +++ b/business/partner/purchase/mtwm/order.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "net/url" + "regexp" + "strings" "time" "git.rosy.net.cn/baseapi/platformapi/mtwmapi" @@ -21,9 +23,11 @@ const ( FakeMsgTypeOrderReceived = "orderReceived" FakeMsgTypeOrderDelivering = "orderDelivering" - fakeUserApplyCancel = "fake_user_apply_cancel" - fakeUserUndoApplyCancel = "fake_user_undo_apply_cancel" - fakeOrderAdjustFinished = "fake_order_adjust_finished" + fakeUserApplyCancel = "fake_user_apply_cancel" + fakeMerchantAgreeApplyCancel = "fake_merchant_agree_apply_cancel" + fakeRefuseUserApplyCancel = "fake_refuse_user_apply_cancel" + fakeUserUndoApplyCancel = "fake_user_undo_apply_cancel" + fakeOrderAdjustFinished = "fake_order_adjust_finished" ) const ( @@ -31,12 +35,17 @@ const ( ) const ( - pickupOrderDelay = 260 * time.Second + // pickupOrderDelay = 260 * time.Second + pickupOrderDelay = 1 * time.Second callDeliveryDelay = 10 * time.Minute callDeliveryDelayGap = 30 ) +var ( + specPat = regexp.MustCompile(`(\d+)(.+)`) +) + var ( VendorStatus2StatusMap = map[string]int{ mtwmapi.OrderStatusUserCommitted: model.OrderStatusUnknown, @@ -48,9 +57,17 @@ var ( mtwmapi.OrderStatusFinished: model.OrderStatusFinished, mtwmapi.OrderStatusCanceled: model.OrderStatusCanceled, - fakeOrderAdjustFinished: model.OrderStatusAdjust, - fakeUserApplyCancel: model.OrderStatusApplyCancel, - fakeUserUndoApplyCancel: model.OrderStatusUndoApplyCancel, + fakeOrderAdjustFinished: model.OrderStatusAdjust, + fakeRefuseUserApplyCancel: model.OrderStatusUnlocked, + fakeUserApplyCancel: model.OrderStatusApplyCancel, + fakeUserUndoApplyCancel: model.OrderStatusUndoApplyCancel, + fakeMerchantAgreeApplyCancel: model.OrderStatusCanceled, + } + + skuActTypeMap = map[int]int{ + mtwmapi.ExtrasPromotionTypeTeJiaCai: 1, + mtwmapi.ExtrasPromotionTypeZheKouCai: 1, + mtwmapi.ExtrasPromotionTypeSecondHalfPrice: 1, } ) @@ -101,7 +118,6 @@ func (p *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *mo StatusTime: getTimeFromTimestamp(utils.MustInterface2Int64(result["ctime"])), OriginalData: string(utils.MustMarshal(result)), ActualPayPrice: jxutils.StandardPrice2Int(utils.MustInterface2Float64(result["total"])), - Skus: []*model.OrderSku{}, } if utils.IsTimeZero(order.PickDeadline) && !utils.IsTimeZero(order.StatusTime) { order.PickDeadline = order.StatusTime.Add(pickupOrderDelay) // 美团外卖要求在5分钟内拣货,不然订单会被取消 @@ -122,11 +138,56 @@ func (p *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *mo if err := utils.UnmarshalUseNumber([]byte(result["detail"].(string)), &detail); err != nil { panic(fmt.Sprintf("mtwm Map2Order vendorID:%s failed with error:%v", vendorOrderID, err)) } + + // 添加需要赠送的东西 + if result["extras"] != nil { + var extraList []*mtwmapi.OrderExtraInfo + if err := utils.UnmarshalUseNumber([]byte(result["extras"].(string)), &extraList); err != nil { + panic(fmt.Sprintf("mtwm Map2Order vendorID:%s failed with error:%v", vendorOrderID, err)) + } + for _, extra := range extraList { + if extra.Type == mtwmapi.ExtrasPromotionTypeTaoCanZeng || extra.Type == mtwmapi.ExtrasPromotionTypeManZeng { + sku := &model.OrderSku{ + VendorOrderID: order.VendorOrderID, + VendorID: model.VendorIDMTWM, + Count: 1, + SkuID: 0, + VendorSkuID: "", + SkuName: extra.Remark, + Weight: 0, + SalePrice: 0, + StoreSubName: utils.Int2Str(extra.Type), + } + order.Skus = append(order.Skus, sku) + } + } + } + + if poiReceiveDetailStr := utils.Interface2String(result["poi_receive_detail"]); poiReceiveDetailStr != "" { + var poiReceiveDetail *mtwmapi.PoiReceiveDetailInfo + utils.UnmarshalUseNumber([]byte(poiReceiveDetailStr), &poiReceiveDetail) + if poiReceiveDetail != nil { + order.TotalShopMoney = poiReceiveDetail.WmPoiReceiveCent + for _, v := range poiReceiveDetail.ActOrderChargeByMt { + order.PmSubsidyMoney += v.MoneyCent + } + } + } + + var skuBenefitDetailMap map[string]*mtwmapi.SkuBenefitDetailInfo + if skuBenefitDetai := utils.Interface2String(result["sku_benefit_detail"]); skuBenefitDetai != "" { + skuBenefitDetailMap = make(map[string]*mtwmapi.SkuBenefitDetailInfo) + var skuBenefitDetailList []*mtwmapi.SkuBenefitDetailInfo + utils.UnmarshalUseNumber([]byte(skuBenefitDetai), &skuBenefitDetailList) + for _, v := range skuBenefitDetailList { + skuBenefitDetailMap[v.SkuID] = v + } + } + ignoreSkuMap := make(map[int]int) // detail := result["detail"].([]interface{}) for _, product := range detail { // product := product2.(map[string]interface{}) skuName := product["food_name"].(string) - _, _, _, specUnit, _, specQuality := jxutils.SplitSkuName(skuName) skuID := utils.Interface2String(product["sku_id"]) sku := &model.OrderSku{ VendorOrderID: order.VendorOrderID, @@ -135,52 +196,114 @@ func (p *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *mo SkuID: int(utils.Str2Int64WithDefault(skuID, 0)), VendorSkuID: skuID, SkuName: skuName, - Weight: jxutils.FormatSkuWeight(specQuality, specUnit), // 订单信息里没有重量,只有名字里尝试找 + Weight: getSkuWeight(product), + VendorPrice: jxutils.StandardPrice2Int(utils.MustInterface2Float64(product["price"])), SalePrice: jxutils.StandardPrice2Int(utils.MustInterface2Float64(product["price"])), - // PromotionType: int(utils.MustInterface2Int64(product["promotionType"])), } if sku.Weight == 0 { sku.Weight = 222 // 如果名字里找不到缺省给半斤左右的一个特别值 } + if skuBenefitDetailMap != nil && skuBenefitDetailMap[sku.VendorSkuID] != nil && ignoreSkuMap[sku.SkuID] == 0 /* && sku.Count == 1 */ { + for _, v := range skuBenefitDetailMap[sku.VendorSkuID].WmAppOrderActDetails { + if /*skuActTypeMap[v.Type] == 1 && */ strings.Index(v.Remark, skuName) >= 0 && sku.Count == v.Count { + ignoreSkuMap[sku.SkuID] = 1 + sku.SalePrice -= jxutils.StandardPrice2Int(v.MtCharge + v.PoiCharge) + sku.StoreSubName = utils.Int2Str(v.Type) + } + } + } + // if product["isGift"].(bool) { // sku.SkuType = 1 // } order.Skus = append(order.Skus, sku) - order.SkuCount++ - order.GoodsCount += sku.Count - order.SalePrice += sku.SalePrice * int64(sku.Count) - order.Weight += sku.Weight * sku.Count } - // setOrederDetailFee(result, order) + + jxutils.RefreshOrderSkuRelated(order) return order } +func getRefundSkuDetailList(msg *mtwmapi.CallbackMsg) (skuList []*mtwmapi.RefundSkuDetail, err error) { + if false { + skuList = api.MtwmAPI.GetRefundSkuDetailFromMsg(msg) + } else { + refundOrderDetailList, err2 := api.MtwmAPI.GetOrderRefundDetail(utils.Str2Int64(GetOrderIDFromMsg(msg)), mtwmapi.RefundTypePart) + if err = err2; err == nil { + for _, v := range refundOrderDetailList { + skuList = append(skuList, v.WmAppRetailForOrderPartRefundList...) + } + } + } + globals.SugarLogger.Debugf("getRefundSkuDetailList orderID:%s skuList:%s", GetOrderIDFromMsg(msg), utils.Format4Output(skuList, true)) + return skuList, err +} + +func getSkuWeight(product map[string]interface{}) (weight int) { + searchResult := specPat.FindStringSubmatch(product["spec"].(string)) + if len(searchResult) == 3 { + weight = jxutils.FormatSkuWeight(float32(utils.Str2Float64WithDefault(searchResult[1], 0)), utils.TrimBlankChar(searchResult[2])) + } + if weight == 0 { + _, _, _, specUnit, _, specQuality := jxutils.SplitSkuName(product["food_name"].(string)) + weight = jxutils.FormatSkuWeight(specQuality, specUnit) + } + return weight +} + func (c *PurchaseHandler) onOrderMsg(msg *mtwmapi.CallbackMsg) (response *mtwmapi.CallbackResponse) { var err error - if msg.Cmd == mtwmapi.MsgTypeNewOrder { - order, orderMap, err2 := c.getOrder(GetOrderIDFromMsg(msg)) - if err = err2; err == nil { - err = partner.CurOrderManager.OnOrderNew(order, order.VendorStatus) - if err == nil { - utils.CallFuncAsync(func() { - if msg.Cmd == mtwmapi.MsgTypeNewOrder { - c.OnOrderDetail(orderMap, partner.CreatedPeration) - } else { - c.OnOrderDetail(orderMap, partner.UpdatedPeration) - } - }) - } - } + if c.isAfsMsg(msg) { + response = c.OnAfsOrderMsg(msg) } else { - if status := c.callbackMsg2Status(msg); status != nil { - err = partner.CurOrderManager.OnOrderStatusChanged(status) - if err == nil && msg.Cmd == mtwmapi.MsgTypeOrderFinished { - utils.CallFuncAsync(func() { - orderMap, err := api.MtwmAPI.OrderGetOrderDetail(utils.Str2Int64(GetOrderIDFromMsg(msg)), true) - if err == nil && utils.MustInterface2Int64(orderMap["is_third_shipping"]) == SelfDeliveryCarrierNo { - c.OnOrderDetail(orderMap, partner.UpdatedPeration) + status := c.callbackMsg2Status(msg) + if partner.CurOrderManager.GetStatusDuplicatedCount(status) > 0 { + return nil + } + if msg.Cmd == mtwmapi.MsgTypeNewOrder { + order, orderMap, err2 := c.getOrder(GetOrderIDFromMsg(msg)) + if err = err2; err == nil { + err = partner.CurOrderManager.OnOrderNew(order, c.callbackMsg2Status(msg)) + if err == nil { + utils.CallFuncAsync(func() { + if msg.Cmd == mtwmapi.MsgTypeNewOrder { + c.OnOrderDetail(orderMap, partner.CreatedPeration) + } else { + c.OnOrderDetail(orderMap, partner.UpdatedPeration) + } + }) + } + } + } else { + if status != nil { + if status.Status == model.OrderStatusAdjust { + var order *model.GoodsOrder + if order, err = c.GetOrder(GetOrderIDFromMsg(msg)); err == nil { + skuList, err2 := getRefundSkuDetailList(msg) + if err = err2; err == nil { + var removedSkuList []*model.OrderSku + for _, mtwmSku := range skuList { + order.ActualPayPrice -= jxutils.StandardPrice2Int(mtwmSku.RefundPrice) * int64(mtwmSku.Count) + removedSkuList = append(removedSkuList, &model.OrderSku{ + SkuID: int(utils.Str2Int64WithDefault(mtwmSku.SkuID, 0)), + Count: mtwmSku.Count, + }) + } + order = jxutils.RemoveSkuFromOrder(order, removedSkuList) + jxutils.RefreshOrderSkuRelated(order) + err = partner.CurOrderManager.OnOrderAdjust(order, status) + } } - }) + } else { + err = partner.CurOrderManager.OnOrderStatusChanged(status) + if err == nil && msg.Cmd == mtwmapi.MsgTypeOrderFinished { + utils.CallFuncAsync(func() { + orderMap, err := api.MtwmAPI.OrderGetOrderDetail(utils.Str2Int64(GetOrderIDFromMsg(msg)), true) + if err == nil && utils.MustInterface2Int64(orderMap["is_third_shipping"]) == SelfDeliveryCarrierNo { + c.OnOrderDetail(orderMap, partner.UpdatedPeration) + } + }) + } + } } } } @@ -191,24 +314,27 @@ func (c *PurchaseHandler) callbackMsg2Status(msg *mtwmapi.CallbackMsg) (orderSta orderID := GetOrderIDFromMsg(msg) vendorStatus := msg.Cmd remark := "" - statusTime := utils.Str2Int64(msg.Data.Get("timestamp")) + statusTime := utils.Str2Int64(msg.FormData.Get("timestamp")) switch msg.Cmd { case mtwmapi.MsgTypeUserUrgeOrder, mtwmapi.MsgTypeOrderModified, mtwmapi.MsgTypeOrderFinancial: vendorStatus = msg.Cmd case mtwmapi.MsgTypeOrderCanceled: vendorStatus = mtwmapi.OrderStatusCanceled - remark = msg.Data.Get("reason") + remark = msg.FormData.Get("reason") case mtwmapi.MsgTypeNewOrder, FakeMsgTypeOrderReceived, mtwmapi.MsgTypeOrderAccepted, FakeMsgTypeOrderDelivering, mtwmapi.MsgTypeOrderFinished: - vendorStatus = msg.Data.Get("status") - statusTime = utils.Str2Int64(msg.Data.Get("utime")) + vendorStatus = msg.FormData.Get("status") + statusTime = utils.Str2Int64(msg.FormData.Get("utime")) case mtwmapi.MsgTypeOrderRefund, mtwmapi.MsgTypeOrderPartialRefund: - notifyType := msg.Data.Get("notify_type") + notifyType := msg.FormData.Get("notify_type") vendorStatus = msg.Cmd + "-" + notifyType - if !isOrderFinished(utils.Str2Int64(orderID)) { - remark = msg.Data.Get("reason") + if true { // 已经提前判断了,到这里的都是售中 + remark = msg.FormData.Get("reason") if msg.Cmd == mtwmapi.MsgTypeOrderPartialRefund { if notifyType == mtwmapi.NotifyTypePartyApply { - api.MtwmAPI.OrderRefundReject(utils.Str2Int64(orderID), "bu") // todo 京东与饿百都没有售前用户提出订单调整的,自动拒绝调整单 + if globals.EnableMtwmStoreWrite { + // api.MtwmAPI.OrderRefundReject(utils.Str2Int64(orderID), "请联系商户,让商户发起订单调整") // todo 京东与饿百都没有售前用户提出订单调整的,自动拒绝调整单 + api.MtwmAPI.OrderRefundAgree(utils.Str2Int64(orderID), "自动确认退款") + } } else if notifyType == mtwmapi.NotifyTypeSuccess { vendorStatus = fakeOrderAdjustFinished } @@ -217,6 +343,10 @@ func (c *PurchaseHandler) callbackMsg2Status(msg *mtwmapi.CallbackMsg) (orderSta vendorStatus = fakeUserApplyCancel } else if notifyType == mtwmapi.NotifyTypeCancelRefund { vendorStatus = fakeUserUndoApplyCancel + } else if notifyType == mtwmapi.NotifyTypeReject { + vendorStatus = fakeRefuseUserApplyCancel + } else if notifyType == mtwmapi.NotifyTypeSuccess { + vendorStatus = fakeMerchantAgreeApplyCancel } } } @@ -241,14 +371,14 @@ func (c *PurchaseHandler) callbackMsg2Status(msg *mtwmapi.CallbackMsg) (orderSta func (c *PurchaseHandler) postFakeMsg(vendorOrderID, cmd, vendorStatus string) { msg := &mtwmapi.CallbackMsg{ - Cmd: cmd, - Data: make(url.Values), + Cmd: cmd, + FormData: make(url.Values), } timeStr := utils.Int64ToStr(time.Now().Unix()) - msg.Data.Set(mtwmapi.KeyOrderID, vendorOrderID) - msg.Data.Set("status", vendorStatus) - msg.Data.Set("timestamp", timeStr) - msg.Data.Set("utime", timeStr) + msg.FormData.Set(mtwmapi.KeyOrderID, vendorOrderID) + msg.FormData.Set("status", vendorStatus) + msg.FormData.Set("timestamp", timeStr) + msg.FormData.Set("utime", timeStr) utils.CallFuncAsync(func() { OnOrderCallbackMsg(msg) }) @@ -264,7 +394,9 @@ func (c *PurchaseHandler) AcceptOrRefuseOrder(order *model.GoodsOrder, isAcceptI c.postFakeMsg(order.VendorOrderID, FakeMsgTypeOrderReceived, mtwmapi.OrderStatusReceived) } } else { - err = c.CancelOrder(jxcontext.AdminCtx, order, "bu") + if globals.EnableMtwmStoreWrite { + err = c.CancelOrder(jxcontext.AdminCtx, order, "bu") + } } return err } @@ -302,7 +434,9 @@ func (c *PurchaseHandler) Swtich2SelfDeliver(order *model.GoodsOrder, userName s func (c *PurchaseHandler) Swtich2SelfDelivered(order *model.GoodsOrder, userName string) (err error) { globals.SugarLogger.Debugf("mtwm Swtich2SelfDelivered orderID:%s", order.VendorOrderID) if globals.EnableMtwmStoreWrite { - err = api.MtwmAPI.OrderArrived(utils.Str2Int64(order.VendorOrderID)) + // 您好,之前的答复已经更正为,调用变更配送状态的接口,会校验门店的配送类型。美团配送的门店即便转自配后因门店配送类型是美团配送所以无法调用接口变更配送状态。可提醒顾客点击确认收货。谢谢 + // 非自配送门店订单调用OrderArrived好像会报错:{"data":"ng","error":{"code":1038,"msg":"只允许商家配送调用该接口"}} + // err = api.MtwmAPI.OrderArrived(utils.Str2Int64(order.VendorOrderID)) } return err } @@ -371,26 +505,23 @@ func (c *PurchaseHandler) CancelOrder(ctx *jxcontext.Context, order *model.Goods } func (c *PurchaseHandler) AdjustOrder(ctx *jxcontext.Context, order *model.GoodsOrder, removedSkuList []*model.OrderSku, reason string) (err error) { - var skuList []*mtwmapi.RefundSku - for _, sku := range removedSkuList { - skuID := utils.Int2Str(jxutils.GetSkuIDFromOrderSku(sku)) - skuList = append(skuList, &mtwmapi.RefundSku{ - AppFoodCode: skuID, - SkuID: skuID, - Count: sku.Count, - }) + // 美团外卖必须要确认订单后才能调整单 + if order.Status < model.OrderStatusFinishedPickup { + err = c.PickupGoods(order, false, ctx.GetUserName()) } - if globals.EnableMtwmStoreWrite { - err = api.MtwmAPI.OrderApplyPartRefund(utils.Str2Int64(order.VendorOrderID), reason, skuList) + if err == nil { + var skuList []*mtwmapi.RefundSku + for _, sku := range removedSkuList { + skuID := utils.Int2Str(jxutils.GetSkuIDFromOrderSku(sku)) + skuList = append(skuList, &mtwmapi.RefundSku{ + AppFoodCode: skuID, + SkuID: skuID, + Count: sku.Count, + }) + } + if globals.EnableMtwmStoreWrite { + err = api.MtwmAPI.OrderApplyPartRefund(utils.Str2Int64(order.VendorOrderID), reason, skuList) + } } return err } - -func isOrderFinished(vendorOrderID int64) bool { - if status, err := api.MtwmAPI.OrderViewStatus(vendorOrderID); err == nil { - strStatus := utils.Int2Str(status) - return strStatus == mtwmapi.OrderStatusFinished || - strStatus == mtwmapi.OrderStatusCanceled - } - return false -} diff --git a/business/partner/purchase/mtwm/order_afs.go b/business/partner/purchase/mtwm/order_afs.go new file mode 100644 index 000000000..dbdb1b747 --- /dev/null +++ b/business/partner/purchase/mtwm/order_afs.go @@ -0,0 +1,158 @@ +package mtwm + +import ( + "fmt" + "strings" + + "git.rosy.net.cn/baseapi/platformapi/mtwmapi" + "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-callback/business/jxutils" + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "git.rosy.net.cn/jx-callback/business/model" + "git.rosy.net.cn/jx-callback/business/partner" + "git.rosy.net.cn/jx-callback/globals" + "git.rosy.net.cn/jx-callback/globals/api" +) + +var ( + AfsVendorStatus2StatusMap = map[int]int{ + mtwmapi.ResTypePending: model.AfsOrderStatusWait4Approve, + mtwmapi.ResTypeMerchantRefused: model.AfsOrderStatusFailed, + mtwmapi.ResTypeMerchantAgreed: model.AfsOrderStatusFinished, + mtwmapi.ResTypeCSRefused: model.AfsOrderStatusFailed, + mtwmapi.ResTypeCSAgreed: model.AfsOrderStatusFinished, + mtwmapi.ResTypeTimeoutAutoAgreed: model.AfsOrderStatusFinished, + mtwmapi.ResTypeAutoAgreed: model.AfsOrderStatusFinished, + mtwmapi.ResTypeUserCancelApply: model.AfsOrderStatusFailed, + mtwmapi.ResTypeUserCancelComplain: model.AfsOrderStatusFailed, + } +) + +func (c *PurchaseHandler) isAfsMsg(msg *mtwmapi.CallbackMsg) bool { + if msg.Cmd == mtwmapi.MsgTypeOrderRefund || msg.Cmd == mtwmapi.MsgTypeOrderPartialRefund { + // refundData := msg.Data.(*mtwmapi.CallbackRefundInfo) + orderID := utils.Str2Int64(GetOrderIDFromMsg(msg)) + orderInfo, err := api.MtwmAPI.OrderGetOrderDetail2(orderID, false) + if err == nil { + return orderInfo.Status == int(utils.Str2Int64(mtwmapi.OrderStatusFinished)) + } + globals.SugarLogger.Warnf("mtwm isAfsMsg OrderGetOrderDetail2 orderID:%d failed with error:%v", orderID, err) + } + return false +} + +func (c *PurchaseHandler) OnAfsOrderMsg(msg *mtwmapi.CallbackMsg) (retVal *mtwmapi.CallbackResponse) { + jxutils.CallMsgHandlerAsync(func() { + retVal = c.onAfsOrderMsg(msg) + }, jxutils.ComposeUniversalOrderID(GetOrderIDFromMsg(msg), model.VendorIDEBAI)) + return retVal +} + +func (c *PurchaseHandler) onAfsOrderMsg(msg *mtwmapi.CallbackMsg) (retVal *mtwmapi.CallbackResponse) { + var err error + orderStatus := c.callbackAfsMsg2Status(msg) + if orderStatus.Status == model.AfsOrderStatusWait4Approve || orderStatus.Status == model.AfsOrderStatusNew { + var afsOrder *model.AfsOrder + refundData := msg.Data.(*mtwmapi.CallbackRefundInfo) + if msg.Cmd == mtwmapi.MsgTypeOrderPartialRefund { + afsOrder = &model.AfsOrder{ + VendorID: model.VendorIDMTWM, + AfsOrderID: orderStatus.VendorOrderID, + VendorOrderID: orderStatus.RefVendorOrderID, + VendorStoreID: "", + StoreID: 0, + AfsCreatedAt: utils.Timestamp2Time(refundData.Timestamp), + VendorAppealType: "", + AppealType: model.AfsAppealTypeRefund, + VendorReasonType: "", + ReasonType: model.AfsReasonNotOthers, + ReasonDesc: utils.LimitUTF8StringLen(refundData.Reason, 1024), + ReasonImgList: utils.LimitUTF8StringLen(strings.Join(refundData.PictureList, ","), 1024), + RefundType: model.AfsTypePartRefund, + + // FreightUserMoney: afsInfo.OrderFreightMoney, + // AfsFreightMoney: afsInfo.AfsFreight, + // BoxMoney: afsInfo.PackagingMoney, + // TongchengFreightMoney: afsInfo.TongchengFreightMoney, + // SkuBoxMoney: afsInfo.MealBoxMoney, + } + for _, sku := range refundData.FoodList { + orderSku := &model.OrderSkuFinancial{ + // VendorID: model.VendorIDMTWM, + // AfsOrderID: afsOrder.AfsOrderID, + // VendorOrderID: afsOrder.VendorOrderID, + // VendorStoreID: afsOrder.VendorStoreID, + // StoreID: afsOrder.StoreID, + // IsAfsOrder: 1, + + Count: sku.Count, + // ConfirmTime: afsOrder.AfsCreateAt, + VendorSkuID: sku.SkuID, + SkuID: int(utils.Str2Int64WithDefault(sku.SkuID, 0)), + Name: sku.FoodName, + UserMoney: jxutils.StandardPrice2Int(sku.RefundPrice)*int64(sku.Count) + jxutils.StandardPrice2Int(sku.BoxPrice)*int64(sku.BoxNum), + } + afsOrder.SkuUserMoney += orderSku.UserMoney + afsOrder.Skus = append(afsOrder.Skus, orderSku) + } + afsOrder.PmSubsidyMoney += afsOrder.RefundMoney - afsOrder.SkuUserMoney + } else { + if orderFinancial, err2 := partner.CurOrderManager.LoadOrderFinancial(orderStatus.RefVendorOrderID, model.VendorIDMTWM); err2 == nil { + afsOrder = c.OrderFinancialDetail2Refund(orderFinancial, msg.FormData) + afsOrder.AfsOrderID = orderStatus.VendorOrderID + afsOrder.RefundType = model.AfsTypeFullRefund + afsOrder.AppealType = model.AfsAppealTypeRefund + afsOrder.VendorReasonType = "" + afsOrder.ReasonType = model.AfsReasonNotOthers + afsOrder.ReasonDesc = utils.LimitUTF8StringLen(refundData.Reason, 1024) + afsOrder.ReasonImgList = utils.LimitUTF8StringLen(strings.Join(refundData.PictureList, ","), 1024) + } + } + if afsOrder != nil { + err = partner.CurOrderManager.OnAfsOrderNew(afsOrder, orderStatus) + } + } else { + err = partner.CurOrderManager.OnAfsOrderStatusChanged(orderStatus) + } + return mtwmapi.Err2CallbackResponse(err, "") +} + +func (c *PurchaseHandler) callbackAfsMsg2Status(msg *mtwmapi.CallbackMsg) (orderStatus *model.OrderStatus) { + refundData := msg.Data.(*mtwmapi.CallbackRefundInfo) + orderStatus = &model.OrderStatus{ + VendorID: model.VendorIDMTWM, + OrderType: model.OrderTypeAfsOrder, + RefVendorOrderID: utils.Int64ToStr(refundData.OrderID), + RefVendorID: model.VendorIDMTWM, + VendorStatus: utils.Int2Str(refundData.ResType), + Status: c.GetAfsStatusFromVendorStatus(refundData.ResType), + StatusTime: utils.Timestamp2Time(refundData.Timestamp), + Remark: refundData.Reason, + } + if refundData.RefundID > 0 { + orderStatus.VendorOrderID = utils.Int64ToStr(refundData.RefundID) + } + return orderStatus +} + +func (c *PurchaseHandler) GetAfsStatusFromVendorStatus(vendorStatus int) int { + return AfsVendorStatus2StatusMap[vendorStatus] +} + +// 审核售后单申请 +func (c *PurchaseHandler) AgreeOrRefuseRefund(ctx *jxcontext.Context, order *model.AfsOrder, approveType int, reason string) (err error) { + if globals.EnableMtwmStoreWrite { + if approveType == partner.AfsApproveTypeRefused { + err = api.MtwmAPI.OrderRefundReject(utils.Str2Int64(order.VendorOrderID), reason) + } else { + err = api.MtwmAPI.OrderRefundAgree(utils.Str2Int64(order.VendorOrderID), reason) + } + } + return err +} + +// 确认收到退货 +func (c *PurchaseHandler) ConfirmReceivedReturnGoods(ctx *jxcontext.Context, order *model.AfsOrder) (err error) { + err = fmt.Errorf("内部错误,美团外卖平台不支持确认收到退货操作") + return err +} diff --git a/business/partner/purchase/mtwm/order_comment.go b/business/partner/purchase/mtwm/order_comment.go index 9172a2ab1..ae1d8b6a7 100644 --- a/business/partner/purchase/mtwm/order_comment.go +++ b/business/partner/purchase/mtwm/order_comment.go @@ -1,10 +1,80 @@ package mtwm import ( + "time" + + "git.rosy.net.cn/baseapi/platformapi/mtwmapi" + + "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" + + "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" "git.rosy.net.cn/jx-callback/business/model" + "git.rosy.net.cn/jx-callback/business/model/dao" + "git.rosy.net.cn/jx-callback/globals" + "git.rosy.net.cn/jx-callback/globals/api" ) -func (c *PurchaseHandler) ReplyOrderComment(ctx *jxcontext.Context, orderComment *model.OrderComment, replyComment string) (err error) { +const ( + RefreshCommentTime = 36 * time.Hour + RefreshCommentTimeInterval = 60 * time.Minute + BAD_COMMENTS_MAX_MODIFY_TIME = 24 // 小时 +) + +func (c *PurchaseHandler) StartRefreshComment() { + if globals.ReallyCallPlatformAPI { + utils.AfterFuncWithRecover(5*time.Second, func() { + c.refreshCommentOnce() + }) + } +} + +func (c *PurchaseHandler) refreshCommentOnce() { + c.RefreshComment(time.Now().Add(-RefreshCommentTime), time.Now()) + utils.AfterFuncWithRecover(RefreshCommentTimeInterval, func() { + c.refreshCommentOnce() + }) +} + +func (c *PurchaseHandler) RefreshComment(fromTime, toTime time.Time) (err error) { + if globals.EnableMtwmStoreWrite { + storeMapList, err2 := dao.GetStoresMapList(dao.GetDB(), []int{model.VendorIDMTWM}, nil, model.StoreStatusAll) + if err = err2; err != nil { + return err + } + task := tasksch.NewParallelTask("mtwm RefreshComment", nil, jxcontext.AdminCtx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + storeMap := batchItemList[0].(*model.StoreMap) + startDateStr := time.Now().Format("20060102") + endDateStr := time.Now().Add(-RefreshCommentTime).Format("20060102") + commentList, err2 := api.MtwmAPI.CommentQuery(storeMap.VendorStoreID, startDateStr, endDateStr, 0, 0, mtwmapi.CommentReplyStatusAll) + if err = err2; err != nil { + return nil, err + } + return commentList, nil + }, storeMapList) + task.Run() + resultList, err2 := task.GetResult(0) + if err = err2; err != nil { + return err + } + var orderCommentList []*model.OrderComment + for _, result := range resultList { + mtwmComment := result.(*mtwmapi.OrderComment) + orderComment := &model.OrderComment{ + VendorID: model.VendorIDMTWM, + TagList: mtwmComment.CommentLables, + Score: int8(mtwmComment.FoodCommentScore), + } + orderCommentList = append(orderCommentList, orderComment) + } + } + return err +} + +func (c *PurchaseHandler) ReplyOrderComment(ctx *jxcontext.Context, orderComment *model.OrderComment, replyComment string) (err error) { + if globals.EnableMtwmStoreWrite { + err = api.MtwmAPI.CommentAddReply(orderComment.VendorStoreID, utils.Str2Int64(orderComment.UserCommentID), replyComment) + } return err } diff --git a/business/partner/purchase/mtwm/store.go b/business/partner/purchase/mtwm/store.go index ffd74a17e..b19fc2013 100644 --- a/business/partner/purchase/mtwm/store.go +++ b/business/partner/purchase/mtwm/store.go @@ -37,13 +37,13 @@ func (p *PurchaseHandler) ReadStore(vendorStoreID string) (retVal *model.Store, if err == nil { // globals.SugarLogger.Debug(utils.Format4Output(result, false)) retVal = &model.Store{ - Address: utils.Interface2String(result["address"]), - Tel1: utils.Interface2String(result["phone"]), + Address: result.Address, + Tel1: result.Phone, } - retVal.OriginalName = utils.Interface2String(result["name"]) + retVal.OriginalName = result.Name _, retVal.Name = jxutils.SplitStoreName(retVal.OriginalName, partner.StoreNameSeparator, VendorStorePrefix) - openTimes := openTimeMtwm2JX(result["shipping_time"].(string)) + openTimes := openTimeMtwm2JX(result.ShippingTime) if len(openTimes) > 0 { retVal.OpenTime1 = openTimes[0][0] retVal.CloseTime1 = openTimes[0][1] @@ -52,15 +52,15 @@ func (p *PurchaseHandler) ReadStore(vendorStoreID string) (retVal *model.Store, retVal.CloseTime2 = openTimes[1][1] } } - retVal.Status = bizStatusMtwm2JX(int(utils.MustInterface2Int64(result["open_level"])), int(utils.MustInterface2Int64(result["is_online"]))) + retVal.Status = bizStatusMtwm2JX(result.OpenLevel, result.IsOnline) - tel2 := utils.Interface2String(result["standby_tel"]) + tel2 := result.StandbyTel if tel2 != "" && tel2 != retVal.Tel1 { retVal.Tel2 = tel2 } - retVal.Lng = int(utils.MustInterface2Float64(result["longitude"])) - retVal.Lat = int(utils.MustInterface2Float64(result["latitude"])) + retVal.Lng = int(result.Longitude) + retVal.Lat = int(result.Latitude) lng := jxutils.IntCoordinate2Standard(retVal.Lng) lat := jxutils.IntCoordinate2Standard(retVal.Lat) @@ -69,7 +69,7 @@ func (p *PurchaseHandler) ReadStore(vendorStoreID string) (retVal *model.Store, if district, err := dao.GetPlaceByCode(db, retVal.DistrictCode); err == nil { retVal.CityCode = district.ParentCode } - poiCode := utils.Interface2String(result["app_poi_code"]) + poiCode := result.AppPoiCode retVal.ID = int(utils.Str2Int64WithDefault(poiCode, 0)) retVal.DeliveryRangeType = model.DeliveryRangeTypePolygon var deliveryRangeInfo []map[string]interface{} @@ -140,7 +140,7 @@ func (p *PurchaseHandler) UpdateStore(db *dao.DaoDB, storeID int, userName strin if err = err2; err != nil { return err } - if int(utils.MustInterface2Int64(remoteStoreInfo["is_online"])) == mtwmapi.PoiStatusOnline { + if remoteStoreInfo.IsOnline == mtwmapi.PoiStatusOnline { if openLevel == mtwmapi.PoiOpenLevelHaveRest { err = api.MtwmAPI.PoiClose(storeDetail.VendorStoreID) } else { @@ -164,8 +164,8 @@ func (p *PurchaseHandler) RefreshAllStoresID(ctx *jxcontext.Context, parentTask func (p *PurchaseHandler) onStoreStatusChanged(msg *mtwmapi.CallbackMsg) (response *mtwmapi.CallbackResponse) { var err error - poiStatus := int(utils.Str2Int64(msg.Data.Get("poi_status"))) - vendorStoreID := msg.Data.Get("app_poi_code") + poiStatus := int(utils.Str2Int64(msg.FormData.Get("poi_status"))) + vendorStoreID := msg.FormData.Get("app_poi_code") storeStatus := 0 if poiStatus == mtwmapi.MsgPoiStatusOpened { storeStatus = model.StoreStatusOpened @@ -186,7 +186,7 @@ func (p *PurchaseHandler) onStoreStatusChanged(msg *mtwmapi.CallbackMsg) (respon func (p *PurchaseHandler) GetStoreStatus(ctx *jxcontext.Context, vendorStoreID string) (storeStatus int, err error) { result, err := api.MtwmAPI.PoiGet(vendorStoreID) if err == nil { - return bizStatusMtwm2JX(int(utils.MustInterface2Int64(result["open_level"])), int(utils.MustInterface2Int64(result["is_online"]))), nil + return bizStatusMtwm2JX(result.OpenLevel, result.IsOnline), nil } return 0, err } diff --git a/business/partner/purchase/mtwm/store_sku.go b/business/partner/purchase/mtwm/store_sku.go index eefa2ee33..b085c40fa 100644 --- a/business/partner/purchase/mtwm/store_sku.go +++ b/business/partner/purchase/mtwm/store_sku.go @@ -3,6 +3,8 @@ package mtwm import ( "errors" "fmt" + "strings" + "time" "git.rosy.net.cn/baseapi/platformapi/mtwmapi" "git.rosy.net.cn/baseapi/utils" @@ -11,6 +13,7 @@ import ( "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/partner" "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/globals/api" ) @@ -34,44 +37,49 @@ func (p *PurchaseHandler) SyncStoreCategory(ctx *jxcontext.Context, parentTask t task := tasksch.NewParallelTask(fmt.Sprintf("美团外卖SyncStoreCategory step2, level=%d", level), nil, ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { updateFields := []string{dao.GetSyncStatusStructField(model.VendorNames[model.VendorIDMTWM])} - catInfo := batchItemList[0].(*dao.StoreCatSyncInfo) - if globals.EnableStoreWrite && globals.EnableMtwmStoreWrite { - if catInfo.MtwmSyncStatus&model.SyncFlagDeletedMask != 0 { // 删除 - if catInfo.MtwmSyncStatus&model.SyncFlagNewMask == 0 && catInfo.MtwmID != "" { - globals.SugarLogger.Debugf("RetailCatDelete vendorStoreID:%s, MtwmID:%s", vendorStoreID, catInfo.MtwmID) - err = api.MtwmAPI.RetailCatDelete(vendorStoreID, catInfo.MtwmID) + catInfo := batchItemList[0].(*dao.SkuStoreCatInfo) + storeCatMap := &model.StoreSkuCategoryMap{} + storeCatMap.ID = catInfo.MapID + if catInfo.StoreCatSyncStatus&model.SyncFlagDeletedMask != 0 { // 删除 + if catInfo.StoreCatSyncStatus&model.SyncFlagNewMask == 0 && !dao.IsVendorThingIDEmpty(catInfo.VendorCatID) { + globals.SugarLogger.Debugf("RetailCatDelete vendorStoreID:%s, MtwmID:%s", vendorStoreID, catInfo.VendorCatID) + if globals.EnableMtwmStoreWrite { + err = api.MtwmAPI.RetailCatDelete(vendorStoreID, catInfo.VendorCatID) } - } else if catInfo.MtwmSyncStatus&(model.SyncFlagNewMask|model.SyncFlagModifiedMask) != 0 { // 新增 - catName := catInfo.CatName - subCatName := "" - originName := "" - if catInfo.MtwmSyncStatus&model.SyncFlagNewMask == 0 { - originName = catInfo.MtwmID - } - if level == 2 { - originName = catInfo.ParentCatName - catName = catInfo.ParentCatName - subCatName = catInfo.CatName - if catInfo.MtwmSyncStatus&model.SyncFlagNewMask == 0 { - originName = catInfo.MtwmID - catName = catInfo.CatName - subCatName = "" - } - } - if catName == "" { - panic("catName is empty") - } - globals.SugarLogger.Debugf("RetailCatUpdate vendorStoreID:%s, originName:%s, catName:%s, subCatName:%s, seq:%d", vendorStoreID, originName, catName, subCatName, catInfo.Seq) - if err = api.MtwmAPI.RetailCatUpdate(vendorStoreID, originName, catName, subCatName, catInfo.Seq); err == nil { - catInfo.MtwmID = catInfo.CatName - updateFields = append(updateFields, dao.GetVendorThingIDStructField(model.VendorNames[model.VendorIDMTWM])) + } + } else if catInfo.StoreCatSyncStatus&(model.SyncFlagNewMask|model.SyncFlagStoreSkuModifiedMask) != 0 { // 新增 + catName := catInfo.Name + subCatName := "" + originName := "" + if catInfo.StoreCatSyncStatus&model.SyncFlagNewMask == 0 { + originName = catInfo.VendorCatID + } + if level == 2 { + originName = catInfo.ParentCatName + catName = catInfo.ParentCatName + subCatName = catInfo.Name + if catInfo.StoreCatSyncStatus&model.SyncFlagNewMask == 0 { + originName = catInfo.VendorCatID + catName = catInfo.Name + subCatName = "" } } + if catName == "" { + panic("catName is empty") + } + globals.SugarLogger.Debugf("RetailCatUpdate vendorStoreID:%s, originName:%s, catName:%s, subCatName:%s, seq:%d", vendorStoreID, originName, catName, subCatName, catInfo.Seq) + if globals.EnableMtwmStoreWrite { + err = api.MtwmAPI.RetailCatUpdate(vendorStoreID, originName, catName, subCatName, catInfo.Seq) + } + if err == nil { + storeCatMap.MtwmID = catInfo.Name + updateFields = append(updateFields, dao.GetVendorThingIDStructField(model.VendorNames[model.VendorIDMTWM])) + } } if err == nil { db2 := dao.GetDB() - catInfo.MtwmSyncStatus = 0 - _, err = dao.UpdateEntity(db2, &catInfo.StoreSkuCategoryMap, updateFields...) + storeCatMap.MtwmSyncStatus = 0 + _, err = dao.UpdateEntity(db2, storeCatMap, updateFields...) } return nil, err }, catList) @@ -129,7 +137,7 @@ func (p *PurchaseHandler) SyncLocalStoreCategory(ctx *jxcontext.Context, db *dao localCat := catMap[level-1][parentCatName+"/"+catName] // globals.SugarLogger.Debug(parentCatName, " ", catName, " ", localCat) if localCat == nil { // 本地分类就没有这个名字,直接删除 - if globals.EnableStoreWrite && globals.EnableMtwmStoreWrite { + if globals.EnableMtwmStoreWrite { globals.SugarLogger.Debugf("RetailCatDelete2 vendorStoreID:%s, catName:%s", vendorStoreID, catName) if err = api.MtwmAPI.RetailCatDelete(vendorStoreID, catName); err != nil { return err @@ -174,7 +182,7 @@ func (p *PurchaseHandler) SyncLocalStoreCategory(ctx *jxcontext.Context, db *dao return "", err } } else { - if v.VendorCatID == "" || v.VendorCatID == "0" { + if dao.IsVendorThingIDEmpty(v.VendorCatID) { num++ } if isCheckRemote { @@ -255,52 +263,101 @@ func (p *PurchaseHandler) SyncStoreSkus(ctx *jxcontext.Context, parentTask tasks updateFields := []string{model.FieldMtwmSyncStatus} storeSkuBind := &model.StoreSkuBind{} storeSkuBind.ID = skuItem.BindID - if skuItem.ID == 0 || skuItem.SkuSyncStatus&model.SyncFlagDeletedMask != 0 { - if skuItem.SkuSyncStatus&model.SyncFlagNewMask == 0 && skuItem.VendorSkuID != "" { - err = api.MtwmAPI.RetailDelete(vendorStoreID, skuItem.VendorSkuID) + if skuItem.NameID == 0 || skuItem.StoreSkuSyncStatus&model.SyncFlagDeletedMask != 0 { + if skuItem.StoreSkuSyncStatus&model.SyncFlagNewMask == 0 && !dao.IsVendorThingIDEmpty(skuItem.VendorSkuID) { + if globals.EnableMtwmStoreWrite { + err = api.MtwmAPI.RetailDelete(vendorStoreID, skuItem.VendorSkuID) + } + err = ignoreNoAppFoodErr(err) } - } else if skuItem.SkuSyncStatus&(model.SyncFlagModifiedMask|model.SyncFlagNewMask) != 0 { + if err == nil { + storeSkuBind.DeletedAt = time.Now() + updateFields = append(updateFields, model.FieldDeletedAt) + if !dao.IsVendorThingIDEmpty(skuItem.VendorSkuID) { + storeSkuBind.MtwmID = 0 + updateFields = append(updateFields, model.FieldMtwmID) + } + } + } else if skuItem.StoreSkuSyncStatus&(model.SyncFlagStoreSkuModifiedMask|model.SyncFlagNewMask) != 0 { foodData := make(map[string]interface{}) foodDataList[0] = foodData - foodData[mtwmapi.KeyAppFoodCode] = utils.Int2Str(skuItem.ID) - foodData["name"] = jxutils.ComposeSkuName(skuItem.Prefix, skuItem.Name, skuItem.Comment, skuItem.Unit, skuItem.SpecQuality, skuItem.SpecUnit, 30) - foodData["description"] = skuItem.Comment - foodData["price"] = jxutils.IntPrice2Standard(int64(jxutils.CaculateSkuVendorPrice(int(skuItem.Price), int(storeDetail.PricePercentage)))) - foodData["min_order_count"] = 1 - foodData["unit"] = skuItem.Unit - foodData["box_num"] = 0 - foodData["box_price"] = 0.0 - foodData["category_name"] = skuItem.VendorCatID - foodData["is_sold_out"] = skuStatusJX2Mtwm(jxutils.MergeSkuStatus(skuItem.Status, skuItem.StoreSkuStatus)) - foodData["picture"] = skuItem.Img - foodData["sequence"] = skuItem.Seq - if skuItem.VendorVendorCatID != 0 { - foodData["tag_id"] = utils.Int64ToStr(skuItem.VendorVendorCatID) - } else { - // foodData["tag_id"] = utils.Int64ToStr(defVendorCatID) - } + foodData[mtwmapi.KeyAppFoodCode] = utils.Int2Str(skuItem.SkuID) skus := []map[string]interface{}{ map[string]interface{}{ "sku_id": foodData[mtwmapi.KeyAppFoodCode], - "spec": jxutils.ComposeSkuSpec(skuItem.SpecQuality, skuItem.SpecUnit), - "price": foodData["price"], - "stock": "*", - "upc": skuItem.Upc, }, } - if foodData["tag_id"] != nil { - skus[0]["weight"] = skuItem.Weight // weight字段仅限服饰鞋帽、美妆、日用品、母婴、生鲜果蔬、生活超市下的便利店/超市门店品类的商家使用 - } foodData["skus"] = skus - if globals.EnableStoreWrite && globals.EnableMtwmStoreWrite { - if err = api.MtwmAPI.RetailBatchInitData(vendorStoreID, foodDataList); err == nil { - storeSkuBind.MtwmID = int64(skuItem.ID) - updateFields = append(updateFields, model.FieldMtwmID) + shouldCallSellStatus := !(skuItem.StoreSkuSyncStatus&(model.SyncFlagModifiedMask|model.SyncFlagNewMask|model.SyncFlagPriceMask) != 0) + if !shouldCallSellStatus { + globals.SugarLogger.Debugf("mtwm SyncStoreSkus3 skuID:%d, SkuSyncStatus:%d", skuItem.SkuID, skuItem.StoreSkuSyncStatus) + mergeStoreSkuStatus := jxutils.MergeSkuStatus(skuItem.Status, skuItem.StoreSkuStatus) + if !(skuItem.StoreSkuSyncStatus&model.SyncFlagNewMask != 0 && mergeStoreSkuStatus != model.SkuStatusNormal) { // 待创建且不可售的,暂不新建 + if skuItem.Img == "" { + err = fmt.Errorf("SKUNAME%d:%s没有图片,同步失败", skuItem.NameID, skuItem.Name) + } else { + foodData["name"] = jxutils.ComposeSkuName(skuItem.Prefix, skuItem.Name, skuItem.Comment, skuItem.Unit, skuItem.SpecQuality, skuItem.SpecUnit, mtwmapi.MaxSkuNameCharCount) + foodData["description"] = skuItem.Comment + foodData["price"] = jxutils.IntPrice2Standard(int64(jxutils.CaculateSkuVendorPrice(int(skuItem.Price), int(storeDetail.PricePercentage), skuItem.CatPricePercentage))) + foodData["min_order_count"] = 1 + foodData["unit"] = skuItem.Unit + foodData["box_num"] = 0 + foodData["box_price"] = 0.0 + foodData["category_name"] = skuItem.VendorCatID + foodData["is_sold_out"] = skuStatusJX2Mtwm(mergeStoreSkuStatus) + foodData["picture"] = skuItem.Img + if skuItem.DescImg != "" { + foodData["picture_contents"] = skuItem.DescImg + } + foodData["sequence"] = skuItem.Price + if skuItem.VendorVendorCatID != 0 { + foodData["tag_id"] = utils.Int64ToStr(skuItem.VendorVendorCatID) + } else { + // foodData["tag_id"] = utils.Int64ToStr(defVendorCatID) + } + skus[0]["spec"] = jxutils.ComposeSkuSpec(skuItem.SpecQuality, skuItem.SpecUnit) + skus[0]["price"] = foodData["price"] + skus[0]["stock"] = "*" + skus[0]["upc"] = skuItem.Upc + if foodData["tag_id"] != nil { + skus[0]["weight"] = skuItem.Weight // weight字段仅限服饰鞋帽、美妆、日用品、母婴、生鲜果蔬、生活超市下的便利店/超市门店品类的商家使用 + } + if globals.EnableMtwmStoreWrite { + err = api.MtwmAPI.RetailBatchInitData(vendorStoreID, foodDataList) + } + if err == nil { + storeSkuBind.MtwmID = int64(skuItem.SkuID) + updateFields = append(updateFields, model.FieldMtwmID) + } + } + } else { + // 暂不创建 + updateFields = nil + } + } + if err != nil { + if isErrModifyPrice(err) { + shouldCallSellStatus = true + } + } + if shouldCallSellStatus { + if skuItem.StoreSkuSyncStatus&(model.SyncFlagSaleMask) != 0 { + globals.SugarLogger.Debugf("mtwm SyncStoreSkus4 skuID:%d, SkuSyncStatus:%d", skuItem.SkuID, skuItem.StoreSkuSyncStatus) + sellStatus := skuStatusJX2Mtwm(jxutils.MergeSkuStatus(skuItem.Status, skuItem.StoreSkuStatus)) + if globals.EnableMtwmStoreWrite { + if err2 := api.MtwmAPI.RetailSkuSellStatus(vendorStoreID, foodDataList, sellStatus); err2 != nil { + err = err2 + } + } } } } if err == nil { - _, err = dao.UpdateEntity(db, storeSkuBind, updateFields...) + if len(updateFields) > 0 { + _, err = dao.UpdateEntity(db, storeSkuBind, updateFields...) + } + } else if isErrModifyPrice(err) { + err = partner.NewErrorCode(err.Error(), partner.ErrCodeChangePriceFailed, model.VendorIDMTWM) } return nil, err }, skus) @@ -317,6 +374,18 @@ func (p *PurchaseHandler) SyncStoreSkus(ctx *jxcontext.Context, parentTask tasks return hint, err } +func isErrModifyPrice(err error) bool { + if errExt, ok := err.(*utils.ErrorWithCode); ok && errExt.IntCode() == 1 { + for _, v := range []string{ + "折扣商品原价不允许修改", + } { + if strings.Index(errExt.ErrMsg(), v) >= 0 { + return true + } + } + } + return false +} func (p *PurchaseHandler) RefreshStoresAllSkusID(ctx *jxcontext.Context, parentTask tasksch.ITask, isAsync bool, storeIDs []int) (hint string, err error) { return hint, err } @@ -335,10 +404,10 @@ func (p *PurchaseHandler) FullSyncStoreSkus(ctx *jxcontext.Context, parentTask t err = nil } case 1: - _, err = dao.SetStoreSkuSyncStatus(db, model.VendorIDMTWM, storeID, nil, model.SyncFlagNewMask) + _, err = dao.SetStoreSkuSyncStatus(db, model.VendorIDMTWM, []int{storeID}, nil, model.SyncFlagNewMask) case 2: if err = p.DeleteRemoteCategories(ctx, rootTask, storeID, nil); err == nil { - _, err = dao.SetStoreCategorySyncStatus(db, model.VendorIDMTWM, storeID, nil, model.SyncFlagNewMask) + _, err = dao.SetStoreCategorySyncStatus(db, model.VendorIDMTWM, []int{storeID}, nil, model.SyncFlagNewMask) } case 3: _, err = p.SyncLocalStoreCategory(ctx, db, storeID, true, nil) @@ -373,7 +442,7 @@ func (p *PurchaseHandler) DeleteRemoteSkus(ctx *jxcontext.Context, parentTask ta vendorStoreID := storeDetail.VendorStoreID task := tasksch.NewParallelTask("mtwm DeleteRemoteSkus", tasksch.NewParallelConfig().SetIsContinueWhenError(true), ctx, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { - if globals.EnableStoreWrite && globals.EnableMtwmStoreWrite { + if globals.EnableMtwmStoreWrite { // globals.SugarLogger.Debugf("mtwm RetailDelete vendorStoreID:%s, sku:%s", vendorStoreID, batchItemList[0].(string)) err = api.MtwmAPI.RetailDelete(vendorStoreID, batchItemList[0].(string)) } @@ -434,7 +503,7 @@ func (p *PurchaseHandler) DeleteRemoteCategories(ctx *jxcontext.Context, parentT if len(catIDs) > 0 { task := tasksch.NewParallelTask("mtwm DeleteRemoteCategories paralle", tasksch.NewParallelConfig().SetIsContinueWhenError(true), ctx, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { - if globals.EnableStoreWrite && globals.EnableMtwmStoreWrite { + if globals.EnableMtwmStoreWrite { err = api.MtwmAPI.RetailCatDelete(vendorStoreID, batchItemList[0].(string)) } return nil, err @@ -466,10 +535,10 @@ func (p *PurchaseHandler) DeleteRemoteStoreSkus(ctx *jxcontext.Context, parentTa err = nil } case 1: - _, err = dao.SetStoreSkuSyncStatus(db, model.VendorIDMTWM, storeID, nil, model.SyncFlagNewMask) + _, err = dao.SetStoreSkuSyncStatus(db, model.VendorIDMTWM, []int{storeID}, nil, model.SyncFlagNewMask) case 2: if err = p.DeleteRemoteCategories(ctx, rootTask, storeID, nil); err == nil { - _, err = dao.SetStoreCategorySyncStatus(db, model.VendorIDMTWM, storeID, nil, model.SyncFlagNewMask) + _, err = dao.SetStoreCategorySyncStatus(db, model.VendorIDMTWM, []int{storeID}, nil, model.SyncFlagNewMask) } } return nil, err @@ -483,3 +552,18 @@ func (p *PurchaseHandler) DeleteRemoteStoreSkus(ctx *jxcontext.Context, parentTa } return rootTask.ID, err } + +func (p *PurchaseHandler) GetStoresSku(ctx *jxcontext.Context, parentTask tasksch.ITask, storeIDs []int) (storeSkuList []*model.StoreSkuBind, err error) { + return storeSkuList, err +} + +func ignoreNoAppFoodErr(err error) error { + if err != nil { + if codeErr, ok := err.(*utils.ErrorWithCode); ok { + if codeErr.IntCode() == mtwmapi.ErrCodeNoAppFood { + err = nil + } + } + } + return err +} diff --git a/business/partner/purchase/mtwm/store_sku2.go b/business/partner/purchase/mtwm/store_sku2.go new file mode 100644 index 000000000..3624ec624 --- /dev/null +++ b/business/partner/purchase/mtwm/store_sku2.go @@ -0,0 +1,254 @@ +package mtwm + +import ( + "git.rosy.net.cn/baseapi/platformapi/mtwmapi" + "git.rosy.net.cn/baseapi/utils" + "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/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/partner" + "git.rosy.net.cn/jx-callback/globals" + "git.rosy.net.cn/jx-callback/globals/api" +) + +// 门店分类 +func (p *PurchaseHandler) ReadStoreCategory(ctx *jxcontext.Context, vendorStoreID string) (cats []*partner.BareCategoryInfo, err error) { + remoteCats, err := api.MtwmAPI.RetailCatList(vendorStoreID) + if err == nil { + cats = convertVendorCatList(remoteCats) + } + return cats, err +} + +func convertVendorCatList(remoteCats []*mtwmapi.RetailCategoryInfo) (cats []*partner.BareCategoryInfo) { + for _, rCat := range remoteCats { + cat := &partner.BareCategoryInfo{ + VendorCatID: rCat.Name, + Name: rCat.Name, + Level: rCat.Level, + Seq: rCat.Sequence, + Children: convertVendorCatList(rCat.Children), + } + cats = append(cats, cat) + } + return cats +} + +func (p *PurchaseHandler) CreateStoreSkuCategory(ctx *jxcontext.Context, vendorStoreID string, storeCat *dao.SkuStoreCatInfo) (err error) { + level := 1 + if storeCat.ParentCatName != "" { + level = 2 + } + catName := storeCat.Name + subCatName := "" + originName := "" + if storeCat.StoreCatSyncStatus&model.SyncFlagNewMask == 0 { + originName = storeCat.VendorCatID + } + if level == 2 { + originName = storeCat.ParentCatName + catName = storeCat.ParentCatName + subCatName = storeCat.Name + if storeCat.StoreCatSyncStatus&model.SyncFlagNewMask == 0 { + originName = storeCat.VendorCatID + catName = storeCat.Name + subCatName = "" + } + } + if catName == "" { + panic("catName is empty") + } + globals.SugarLogger.Debugf("mtwm CreateStoreSkuCategory vendorStoreID:%s, originName:%s, catName:%s, subCatName:%s, seq:%d", vendorStoreID, originName, catName, subCatName, storeCat.Seq) + if globals.EnableMtwmStoreWrite { + err = api.MtwmAPI.RetailCatUpdate(vendorStoreID, originName, catName, subCatName, storeCat.Seq) + } + if err == nil { + storeCat.VendorCatID = storeCat.Name + } + return err +} + +func (p *PurchaseHandler) UpdateStoreSkuCategory(ctx *jxcontext.Context, vendorStoreID string, storeCat *dao.SkuStoreCatInfo) (err error) { + return p.CreateStoreSkuCategory(ctx, vendorStoreID, storeCat) +} + +func (p *PurchaseHandler) DeleteStoreSkuCategory(ctx *jxcontext.Context, vendorStoreID, vendorCatID string) (err error) { + if globals.EnableMtwmStoreWrite { + err = api.MtwmAPI.RetailCatDelete(vendorStoreID, vendorCatID) + } + return err +} + +// 门店商品 + +// 多门店平台不需要实现这个接口 +func (p *PurchaseHandler) UpdateStoreSkus(ctx *jxcontext.Context, vendorStoreID string, storeSkuList []*dao.StoreSkuSyncInfo) (err error) { + return p.CreateStoreSkus(ctx, vendorStoreID, storeSkuList) +} + +// 通用 + +func (p *PurchaseHandler) GetStoreSkusBatchSize(funcID int) int { + return 1 +} + +// 对于多门店平台来说,storeSkuList中只有SkuID与VendorSkuID有意义 +func (p *PurchaseHandler) CreateStoreSkus(ctx *jxcontext.Context, vendorStoreID string, storeSkuList []*dao.StoreSkuSyncInfo) (err error) { + foodDataList := make([]map[string]interface{}, len(storeSkuList)) + for i, storeSku := range storeSkuList { + foodData := make(map[string]interface{}) + foodDataList[i] = foodData + foodData[mtwmapi.KeyAppFoodCode] = utils.Int2Str(storeSku.SkuID) + skus := []map[string]interface{}{ + map[string]interface{}{ + "sku_id": foodData[mtwmapi.KeyAppFoodCode], + }, + } + foodData["skus"] = skus + foodData["name"] = utils.LimitUTF8StringLen(storeSku.Name, 30) + foodData["description"] = storeSku.Comment + foodData["price"] = storeSku.Price + foodData["min_order_count"] = 1 + foodData["unit"] = storeSku.Unit + foodData["box_num"] = 0 + foodData["box_price"] = 0.0 + foodData["category_name"] = storeSku.VendorCatID + foodData["is_sold_out"] = skuStatusJX2Mtwm(storeSku.StoreSkuStatus) + foodData["picture"] = storeSku.Img + if storeSku.DescImg != "" { + foodData["picture_contents"] = storeSku.DescImg + } + foodData["sequence"] = storeSku.Price + if storeSku.VendorVendorCatID != 0 { + foodData["tag_id"] = utils.Int64ToStr(storeSku.VendorVendorCatID) + } else { + // foodData["tag_id"] = utils.Int64ToStr(defVendorCatID) + } + skus[0]["spec"] = jxutils.ComposeSkuSpec(storeSku.SpecQuality, storeSku.SpecUnit) + skus[0]["price"] = foodData["price"] + skus[0]["stock"] = "*" + skus[0]["upc"] = storeSku.Upc + if foodData["tag_id"] != nil { + skus[0]["weight"] = storeSku.Weight // weight字段仅限服饰鞋帽、美妆、日用品、母婴、生鲜果蔬、生活超市下的便利店/超市门店品类的商家使用 + } + } + if globals.EnableMtwmStoreWrite { + err = api.MtwmAPI.RetailBatchInitData(vendorStoreID, foodDataList) + } + if err == nil { + for _, storeSku := range storeSkuList { + storeSku.VendorSkuID = utils.Int2Str(storeSku.SkuID) + } + } + return err +} + +func (p *PurchaseHandler) DeleteStoreSkus(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*partner.BareStoreSkuInfo) (err error) { + if globals.EnableMtwmStoreWrite { + if len(storeSkuList) == 1 { + err = api.MtwmAPI.RetailDelete(vendorStoreID, storeSkuList[0].VendorSkuID) + } else { + err = api.MtwmAPI.RetailCatSkuBatchDelete(vendorStoreID, nil, nil, partner.BareStoreSkuInfoList(storeSkuList).GetVendorSkuIDList()) + } + } + return ignoreNoAppFoodErr(err) +} + +func (p *PurchaseHandler) UpdateStoreSkusStatus(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*partner.BareStoreSkuInfo) (err error) { + var validSkus, invalidSkus []*mtwmapi.BareStoreFoodInfo + for _, storeSku := range storeSkuList { + skuInfo := &mtwmapi.BareStoreFoodInfo{ + AppFoodCode: storeSku.VendorSkuID, + Skus: []*mtwmapi.BareStoreSkuInfo{ + &mtwmapi.BareStoreSkuInfo{ + SkuID: storeSku.VendorSkuID, + }, + }, + } + if storeSku.Status == model.SkuStatusNormal { + validSkus = append(validSkus, skuInfo) + } else { + invalidSkus = append(invalidSkus, skuInfo) + } + } + if globals.EnableMtwmStoreWrite { + if len(invalidSkus) > 0 { + err = api.MtwmAPI.RetailSkuSellStatus2(vendorStoreID, invalidSkus, 1) + } + if err == nil && len(validSkus) > 0 { + err = api.MtwmAPI.RetailSkuSellStatus2(vendorStoreID, validSkus, 0) + } + } + return err +} + +func (p *PurchaseHandler) UpdateStoreSkusPrice(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*partner.BareStoreSkuInfo) (err error) { + var priceList []*mtwmapi.BareStoreFoodInfo + for _, storeSku := range storeSkuList { + skuInfo := &mtwmapi.BareStoreFoodInfo{ + AppFoodCode: storeSku.VendorSkuID, + Skus: []*mtwmapi.BareStoreSkuInfo{ + &mtwmapi.BareStoreSkuInfo{ + SkuID: storeSku.VendorSkuID, + Price: utils.Int64ToStr(storeSku.Price), + }, + }, + } + priceList = append(priceList, skuInfo) + } + if globals.EnableMtwmStoreWrite { + err = api.MtwmAPI.RetailSkuPrice(vendorStoreID, priceList) + } + return err +} + +func (p *PurchaseHandler) GetStoreSkusInfo(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, vendorStoreID string, inStoreSkuList []*partner.BareStoreSkuInfo) (outStoreSkuList []*partner.BareStoreSkuInfo, err error) { + vendorSkuIDList := partner.BareStoreSkuInfoList(inStoreSkuList).GetVendorSkuIDList() + var vendorFoodList []*mtwmapi.AppFood + if len(vendorSkuIDList) > 1 { + task := tasksch.NewParallelTask("获取饿百平台门店商品信息", nil, ctx, + func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + vendorSkuID := batchItemList[0].(string) + skuInfo, err := api.MtwmAPI.RetailGet(vendorStoreID, vendorSkuID) + if err == nil { + vendorFoodList = []*mtwmapi.AppFood{skuInfo} + return vendorFoodList, nil + } + return nil, err + }, vendorSkuIDList) + tasksch.HandleTask(task, parentTask, false).Run() + _, err = task.GetResult(0) + } else if len(vendorSkuIDList) == 1 { + skuInfo, err2 := api.MtwmAPI.RetailGet(vendorStoreID, vendorSkuIDList[0]) + if err = err2; err == nil { + vendorFoodList = []*mtwmapi.AppFood{skuInfo} + } + } else { + return nil, nil + } + if err == nil { + storeSkuMap := make(map[string]*partner.BareStoreSkuInfo) + for _, v := range inStoreSkuList { + storeSkuMap[v.VendorSkuID] = v + } + for _, foodInfo := range vendorFoodList { + vendorSku := foodInfo.SkuList[0] + storeSku := storeSkuMap[vendorSku.SkuID] + outStoreSkuList = append(outStoreSkuList, storeSku) + storeSku.Price = jxutils.StandardPrice2Int(utils.Str2Float64WithDefault(vendorSku.Price, 0)) + storeSku.Status = mtwmSkuStatus2Jx(foodInfo.IsSoldOut) + } + } + return outStoreSkuList, err +} + +func mtwmSkuStatus2Jx(mtwmSkuStatus int) (jxSkuStatus int) { + if mtwmSkuStatus == 0 { + jxSkuStatus = model.SkuStatusNormal + } else { + jxSkuStatus = model.SkuStatusDontSale + } + return jxSkuStatus +} diff --git a/business/partner/purchase/mtwm/waybill.go b/business/partner/purchase/mtwm/waybill.go index 42fd025a5..39fa708cd 100644 --- a/business/partner/purchase/mtwm/waybill.go +++ b/business/partner/purchase/mtwm/waybill.go @@ -13,6 +13,7 @@ import ( var ( VendorWaybillStatus2StatusMap = map[string]int{ mtwmapi.WaybillStatusWait4Delivery: model.WaybillStatusNew, + mtwmapi.WaybillStatusPending: model.WaybillStatusPending, mtwmapi.WaybillStatusAccepted: model.WaybillStatusAccepted, mtwmapi.WaybillStatusCourierArrived: model.WaybillStatusCourierArrived, mtwmapi.WaybillStatusPickedup: model.WaybillStatusDelivering, @@ -33,13 +34,13 @@ func (c *PurchaseHandler) onWaybillMsg(msg *mtwmapi.CallbackMsg) (response *mtwm err := partner.CurOrderManager.OnWaybillStatusChanged(waybill) if err == nil && waybill.Status == model.WaybillStatusDelivering { msg := &mtwmapi.CallbackMsg{ - Cmd: FakeMsgTypeOrderDelivering, - Data: url.Values{}, + Cmd: FakeMsgTypeOrderDelivering, + FormData: url.Values{}, } - msg.Data.Set("timestamp", utils.Int64ToStr(time.Now().Unix())) - msg.Data.Set("utime", msg.Data.Get("timestamp")) - msg.Data.Set(mtwmapi.KeyOrderID, waybill.VendorOrderID) - msg.Data.Set("status", mtwmapi.OrderStatusDelivering) + msg.FormData.Set("timestamp", utils.Int64ToStr(time.Now().Unix())) + msg.FormData.Set("utime", msg.FormData.Get("timestamp")) + msg.FormData.Set(mtwmapi.KeyOrderID, waybill.VendorOrderID) + msg.FormData.Set("status", mtwmapi.OrderStatusDelivering) utils.CallFuncAsync(func() { OnOrderCallbackMsg(msg) }) @@ -49,17 +50,17 @@ func (c *PurchaseHandler) onWaybillMsg(msg *mtwmapi.CallbackMsg) (response *mtwm func (c *PurchaseHandler) callbackMsg2Waybill(msg *mtwmapi.CallbackMsg) (retVal *model.Waybill) { orderID := GetOrderIDFromMsg(msg) - vendorStatus := msg.Data.Get("logistics_status") + vendorStatus := msg.FormData.Get("logistics_status") retVal = &model.Waybill{ VendorOrderID: orderID, OrderVendorID: model.VendorIDMTWM, VendorWaybillID: orderID, WaybillVendorID: model.VendorIDMTWM, - CourierName: msg.Data.Get("dispatcher_name"), - CourierMobile: msg.Data.Get("dispatcher_mobile"), + CourierName: msg.FormData.Get("dispatcher_name"), + CourierMobile: msg.FormData.Get("dispatcher_mobile"), VendorStatus: vendorStatus, Status: c.GetWaybillStatusFromVendorStatus(vendorStatus), - StatusTime: getTimeFromTimestamp(utils.Str2Int64(msg.Data.Get("time"))), + StatusTime: getTimeFromTimestamp(utils.Str2Int64(msg.FormData.Get("time"))), Remark: "", } return retVal diff --git a/business/partner/purchase/weimob/wsc/act.go b/business/partner/purchase/weimob/wsc/act.go new file mode 100644 index 000000000..12d69f212 --- /dev/null +++ b/business/partner/purchase/weimob/wsc/act.go @@ -0,0 +1,19 @@ +package wsc + +import ( + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" + "git.rosy.net.cn/jx-callback/business/model" +) + +func (c *PurchaseHandler) CreateAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actOrderRules []*model.ActOrderRule, actStoreMap []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + return err +} + +func (c *PurchaseHandler) UpdateAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actOrderRules []*model.ActOrderRule, actStoreMap2Remove, actStoreMap2Add, actStoreMap2Update []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + return err +} + +func (c *PurchaseHandler) CancelAct(ctx *jxcontext.Context, parentTask tasksch.ITask, act *model.Act2, actStoreMap []*model.ActStore2, actStoreSku []*model.ActStoreSku2) (err error) { + return err +} diff --git a/business/partner/purchase/weimob/wsc/order.go b/business/partner/purchase/weimob/wsc/order.go index 24f323507..66eef0675 100644 --- a/business/partner/purchase/weimob/wsc/order.go +++ b/business/partner/purchase/weimob/wsc/order.go @@ -52,10 +52,10 @@ func (p *PurchaseHandler) onOrderMsg(msg *weimobapi.CallbackMsg) (response *weim orderMapData, err = api.WeimobAPI.QueryOrderDetail(msg.OrderNo, false) if err == nil { status = p.callbackMsg2Status(msg, orderMapData) - if status.Status == model.OrderStatusNew { + if msg.Event == weimobapi.MsgEventCreateOrder || status.Status == model.OrderStatusNew { order := p.Map2Order(orderMapData) order.StatusTime = msg.StatusTime - err = partner.CurOrderManager.OnOrderNew(order, order.VendorStatus) + err = partner.CurOrderManager.OnOrderNew(order, status) } else { err = partner.CurOrderManager.OnOrderStatusChanged(status) } @@ -115,20 +115,13 @@ func (p *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *mo result := orderData vendorOrderID := utils.Int64ToStr(utils.MustInterface2Int64(result["orderNo"])) deliveryDetail := result["deliveryDetail"].(map[string]interface{}) - logisticsDeliveryDetail := deliveryDetail["logisticsDeliveryDetail"].(map[string]interface{}) + logisticsDeliveryDetail, _ := deliveryDetail["logisticsDeliveryDetail"].(map[string]interface{}) // paymentInfo := result["paymentInfo"].(map[string]interface{}) order = &model.GoodsOrder{ VendorOrderID: vendorOrderID, VendorID: model.VendorIDWSC, VendorStoreID: utils.Int64ToStr(utils.MustInterface2Int64(result["processStoreId"])), // 这个不是通常意义上的vendor store id // StoreID - StoreName: utils.Interface2String(logisticsDeliveryDetail["processStoreTitle"]), - ConsigneeName: utils.Interface2String(logisticsDeliveryDetail["receiverName"]), - ConsigneeMobile: utils.Interface2String(logisticsDeliveryDetail["receiverMobile"]), - ConsigneeAddress: utils.Interface2String(logisticsDeliveryDetail["receiverAddress"]), - CoordinateType: model.CoordinateTypeMars, - ConsigneeLng: jxutils.StandardCoordinate2Int(utils.Str2Float64(utils.Interface2String(logisticsDeliveryDetail["receiverLongitude"]))), - ConsigneeLat: jxutils.StandardCoordinate2Int(utils.Str2Float64(utils.Interface2String(logisticsDeliveryDetail["receiverLatitude"]))), BuyerComment: utils.Interface2String(result["buyerRemark"]), ExpectedDeliveredTime: utils.DefaultTimeValue, PickDeadline: utils.DefaultTimeValue, @@ -138,9 +131,23 @@ func (p *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *mo ActualPayPrice: jxutils.StandardPrice2Int(utils.MustInterface2Float64(result["totalAmount"])), BusinessType: model.BusinessTypeImmediate, } - // if expectDeliveryTimeStr, ok := logisticsDeliveryDetail["expectDeliveryTime"].(string); ok { - // if expectDeliveryTimeStr = - // } + if logisticsDeliveryDetail != nil { + order.StoreName = utils.Interface2String(logisticsDeliveryDetail["processStoreTitle"]) + order.ConsigneeName = utils.Interface2String(logisticsDeliveryDetail["receiverName"]) + order.ConsigneeMobile = utils.Interface2String(logisticsDeliveryDetail["receiverMobile"]) + order.ConsigneeAddress = utils.Interface2String(logisticsDeliveryDetail["receiverAddress"]) + order.ConsigneeLng = jxutils.StandardCoordinate2Int(utils.Str2Float64(utils.Interface2String(logisticsDeliveryDetail["receiverLongitude"]))) + order.ConsigneeLat = jxutils.StandardCoordinate2Int(utils.Str2Float64(utils.Interface2String(logisticsDeliveryDetail["receiverLatitude"]))) + order.CoordinateType = model.CoordinateTypeMars + // if expectDeliveryTimeStr, ok := logisticsDeliveryDetail["expectDeliveryTime"].(string); ok { + // if expectDeliveryTimeStr = + // } + } else { + selfPickupDetail, _ := deliveryDetail["selfPickupDetail"].(map[string]interface{}) + if selfPickupDetail != nil { + order.StoreName = utils.Interface2String(selfPickupDetail["selfPickupSiteName"]) + } + } order.Status = p.GetStatusFromVendorStatus(order.VendorStatus) itemList := result["itemList"].([]interface{}) for _, v := range itemList { @@ -158,13 +165,13 @@ func (p *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *mo SalePrice: jxutils.StandardPrice2Int(utils.MustInterface2Float64(item["price"])), } order.Skus = append(order.Skus, sku) - order.SkuCount++ - order.GoodsCount += sku.Count - order.SalePrice += sku.SalePrice * int64(sku.Count) - order.Weight += sku.Weight * sku.Count } - p.arrangeSaleStore(order, utils.Interface2String(logisticsDeliveryDetail["receiverCity"]), utils.Interface2String(logisticsDeliveryDetail["receiverProvince"])) + jxutils.RefreshOrderSkuRelated(order) + if logisticsDeliveryDetail != nil { + p.arrangeSaleStore(order, utils.Interface2String(logisticsDeliveryDetail["receiverCity"]), utils.Interface2String(logisticsDeliveryDetail["receiverProvince"])) + } p.setStoreOrderSeq(order) + globals.SugarLogger.Debug(utils.Format4Output(order, false)) return order } diff --git a/business/partner/purchase/weimob/wsc/order_afs.go b/business/partner/purchase/weimob/wsc/order_afs.go new file mode 100644 index 000000000..70e64f713 --- /dev/null +++ b/business/partner/purchase/weimob/wsc/order_afs.go @@ -0,0 +1,16 @@ +package wsc + +import ( + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "git.rosy.net.cn/jx-callback/business/model" +) + +// 审核售后单申请 +func (c *PurchaseHandler) AgreeOrRefuseRefund(ctx *jxcontext.Context, order *model.AfsOrder, approveType int, reason string) (err error) { + return err +} + +// 确认收到退货 +func (c *PurchaseHandler) ConfirmReceivedReturnGoods(ctx *jxcontext.Context, order *model.AfsOrder) (err error) { + return err +} diff --git a/business/partner/purchase/weimob/wsc/store_sku.go b/business/partner/purchase/weimob/wsc/store_sku.go index d0246cf44..2e926ba4b 100644 --- a/business/partner/purchase/weimob/wsc/store_sku.go +++ b/business/partner/purchase/weimob/wsc/store_sku.go @@ -37,29 +37,36 @@ func (p *PurchaseHandler) SyncStoreCategory(ctx *jxcontext.Context, parentTask t task := tasksch.NewParallelTask(fmt.Sprintf("微盟微商城SyncStoreCategory step2, level=%d", level), nil, ctx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { updateFields := []string{dao.GetSyncStatusStructField(model.VendorNames[model.VendorIDWSC])} - catInfo := batchItemList[0].(*dao.StoreCatSyncInfo) - if globals.EnableStoreWrite && globals.EnableWscStoreWrite { - if catInfo.WscSyncStatus&model.SyncFlagDeletedMask != 0 { // 删除 - if catInfo.WscSyncStatus&model.SyncFlagNewMask == 0 { - globals.SugarLogger.Debugf("UpdateClassify strStoreID:%s, WscID:%d", strStoreID, catInfo.WscID) - err = api.WeimobAPI.UpdateClassify(catInfo.WscID, composeFakeDelName(catInfo.CatName), "") + catInfo := batchItemList[0].(*dao.SkuStoreCatInfo) + storeCatMap := &model.StoreSkuCategoryMap{} + storeCatMap.ID = catInfo.MapID + if catInfo.StoreCatSyncStatus&model.SyncFlagDeletedMask != 0 { // 删除 + if catInfo.StoreCatSyncStatus&model.SyncFlagNewMask == 0 && !dao.IsVendorThingIDEmpty(catInfo.VendorCatID) { + globals.SugarLogger.Debugf("UpdateClassify strStoreID:%s, WscID:%s", strStoreID, catInfo.VendorCatID) + if globals.EnableWscStoreWrite { + err = api.WeimobAPI.UpdateClassify(utils.Str2Int64(catInfo.VendorCatID), composeFakeDelName(catInfo.Name), "") } - } else if catInfo.WscSyncStatus&(model.SyncFlagNewMask|model.SyncFlagModifiedMask) != 0 { // 新增 - catImg := "" - if level == 2 { - catImg = DefCatImg - } - if catInfo.WscID, err = api.WeimobAPI.AddClassify(catInfo.CatName, utils.Str2Int64WithDefault(catInfo.ParentVendorCatID, 0), catImg); err == nil { - updateFields = append(updateFields, dao.GetVendorThingIDStructField(model.VendorNames[model.VendorIDWSC])) - } - } else if catInfo.WscSyncStatus&(model.SyncFlagModifiedMask|model.SyncFlagModifiedMask) != 0 { // 修改 - err = api.WeimobAPI.UpdateClassify(catInfo.WscID, catInfo.CatName, "") + } + } else if catInfo.StoreCatSyncStatus&(model.SyncFlagNewMask|model.SyncFlagModifiedMask) != 0 { // 新增 + catImg := "" + if level == 2 { + catImg = DefCatImg + } + if globals.EnableWscStoreWrite { + storeCatMap.WscID, err = api.WeimobAPI.AddClassify(catInfo.Name, utils.Str2Int64WithDefault(catInfo.ParentVendorCatID, 0), catImg) + } + if err == nil { + updateFields = append(updateFields, dao.GetVendorThingIDStructField(model.VendorNames[model.VendorIDWSC])) + } + } else if catInfo.StoreCatSyncStatus&(model.SyncFlagModifiedMask) != 0 { // 修改 + if globals.EnableWscStoreWrite { + err = api.WeimobAPI.UpdateClassify(utils.Str2Int64(catInfo.VendorCatID), catInfo.Name, "") } } if err == nil { db2 := dao.GetDB() - catInfo.WscSyncStatus = 0 - _, err = dao.UpdateEntity(db2, &catInfo.StoreSkuCategoryMap, updateFields...) + storeCatMap.WscSyncStatus = 0 + _, err = dao.UpdateEntity(db2, storeCatMap, updateFields...) } return nil, err }, catList) @@ -129,7 +136,7 @@ func (p *PurchaseHandler) SyncLocalStoreCategory(ctx *jxcontext.Context, db *dao return "", err } } else { - if (v.VendorCatID == "0" || v.VendorCatID == "") && ((v.CatSyncStatus & model.SyncFlagNewMask) == 0) { + if dao.IsVendorThingIDEmpty(v.VendorCatID) && ((v.StoreCatSyncStatus & model.SyncFlagNewMask) == 0) { catMap := &model.StoreSkuCategoryMap{} catMap.ID = v.MapID if _, err = dao.UpdateEntityLogically(db, catMap, map[string]interface{}{ @@ -180,16 +187,18 @@ func (p *PurchaseHandler) SyncStoreSkus(ctx *jxcontext.Context, parentTask tasks updateFields := []string{model.FieldWscSyncStatus} storeSkuBind := &model.StoreSkuBind{} storeSkuBind.ID = skuItem.BindID - if skuItem.SkuSyncStatus&model.SyncFlagDeletedMask != 0 { - if skuItem.SkuSyncStatus&model.SyncFlagNewMask == 0 { + if skuItem.StoreSkuSyncStatus&model.SyncFlagDeletedMask != 0 { + if skuItem.StoreSkuSyncStatus&model.SyncFlagNewMask == 0 { goodsID := utils.Str2Int64WithDefault(skuItem.VendorNameID, 0) - if err = api.WeimobAPI.UpdateGoodsShelfStatus([]int64{goodsID}, false); err == nil { - err = api.WeimobAPI.UpdateGoodsTitle(goodsID, composeFakeDelName(skuItem.Name)) - } else if intErr, ok := err.(*utils.ErrorWithCode); ok && intErr.Code() == "1001930300001" { // 商品不存在错 - err = nil // 强制忽略 + if globals.EnableWscStoreWrite { + if err = api.WeimobAPI.UpdateGoodsShelfStatus([]int64{goodsID}, false); err == nil { + err = api.WeimobAPI.UpdateGoodsTitle(goodsID, composeFakeDelName(skuItem.Name)) + } else if intErr, ok := err.(*utils.ErrorWithCode); ok && intErr.Code() == "1001930300001" { // 商品不存在错 + err = nil // 强制忽略 + } } } - } else if skuItem.SkuSyncStatus&(model.SyncFlagModifiedMask|model.SyncFlagNewMask) != 0 { + } else if skuItem.StoreSkuSyncStatus&(model.SyncFlagStoreSkuModifiedMask|model.SyncFlagNewMask) != 0 { outerGoodsCode := utils.Int2Str(skuItem.NameID) title := jxutils.ComposeSkuName(skuItem.Prefix, skuItem.Name, skuItem.Comment, skuItem.Unit, skuItem.SpecQuality, skuItem.SpecUnit, 30) isPutAway := jxutils.MergeSkuStatus(skuItem.Status, skuItem.StoreSkuStatus) == model.SkuStatusNormal @@ -208,7 +217,7 @@ func (p *PurchaseHandler) SyncStoreSkus(ctx *jxcontext.Context, parentTask tasks DeliveryTypeIdList: []int64{DefDeliveryTypeId}, B2cGoodsType: weimobapi.GoodsTypeNormal, } - salePrice := int64(jxutils.CaculateSkuVendorPrice(int(skuItem.Price), int(storeDetail.PricePercentage))) + salePrice := int64(jxutils.CaculateSkuVendorPrice(int(skuItem.Price), int(storeDetail.PricePercentage), skuItem.CatPricePercentage)) skuList := []map[string]interface{}{ map[string]interface{}{ weimobapi.KeyOuterSkuCode: utils.Int2Str(skuItem.ID), @@ -222,26 +231,36 @@ func (p *PurchaseHandler) SyncStoreSkus(ctx *jxcontext.Context, parentTask tasks }, }, } - if globals.EnableStoreWrite && globals.EnableWscStoreWrite { - if skuItem.SkuSyncStatus&model.SyncFlagNewMask != 0 { - goodsID, skuMap, err2 := api.WeimobAPI.AddGoods(outerGoodsCode, title, false, []string{skuItem.Img}, skuItem.Comment, isPutAway, 0, categoryId, classifyIdList, b2cGoods, skuList, nil) - if err = err2; err == nil { - storeSkuBind.WscID = skuMap[utils.Int2Str(skuItem.ID)] - storeSkuBind.WscID2 = goodsID - updateFields = append(updateFields, model.FieldWscID, model.FieldWscID2) - } + if skuItem.StoreSkuSyncStatus&model.SyncFlagNewMask != 0 { + var ( + goodsID int64 + skuMap map[string]int64 + ) + if globals.EnableWscStoreWrite { + goodsID, skuMap, err = api.WeimobAPI.AddGoods(outerGoodsCode, title, false, []string{skuItem.Img}, skuItem.Comment, isPutAway, 0, categoryId, classifyIdList, b2cGoods, skuList, nil) } else { - goodsID := utils.Str2Int64WithDefault(skuItem.VendorNameID, 0) - - goodsInfo, err2 := api.WeimobAPI.QueryGoodsDetail(goodsID) - if err = err2; err == nil { - // http://open.weimob.com/docapi/article?tag=Af - // sku id,如果为空,则新增sku; 如果更新之前的skuId与入参skuId对应,则更新sku; 如果更新之前的skuId没有和入参的skuId对应,删除更新之前的sku - skuList[0][weimobapi.KeySkuID] = utils.Str2Int64WithDefault(skuItem.VendorSkuID, 0) - remoteSkuList := goodsInfo[weimobapi.KeySkuList].([]interface{}) - if len(remoteSkuList) > 0 { - // skuList[0][weimobapi.KeyEditStockNum] = model.MaxStoreSkuStockQty - int(utils.MustInterface2Int64(remoteSkuList[0].(map[string]interface{})[weimobapi.KeyAvailableStockNum])) - } + goodsID = jxutils.GenFakeID() + skuMap = map[string]int64{ + utils.Int2Str(skuItem.ID): jxutils.GenFakeID(), + } + } + if err == nil { + storeSkuBind.WscID = skuMap[utils.Int2Str(skuItem.ID)] + storeSkuBind.WscID2 = goodsID + updateFields = append(updateFields, model.FieldWscID, model.FieldWscID2) + } + } else { + goodsID := utils.Str2Int64WithDefault(skuItem.VendorNameID, 0) + goodsInfo, err2 := api.WeimobAPI.QueryGoodsDetail(goodsID) + if err = err2; err == nil { + // http://open.weimob.com/docapi/article?tag=Af + // sku id,如果为空,则新增sku; 如果更新之前的skuId与入参skuId对应,则更新sku; 如果更新之前的skuId没有和入参的skuId对应,删除更新之前的sku + skuList[0][weimobapi.KeySkuID] = utils.Str2Int64WithDefault(skuItem.VendorSkuID, 0) + remoteSkuList := goodsInfo[weimobapi.KeySkuList].([]interface{}) + if len(remoteSkuList) > 0 { + // skuList[0][weimobapi.KeyEditStockNum] = model.MaxStoreSkuStockQty - int(utils.MustInterface2Int64(remoteSkuList[0].(map[string]interface{})[weimobapi.KeyAvailableStockNum])) + } + if globals.EnableWscStoreWrite { _, _, err = api.WeimobAPI.UpdateGoods(goodsID, title, false, []string{skuItem.Img}, skuItem.Comment, isPutAway, 0, categoryId, classifyIdList, b2cGoods, skuList, nil) } } @@ -278,9 +297,9 @@ func (p *PurchaseHandler) FullSyncStoreSkus(ctx *jxcontext.Context, parentTask t func(rootTask *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { switch step { case 0: - _, err = dao.SetStoreCategorySyncStatus(db, model.VendorIDWSC, storeID, nil, model.SyncFlagNewMask) + _, err = dao.SetStoreCategorySyncStatus(db, model.VendorIDWSC, []int{storeID}, nil, model.SyncFlagNewMask) case 1: - _, err = dao.SetStoreSkuSyncStatus(db, model.VendorIDWSC, storeID, nil, model.SyncFlagNewMask) + _, err = dao.SetStoreSkuSyncStatus(db, model.VendorIDWSC, []int{storeID}, nil, model.SyncFlagNewMask) case 2: _, err = p.SyncLocalStoreCategory(ctx, db, storeID, false) case 3: @@ -318,3 +337,7 @@ func composeFakeDelName(name string) string { // func ComposeJXVendorSkuIDFromGoodsAndSkuID(goodsID, skuID int64) (vendorSkuID string) { // return utils.Int64ToStr(skuID) + "," + utils.Int64ToStr(goodsID) // } + +func (p *PurchaseHandler) GetStoresSku(ctx *jxcontext.Context, parentTask tasksch.ITask, storeIDs []int) (storeSkuList []*model.StoreSkuBind, err error) { + return storeSkuList, err +} diff --git a/business/partner/purchase/weimob/wsc/store_sku2.go b/business/partner/purchase/weimob/wsc/store_sku2.go new file mode 100644 index 000000000..62af39a4f --- /dev/null +++ b/business/partner/purchase/weimob/wsc/store_sku2.go @@ -0,0 +1,192 @@ +package wsc + +import ( + "fmt" + "math/rand" + + "git.rosy.net.cn/baseapi/platformapi/weimobapi" + "git.rosy.net.cn/baseapi/utils" + "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/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/partner" + "git.rosy.net.cn/jx-callback/globals" + "git.rosy.net.cn/jx-callback/globals/api" +) + +// 门店分类 +func (p *PurchaseHandler) ReadStoreCategory(ctx *jxcontext.Context, vendorStoreID string) (cats []*partner.BareCategoryInfo, err error) { + remoteCats, err := api.WeimobAPI.QueryClassifyInfoList() + if err == nil { + cats = convertVendorCatList(remoteCats) + } + return cats, err +} + +func convertVendorCatList(remoteCats []*weimobapi.GoodsClassify) (cats []*partner.BareCategoryInfo) { + for _, rCat := range remoteCats { + cat := &partner.BareCategoryInfo{ + VendorCatID: utils.Int64ToStr(rCat.ClassifyID), + Name: rCat.Title, + Level: rCat.Level, + Seq: 0, + Children: convertVendorCatList(rCat.ChildrenClassify), + } + cats = append(cats, cat) + } + return cats +} + +func (p *PurchaseHandler) CreateStoreSkuCategory(ctx *jxcontext.Context, vendorStoreID string, storeCat *dao.SkuStoreCatInfo) (err error) { + var vendorCatID int64 + if globals.EnableWscStoreWrite { + vendorCatID, err = api.WeimobAPI.AddClassify(storeCat.Name, utils.Str2Int64WithDefault(storeCat.ParentVendorCatID, 0), "") + } else { + vendorCatID = jxutils.GenFakeID() + } + storeCat.VendorCatID = utils.Int64ToStr(vendorCatID) + return err +} + +func (p *PurchaseHandler) UpdateStoreSkuCategory(ctx *jxcontext.Context, vendorStoreID string, storeCat *dao.SkuStoreCatInfo) (err error) { + if globals.EnableWscStoreWrite { + err = api.WeimobAPI.UpdateClassify(utils.Str2Int64WithDefault(storeCat.VendorCatID, 0), storeCat.Name, "") + } + return err +} + +func (p *PurchaseHandler) DeleteStoreSkuCategory(ctx *jxcontext.Context, vendorStoreID, vendorCatID string) (err error) { + if globals.EnableWscStoreWrite { + err = api.WeimobAPI.UpdateClassify(utils.Str2Int64WithDefault(vendorCatID, 0), composeFakeDelName(vendorCatID), "") + } + return err +} + +// 门店商品 + +// 多门店平台不需要实现这个接口 +func (p *PurchaseHandler) UpdateStoreSkus(ctx *jxcontext.Context, vendorStoreID string, storeSkuList []*dao.StoreSkuSyncInfo) (err error) { + storeSku := storeSkuList[0] + goodsInfo := genSkuParams(storeSku) + if globals.EnableWscStoreWrite { + _, _, err = api.WeimobAPI.UpdateGoods2(goodsInfo) + } + return err +} + +// 通用 + +func (p *PurchaseHandler) GetStoreSkusBatchSize(funcID int) int { + return 1 +} + +func genSkuParams(storeSku *dao.StoreSkuSyncInfo) (goodsInfo *weimobapi.PendingSaveGoodsVo) { + outerGoodsCode := utils.Int2Str(storeSku.NameID) + isPutAway := storeSku.StoreSkuStatus == model.SkuStatusNormal + categoryId := storeSku.VendorVendorCatID + if categoryId == 0 { + categoryId = DefVendorCategoryId + } + classifyIdList := []int64{utils.Str2Int64WithDefault(storeSku.VendorCatID, 0)} + if storeSku.SkuVendorCatID != "" { + if int64Value := utils.Str2Int64WithDefault(storeSku.SkuVendorCatID, 0); int64Value > 0 { + classifyIdList = append(classifyIdList, int64Value) + } + } + b2cGoods := []*weimobapi.PendingSaveB2CGoodsVo{ + &weimobapi.PendingSaveB2CGoodsVo{ + FreightTemplateId: DefFreightTemplateId, + DeliveryTypeIdList: []int64{DefDeliveryTypeId}, + B2cGoodsType: weimobapi.GoodsTypeNormal, + }, + } + salePrice := int64(storeSku.Price) + goodsInfo = &weimobapi.PendingSaveGoodsVo{ + Title: storeSku.Name, + OuterGoodsCode: outerGoodsCode, + IsMultiSku: 0, + GoodsImageURL: []string{storeSku.Img}, + InitialSales: 0, + IsPutAway: utils.Bool2Int(!isPutAway), + Sort: int(storeSku.Price), + CategoryID: categoryId, + B2cGoods: b2cGoods, + SkuList: []*weimobapi.PendingSaveSkuVo{ + &weimobapi.PendingSaveSkuVo{ + OuterSkuCode: utils.Int2Str(storeSku.SkuID), + ImageURL: storeSku.Img, + SalePrice: jxutils.IntPrice2Standard(salePrice), + CostPrice: jxutils.IntPrice2Standard(salePrice * 8 / 10), + OriginalPrice: jxutils.IntPrice2Standard(salePrice * 10 / (6 + rand.Int63n(4))), + EditStockNum: 0, + B2cSku: &weimobapi.PendingSaveB2CSkuVo{ + Weight: jxutils.IntWeight2Float(storeSku.Weight), + }, + }, + }, + } + return goodsInfo +} + +// 对于多门店平台来说,storeSkuList中只有SkuID与VendorSkuID有意义 +func (p *PurchaseHandler) CreateStoreSkus(ctx *jxcontext.Context, vendorStoreID string, storeSkuList []*dao.StoreSkuSyncInfo) (err error) { + storeSku := storeSkuList[0] + goodsInfo := genSkuParams(storeSku) + var goodsID, vendorSkuID int64 + if globals.EnableWscStoreWrite { + var skuMap map[string]int64 + if goodsID, skuMap, err = api.WeimobAPI.AddGoods2(goodsInfo); err == nil { + vendorSkuID = skuMap[utils.Int2Str(storeSku.SkuID)] + } + } else { + goodsID = jxutils.GenFakeID() + vendorSkuID = jxutils.GenFakeID() + } + storeSku.VendorSkuID = utils.Int64ToStr(vendorSkuID) + storeSku.VendorNameID = utils.Int64ToStr(goodsID) + return err +} + +func (p *PurchaseHandler) DeleteStoreSkus(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*partner.BareStoreSkuInfo) (err error) { + storeSku := storeSkuList[0] + goodsID := utils.Str2Int64(storeSku.VendorNameID) + if globals.EnableWscStoreWrite { + if err = api.WeimobAPI.UpdateGoodsShelfStatus([]int64{goodsID}, false); err == nil { + err = api.WeimobAPI.UpdateGoodsTitle(goodsID, composeFakeDelName(storeSku.VendorNameID)) + } else if intErr, ok := err.(*utils.ErrorWithCode); ok && intErr.Code() == "1001930300001" { // 商品不存在错 + err = nil // 强制忽略 + } + } + return err +} + +func (p *PurchaseHandler) UpdateStoreSkusStatus(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*partner.BareStoreSkuInfo) (err error) { + var validGoodsIDList, invalidGoodsIDList []int64 + for _, storeSku := range storeSkuList { + if storeSku.Status == model.SkuStatusNormal { + validGoodsIDList = append(validGoodsIDList, utils.Str2Int64(storeSku.VendorSkuID)) + } else { + invalidGoodsIDList = append(invalidGoodsIDList, utils.Str2Int64(storeSku.VendorSkuID)) + } + } + if globals.EnableWscStoreWrite { + if len(validGoodsIDList) > 0 { + err = api.WeimobAPI.UpdateGoodsShelfStatus(validGoodsIDList, true) + } + if err == nil && len(invalidGoodsIDList) > 0 { + err = api.WeimobAPI.UpdateGoodsShelfStatus(invalidGoodsIDList, false) + } + } + return err +} + +func (p *PurchaseHandler) UpdateStoreSkusPrice(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*partner.BareStoreSkuInfo) (err error) { + err = fmt.Errorf("内部错误,微商城不支持UpdateStoreSkusPrice!") + return err +} + +func (p *PurchaseHandler) GetStoreSkusInfo(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, vendorStoreID string, inStoreSkuList []*partner.BareStoreSkuInfo) (outStoreSkuList []*partner.BareStoreSkuInfo, err error) { + return outStoreSkuList, err +} diff --git a/conf/app.conf b/conf/app.conf index f2a3decd9..54dc6320c 100644 --- a/conf/app.conf +++ b/conf/app.conf @@ -19,8 +19,9 @@ mtwmCallbackURL = "http://callback.jxc4.com" autonaviKey = "4427170f870af2110becb8852d36ab08" enableStoreWrite = false +enableJdStoreWrite = false enableEbaiStoreWrite = false -enableElmStoreWrite = true +enableElmStoreWrite = false enableMtwmStoreWrite = false enableWscStoreWrite = false @@ -48,9 +49,9 @@ weixinMiniSecret = "2a57228a716ce991a52739f0ff41111d" backstageHost = "http://www.jxc4.com" wxBackstageHost = "http://wx.jxc4.com" -jdStorePageCookie = "YYJV3NHVBPHLD36FWP6F3EM5PTXJ2XZQS7U4HWRIDPP4IWGUKUIB4XG5N26CZRDLDF7PKOXBPD6BNTUAJLETLZOIWMCVFI3K6MYZIY4QBIXIMXYDJNUKFGJVQTN5356SAD6WPCIHWNQAG7DDMF7L7S3SHBUOPCIUXDX4MQEAYEPUFFOAD4WJECT4R3K22T24MKC7OMIRDLX7S55243TDVXLO25PP4UYSPTTPMNRUFXDNP4WPE566Q6V4AH32F7HT" -ebaiStorePageCookieWMUSS = "AADIBAABbDlEpGl47c1EyBFcJSidBTBJHFHZEXyMSdBllJTZ9AUNOKV0tZFB9FlRVM73gEAIHRjBVagwAAHh98X2oPJ34Gal0ofFJBYXZ2Xnc6LCEXWQVnVxs7LDlaKBlFNz9DPCogYyZxJQhoHGVfVRIBa2oFUkEfDm1YZxZwLEwvZMjpB18rjw%7E3CaMQAo" -ebaiStorePageCookieWMSTOKEN = "HwXAAB9SGxnTT8pbEwWRDQsNGB3Y09_PF5rO157QUcoLRQAAAoKlgUEiVTOhIf5LjkCoFpAwwCaAAAUr-GEu-yDBeNAQAAHNneF25-uRjhYtgX4rsAAGHurh8C5GAQAA" +jdStorePageCookie = "YYJV3NHVBPHLD36FWP6F3EM5PTXJ2XZQS7U4HWRIDPP4IWGUKUIB4XG5N26CZRDLDF7PKOXBPD6BNTUAJLETLZOIWMCVFI3K6MYZIY4QBIXIMXYDJNUKFGJVQTN5356SAD6WPCIHWNQAG7DDMF7L7S3SHDYZP7PPVMRGO4VWG2JRBMKFTOGIWZ5L2XHXC3SXQ4OLX7EL4RKUPZQT6GOH63KE3EVK37L5LG7TGSDGXFQP4377YK72UB5YZG6IJH6PY25YLLCJYPMDSHKPGYBUFJ4MMMKGN6MWB37CP7XVDBBZJ3U462ENTEXH744AWCQCIG2AAE2PKYVHC" +ebaiStorePageCookieWMUSS = "4AAPQCAAB5PF0aUGcBVzoRTCEkOFhFIhx-Yk9vN2EfPHYoLlROKBEsQmAUQjhNUgRt0ADAP5x-RFklwAAdjxGO11iOj8xKXYSSDIJb2BcPghsaklNfQwGS10JOVRFfhAiYElhEXFXIzoJKyloCGdwdFE6Qk9FRxojUFN3FVEHNjJPZJu4Bt9nxQ13cwoMbjA" +ebaiStorePageCookieWMSTOKEN = "AcAANQZAABbC04rUBZFc2UYanlocDAaP0dcfzZCeS1SHQ1qJ15ExgAA13A2dGLjdbcitBZJu4Bn6B_g6cZAAA0tyyFm8cdBaNAQAAwug8HTG0xRjwt1UZzbcAAN7ofRO" weimobAppID = "319F5E7FB6784DFCA3684C9333EB7744" weimobAppSecret = "7267AA7F58261F6965599218F5A1D592" @@ -82,6 +83,7 @@ zhongwuAppID = 8000192 zhongwuAppSecret = "29435497822f52f3cf659c65da548a79" getWeixinTokenKey = "c928ed0d-87a3-441a-8517-f92f0167296f" +storeName = "京西菜市" [dev] jdToken = "df97f334-f7d8-4b36-9664-5784d8ae0baf" @@ -139,6 +141,7 @@ weixinSecret = "6bbbed1443cc062c20a015a64c07a531" dbConnectStr = "root:WebServer@1@tcp(db1.int.jxc4.com:3306)/jxd_dev_0?charset=utf8mb4&loc=Local&parseTime=true" enableStoreWrite = true +enableJdStoreWrite = true enableEbaiStoreWrite = true enableMtwmStoreWrite = true enableWscStoreWrite = true @@ -160,15 +163,19 @@ dbConnectStr = "root:WebServer@1@tcp(127.0.0.1:3306)/jxgy?charset=utf8mb4&loc=Lo jdToken = "84541069-fbe2-424b-b625-9b2ba1d4c9e6" jdAppKey = "5d5577a2506f41b8b4ec520ba83490f5" jdSecret = "0b01b9eeb15b41dab1c3d05d95c17a26" +jdStorePageCookie = "YDYCWYUGKSROMV3MKJQIFINJ5CLPYH6IRVFUMFJD3JI6VQKHX3YPHTWDIDBNMCBUKSY3P7SKAHHKR7PHQDSFRXZEWXA4XOUTALIQDGDYIEUCMDPWSYGDVT42DJ27CD27QKCR3UN7KF7EPIHGPR7GCRTBD7SYBZDLN74NRL62XOF7D7DWFVGOTM4HRJBSMMQJ77SR62PYMP6RCG33IRQGP6PRCKAO4M7FUA7G2ZM2SFQ6F6WUNO5GXDJSVUELLBJT" ebaiSource = "35957" ebaiSecret = "10013fbb7c2ddad7" +ebaiStorePageCookieWMUSS = "AAHYDAAA-WUYHCDg5IVUYE1ZgVgEsWVIWZEJALR14JCJrZBIKImpPKHIIYRNceCYzDAPIXvhaFigAAWXw0BVUHGzpIW2ZkVAoHP24ZSBADGDh1HxoJexMvL2srYnABaQw6MWYgFjoqHHkYNEIoN1lfLXd5NXMMYyJ2DgVnDm1yBPxVClFNXg6wz00RdBUNAw" +ebaiStorePageCookieWMSTOKEN = "QAAHAZAAAoL2cgKBwUM341Kg06EhsoBWJFXUl4cVpIfgJMTVR2N3AAAEHdX9tFicJKVoIBM8kCfKvNA1dZwAA0tyyFu9YqhQ9AQAAr-R3GModaxvgfY8YW6gAAFJE5HB" disableElm = true disableMtwm = true disableWeimob = true enableStoreWrite = true +enableJdStoreWrite = true enableEbaiStoreWrite = true mtpsAppKey = "3c0a05d464c247c19d7ec13accc78605" @@ -197,72 +204,9 @@ dingdingQRCodeSecret = "N9dyC9qB84sauQPs4_JYrILMsG5Krqm9-PSSVJ8t9hb87rrHiFUirISx dingdingCallbackURL = "http://callback-jxgy.jxc4.com/dingding/msg" getWeixinTokenURL = "http://www.jxc4.com/v2/sys/GetWXToken" - -[prod2] -httpport = 8082 - -disableJd = false -jdToken = "46058015-11b1-485d-9622-b7a91e446023" -jdAppKey = "8410aba1b67e4d3199098e944f91cb68" -jdSecret = "14af1be608934b73accab57b9efe8c96" - -disableElm = true -disableMtps = true -disableDada = true -disableWeixin = true -disableEbai = true - -dbConnectStr = "root:WebServer@1@tcp(db1.int.jxc4.com:3306)/jxd_dev_2?charset=utf8mb4&loc=Local&parseTime=true" - -enableStoreWrite = true - -[prod3] -httpport = 8082 - -disableJd = false -jdToken = "8a2af9af-14bb-4464-8676-aaacf6348e5c" -jdAppKey = "6c269bdfa1dd496c8194aa8fb569e71b" -jdSecret = "d678e48d202c4bf29d66c3b6642ece80" - -disableElm = true -disableMtps = true -disableDada = true -disableWeixin = true -disableEbai = true - -dbConnectStr = "root:WebServer@1@tcp(db1.int.jxc4.com:3306)/jxd_dev_2?charset=utf8mb4&loc=Local&parseTime=true" - -enableStoreWrite = true +storeName = "京西果园" [test] -jdToken = "ccb10daf-e6f5-4a58-ada5-b97f9073a137" -jdAppKey = "1dba76d40cac446ca500c0391a0b6c9d" -jdSecret = "a88d031a1e7b462cb1579f12e97fe7f4" - -elmIsProd = true -elmToken = "" -elmAppKey = "KLRDcOZGrk" -elmSecret = "1fc221f8265506531da36fb613d5f5ad673f2e9a" - -ebaiSource = "34665" -ebaiSecret = "c3db75b754ea2d89" - -mtpsAppKey = "3c0a05d464c247c19d7ec13accc78605" -mtpsSecret = "b1M}9?:sTbsB[OF2gNORnN(|(iy9rB8(`7]|[wGLnbmt`evfM>E:A90DjHAW:UPE" - -dadaIsProd = true -dadaCallbackURL = "http://callback.test.jxc4.com/dadadelivery/msg" -dadaSourceID = "6660" - -weixinAppID = "wx2bb99eb5d2c9b82c" -weixinSecret = "6bbbed1443cc062c20a015a64c07a531" - -dbConnectStr = "root:WebServer@1@tcp(127.0.0.1:3306)/jxd_dev_0?charset=utf8mb4&loc=Local&parseTime=true" - -[alpha] -httpport = 8088 - -# xiaan jdToken = "df97f334-f7d8-4b36-9664-5784d8ae0baf" jdAppKey = "06692746f7224695ad4788ce340bc854" jdSecret = "d6b42a35a7414a5490d811654d745c84" @@ -279,20 +223,57 @@ mtpsAppKey = "25e816550bc9484480642f19a95f13fd" mtpsSecret = "r4$HqrKx9~=7?2Jfo,$Z~a7%~k!Au&pEdI2)oPJvSbH2ao@2N0[8wSIvtuumh_J^" dadaIsProd = false -dadaCallbackURL = "http://callback.alpha.jxc4.com/dadadelivery/msg" +dadaCallbackURL = "http://callback.test.jxc4.com/dadadelivery/msg" dadaSourceID = "73753" weixinAppID = "wxbf235770edaabc5c" weixinSecret = "ba32b269a068a5b72486a0beafd171e8" +dbConnectStr = "root:WebServer@1@tcp(127.0.0.1:3306)/jxd_dev_0?charset=utf8mb4&loc=Local&parseTime=true" + +[alpha] +httpport = 8088 + +# xiaan +jdToken = "df97f334-f7d8-4b36-9664-5784d8ae0baf" +jdAppKey = "06692746f7224695ad4788ce340bc854" +jdSecret = "d6b42a35a7414a5490d811654d745c84" + +disableElm = true + +ebaiSource = "62289" +ebaiSecret = "d3ec2358d6a819ea" + +mtpsAppKey = "25e816550bc9484480642f19a95f13fd" +mtpsSecret = "r4$HqrKx9~=7?2Jfo,$Z~a7%~k!Au&pEdI2)oPJvSbH2ao@2N0[8wSIvtuumh_J^" + +dadaIsProd = false +dadaCallbackURL = "http://callback.alpha.jxc4.com/dadadelivery/msg" +dadaSourceID = "73753" + +weixinAppID = "wx2bb99eb5d2c9b82c" +weixinSecret = "6bbbed1443cc062c20a015a64c07a531" + +mtwmCallbackURL = "http://callback.alpha.jxc4.com" + dbConnectStr = "root:WebServer@1@tcp(127.0.0.1:3306)/jxstore_alpha?charset=utf8mb4&loc=Local&parseTime=true" enableStoreWrite = true +enableJdStoreWrite = true enableEbaiStoreWrite = true enableMtwmStoreWrite = true enableWscStoreWrite = false +getWeixinTokenURL = "http://www.jxc4.com/v2/sys/GetWXToken" + [beta] +enableStoreWrite = false +enableJdStoreWrite = false +enableEbaiStoreWrite = false +enableElmStoreWrite = false +enableMtwmStoreWrite = false +enableWscStoreWrite = false + jdToken = "ccb10daf-e6f5-4a58-ada5-b97f9073a137" jdAppKey = "1dba76d40cac446ca500c0391a0b6c9d" jdSecret = "a88d031a1e7b462cb1579f12e97fe7f4" diff --git a/controllers/cms.go b/controllers/cms.go index 1a8df5b18..02548e393 100644 --- a/controllers/cms.go +++ b/controllers/cms.go @@ -52,17 +52,26 @@ func (c *CmsController) UpdatePlaces() { // @Title 修改地点信息 // @Description 只支持修改enabled, jd_code和mtps_price这三个属性 // @Param token header string true "认证token" -// @Param code formData int true "地点编号,注意是code不是ID,payload中的code会被忽略" -// @Param payload formData string true "json数据,place对象" +// @Param code formData int true "地点编号,注意是code不是ID" +// @Param enabled formData bool true "是否启用" +// @Param name formData string false "地点名" +// @Param mtpsPrice formData int false "美团配送基础价格" // @Success 200 {object} controllers.CallResult // @Failure 200 {object} controllers.CallResult // @router /UpdatePlace [put] func (c *CmsController) UpdatePlace() { c.callUpdatePlace(func(params *tCmsUpdatePlaceParams) (retVal interface{}, errCode string, err error) { - place := make(map[string]interface{}, 0) - if err = utils.UnmarshalUseNumber([]byte(params.Payload), &place); err == nil { - retVal, err = cms.UpdatePlace(params.Ctx, params.Code, place, params.Ctx.GetUserName()) + payload := map[string]interface{}{ + "code": params.Code, + "enabled": params.Enabled, } + if params.Name != "" { + payload["name"] = params.Name + } + if params.MtpsPrice > 0 { + payload["mtpsPrice"] = params.MtpsPrice + } + retVal, err = cms.UpdatePlaces(params.Ctx, []map[string]interface{}{payload}, params.Ctx.GetUserName()) return retVal, "", err }) } diff --git a/controllers/cms_store.go b/controllers/cms_store.go index d76966770..8f5d181dd 100644 --- a/controllers/cms_store.go +++ b/controllers/cms_store.go @@ -89,6 +89,21 @@ func (c *StoreController) DeleteStore() { }) } +// @Title 启用所有临时休息门店 +// @Description 启用所有临时休息门店 +// @Param token header string true "认证token" +// @Param isAsync formData bool true "是否异步操作" +// @Param isContinueWhenError formData bool false "单个同步失败是否继续,缺省false" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /EnableHaveRestStores [put] +func (c *StoreController) EnableHaveRestStores() { + c.callEnableHaveRestStores(func(params *tStoreEnableHaveRestStoresParams) (retVal interface{}, errCode string, err error) { + retVal, err = cms.EnableHaveRestStores(params.Ctx, params.IsAsync, params.IsContinueWhenError) + return retVal, "", err + }) +} + // @Title 创建京西门店 // @Description 创建京西门店 // @Param token header string true "认证token" @@ -336,3 +351,37 @@ func (c *StoreController) RefreshMissingDadaStores() { return retVal, "", err }) } + +// @Title 导出门店健康度信息 +// @Description 导出门店健康度信息 +// @Param token header string true "认证token" +// @Param vendorIDs query string false "平台ID列表" +// @Param storeIDs query string false "门店ID列表" +// @Param isAsync query bool false "是否异步操作" +// @Param isContinueWhenError query bool false "单个同步失败是否继续,缺省false" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /ExportShopsHealthInfo [get] +func (c *StoreController) ExportShopsHealthInfo() { + c.callExportShopsHealthInfo(func(params *tStoreExportShopsHealthInfoParams) (retVal interface{}, errCode string, err error) { + var vendorIDList, storeIDList []int + if err = jxutils.Strings2Objs(params.VendorIDs, &vendorIDList, params.StoreIDs, &storeIDList); err == nil { + retVal, err = cms.ExportShopsHealthInfo(params.Ctx, vendorIDList, storeIDList, params.IsAsync, params.IsContinueWhenError) + } + return retVal, "", err + }) +} + +// @Title 得到工商执照企业信息 +// @Description 得到工商执照企业信息 +// @Param token header string true "认证token" +// @Param licenceCode query string true "营业执照号" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /GetCorporationInfo [get] +func (c *StoreController) GetCorporationInfo() { + c.callGetCorporationInfo(func(params *tStoreGetCorporationInfoParams) (retVal interface{}, errCode string, err error) { + retVal, err = cms.GetCorporationInfo(params.Ctx, params.LicenceCode) + return retVal, "", err + }) +} diff --git a/controllers/cms_store_sku.go b/controllers/cms_store_sku.go index eab28cd93..5b080ecd1 100644 --- a/controllers/cms_store_sku.go +++ b/controllers/cms_store_sku.go @@ -40,6 +40,9 @@ type StoreSkuController struct { // @Param offset query int false "门店列表起始序号(以0开始,缺省为0)" // @Param pageSize query int false "门店列表页大小(缺省为50,-1表示全部)" // @Param isBySku query bool false "是否按SKU分拆" +// @Param jdSyncStatus query int false "京东同步标识" +// @Param ebaiSyncStatus query int false "饿百同步标识" +// @Param mtwmSyncStatus query int false "美团外卖同步标识" // @Success 200 {object} controllers.CallResult // @Failure 200 {object} controllers.CallResult // @router /GetStoreSkus [get] @@ -73,6 +76,9 @@ func (c *StoreSkuController) GetStoreSkus() { // @Param offset query int false "门店列表起始序号(以0开始,缺省为0)" // @Param pageSize query int false "门店列表页大小(缺省为50,-1表示全部)" // @Param isBySku query bool false "是否按SKU分拆" +// @Param jdSyncStatus query int false "京东同步标识" +// @Param ebaiSyncStatus query int false "饿百同步标识" +// @Param mtwmSyncStatus query int false "美团外卖同步标识" // @Success 200 {object} controllers.CallResult // @Failure 200 {object} controllers.CallResult // @router /GetStoresSkus [get] @@ -86,6 +92,45 @@ func (c *StoreSkuController) GetStoresSkus() { }) } +// @Title 得到异常门店商品数量 +// @Description 得到异常门店商品数量 +// @Param token header string true "认证token" +// @Param storeID query int true "门店ID" +// @Param syncStatus query int true "同步标志掩码" +// @Param isBySku query bool false "是否按SKU分拆" +// @Param fromStatus query int false "查询起始状态(0:不可售,1:可售)" +// @Param toStatus query int false "查询结束状态(0:不可售,1:可售)" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /GetStoreAbnormalSkuCount [get] +func (c *StoreSkuController) GetStoreAbnormalSkuCount() { + c.callGetStoreAbnormalSkuCount(func(params *tStoreSkuGetStoreAbnormalSkuCountParams) (retVal interface{}, errCode string, err error) { + retVal, err = cms.GetStoreAbnormalSkuCount(params.Ctx, params.StoreID, params.SyncStatus, params.IsBySku, params.MapData) + return retVal, "", err + }) +} + +// @Title 得到门店商品全信息 +// @Description 得到异常门店商品数量 +// @Param token header string true "认证token" +// @Param storeID query int true "门店ID" +// @Param vendorIDs query string false "厂商ID列表" +// @Param skuIDs query string true "Sku ID列表对象" +// @Param isContinueWhenError query bool false "单个同步失败是否继续,缺省false" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /GetVendorStoreSkusInfo [get] +func (c *StoreSkuController) GetVendorStoreSkusInfo() { + c.callGetVendorStoreSkusInfo(func(params *tStoreSkuGetVendorStoreSkusInfoParams) (retVal interface{}, errCode string, err error) { + var vendorIDs, skuIDs []int + err = jxutils.Strings2Objs(params.VendorIDs, &vendorIDs, params.SkuIDs, &skuIDs) + if err == nil { + retVal, err = cms.GetVendorStoreSkusInfo(params.Ctx, params.StoreID, vendorIDs, skuIDs, params.IsContinueWhenError) + } + return retVal, "", err + }) +} + // @Title 修改商家商品绑定 // @Description 修改商家商品绑定,请换用UpdateStoresSkus // @Param token header string true "认证token" @@ -129,10 +174,11 @@ func (c *StoreSkuController) UpdateStoreSkus() { // @Title 同步商家商品信息 // @Description 同步商家商品信息 // @Param token header string true "认证token" -// @Param storeIDs formData string true "门店ID列表" -// @Param vendorIDs formData string true "厂商ID列表" -// @Param isAsync formData bool true "是否异步操作" +// @Param vendorIDs formData string false "厂商ID列表" +// @Param storeIDs formData string false "门店ID列表" // @Param skuIDs formData string false "SKU ID列表,缺省为全部" +// @Param isForce formData bool false "是否强制(设置修改标志)" +// @Param isAsync formData bool true "是否异步操作" // @Param isContinueWhenError formData bool false "单个同步失败是否继续,缺省false" // @Success 200 {object} controllers.CallResult // @Failure 200 {object} controllers.CallResult @@ -144,7 +190,7 @@ func (c *StoreSkuController) SyncStoresSkus() { if err = jxutils.Strings2Objs(params.StoreIDs, &storeIDs, params.SkuIDs, &skuIDs, params.VendorIDs, &vendorIDs); err != nil { return retVal, "", err } - retVal, err = cms.CurVendorSync.SyncStoresSkus(params.Ctx, db, vendorIDs, storeIDs, skuIDs, params.IsAsync, params.IsContinueWhenError) + retVal, err = cms.CurVendorSync.SyncStoresSkus(params.Ctx, db, vendorIDs, storeIDs, skuIDs, params.IsForce, params.IsAsync, params.IsContinueWhenError) return retVal, "", err }) } @@ -171,6 +217,26 @@ func (c *StoreSkuController) UpdateStoresSkus() { }) } +// @Title 按门店商品维度批量修改多商家商品绑定 +// @Description 按门店商品维度批量修改多商家商品绑定 +// @Param token header string true "认证token" +// @Param payload formData string true "json数据,StoreSkuBindInfo对象数组" +// @Param isContinueWhenError formData bool false "单个同步失败是否继续,缺省false" +// @Param isAsync formData bool false "是否异步操作" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /UpdateStoresSkusByBind [put] +func (c *StoreSkuController) UpdateStoresSkusByBind() { + c.callUpdateStoresSkusByBind(func(params *tStoreSkuUpdateStoresSkusByBindParams) (retVal interface{}, errCode string, err error) { + var skuBindInfos []*cms.StoreSkuBindInfo + if err = jxutils.Strings2Objs(params.Payload, &skuBindInfos); err != nil { + return retVal, "", err + } + retVal, err = cms.UpdateStoresSkusByBind(params.Ctx, skuBindInfos, params.IsAsync, params.IsContinueWhenError) + return retVal, "", err + }) +} + // @Title 拷贝门店SKU信息 // @Description 拷贝门店SKU信息(此函数当前只是本地数据操作,要同步到远端需要调用SyncStoresSkus) // @Param token header string true "认证token" @@ -303,3 +369,60 @@ func (c *StoreSkuController) HandleStoreOpRequest() { return retVal, "", err }) } + +// @Title 根据厂家门店商品信息相应刷新本地数据 +// @Description 根据厂家门店商品信息相应刷新本地数据 +// @Param token header string true "认证token" +// @Param storeIDs formData string true "门店ID列表" +// @Param vendorID formData int true "厂商ID(当前只支持京东)" +// @Param isAsync formData bool false "是否异步操作" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /RefreshStoresSkuByVendor [put] +func (c *StoreSkuController) RefreshStoresSkuByVendor() { + c.callRefreshStoresSkuByVendor(func(params *tStoreSkuRefreshStoresSkuByVendorParams) (retVal interface{}, errCode string, err error) { + var storeIDList []int + if err = jxutils.Strings2Objs(params.StoreIDs, &storeIDList); err == nil { + retVal, err = cms.RefreshStoresSkuByVendor(params.Ctx, storeIDList, params.VendorID, params.IsAsync) + } + return retVal, "", err + }) +} + +// @Title 京东商家商品状态同步 +// @Description 京东商家商品状态同步 +// @Param token header string true "认证token" +// @Param storeIDs formData string true "门店ID列表" +// @Param skuIDs formData string false "SKU ID列表,缺省为全部" +// @Param isAsync formData bool false "是否异步操作" +// @Param isContinueWhenError formData bool false "单个同步失败是否继续,缺省false" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /SyncJdStoreProducts [put] +func (c *StoreSkuController) SyncJdStoreProducts() { + c.callSyncJdStoreProducts(func(params *tStoreSkuSyncJdStoreProductsParams) (retVal interface{}, errCode string, err error) { + var storeIDList, skuIDList []int + err = jxutils.Strings2Objs(params.StoreIDs, &storeIDList, params.SkuIDs, &skuIDList) + if err == nil { + retVal, err = cms.SyncJdStoreProducts(params.Ctx, storeIDList, skuIDList, params.IsAsync, params.IsContinueWhenError) + } + return retVal, "", err + }) +} + +// @Title 从订单得到本地没有关注的商品信息 +// @Description 从订单得到本地没有关注的商品信息 +// @Param token header string true "认证token" +// @Param fromTime query string true "扫描的订单开始时间" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /GetMissingStoreSkuFromOrder [get] +func (c *StoreSkuController) GetMissingStoreSkuFromOrder() { + c.callGetMissingStoreSkuFromOrder(func(params *tStoreSkuGetMissingStoreSkuFromOrderParams) (retVal interface{}, errCode string, err error) { + timeList, err := jxutils.BatchStr2Time(params.FromTime) + if err == nil { + retVal, err = cms.GetMissingStoreSkuFromOrder(params.Ctx, timeList[0]) + } + return retVal, "", err + }) +} diff --git a/controllers/cms_sync.go b/controllers/cms_sync.go index 1a3048d11..6e11a77da 100644 --- a/controllers/cms_sync.go +++ b/controllers/cms_sync.go @@ -17,8 +17,9 @@ type SyncController struct { // @Param token header string true "认证token" // @Param storeIDs formData string true "门店ID列表" // @Param vendorIDs formData string true "厂商ID列表" -// @Param isAsync formData bool true "是否异步操作" // @Param skuIDs formData string false "SKU ID列表,缺省为全部" +// @Param isForce formData bool false "是否强制(设置修改标志)" +// @Param isAsync formData bool true "是否异步操作" // @Param isContinueWhenError formData bool false "单个同步失败是否继续,缺省false" // @Success 200 {object} controllers.CallResult // @Failure 200 {object} controllers.CallResult @@ -30,7 +31,7 @@ func (c *SyncController) SyncStoresSkus() { if err = jxutils.Strings2Objs(params.StoreIDs, &storeIDs, params.SkuIDs, &skuIDs, params.VendorIDs, &vendorIDs); err != nil { return retVal, "", err } - retVal, err = cms.CurVendorSync.SyncStoresSkus(params.Ctx, db, vendorIDs, storeIDs, skuIDs, params.IsAsync, params.IsContinueWhenError) + retVal, err = cms.CurVendorSync.SyncStoresSkus(params.Ctx, db, vendorIDs, storeIDs, skuIDs, params.IsForce, params.IsAsync, params.IsContinueWhenError) return retVal, "", err }) } @@ -40,6 +41,7 @@ func (c *SyncController) SyncStoresSkus() { // @Param token header string true "认证token" // @Param storeIDs formData string true "门店ID列表" // @Param vendorIDs formData string true "厂商ID列表" +// @Param isForce formData bool false "是否强制(设置修改标志)" // @Param isAsync formData bool true "是否异步操作" // @Param isContinueWhenError formData bool false "单个同步失败是否继续,缺省false" // @Success 200 {object} controllers.CallResult @@ -52,7 +54,7 @@ func (c *SyncController) SyncStoresCategory() { if err = jxutils.Strings2Objs(params.StoreIDs, &storeIDs, params.VendorIDs, &vendorIDs); err != nil { return retVal, "", err } - retVal, err = cms.CurVendorSync.SyncStoresCategory(params.Ctx, db, vendorIDs, storeIDs, params.IsAsync) + retVal, err = cms.CurVendorSync.SyncStoresCategory(params.Ctx, db, vendorIDs, storeIDs, params.IsForce, params.IsAsync, params.IsContinueWhenError) return retVal, "", err }) } diff --git a/controllers/jd_callback.go b/controllers/jd_callback.go index 1c53dae93..1156f1b09 100644 --- a/controllers/jd_callback.go +++ b/controllers/jd_callback.go @@ -1,6 +1,8 @@ package controllers import ( + "bytes" + "io/ioutil" "net/http" "git.rosy.net.cn/baseapi/platformapi/jdapi" @@ -10,6 +12,7 @@ import ( "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/globals/api" "github.com/astaxie/beego" + "github.com/astaxie/beego/context" ) // Operations about JDOrder @@ -23,14 +26,15 @@ func (c *DjswController) orderStatus(isCancelOrder bool) { var callbackResponse *jdapi.CallbackResponse if isCancelOrder { - obj, callbackResponse = api.JdAPI.GetOrderApplyCancelCallbackMsg(c.Ctx.Input.RequestBody) + obj, callbackResponse = api.JdAPI.GetOrderApplyCancelCallbackMsg(getUsefulRequest(c.Ctx)) } else { - obj, callbackResponse = api.JdAPI.GetOrderCallbackMsg(c.Ctx.Input.RequestBody) + obj, callbackResponse = api.JdAPI.GetOrderCallbackMsg(getUsefulRequest(c.Ctx)) } + globals.SugarLogger.Debug(utils.Format4Output(obj, true)) if callbackResponse == nil { callbackResponse = jd.OnOrderMsg(obj) } - c.Data["json"] = c.transferResponse(callbackResponse) + c.Data["json"] = c.transferResponse("orderStatus", callbackResponse) c.ServeJSON() } else { c.Abort("404") @@ -79,11 +83,11 @@ func (c *DjswController) ApplyCancelOrder() { func (c *DjswController) PushDeliveryStatus() { if c.Ctx.Input.Method() == http.MethodPost { - obj, callbackResponse := api.JdAPI.GetOrderDeliveryCallbackMsg(c.Ctx.Input.RequestBody) + obj, callbackResponse := api.JdAPI.GetOrderDeliveryCallbackMsg(getUsefulRequest(c.Ctx)) if callbackResponse == nil { callbackResponse = jd.OnWaybillMsg(obj) } - c.Data["json"] = c.transferResponse(callbackResponse) + c.Data["json"] = c.transferResponse("PushDeliveryStatus", callbackResponse) c.ServeJSON() } else { c.Abort("404") @@ -99,19 +103,19 @@ func (c *DjswController) Token() { if err == nil { globals.SugarLogger.Info(utils.Format4Output(utils.URLValues2Map(urlValues), false)) } - c.Data["json"] = c.transferResponse(nil) + c.Data["json"] = c.transferResponse("Token", nil) c.ServeJSON() } func (c *DjswController) StockIsHave() { // globals.SugarLogger.Info(string(c.Ctx.Input.RequestBody)) if c.Ctx.Input.Method() == http.MethodPost { - obj, callbackResponse := api.JdAPI.GetStoreStockCallbackMsg(c.Ctx.Input.RequestBody) + obj, callbackResponse := api.JdAPI.GetStoreStockCallbackMsg(getUsefulRequest(c.Ctx)) if callbackResponse == nil { // globals.SugarLogger.Debugf("StockIsHave, obj:%s", utils.Format4Output(obj, false)) callbackResponse = promotion.OnStoreStockMsg(obj) } - c.Data["json"] = c.transferResponse(callbackResponse) + c.Data["json"] = c.transferResponse("StockIsHave", callbackResponse) c.ServeJSON() } else { c.Abort("404") @@ -120,11 +124,11 @@ func (c *DjswController) StockIsHave() { func (c *DjswController) SinglePromoteCreate() { if c.Ctx.Input.Method() == http.MethodPost { - obj, callbackResponse := api.JdAPI.GetOrderCallbackMsg(c.Ctx.Input.RequestBody) + obj, callbackResponse := api.JdAPI.GetOrderCallbackMsg(getUsefulRequest(c.Ctx)) if callbackResponse == nil { callbackResponse = promotion.OnNewPromotionMsg(obj) } - c.Data["json"] = c.transferResponse(callbackResponse) + c.Data["json"] = c.transferResponse("SinglePromoteCreate", callbackResponse) c.ServeJSON() } else { c.Abort("404") @@ -133,42 +137,66 @@ func (c *DjswController) SinglePromoteCreate() { func (c *DjswController) StoreCrud() { if c.Ctx.Input.Method() == http.MethodPost { - obj, callbackResponse := api.JdAPI.GetOrderCallbackMsg(c.Ctx.Input.RequestBody) + obj, callbackResponse := api.JdAPI.GetOrderCallbackMsg(getUsefulRequest(c.Ctx)) if callbackResponse == nil { callbackResponse = jd.OnStoreMsg(obj) } - c.Data["json"] = c.transferResponse(callbackResponse) + c.Data["json"] = c.transferResponse("StoreCrud", callbackResponse) c.ServeJSON() } else { c.Abort("404") } } -func (c *DjswController) transferResponse(inCallbackResponse *jdapi.CallbackResponse) (outCallbackResponse *jdapi.CallbackResponse) { +func (c *DjswController) transferResponse(funcName string, inCallbackResponse *jdapi.CallbackResponse) (outCallbackResponse *jdapi.CallbackResponse) { if globals.IsCallbackAlwaysReturnSuccess() { return jdapi.SuccessResponse } if inCallbackResponse == nil { return jdapi.SuccessResponse } + globals.SugarLogger.Debugf("%s callbackResponse:%s", funcName, utils.Format4Output(inCallbackResponse, true)) return inCallbackResponse } -func (c *DjswController) afterSaleBillStatus() { - var obj *jdapi.CallbackOrderMsg - var callbackResponse *jdapi.CallbackResponse - obj, callbackResponse = api.JdAPI.GetOrderCallbackMsg(c.Ctx.Input.RequestBody) - if callbackResponse == nil { - callbackResponse = jd.OnAfterSaleMsg(obj) - } - c.Data["json"] = c.transferResponse(callbackResponse) - c.ServeJSON() +func (c *DjswController) EndOrderFinance() { + c.nullOperation() +} + +func (c *DjswController) FinanceAdjustment() { + c.nullOperation() +} + +func (c *DjswController) DeliveryCarrierModify() { + c.nullOperation() +} + +func (c *DjswController) NewApplyAfterSaleBill() { + c.nullOperation() +} + +func (c *DjswController) UpdateApplyAfterSaleBill() { + c.nullOperation() +} + +func (c *DjswController) NewAfterSaleBill() { + c.nullOperation() } func (c *DjswController) AfterSaleBillStatus() { - c.afterSaleBillStatus() + c.orderStatus(false) } func (c *DjswController) OrderAccounting() { - c.afterSaleBillStatus() + c.orderStatus(false) +} + +func getUsefulRequest(ctx *context.Context) *http.Request { + ctx.Request.Body = ioutil.NopCloser(bytes.NewReader(ctx.Input.RequestBody)) + return ctx.Request +} + +func (c *DjswController) nullOperation() { + c.Data["json"] = c.transferResponse("nullOperation", nil) + c.ServeJSON() } diff --git a/controllers/jx_order.go b/controllers/jx_order.go index d9f05b48b..168c51985 100644 --- a/controllers/jx_order.go +++ b/controllers/jx_order.go @@ -78,18 +78,39 @@ func (c *OrderController) SelfDelivered() { }) } +// @Title 查询三方运单费用信息 +// @Description 查询三方运单费用信息 +// @Param token header string true "认证token" +// @Param vendorOrderID query string true "订单ID" +// @Param vendorID query int true "订单所属的厂商ID" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /QueryOrderWaybillFeeInfo [get] +func (c *OrderController) QueryOrderWaybillFeeInfo() { + c.callQueryOrderWaybillFeeInfo(func(params *tOrderQueryOrderWaybillFeeInfoParams) (retVal interface{}, errCode string, err error) { + retVal, err = defsch.FixedScheduler.QueryOrderWaybillFeeInfoEx(params.Ctx, params.VendorOrderID, params.VendorID) + return retVal, "", err + }) +} + // @Title 创建三方运单 // @Description 创建三方运单 // @Param token header string true "认证token" // @Param vendorOrderID formData string true "订单ID" // @Param vendorID formData int true "订单所属的厂商ID" -// @Param forceCreate formData bool false "是否强制创建(忽略订单状态检查)" +// @Param courierVendorIDs formData string false "运单厂商ID(缺省全部)" +// @Param forceCreate formData bool false "是否强制创建(忽略订单状态检查及其它参数)" +// @Param maxAddFee formData int false "最大加价,单位为分(为0时为缺省值)" +// @Param maxDiffFee2Mtps formData int false "最大与美团配送差价,单位为分(maxAddFee不为0时才可能有效)" // @Success 200 {object} controllers.CallResult // @Failure 200 {object} controllers.CallResult // @router /CreateWaybillOnProviders [post] func (c *OrderController) CreateWaybillOnProviders() { c.callCreateWaybillOnProviders(func(params *tOrderCreateWaybillOnProvidersParams) (retVal interface{}, errCode string, err error) { - retVal, err = defsch.FixedScheduler.CreateWaybillOnProvidersEx(params.Ctx, params.VendorOrderID, params.VendorID, params.ForceCreate) + var courierVendorIDs []int + if err = jxutils.Strings2Objs(params.CourierVendorIDs, &courierVendorIDs); err == nil { + retVal, err = defsch.FixedScheduler.CreateWaybillOnProvidersEx(params.Ctx, params.VendorOrderID, params.VendorID, courierVendorIDs, params.ForceCreate, int64(params.MaxAddFee), int64(params.MaxDiffFee2Mtps)) + } return retVal, "", err }) } @@ -110,10 +131,31 @@ func (c *OrderController) CancelAll3rdWaybills() { }) } +// @Title 取消三方运单 +// @Description 取消三方运单 +// @Param token header string true "认证token" +// @Param vendorWaybillID formData string true "运单ID" +// @Param waybillVendorID formData int true "运单所属的厂商ID" +// @Param reasonID formData int false "原因ID" +// @Param reason formData string false "取消原因" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /CancelWaybill [post] +func (c *OrderController) CancelWaybill() { + c.callCancelWaybill(func(params *tOrderCancelWaybillParams) (retVal interface{}, errCode string, err error) { + reasonID := params.ReasonID + if reasonID == 0 { + reasonID = partner.CancelWaybillReasonOther + } + err = defsch.FixedScheduler.CancelWaybillByID(params.Ctx, params.VendorWaybillID, params.WaybillVendorID, reasonID, params.Reason) + return retVal, "", err + }) +} + // @Title 得到门店订单信息 // @Description 得到门店订单信息 // @Param token header string true "认证token" -// @Param storeID query string true "京西门店ID" +// @Param storeID query int true "京西门店ID" // @Param lastHours query int false "最近多少小时的信息(缺省为两天)" // @Success 200 {object} controllers.CallResult // @Failure 200 {object} controllers.CallResult @@ -135,7 +177,7 @@ func (c *OrderController) GetStoreOrderCountInfo() { // @router /GetStoreOrderCountInfo2 [get] func (c *OrderController) GetStoreOrderCountInfo2() { c.callGetStoreOrderCountInfo2(func(params *tOrderGetStoreOrderCountInfo2Params) (retVal interface{}, errCode string, err error) { - retVal, err = orderman.FixedOrderManager.GetStoreOrderCountInfo2(params.Ctx, params.StoreID, params.LastHours) + retVal, err = orderman.FixedOrderManager.GetStoreOrderCountInfo(params.Ctx, params.StoreID, params.LastHours) return retVal, "", err }) } @@ -176,12 +218,13 @@ func (c *OrderController) GetOrderInfo() { // @Param token header string true "认证token" // @Param vendorOrderID query string true "订单ID" // @Param vendorID query int true "订单所属的厂商ID" +// @Param isNotEnded query bool false "是否只是没有结束的运单" // @Success 200 {object} controllers.CallResult // @Failure 200 {object} controllers.CallResult // @router /GetOrderWaybillInfo [get] func (c *OrderController) GetOrderWaybillInfo() { c.callGetOrderWaybillInfo(func(params *tOrderGetOrderWaybillInfoParams) (retVal interface{}, errCode string, err error) { - retVal, err = orderman.FixedOrderManager.GetOrderWaybillInfo(params.Ctx, params.VendorOrderID, params.VendorID) + retVal, err = orderman.FixedOrderManager.GetOrderWaybillInfo(params.Ctx, params.VendorOrderID, params.VendorID, params.IsNotEnded) return retVal, "", err }) } @@ -241,6 +284,78 @@ func (c *OrderController) GetOrders() { }) } +// @Title 导出订单(包括SKU信息) +// @Description 导出订单(包括SKU信息) +// @Param token header string true "认证token" +// @Param orderID query string false "订单号,如果此项不为空,忽略其它所有查询条件(此项会废弃,用vendorOderID)" +// @Param vendorOrderID query string false "订单号,如果此项不为空,忽略其它所有查询条件" +// @Param keyword query string false "查询关键字" +// @Param fromDate query string false "开始日期(包含),格式(2006-01-02),如果订单号为空此项必须要求" +// @Param toDate query string false "结束日期(包含),格式(2006-01-02),如果订单号为空此项必须要求" +// @Param vendorIDs query string false "订单所属厂商列表[1,2,3],缺省不限制" +// @Param waybillVendorIDs query string false "承运人所属厂商列表[1,2,3],缺省不限制" +// @Param storeIDs query string false "京西门店ID列表[1,2,3],缺省不限制" +// @Param statuss query string false "订单状态列表[1,2,3],缺省不限制" +// @Param lockStatuss query string false "订单锁定状态列表[1,2,3],缺省不限制" +// @Param cities query string false "城市code列表[1,2,3],缺省不限制" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /ExportOrders [get] +func (c *OrderController) ExportOrders() { + c.callExportOrders(func(params *tOrderExportOrdersParams) (retVal interface{}, errCode string, err error) { + retVal, err = orderman.FixedOrderManager.ExportOrders(params.Ctx, params.FromDate, params.ToDate, params.MapData) + return retVal, "", err + }) +} + +// @Title 查询售后单 +// @Description 查询售后单 +// @Param token header string true "认证token" +// @Param afsOrderID query string false "售后单号,如果此项不为空,忽略其它所有查询条件" +// @Param vendorOrderID query string false "订单号,如果此项不为空,忽略其它所有查询条件" +// @Param vendorIDs query string false "订单所属厂商列表[1,2,3],缺省不限制" +// @Param appealTypes query string false "售后处理s方式列表" +// @Param storeIDs query string false "京西门店ID列表[1,2,3],缺省不限制" +// @Param statuss query string false "订单状态列表[1,2,3],缺省不限制" +// @Param keyword query string false "查询关键字" +// @Param fromTime query string false "开始时间,如果订单号为空此项必须要求" +// @Param toTime query string false "结束时间,如果订单号为空此项必须要求" +// @Param offset query int false "结果起始序号(以0开始,缺省为0)" +// @Param pageSize query int false "结果页大小(缺省为50,-1表示全部)" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /GetAfsOrders [get] +func (c *OrderController) GetAfsOrders() { + c.callGetAfsOrders(func(params *tOrderGetAfsOrdersParams) (retVal interface{}, errCode string, err error) { + timeList, err := jxutils.BatchStr2Time(params.FromTime, params.ToTime) + if err == nil { + var vendorIDList, appealTypeList, storeIDList, statusList []int + if err = jxutils.Strings2Objs(params.VendorIDs, &vendorIDList, params.AppealTypes, &appealTypeList, + params.StoreIDs, &storeIDList, params.Statuss, &statusList); err == nil { + retVal, err = orderman.FixedOrderManager.GetAfsOrders(params.Ctx, params.Keyword, params.AfsOrderID, + params.VendorOrderID, vendorIDList, appealTypeList, storeIDList, statusList, timeList[0], timeList[1], + params.Offset, params.PageSize) + } + } + return retVal, "", err + }) +} + +// @Title 得到售后单SKU信息 +// @Description 得到售后单SKU信息 +// @Param token header string true "认证token" +// @Param afsOrderID query string true "售后单ID" +// @Param vendorID query int true "售后单所属的厂商ID" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /GetAfsOrderSkuInfo [get] +func (c *OrderController) GetAfsOrderSkuInfo() { + c.callGetAfsOrderSkuInfo(func(params *tOrderGetAfsOrderSkuInfoParams) (retVal interface{}, errCode string, err error) { + retVal, err = orderman.FixedOrderManager.GetAfsOrderSkuInfo(params.Ctx, params.AfsOrderID, params.VendorID) + return retVal, "", err + }) +} + // @Title 查询运单 // @Description 查询运单 // @Param token header string true "认证token" @@ -277,11 +392,35 @@ func (c *OrderController) GetOrderStatusList() { }) } +// @Title 查询门店营业数据 +// @Description 查询门店营业数据 +// @Param token header string true "认证token" +// @Param storeIDs query string false "京西门店ID列表[1,2,3],缺省不限制" +// @Param fromTime query string true "起始时间" +// @Param toTime query string true "结束时间" +// @Param statuss query string false "订单状态列表[1,2,3],缺省不限制" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /GetStoresOrderSaleInfo [get] +func (c *OrderController) GetStoresOrderSaleInfo() { + c.callGetStoresOrderSaleInfo(func(params *tOrderGetStoresOrderSaleInfoParams) (retVal interface{}, errCode string, err error) { + timeList, err := jxutils.BatchStr2Time(params.FromTime, params.ToTime) + if err == nil { + var storeIDList []int + var statusList []int + if err = jxutils.Strings2Objs(params.StoreIDs, &storeIDList, params.Statuss, &statusList); err == nil { + retVal, err = orderman.FixedOrderManager.GetStoresOrderSaleInfo(params.Ctx, storeIDList, timeList[0], timeList[1], statusList) + } + } + return retVal, "", err + }) +} + // @Title 刷新订单真实手机号 // @Description 刷新订单真实手机号 // @Param token header string true "认证token" // @Param vendorID formData int true "厂商ID" -// @Param fromTime formData string false "起始时间" +// @Param fromTime formData string true "起始时间" // @Param toTime formData string false "结束时间" // @Param isAsync formData bool true "是否异步操作" // @Param isContinueWhenError formData bool false "单个同步失败是否继续,缺省false" @@ -347,10 +486,12 @@ func skuList2Map(skuList []*model.OrderSku) (skuMap map[int]*model.OrderSku) { skuMap = make(map[int]*model.OrderSku) for _, sku := range skuList { skuID := jxutils.GetSkuIDFromOrderSku(sku) - if skuMap[skuID] == nil { - skuMap[skuID] = sku - } else { - skuMap[skuID].Count += sku.Count + if sku.SkuID > 0 { + if skuMap[skuID] == nil { + skuMap[skuID] = sku + } else { + skuMap[skuID].Count += sku.Count + } } } return skuMap @@ -489,8 +630,58 @@ func (c *OrderController) AgreeOrRefuseCancel() { c.callAgreeOrRefuseCancel(func(params *tOrderAgreeOrRefuseCancelParams) (retVal interface{}, errCode string, err error) { order, err := partner.CurOrderManager.LoadOrder(params.VendorOrderID, params.VendorID) if err == nil { - err = defsch.FixedScheduler.AgreeOrRefuseCancel(params.Ctx, order, params.AcceptIt, params.Reason) + if err = defsch.FixedScheduler.AgreeOrRefuseCancel(params.Ctx, order, params.AcceptIt, params.Reason); err == nil && params.AcceptIt { + order.LockStatus = model.OrderStatusUnknown + err = partner.CurOrderManager.UpdateOrderFields(order, []string{"LockStatus"}) + } } return retVal, "", err }) } + +// @Title 审核售后单申请 +// @Description 审核售后单申请 +// @Param token header string true "认证token" +// @Param afsOrderID formData string true "售后ID" +// @Param vendorID formData int true "订单所属厂商ID" +// @Param approveType formData int true "操作类型" +// @Param reason formData string false "原因" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /AgreeOrRefuseRefund [put] +func (c *OrderController) AgreeOrRefuseRefund() { + c.callAgreeOrRefuseRefund(func(params *tOrderAgreeOrRefuseRefundParams) (retVal interface{}, errCode string, err error) { + err = defsch.FixedScheduler.AgreeOrRefuseRefund(params.Ctx, params.AfsOrderID, params.VendorID, params.ApproveType, params.Reason) + return retVal, "", err + }) +} + +// @Title 确认收到售后退货 +// @Description 确认收到售后退货 +// @Param token header string true "认证token" +// @Param afsOrderID formData string true "售后ID" +// @Param vendorID formData int true "订单所属厂商ID" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /ConfirmReceivedReturnGoods [put] +func (c *OrderController) ConfirmReceivedReturnGoods() { + c.callConfirmReceivedReturnGoods(func(params *tOrderConfirmReceivedReturnGoodsParams) (retVal interface{}, errCode string, err error) { + err = defsch.FixedScheduler.ConfirmReceivedReturnGoods(params.Ctx, params.AfsOrderID, params.VendorID) + return retVal, "", err + }) +} + +// @Title 得到门店售后单信息 +// @Description 得到门店售后单信息 +// @Param token header string true "认证token" +// @Param storeID query int true "京西门店ID" +// @Param lastHours query int false "最近多少小时的信息(缺省为两天)" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /GetStoreAfsOrderCountInfo [get] +func (c *OrderController) GetStoreAfsOrderCountInfo() { + c.callGetStoreAfsOrderCountInfo(func(params *tOrderGetStoreAfsOrderCountInfoParams) (retVal interface{}, errCode string, err error) { + retVal, err = orderman.FixedOrderManager.GetStoreAfsOrderCountInfo(params.Ctx, params.StoreID, params.LastHours) + return retVal, "", err + }) +} diff --git a/controllers/promotion.go b/controllers/promotion.go index fb44be4c9..f4773d3d9 100644 --- a/controllers/promotion.go +++ b/controllers/promotion.go @@ -1,13 +1,12 @@ package controllers import ( - "errors" + "git.rosy.net.cn/jx-callback/business/jxutils" "github.com/astaxie/beego" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxstore/promotion" - "git.rosy.net.cn/jx-callback/business/model" ) type PromotionController struct { @@ -17,7 +16,7 @@ type PromotionController struct { // @Title 创建活动 // @Description 创建活动 // @Param token header string true "认证token" -// @Param vendorID formData int true "厂商ID,当前只支持京东:0 " +// @Param vendorID formData int true "厂商ID,当前只支持,京东:0,京西(用于记录活动信息):99" // @Param name formData string true "活动名,必须唯一(所以名子上最好带上日期)" // @Param beginAt formData string true "开始日期" // @Param endAt formData string true "结束日期" @@ -37,9 +36,6 @@ type PromotionController struct { // @router /CreatePromotion [post] func (c *PromotionController) CreatePromotion() { c.callCreatePromotion(func(params *tPromotionCreatePromotionParams) (retVal interface{}, errCode string, err error) { - if params.VendorID != model.VendorIDJD { - return nil, "", errors.New("当前只支持创建京东活动") - } beginAt, err := utils.TryStr2Time(params.BeginAt) if err != nil { return retVal, "", err @@ -57,7 +53,7 @@ func (c *PromotionController) CreatePromotion() { } if err = utils.UnmarshalUseNumber([]byte(params.StoreIDs), &promotionParams.StoreIDs); err == nil { if err = utils.UnmarshalUseNumber([]byte(params.SkuPrices), &promotionParams.SkuPrices); err == nil { - retVal, err = promotion.CreateJdPromotion(params.Ctx, false, params.IsAsync, params.IsContinueWhenError, params.VendorPromotionID, promotionParams, params.MapData) + retVal, err = promotion.CreateJdPromotion(params.Ctx, params.VendorID, false, params.IsAsync, params.IsContinueWhenError, params.VendorPromotionID, promotionParams, params.MapData) } } return retVal, "", err @@ -177,6 +173,24 @@ func (c *PromotionController) LockPromotionSkus() { }) } +// @Title 修改活动商品门店结算价 +// @Description 修改活动商品门店结算价 +// @Param token header string true "认证token" +// @Param promotionID formData int true "活动id" +// @Param skuPrices formData string true "json数据,价格信息列表(只有EarningPrice有效)" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /UpdatePromotionSkusEarningPrice [put] +func (c *PromotionController) UpdatePromotionSkusEarningPrice() { + c.callUpdatePromotionSkusEarningPrice(func(params *tPromotionUpdatePromotionSkusEarningPriceParams) (retVal interface{}, errCode string, err error) { + var skuPriceList []*promotion.SkuPrice + if err = jxutils.Strings2Objs(params.SkuPrices, &skuPriceList); err == nil { + retVal, err = promotion.UpdatePromotionSkusEarningPrice(params.Ctx, params.PromotionID, skuPriceList) + } + return retVal, "", err + }) +} + // @Title 从远程更新活动状态 // @Description 从远程更新活动状态 // @Param token header string true "认证token" diff --git a/controllers/sys.go b/controllers/sys.go index a74b792ee..96d67fdc6 100644 --- a/controllers/sys.go +++ b/controllers/sys.go @@ -1,11 +1,18 @@ package controllers import ( + "io" + "net/http" + "net/url" + "strings" "time" + "git.rosy.net.cn/baseapi/platformapi/ebaiapi" + "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/business/jxutils/eventhub/syseventhub" + "git.rosy.net.cn/jx-callback/business/model" "github.com/astaxie/beego" ) @@ -29,3 +36,23 @@ func (c *SysController) GetWXToken() { return retVal, "", err }) } + +// @Title 得到饿百RTF转换内容 +// @Description 得到饿百RTF转换内容 +// @Param imgListStr query string true "逗号分隔的图片列表可以是转义后的" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /GetEbaiRTFDetail [get] +func (c *SysController) GetEbaiRTFDetail() { + var html string + c.callGetEbaiRTFDetail(func(params *tSysGetEbaiRTFDetailParams) (retVal interface{}, errCode string, err error) { + params.ImgListStr, _ = url.QueryUnescape(params.ImgListStr) + imgList := strings.Split(params.ImgListStr, ",") + html = ebaiapi.BuildRFTFromImgs(imgList...) + return retVal, model.ErrorCodeIgnore, err + }) + w := c.Ctx.ResponseWriter + w.Header().Add("Content-Type", "text/html") + w.WriteHeader(http.StatusOK) + io.WriteString(w, html) +} diff --git a/controllers/temp_op.go b/controllers/temp_op.go index c61beb02c..08facefef 100644 --- a/controllers/temp_op.go +++ b/controllers/temp_op.go @@ -215,3 +215,18 @@ func (c *TempOpController) RetrieveEbaiShopLicence() { return retVal, "", err }) } + +// @Title 刷新美团配送价格 +// @Description 刷新美团配送价格 +// @Param token header string true "认证token" +// @Param isAsync formData bool false "是否异步操作" +// @Param isContinueWhenError formData bool false "单个同步失败是否继续,缺省false" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /RefreshMtpsWaybillFee [post] +func (c *TempOpController) RefreshMtpsWaybillFee() { + c.callRefreshMtpsWaybillFee(func(params *tTempopRefreshMtpsWaybillFeeParams) (retVal interface{}, errCode string, err error) { + retVal, err = tempop.RefreshMtpsWaybillFee(params.Ctx, params.IsAsync, params.IsContinueWhenError) + return retVal, "", err + }) +} diff --git a/globals/beegodb/beegodb.go b/globals/beegodb/beegodb.go index d9299e2ab..541ab8bf6 100644 --- a/globals/beegodb/beegodb.go +++ b/globals/beegodb/beegodb.go @@ -47,6 +47,8 @@ func Init() { // 如下语句建表时要出错(INDEX名字太长了),暂时放一下,必须放最后一句 orm.RegisterModel(&model.OrderFinancial{}, &model.AfsOrder{}, &model.OrderDiscountFinancial{}, &model.OrderSkuFinancial{}) + // orm.RegisterModel(&model.Act{}, &model.ActOrderRule{}, &model.ActStoreSku{}) + // orm.RegisterModel(&model.ActMap{}, &model.ActStoreMap{}, &model.ActStoreSkuMap{}) // create table orm.RunSyncdb("default", false, true) } diff --git a/globals/globals.go b/globals/globals.go index 117aa7758..ea92b747c 100644 --- a/globals/globals.go +++ b/globals/globals.go @@ -25,6 +25,7 @@ var ( AliSecret string EnableStoreWrite bool + EnableJdStoreWrite bool EnableEbaiStoreWrite bool EnableElmStoreWrite bool EnableMtwmStoreWrite bool @@ -40,6 +41,8 @@ var ( GetWeixinTokenURL string GetWeixinTokenKey string + + StoreName string ) func init() { @@ -55,13 +58,14 @@ func init() { func Init() { SugarLogger.Infof("globals RunMode=%s", beego.BConfig.RunMode) ReallyCallPlatformAPI = (beego.BConfig.RunMode != "dev" && beego.BConfig.RunMode != "test") - ReallySendWeixinMsg = ReallyCallPlatformAPI && beego.BConfig.RunMode == "prod" - ReallyReplyComment = ReallyCallPlatformAPI && beego.BConfig.RunMode == "prod" + ReallySendWeixinMsg = ReallyCallPlatformAPI && IsProductEnv() + ReallyReplyComment = ReallyCallPlatformAPI && IsProductEnv() AliKey = beego.AppConfig.DefaultString("aliKey", "") AliSecret = beego.AppConfig.DefaultString("aliSecret", "") EnableStoreWrite = beego.AppConfig.DefaultBool("enableStoreWrite", false) + EnableJdStoreWrite = EnableStoreWrite && beego.AppConfig.DefaultBool("enableJdStoreWrite", false) EnableEbaiStoreWrite = EnableStoreWrite && beego.AppConfig.DefaultBool("enableEbaiStoreWrite", false) EnableElmStoreWrite = EnableStoreWrite && beego.AppConfig.DefaultBool("enableElmStoreWrite", false) EnableMtwmStoreWrite = EnableStoreWrite && beego.AppConfig.DefaultBool("enableMtwmStoreWrite", false) @@ -78,6 +82,8 @@ func Init() { GetWeixinTokenURL = beego.AppConfig.DefaultString("getWeixinTokenURL", "") GetWeixinTokenKey = beego.AppConfig.DefaultString("getWeixinTokenKey", "") + + StoreName = beego.AppConfig.DefaultString("storeName", "京西菜市") } func IsCallbackAlwaysReturnSuccess() bool { diff --git a/globals/refutil/refutil.go b/globals/refutil/refutil.go index d003db82c..70ed494ff 100644 --- a/globals/refutil/refutil.go +++ b/globals/refutil/refutil.go @@ -20,6 +20,10 @@ func CheckAndGetStructValue(item interface{}) *reflect.Value { return &value } +func IsFieldExist(obj interface{}, fieldName string) bool { + return reflect.Indirect(reflect.ValueOf(obj)).FieldByName(fieldName).IsValid() +} + func GetObjFieldByName(obj interface{}, fieldName string) interface{} { return reflect.Indirect(reflect.ValueOf(obj)).FieldByName(fieldName).Interface() } diff --git a/main.go b/main.go index f89c17ca9..d815b6ca5 100644 --- a/main.go +++ b/main.go @@ -46,9 +46,10 @@ func Init() { cms.InitServiceInfo(Version, buildTime, GitCommit) promotion.Init() - if beego.BConfig.RunMode == "prod" { + if globals.IsProductEnv() { misc.StartRefreshEbaiRealMobile() ebai.CurPurchaseHandler.StartRefreshComment() + misc.StartDailyWork() } misc.StartGetCityStoreInfo() } @@ -109,10 +110,6 @@ func main() { globals.SugarLogger.Errorf("RefreshWeimobToken failed with error:%s", err) return } - if err := tasks.RefreshDingDingToken(); err != nil { - globals.SugarLogger.Errorf("RefreshDingDingToken failed with error:%s", err) - return - } if err := tasks.RefreshYilianyunToken(); err != nil { globals.SugarLogger.Errorf("RefreshYilianyunToken failed with error:%s", err) return @@ -121,9 +118,12 @@ func main() { orderman.LoadPendingOrders() // 延时的原因是等回调准备好 - if beego.BConfig.RunMode == "prod" || - beego.BConfig.RunMode == "jxgy" { + if globals.IsProductEnv() { utils.AfterFuncWithRecover(2*time.Second, func() { + if err := tasks.RefreshDingDingToken(); err != nil { + globals.SugarLogger.Errorf("RefreshDingDingToken failed with error:%s", err) + return + } api.DingDingAPI.DeleteCallback() if err := api.DingDingAPI.RegisterCallback([]string{dingdingapi.CBTagUserAddOrg, dingdingapi.CBTagUserModifyOrg, dingdingapi.CBTagUserLeaveOrg}, beego.AppConfig.DefaultString("dingdingCallbackToken", ""), beego.AppConfig.DefaultString("dingdingCallbackAESKey", ""), beego.AppConfig.DefaultString("dingdingCallbackURL", "")); err != nil { globals.SugarLogger.Warnf("dingding RegisterCallback failed with error:%v", err) diff --git a/routers/commentsRouter_controllers.go b/routers/commentsRouter_controllers.go index 1261ef2b0..82f7e70ae 100644 --- a/routers/commentsRouter_controllers.go +++ b/routers/commentsRouter_controllers.go @@ -538,6 +538,15 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], + beego.ControllerComments{ + Method: "AgreeOrRefuseRefund", + Router: `/AgreeOrRefuseRefund`, + AllowHTTPMethods: []string{"put"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], beego.ControllerComments{ Method: "CallPMCourier", @@ -565,6 +574,15 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], + beego.ControllerComments{ + Method: "CancelWaybill", + Router: `/CancelWaybill`, + AllowHTTPMethods: []string{"post"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], beego.ControllerComments{ Method: "ConfirmReceiveGoods", @@ -574,6 +592,15 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], + beego.ControllerComments{ + Method: "ConfirmReceivedReturnGoods", + Router: `/ConfirmReceivedReturnGoods`, + AllowHTTPMethods: []string{"put"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], beego.ControllerComments{ Method: "CreateWaybillOnProviders", @@ -592,6 +619,15 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], + beego.ControllerComments{ + Method: "ExportOrders", + Router: `/ExportOrders`, + AllowHTTPMethods: []string{"get"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], beego.ControllerComments{ Method: "FinishedPickup", @@ -601,6 +637,24 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], + beego.ControllerComments{ + Method: "GetAfsOrderSkuInfo", + Router: `/GetAfsOrderSkuInfo`, + AllowHTTPMethods: []string{"get"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], + beego.ControllerComments{ + Method: "GetAfsOrders", + Router: `/GetAfsOrders`, + AllowHTTPMethods: []string{"get"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], beego.ControllerComments{ Method: "GetOrderInfo", @@ -655,6 +709,15 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], + beego.ControllerComments{ + Method: "GetStoreAfsOrderCountInfo", + Router: `/GetStoreAfsOrderCountInfo`, + AllowHTTPMethods: []string{"get"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], beego.ControllerComments{ Method: "GetStoreOrderCountInfo", @@ -673,6 +736,15 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], + beego.ControllerComments{ + Method: "GetStoresOrderSaleInfo", + Router: `/GetStoresOrderSaleInfo`, + AllowHTTPMethods: []string{"get"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], beego.ControllerComments{ Method: "GetWaybills", @@ -691,6 +763,15 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], + beego.ControllerComments{ + Method: "QueryOrderWaybillFeeInfo", + Router: `/QueryOrderWaybillFeeInfo`, + AllowHTTPMethods: []string{"get"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:OrderController"], beego.ControllerComments{ Method: "RefreshOrderRealMobile", @@ -781,6 +862,15 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:PromotionController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:PromotionController"], + beego.ControllerComments{ + Method: "UpdatePromotionSkusEarningPrice", + Router: `/UpdatePromotionSkusEarningPrice`, + AllowHTTPMethods: []string{"put"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:SkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:SkuController"], beego.ControllerComments{ Method: "AddCategory", @@ -997,6 +1087,33 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreController"], + beego.ControllerComments{ + Method: "EnableHaveRestStores", + Router: `/EnableHaveRestStores`, + AllowHTTPMethods: []string{"put"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreController"], + beego.ControllerComments{ + Method: "ExportShopsHealthInfo", + Router: `/ExportShopsHealthInfo`, + AllowHTTPMethods: []string{"get"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreController"], + beego.ControllerComments{ + Method: "GetCorporationInfo", + Router: `/GetCorporationInfo`, + AllowHTTPMethods: []string{"get"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreController"], beego.ControllerComments{ Method: "GetStoreCourierMaps", @@ -1114,6 +1231,24 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], + beego.ControllerComments{ + Method: "GetMissingStoreSkuFromOrder", + Router: `/GetMissingStoreSkuFromOrder`, + AllowHTTPMethods: []string{"get"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], + beego.ControllerComments{ + Method: "GetStoreAbnormalSkuCount", + Router: `/GetStoreAbnormalSkuCount`, + AllowHTTPMethods: []string{"get"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], beego.ControllerComments{ Method: "GetStoreOpRequests", @@ -1150,6 +1285,15 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], + beego.ControllerComments{ + Method: "GetVendorStoreSkusInfo", + Router: `/GetVendorStoreSkusInfo`, + AllowHTTPMethods: []string{"get"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], beego.ControllerComments{ Method: "HandleStoreOpRequest", @@ -1159,6 +1303,24 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], + beego.ControllerComments{ + Method: "RefreshStoresSkuByVendor", + Router: `/RefreshStoresSkuByVendor`, + AllowHTTPMethods: []string{"put"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], + beego.ControllerComments{ + Method: "SyncJdStoreProducts", + Router: `/SyncJdStoreProducts`, + AllowHTTPMethods: []string{"put"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], beego.ControllerComments{ Method: "SyncStoresSkus", @@ -1195,6 +1357,15 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], + beego.ControllerComments{ + Method: "UpdateStoresSkusByBind", + Router: `/UpdateStoresSkusByBind`, + AllowHTTPMethods: []string{"put"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], beego.ControllerComments{ Method: "UpdateStoresSkusSale", @@ -1267,6 +1438,15 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:SysController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:SysController"], + beego.ControllerComments{ + Method: "GetEbaiRTFDetail", + Router: `/GetEbaiRTFDetail`, + AllowHTTPMethods: []string{"get"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:SysController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:SysController"], beego.ControllerComments{ Method: "GetWXToken", @@ -1303,6 +1483,15 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:TempOpController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:TempOpController"], + beego.ControllerComments{ + Method: "RefreshMtpsWaybillFee", + Router: `/RefreshMtpsWaybillFee`, + AllowHTTPMethods: []string{"post"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:TempOpController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:TempOpController"], beego.ControllerComments{ Method: "RetrieveEbaiShopLicence",