You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
304 lines
18 KiB
304 lines
18 KiB
using BBWY.Common.Models;
|
|
using BBWY.Server.Model;
|
|
using BBWY.Server.Model.Db;
|
|
using BBWY.Server.Model.Dto;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Yitter.IdGenerator;
|
|
|
|
namespace BBWY.Server.Business
|
|
{
|
|
public class JDStockNumWarningBusiness : BaseSyncBusiness, IDenpendency
|
|
{
|
|
private IList<Enums.StorageType> validStorageTypeList;
|
|
private DingDingBusiness dingDingBusiness;
|
|
|
|
public JDStockNumWarningBusiness(NLogManager nLogManager, IFreeSql fsql, IIdGenerator idGenerator, TaskSchedulerManager taskSchedulerManager, IEnumerable<PlatformSDKBusiness> platformSDKBusinessList, VenderBusiness venderBusiness, DingDingBusiness dingDingBusiness) : base(nLogManager, fsql, idGenerator, taskSchedulerManager, platformSDKBusinessList, venderBusiness)
|
|
{
|
|
validStorageTypeList = new List<Enums.StorageType>() {
|
|
Enums.StorageType.云仓,
|
|
Enums.StorageType.京仓,
|
|
Enums.StorageType.本地自发
|
|
};
|
|
this.dingDingBusiness = dingDingBusiness;
|
|
}
|
|
|
|
//public JDStockNumWarningBusiness( NLogManager nLogManager, IFreeSql fsql, IIdGenerator idGenerator, TaskSchedulerManager taskSchedulerManager, VenderBusiness venderBusiness, DingDingBusiness dingDingBusiness, IEnumerable<PlatformSDKBusiness> platformSDKBusinessList) : base(restApiService, options, nLogManager, fsql, idGenerator, taskSchedulerManager, venderBusiness, platformSDKBusinessList)
|
|
//{
|
|
// validStorageTypeList = new List<Enums.StorageType>() {
|
|
// Enums.StorageType.云仓,
|
|
// Enums.StorageType.京仓,
|
|
// Enums.StorageType.本地自发
|
|
// };
|
|
// this.dingDingBusiness = dingDingBusiness;
|
|
//}
|
|
|
|
public void StartCheckStockNum()
|
|
{
|
|
var storeHouseList = fsql.Select<Storehouse>().Where(s => s.Platform == Enums.Platform.京东).ToList();
|
|
var shopList = venderBusiness.GetShopList(platform: Enums.Platform.京东, filterTurnoverDays: true);
|
|
foreach (var shop in shopList)
|
|
{
|
|
Task.Factory.StartNew(() => CheckStockNum(shop, storeHouseList), CancellationToken.None, TaskCreationOptions.LongRunning, taskSchedulerManager.StockNumWarningTaskScheduler);
|
|
}
|
|
}
|
|
|
|
private void CheckStockNum(ShopResponse shop, IList<Storehouse> storeHouseList)
|
|
{
|
|
var loggerName = $"库存预警-{shop.ShopName}";
|
|
try
|
|
{
|
|
long shopId = long.Parse(shop.ShopId);
|
|
var yesterDayDate = DateTime.Now.Date.AddDays(-1);
|
|
var ysterDayTime = DateTime.Now.Date.AddSeconds(-1);
|
|
|
|
//查询符合条件的sku (昨日 && 非赠品 && 销量>取消 && 非代发和刷单)
|
|
var yesterDaySkuIds = fsql.Select<SkuDailySalesDetail, OrderSku, Order>()
|
|
.InnerJoin((s, osku, o) => s.Sku == osku.SkuId)
|
|
.InnerJoin((s, osku, o) => osku.OrderId == o.Id)
|
|
.Where((s, osku, o) => s.ShopId == shopId &&
|
|
s.Date == yesterDayDate &&
|
|
s.IsGift == false &&
|
|
s.ItemTotal > s.CancelItemTotal &&
|
|
o.StartTime >= yesterDayDate &&
|
|
o.StartTime <= ysterDayTime &&
|
|
validStorageTypeList.Contains(o.StorageType.Value))
|
|
.Distinct()
|
|
.ToList((s, osku, o) => s.Sku);
|
|
|
|
//查询近15天的销量(5个周期)
|
|
var allCycleStartDate = yesterDayDate.AddDays(-14);
|
|
var skuSaleDailyList = fsql.Select<SkuDailySalesDetail>()
|
|
.Where(s => s.Date >= allCycleStartDate && s.Date <= yesterDayDate)
|
|
.Where(s => yesterDaySkuIds.Contains(s.Sku))
|
|
.ToList();
|
|
|
|
var firstCycleStartDate = yesterDayDate.AddDays(-8);
|
|
var firstCycleEndDate = firstCycleStartDate.AddDays(2);
|
|
|
|
var secondCycleStartDate = firstCycleEndDate.AddDays(1);
|
|
var secondCycleEndDate = secondCycleStartDate.AddDays(2);
|
|
|
|
var thirdCycleStartDate = secondCycleEndDate.AddDays(1);
|
|
var thirdCycleEndDate = thirdCycleStartDate.AddDays(2);
|
|
|
|
var _7dAvgStartDate = DateTime.Now.Date.AddDays(-7);
|
|
|
|
bool isSendDingTalk = false;
|
|
var dingdingContentBuilder = new StringBuilder();
|
|
dingdingContentBuilder.Append(shop.ShopName);
|
|
dingdingContentBuilder.AppendLine();
|
|
|
|
foreach (var sku in yesterDaySkuIds)
|
|
{
|
|
//第一周期销量
|
|
var firstCycleSaleList = skuSaleDailyList.Where(s => s.Sku == sku && s.Date >= firstCycleStartDate && s.Date <= firstCycleEndDate);
|
|
var firstCycleItemTotal = firstCycleSaleList.Count() > 0 ? firstCycleSaleList.Sum(s => s.ItemTotal - s.CancelItemTotal) : 0;
|
|
if (firstCycleItemTotal < 0)
|
|
firstCycleItemTotal = 0;
|
|
|
|
//第二周期销量
|
|
var secondCycleSaleList = skuSaleDailyList.Where(s => s.Sku == sku && s.Date >= secondCycleStartDate && s.Date <= secondCycleEndDate);
|
|
var secondCycleItemTotal = secondCycleSaleList.Count() > 0 ? secondCycleSaleList.Sum(s => s.ItemTotal - s.CancelItemTotal) : 0;
|
|
if (secondCycleItemTotal < 0)
|
|
secondCycleItemTotal = 0;
|
|
|
|
//第三周期销量
|
|
var thirdCycleSaleList = skuSaleDailyList.Where(s => s.Sku == sku && s.Date >= thirdCycleStartDate && s.Date <= thirdCycleEndDate);
|
|
var thirdCycleItemTotal = thirdCycleSaleList.Count() > 0 ? thirdCycleSaleList.Sum(s => s.ItemTotal - s.CancelItemTotal) : 0;
|
|
if (thirdCycleItemTotal < 0)
|
|
thirdCycleItemTotal = 0;
|
|
|
|
//计算周期增幅
|
|
var _2Ratio = firstCycleItemTotal == 0 ? 0 : 1.0 * secondCycleItemTotal / firstCycleItemTotal - 1;
|
|
var _3Ratio = secondCycleItemTotal == 0 ? 0 : 1.0 * thirdCycleItemTotal / secondCycleItemTotal - 1;
|
|
|
|
|
|
var _7dSaleList = skuSaleDailyList.Where(s => s.Sku == sku && s.Date >= _7dAvgStartDate && s.Date <= yesterDayDate);
|
|
var _15dSaleList = skuSaleDailyList.Where(s => s.Sku == sku);
|
|
var _7dItemTotal = _7dSaleList.Sum(s => s.ItemTotal - s.CancelItemTotal);
|
|
if (_7dItemTotal < 0)
|
|
_7dItemTotal = 0;
|
|
var _15dItemTotal = _15dSaleList.Sum(s => s.ItemTotal - s.CancelItemTotal); //近15天销量
|
|
if (_15dItemTotal < 0)
|
|
_15dItemTotal = 0;
|
|
var _7dAvgItemTotal = 1.0 * _7dItemTotal / 7; //近7天日均销量
|
|
|
|
|
|
Enums.SkuStockNumCycleType skuStockNumCycleType = Enums.SkuStockNumCycleType.暂无周期;
|
|
if (_2Ratio >= 0.2 && _3Ratio >= 0.2)
|
|
skuStockNumCycleType = Enums.SkuStockNumCycleType.增长期;
|
|
else if (_2Ratio >= -0.2 && _2Ratio <= 0.2 && _3Ratio >= -0.2 && _3Ratio <= 0.2)
|
|
skuStockNumCycleType = Enums.SkuStockNumCycleType.稳定期;
|
|
else if (_2Ratio < -0.2 && _3Ratio < -0.2)
|
|
skuStockNumCycleType = Enums.SkuStockNumCycleType.衰退期;
|
|
|
|
var logContentBuilder = new StringBuilder();
|
|
logContentBuilder.AppendLine($"SKU:{sku}");
|
|
logContentBuilder.AppendLine($"商品状态:{skuStockNumCycleType}");
|
|
logContentBuilder.AppendLine($"第一周销量:{firstCycleItemTotal}");
|
|
logContentBuilder.AppendLine($"第二周期销量:{secondCycleItemTotal}, 相比第一周幅度:{_2Ratio * 100}%");
|
|
logContentBuilder.AppendLine($"第三周期销量:{thirdCycleItemTotal}, 相比第二周幅度:{_3Ratio * 100}%");
|
|
logContentBuilder.AppendLine($"近7天销量:{_7dItemTotal}");
|
|
logContentBuilder.AppendLine($"近15天销量:{_15dItemTotal}");
|
|
logContentBuilder.AppendLine($"7天日均销量:{_7dAvgItemTotal}");
|
|
|
|
//dingdingContentBuilder.Append($"SKU:{sku}\n");
|
|
//dingdingContentBuilder.Append($"商品状态:{skuStockNumCycleType}\n");
|
|
//dingdingContentBuilder.Append($"近7天销量:{_7dItemTotal}\n");
|
|
//dingdingContentBuilder.Append($"7天日均销量:{_7dItemTotal}\n");
|
|
|
|
|
|
if (skuStockNumCycleType == Enums.SkuStockNumCycleType.暂无周期)
|
|
{
|
|
nLogManager.GetLogger(loggerName).Info(logContentBuilder);
|
|
continue;
|
|
}
|
|
|
|
Thread.Sleep(1000);
|
|
var response = GetPlatformSDKBusiness(Enums.Platform.京东).GetStockNumBySku(
|
|
new SearchProductSkuRequest()
|
|
{
|
|
AppKey = shop.AppKey,
|
|
AppSecret = shop.AppSecret,
|
|
AppToken = shop.AppToken,
|
|
Platform = shop.PlatformId,
|
|
Sku = sku
|
|
});
|
|
|
|
//var restApiResult = restApiService.SendRequest(GetPlatformRelayAPIHost(shop.PlatformId), "api/platformsdk/GetStockNumBySku", new SearchProductSkuRequest()
|
|
//{
|
|
// AppKey = shop.AppKey,
|
|
// AppSecret = shop.AppSecret,
|
|
// AppToken = shop.AppToken,
|
|
// Platform = shop.PlatformId,
|
|
// Sku = sku
|
|
//}, GetYunDingRequestHeader(), HttpMethod.Post);
|
|
//if (restApiResult.StatusCode != System.Net.HttpStatusCode.OK)
|
|
// throw new Exception($"{sku} {restApiResult.Content}");
|
|
//var response = JsonConvert.DeserializeObject<ApiResponse<JArray>>(restApiResult.Content);
|
|
if (response == null || response?.Count() == 0)
|
|
{
|
|
logContentBuilder.AppendLine("未查询到JD库存数据");
|
|
nLogManager.GetLogger(loggerName).Info(logContentBuilder);
|
|
continue;
|
|
}
|
|
|
|
var skuStockNumList = response.Select(j => new
|
|
{
|
|
StockNum = j.Value<int>("stockNum"),
|
|
Store = storeHouseList.FirstOrDefault(s => s.Id == j.Value<string>("storeId")),
|
|
StoreId = j.Value<string>("storeId"),
|
|
SkuId = sku
|
|
});
|
|
|
|
var skuStockNumGroups = skuStockNumList.GroupBy(s => s.Store?.Type ?? Enums.StockType.商家仓); //按仓库类型算库存
|
|
foreach (var skuStockNumGroup in skuStockNumGroups)
|
|
{
|
|
var totalStockNum = skuStockNumGroup.Sum(s => s.StockNum); //总库存
|
|
var lessDay = _7dAvgItemTotal == 0 ? 0 : totalStockNum / _7dAvgItemTotal; //剩余天数
|
|
bool isWarning = false; //是否触发提醒
|
|
int suggestStockNum = 0; //建议备货量
|
|
if (skuStockNumCycleType == Enums.SkuStockNumCycleType.增长期)
|
|
{
|
|
if (lessDay < 15)
|
|
{
|
|
isWarning = true;
|
|
if (_15dItemTotal <= 2)
|
|
suggestStockNum = 0;
|
|
else if (shop.SkuSafeTurnoverDays == 28)
|
|
suggestStockNum = (int)Math.Ceiling(_7dItemTotal * 4 * 1.5);
|
|
else if (shop.SkuSafeTurnoverDays == 21)
|
|
suggestStockNum = (int)Math.Ceiling(_7dItemTotal * 3 * 1.5);
|
|
else if (shop.SkuSafeTurnoverDays == 14)
|
|
suggestStockNum = (int)Math.Ceiling(_7dItemTotal * 2 * 1.5);
|
|
}
|
|
}
|
|
else if (skuStockNumCycleType == Enums.SkuStockNumCycleType.稳定期)
|
|
{
|
|
if (lessDay < 8)
|
|
{
|
|
isWarning = true;
|
|
if (_15dItemTotal <= 2)
|
|
suggestStockNum = 0;
|
|
else if (shop.SkuSafeTurnoverDays == 28)
|
|
suggestStockNum = _7dItemTotal * 4;
|
|
else if (shop.SkuSafeTurnoverDays == 21)
|
|
suggestStockNum = _7dItemTotal * 3;
|
|
else if (shop.SkuSafeTurnoverDays == 14)
|
|
suggestStockNum = _7dItemTotal * 2;
|
|
}
|
|
}
|
|
else if (skuStockNumCycleType == Enums.SkuStockNumCycleType.衰退期)
|
|
{
|
|
if (lessDay < 8)
|
|
{
|
|
isWarning = true;
|
|
suggestStockNum = 0;
|
|
}
|
|
}
|
|
|
|
if (isWarning)
|
|
{
|
|
isSendDingTalk = true;
|
|
|
|
dingdingContentBuilder.Append($"SKU:{sku}\n");
|
|
dingdingContentBuilder.Append($"商品状态:{skuStockNumCycleType}\n");
|
|
dingdingContentBuilder.Append($"近7天销量:{_7dItemTotal}\n");
|
|
dingdingContentBuilder.Append($"近15天销量:{_15dItemTotal}\n");
|
|
foreach (var stockNumInfo in skuStockNumGroup)
|
|
dingdingContentBuilder.Append($"{stockNumInfo.Store?.Name ?? stockNumInfo.StoreId}:{stockNumInfo.StockNum}件\n");
|
|
if (skuStockNumCycleType == Enums.SkuStockNumCycleType.增长期 || skuStockNumCycleType == Enums.SkuStockNumCycleType.稳定期)
|
|
{
|
|
if (suggestStockNum != 0)
|
|
dingdingContentBuilder.Append($"{skuStockNumGroup.Key}库存低于安全周转天数,建议备货{suggestStockNum}件");
|
|
else
|
|
dingdingContentBuilder.Append($"建议暂停备货,采购代发");
|
|
}
|
|
else if (skuStockNumCycleType == Enums.SkuStockNumCycleType.衰退期)
|
|
dingdingContentBuilder.Append($"{skuStockNumGroup.Key}库存商品进入衰退期,建议暂停备货,采购代发");
|
|
dingdingContentBuilder.AppendLine();
|
|
}
|
|
|
|
#region 拼接sku在当前类型仓库的日志
|
|
logContentBuilder.AppendLine($"仓库类型:{skuStockNumGroup.Key}");
|
|
logContentBuilder.AppendLine($"总库存:{totalStockNum}");
|
|
logContentBuilder.AppendLine($"剩余天数:{lessDay}");
|
|
logContentBuilder.AppendLine($"建议备货量:{suggestStockNum}");
|
|
logContentBuilder.AppendLine($"是否触发预警:{isWarning}");
|
|
#endregion
|
|
}
|
|
nLogManager.GetLogger(loggerName).Info(logContentBuilder);
|
|
}
|
|
|
|
if (isSendDingTalk)
|
|
{
|
|
dingDingBusiness.SendDingDingBotMessage(shop.DingDingKey, shop.DingDingWebHook, dingdingContentBuilder.ToString());
|
|
//var secret = shop.DingDingKey;
|
|
//var timestamp = DateTime.Now.DateTimeToStamp();
|
|
//var stringToSign = timestamp + "\n" + secret;
|
|
//var sign = EncryptWithSHA256(stringToSign, secret);
|
|
//var url = $"{shop.DingDingWebHook}×tamp={timestamp}&sign={sign}";
|
|
//var result = restApiService.SendRequest(url, string.Empty, new
|
|
//{
|
|
// msgtype = "text",
|
|
// text = new
|
|
// {
|
|
// content = dingdingContentBuilder.ToString()
|
|
// }
|
|
//}, null, HttpMethod.Post);
|
|
//if (result.StatusCode != System.Net.HttpStatusCode.OK)
|
|
// throw new Exception($"{shop.ShopName} 发送钉钉库存预警失败 {result.Content}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
nLogManager.GetLogger(loggerName).Error(ex, "查询sku库存失败");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|