using AutoMapper;
|
using MailKit.Search;
|
using Newtonsoft.Json;
|
using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup;
|
using SqlSugar;
|
using System;
|
using System.Collections;
|
using System.Collections.Generic;
|
using System.Linq;
|
using System.Net.Http;
|
using System.Text;
|
using System.Threading.Tasks;
|
using WIDESEA_Common.StockEnum;
|
using WIDESEA_Core;
|
using WIDESEA_Core.BaseServices;
|
using WIDESEA_Core.Enums;
|
using WIDESEA_Core.Helper;
|
using WIDESEA_DTO.Stock;
|
using WIDESEA_IBasicRepository;
|
using WIDESEA_IOutboundRepository;
|
using WIDESEA_IRecordRepository;
|
using WIDESEA_IRecordService;
|
using WIDESEA_IStockRepository;
|
using WIDESEA_IStockService;
|
using WIDESEA_Model.Models;
|
using WIDESEA_StockRepository;
|
|
namespace WIDESEA_StockService
|
{
|
public partial class StockInfoService : ServiceBase<Dt_StockInfo, IStockInfoRepository>, IStockInfoService
|
{
|
private readonly IMapper _mapper;
|
private readonly IBasicRepository _basicRepository;
|
private readonly IOutboundRepository _outboundRepository;
|
|
public IStockInfoRepository Repository => BaseDal;
|
|
public StockInfoService(IStockInfoRepository BaseDal, IMapper mapper, IBasicRepository basicRepository, IOutboundRepository outboundRepository) : base(BaseDal)
|
{
|
_mapper = mapper;
|
_basicRepository = basicRepository;
|
_outboundRepository = outboundRepository;
|
}
|
|
/// <summary>
|
/// 查询订单立库库存视图
|
/// </summary>
|
/// <param name="orderId"></param>
|
/// <param name="materielCode"></param>
|
/// <returns></returns>
|
public List<StockSelectViewDTO> GetStockSelectViews(int orderId, string materielCode)
|
{
|
try
|
{
|
Dt_OutboundOrder outboundOrder = _outboundRepository.OutboundOrderRepository.QueryFirst(x => x.Id == orderId);
|
if (outboundOrder == null)
|
{
|
throw new Exception($"未找到出库单信息");
|
}
|
|
List<string> locationCodes = _basicRepository.LocationInfoRepository.PPGetCanOutLocationCodes(outboundOrder.WarehouseId);
|
|
return BaseDal.QueryTabs<Dt_StockInfo, Dt_StockInfoDetail, StockSelectViewDTO>((a, b) => a.Id == b.StockId, (a, b) => new StockSelectViewDTO
|
{
|
LocationCode = a.LocationCode,
|
MaterielCode = b.MaterielCode,
|
MaterielName = b.MaterielName,
|
PalletCode = a.PalletCode,
|
UseableQuantity = b.StockQuantity - b.OutboundQuantity
|
}, a => locationCodes.Contains(a.LocationCode), b => b.StockQuantity > b.OutboundQuantity && b.MaterielCode == materielCode, x => true).GroupBy(x => x.PalletCode).Select(x => new StockSelectViewDTO
|
{
|
LocationCode = x.FirstOrDefault()?.LocationCode ?? "",
|
MaterielCode = x.FirstOrDefault()?.MaterielCode ?? "",
|
MaterielName = x.FirstOrDefault()?.MaterielName ?? "",
|
PalletCode = x.Key,
|
UseableQuantity = x.Sum(x => x.UseableQuantity)
|
}).ToList();
|
}
|
catch (Exception ex)
|
{
|
return null;
|
}
|
|
}
|
/// <summary>
|
/// 查询订单平库库存视图
|
/// </summary>
|
/// <param name="orderId"></param>
|
/// <param name="materielCode"></param>
|
/// <returns></returns>
|
public List<StockSelectViewDTO> GetPKStockSelectViews(int orderId, string materielCode)
|
{
|
try
|
{
|
Dt_OutboundOrder outboundOrder = _outboundRepository.OutboundOrderRepository.QueryFirst(x => x.Id == orderId);
|
if (outboundOrder == null)
|
{
|
throw new Exception($"未找到出库单信息");
|
}
|
return BaseDal.QueryTabs<Dt_StockInfo, Dt_StockInfoDetail, StockSelectViewDTO>((a, b) => a.Id == b.StockId && a.WarehouseId == outboundOrder.WarehouseId, (a, b) => new StockSelectViewDTO
|
{
|
LocationCode = a.LocationCode,
|
MaterielCode = b.MaterielCode,
|
MaterielName = b.MaterielName,
|
PalletCode = a.PalletCode,
|
UseableQuantity = b.StockQuantity - b.OutboundQuantity
|
}, a => a.LocationCode == "平库位", b => b.StockQuantity > b.OutboundQuantity && b.MaterielCode == materielCode, x => true).GroupBy(x => x.PalletCode).Select(x => new StockSelectViewDTO
|
{
|
LocationCode = x.FirstOrDefault()?.LocationCode ?? "",
|
MaterielCode = x.FirstOrDefault()?.MaterielCode ?? "",
|
MaterielName = x.FirstOrDefault()?.MaterielName ?? "",
|
PalletCode = x.Key,
|
UseableQuantity = x.Sum(x => x.UseableQuantity)
|
}).ToList();
|
}
|
catch (Exception ex)
|
{
|
return null;
|
}
|
}
|
/// <summary>
|
/// 查询订单PP立库库存视图
|
/// </summary>
|
/// <param name="orderId"></param>
|
/// <param name="materielCode"></param>
|
/// <returns></returns>
|
public List<PPStockSelectViewDTO> PPGetStockSelectViews(int orderId, string materielCode)
|
{
|
try
|
{
|
Dt_MesPPCutOutboundOrder mesPPCutOutboundOrder = _outboundRepository.MesPPCutOutboundOrderRepository.QueryFirst(x => x.Id == orderId);
|
if (mesPPCutOutboundOrder == null)
|
{
|
throw new Exception($"未找到出库单信息");
|
}
|
List<string> locationCodes = _basicRepository.LocationInfoRepository.PPGetCanOutLocationCodes(mesPPCutOutboundOrder.WarehouseId);
|
|
return BaseDal.QueryTabs<Dt_StockInfo, Dt_StockInfoDetail, PPStockSelectViewDTO>((a, b) => a.Id == b.StockId, (a, b) => new PPStockSelectViewDTO
|
{
|
LocationCode = a.LocationCode,
|
MaterielCode = b.MaterielCode,
|
MaterielName = b.MaterielName,
|
PalletCode = a.PalletCode,
|
Unit = b.Unit,
|
CutedWidth = b.CutedWidth,
|
UseableQuantity = b.StockQuantity - b.OutboundQuantity
|
}, a => locationCodes.Contains(a.LocationCode), b => b.StockQuantity > b.OutboundQuantity && b.MaterielCode == materielCode, x => true).GroupBy(x => x.PalletCode).Select(x => new PPStockSelectViewDTO
|
{
|
LocationCode = x.FirstOrDefault()?.LocationCode ?? "",
|
MaterielCode = x.FirstOrDefault()?.MaterielCode ?? "",
|
MaterielName = x.FirstOrDefault()?.MaterielName ?? "",
|
Unit = x.FirstOrDefault()?.Unit ?? "",
|
CutedWidth = x.Sum(x => x.CutedWidth),
|
PalletCode = x.Key,
|
UseableQuantity = x.Sum(x => x.UseableQuantity)
|
}).ToList();
|
}
|
catch (Exception ex)
|
{
|
|
return null;
|
}
|
}
|
|
/// <summary>
|
/// 查询订单PP平库库存视图
|
/// </summary>
|
/// <param name="orderId"></param>
|
/// <param name="materielCode"></param>
|
/// <returns></returns>
|
public List<PPStockSelectViewDTO> PPGetPKStockSelectViews(int orderId, string materielCode)
|
{
|
try
|
{
|
Dt_MesPPCutOutboundOrder outboundOrder = _outboundRepository.MesPPCutOutboundOrderRepository.QueryFirst(x => x.Id == orderId);
|
if (outboundOrder == null)
|
{
|
throw new Exception($"未找到出库单信息");
|
}
|
return BaseDal.QueryTabs<Dt_StockInfo, Dt_StockInfoDetail, PPStockSelectViewDTO>((a, b) => a.Id == b.StockId && a.WarehouseId == outboundOrder.WarehouseId, (a, b) => new PPStockSelectViewDTO
|
{
|
LocationCode = a.LocationCode,
|
MaterielCode = b.MaterielCode,
|
MaterielName = b.MaterielName,
|
PalletCode = a.PalletCode,
|
Unit = b.Unit,
|
CutedWidth = b.CutedWidth,
|
UseableQuantity = b.StockQuantity - b.OutboundQuantity
|
}, a => a.LocationCode.Contains("AGV_PP"), b => b.StockQuantity > b.OutboundQuantity && b.MaterielCode == materielCode, x => true).GroupBy(x => x.PalletCode).Select(x => new PPStockSelectViewDTO
|
{
|
LocationCode = x.FirstOrDefault()?.LocationCode ?? "",
|
MaterielCode = x.FirstOrDefault()?.MaterielCode ?? "",
|
MaterielName = x.FirstOrDefault()?.MaterielName ?? "",
|
Unit = x.FirstOrDefault()?.Unit ?? "",
|
CutedWidth = x.Sum(x => x.CutedWidth),
|
PalletCode = x.Key,
|
UseableQuantity = x.Sum(x => x.UseableQuantity)
|
}).ToList();
|
}
|
catch (Exception ex)
|
{
|
return null;
|
}
|
}
|
|
|
|
public WebResponseContent StockQueryData(SaveModel saveModel)
|
{
|
try
|
{
|
var barcode = saveModel.MainData["barcode"].ToString();
|
var warehouseId = saveModel.MainData["warehouseId"].ObjToInt();
|
Dt_StockInfo stockInfo = BaseDal.Db.Queryable<Dt_StockInfo>().Where(x => x.PalletCode == barcode && x.WarehouseId == warehouseId).Includes(x => x.Details).First();
|
if (stockInfo == null) throw new Exception("未找到托盘信息");
|
return WebResponseContent.Instance.OK(data: stockInfo);
|
}
|
catch (Exception ex)
|
{
|
return WebResponseContent.Instance.Error(ex.Message);
|
}
|
}
|
/// <summary>
|
///
|
/// </summary>
|
/// <param name="stockInfos"></param>
|
/// <param name="materielCode"></param>
|
/// <param name="needQuantity"></param>
|
/// <param name="residueQuantity"></param>
|
/// <returns></returns>
|
public List<Dt_StockInfo> GetOutboundStocks(List<Dt_StockInfo> stockInfos, string materielCode, float needQuantity, out float residueQuantity)
|
{
|
List<Dt_StockInfo> outStocks = new List<Dt_StockInfo>();
|
float stockTotalQuantity = stockInfos.Select(x => x.Details.Sum(v => v.StockQuantity - v.OutboundQuantity)).Sum(x => x);
|
//stockInfos = stockInfos.OrderBy(x => x.Id).ToList();
|
if (stockTotalQuantity >= needQuantity)//库存够
|
{
|
int index = 0;
|
while (needQuantity > 0)
|
{
|
Dt_StockInfo stockInfo = stockInfos[index];
|
// 计算可用库存时转换为decimal
|
decimal useableStockQuantity = stockInfo.Details
|
.Where(x => x.MaterielCode == materielCode)
|
.Sum(x => (decimal)x.StockQuantity - (decimal)x.OutboundQuantity);
|
|
// 将needQuantity转换为decimal进行比较
|
if (useableStockQuantity < (decimal)needQuantity && useableStockQuantity > 0)
|
{
|
stockInfo.Details.ForEach(x =>
|
x.OutboundQuantity = x.StockQuantity);
|
|
// 使用decimal进行计算后再转回float
|
needQuantity = (float)((decimal)needQuantity - useableStockQuantity);
|
}
|
else
|
{
|
stockInfo.Details.ForEach(x =>
|
{
|
if (x.StockQuantity > x.OutboundQuantity && x.MaterielCode == materielCode)
|
{
|
// 将相关值转换为decimal进行精确计算
|
decimal currentStock = (decimal)x.StockQuantity;
|
decimal currentOutbound = (decimal)x.OutboundQuantity;
|
decimal currentNeed = (decimal)needQuantity;
|
decimal available = currentStock - currentOutbound;
|
|
if (available >= currentNeed)
|
{
|
x.OutboundQuantity = (float)(currentOutbound + currentNeed);
|
needQuantity = 0;
|
}
|
else
|
{
|
needQuantity = (float)(currentNeed - available);
|
x.OutboundQuantity = x.StockQuantity;
|
}
|
}
|
});
|
}
|
outStocks.Add(stockInfo);
|
index++;
|
}
|
|
}
|
else
|
{
|
throw new Exception("库存不足");
|
}
|
residueQuantity = needQuantity;
|
return outStocks;
|
}
|
|
public List<Dt_StockInfo> GetUseableStocks(string materielCode, string batchNo, int warehoseId)
|
{
|
if ((materielCode.Equals("405000585")|| materielCode.Equals("405000831") || materielCode.Equals("405005565") || materielCode.Equals("405405097") || materielCode.Equals("405005461")) && warehoseId == 5)
|
{
|
warehoseId = 3;
|
}
|
List<string> locationCodes = _basicRepository.LocationInfoRepository.GetCanOutLocationCodes(warehoseId);
|
|
return BaseDal.GetStockInfos(materielCode, batchNo, locationCodes);
|
}
|
|
public List<Dt_StockInfo> GetUseableStocks(string materielCode, string batchNo, string palletcode, int warehoseId)
|
{
|
Dt_StockInfo stockInfo = BaseDal.Db.Queryable<Dt_StockInfo>().Where(x => x.PalletCode == palletcode && x.WarehouseId == warehoseId).Includes(x => x.Details).First();
|
|
List<string> locationCodes = _basicRepository.LocationInfoRepository.GetCanOutLocationCodes(stockInfo.LocationCode);
|
|
return BaseDal.GetStockInfos(materielCode, batchNo, locationCodes);
|
}
|
|
public WebResponseContent UpdateExpirationlabel()
|
{
|
try
|
{
|
var today = DateTime.Today;
|
int batchSize = 1000;
|
int totalUpdated = 0;
|
int skipCount = 0;
|
|
// 只查询需要的字段,减少数据传输和内存占用
|
var query = BaseDal.Db.Queryable<Dt_StockInfoDetail>()
|
.InnerJoin<Dt_StockInfo>((detail, master) => detail.StockId == master.Id)
|
.Select((detail, master) => new
|
{
|
MasterId = master.Id,
|
master.WarehouseId,
|
detail.EffectiveDate,
|
CurrentExpirationlabel = master.Expirationlabel
|
});
|
|
while (true)
|
{
|
var batchData = query.Skip(skipCount).Take(batchSize).ToList();
|
if (!batchData.Any()) break;
|
var groupedData = batchData.GroupBy(item => item.MasterId)
|
.Select(g => new
|
{
|
MasterId = g.Key,
|
WarehouseId = g.First().WarehouseId,
|
// 取最早的有效日期
|
EarliestEffectiveDate = g.Min(item =>
|
{
|
DateTime.TryParse(item.EffectiveDate, out DateTime date);
|
return date;
|
}),
|
CurrentExpirationlabel = g.First().CurrentExpirationlabel
|
})
|
.ToList();
|
|
var updateDic = new Dictionary<long, int>();
|
|
foreach (var group in groupedData)
|
{
|
DateTime effectiveDate = group.EarliestEffectiveDate;
|
if (effectiveDate == default(DateTime)) // 处理解析失败的情况
|
{
|
Console.WriteLine($"主表ID {group.MasterId} 下无有效日期,跳过");
|
continue;
|
}
|
|
int newLabel;
|
if (effectiveDate < today)
|
{
|
newLabel = ExpirationlabelEnum.过期.ObjToInt();
|
}
|
else if (group.WarehouseId == 3)
|
{
|
int daysDiff = (effectiveDate - today).Days;
|
newLabel = daysDiff < 60
|
? ExpirationlabelEnum.临期预警.ObjToInt()
|
: ExpirationlabelEnum.未临期.ObjToInt();
|
}
|
else
|
{
|
int daysDiff = (effectiveDate - today).Days;
|
newLabel = daysDiff < 30
|
? ExpirationlabelEnum.临期预警.ObjToInt()
|
: ExpirationlabelEnum.未临期.ObjToInt();
|
}
|
|
if (newLabel != group.CurrentExpirationlabel && !updateDic.ContainsKey(group.MasterId))
|
{
|
updateDic[group.MasterId] = newLabel;
|
}
|
}
|
|
if (updateDic.Any())
|
{
|
var updateBuilder = BaseDal.Db.Updateable<Dt_StockInfo>();
|
var idsToUpdate = updateDic.Keys.ToList();
|
int updateValue = updateDic.First().Value;
|
updateBuilder.SetColumns(m => m.Expirationlabel == updateValue)
|
.Where(m => idsToUpdate.Contains(m.Id));
|
|
int batchUpdated = updateBuilder.ExecuteCommand();
|
totalUpdated += batchUpdated;
|
|
Console.WriteLine($"批次更新:{batchUpdated} 条,累计更新:{totalUpdated} 条,更新条件:{JsonConvert.SerializeObject(idsToUpdate)}");
|
}
|
|
skipCount += batchSize;
|
}
|
|
return WebResponseContent.Instance.OK($"更新成功,共更新 {totalUpdated} 条记录");
|
}
|
catch (Exception ex)
|
{
|
return WebResponseContent.Instance.Error("更新失败,请联系管理员");
|
}
|
}
|
/// <summary>
|
/// 钉钉机器人消息推送测试
|
/// </summary>
|
/// <returns></returns>
|
public async Task T0DingTalkText(string webhookUrl, string secret)
|
{
|
try
|
{
|
if (webhookUrl == null || secret == null)
|
{
|
webhookUrl = "https://oapi.dingtalk.com/robot/send?access_token=c3e05f2c6bcd595383ee02e713446174b9201bad91db216590620fe0acd4e75e";
|
secret = "SEC617f06140fc7cbd8b91d3e203f270826320637af41e7423d756e62df40f62282";
|
}
|
|
// 1. 定义仓库ID与仓库名称的映射关系(增加成品仓)
|
var warehouseIdToName = new Dictionary<int, string>
|
{
|
{ 2, "油墨仓" },
|
{ 3, "板材仓" },
|
{ 4, "PP仓" },
|
{ 6, "测试架仓" },
|
{ 7, "成品仓" }, // 新增成品仓
|
{ 11, "干膜仓" },
|
{ 12, "阻焊仓" }
|
};
|
|
// 2. 时间相关配置,用于筛选超过90天的数据
|
var currentTime = DateTime.Now;
|
var ninetyDaysAgo = currentTime.AddDays(-90);
|
|
// 3. 处理原材料仓(仓库ID ≠ 7)
|
// 3.1 查询标识码为3(过期)的主表数据(排除仓库ID=5和7)
|
var expirationLabel3Stocks = BaseDal.Db.Queryable<Dt_StockInfo>()
|
.Where(s => s.Expirationlabel == 3 && s.WarehouseId != 5 && s.WarehouseId != 7 && s.LocationCode != "" && s.LocationCode != null)
|
.ToList();
|
|
// 3.2 查询超过90天未修改(未使用)的主表数据(排除仓库ID=5和7)
|
var over90DaysStocks = BaseDal.Db.Queryable<Dt_StockInfo>()
|
.Where(s => s.ModifyDate <= ninetyDaysAgo && s.Expirationlabel != 3 && s.WarehouseId != 5 && s.WarehouseId != 7 && s.LocationCode != "" && s.LocationCode != null)
|
.ToList();
|
|
// 4. 处理成品仓(仓库ID = 7)
|
var proOver90DaysStocks = new List<Dt_ProStockInfo>();
|
if (warehouseIdToName.ContainsKey(7))
|
{
|
proOver90DaysStocks = BaseDal.Db.Queryable<Dt_ProStockInfo>()
|
.Where(s => s.WarehouseId == 7 && s.ModifyDate <= ninetyDaysAgo && s.LocationCode != "" && s.LocationCode != null)
|
.ToList();
|
}
|
|
// 无符合条件数据时直接返回
|
if (!expirationLabel3Stocks.Any() && !over90DaysStocks.Any() && !proOver90DaysStocks.Any())
|
{
|
return;
|
}
|
|
// 5. 提取原材料主表数据的ID
|
var expirationLabel3StockIds = expirationLabel3Stocks.Select(s => s.Id).ToList();
|
var over90DaysStockIds = over90DaysStocks.Select(s => s.Id).ToList();
|
|
// 6. 关联查询原材料明细表数据
|
var expirationLabel3Details = expirationLabel3StockIds.Any()
|
? BaseDal.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(d => expirationLabel3StockIds.Contains(d.StockId))
|
.ToList()
|
: new List<Dt_StockInfoDetail>();
|
|
var over90DaysDetails = over90DaysStockIds.Any()
|
? BaseDal.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(d => over90DaysStockIds.Contains(d.StockId))
|
.ToList()
|
: new List<Dt_StockInfoDetail>();
|
|
// 7. 处理成品仓明细数据
|
var proOver90DaysDetails = new List<Dt_ProStockInfoDetail>();
|
if (proOver90DaysStocks.Any())
|
{
|
var proStockIds = proOver90DaysStocks.Select(s => s.Id).ToList();
|
proOver90DaysDetails = BaseDal.Db.Queryable<Dt_ProStockInfoDetail>()
|
.Where(d => proStockIds.Contains(d.ProStockId))
|
.ToList();
|
}
|
|
// 8. 建立主表ID到仓库ID的映射(包括成品仓)
|
var stockIdToWarehouseId = new Dictionary<int, int>();
|
|
// 原材料仓映射
|
foreach (var stock in expirationLabel3Stocks.Concat(over90DaysStocks))
|
{
|
if (!stockIdToWarehouseId.ContainsKey(stock.Id))
|
{
|
stockIdToWarehouseId[stock.Id] = stock.WarehouseId;
|
}
|
}
|
|
// 成品仓映射
|
foreach (var proStock in proOver90DaysStocks)
|
{
|
if (!stockIdToWarehouseId.ContainsKey(proStock.Id))
|
{
|
stockIdToWarehouseId[proStock.Id] = proStock.WarehouseId;
|
}
|
}
|
|
// 9. 按仓库分组处理物料数据
|
// 9.1 原材料过期物料
|
var expirationLabel3Groups = expirationLabel3Details
|
.GroupBy(d => stockIdToWarehouseId[d.StockId])
|
.ToDictionary(g => g.Key, g => g.Select(d => new
|
{
|
MaterielCode = d.MaterielCode,
|
BatchNo = d.BatchNo,
|
IsProStock = false // 标记为非成品仓
|
}).Distinct().ToList());
|
|
// 9.2 原材料超过90天未使用物料
|
var over90DaysGroups = over90DaysDetails
|
.GroupBy(d => stockIdToWarehouseId[d.StockId])
|
.ToDictionary(g => g.Key, g => g.Select(d => new
|
{
|
MaterielCode = d.MaterielCode,
|
BatchNo = d.BatchNo,
|
IsProStock = false // 标记为非成品仓
|
}).Distinct().ToList());
|
|
// 9.3 成品仓超过90天未使用物料
|
if (proOver90DaysDetails.Any())
|
{
|
var proWarehouseId = 7;
|
var proGroup = proOver90DaysDetails
|
.GroupBy(d => stockIdToWarehouseId[d.ProStockId])
|
.Select(g => new
|
{
|
WarehouseId = g.Key,
|
Materials = g.Select(d => new
|
{
|
MaterielCode = d.ProductCode, // 成品仓使用ProductCode字段
|
BatchNo = d.LotNumber, // 成品仓使用LotNumber字段
|
IsProStock = true // 标记为成品仓
|
}).Distinct().ToList()
|
})
|
.FirstOrDefault();
|
|
if (proGroup != null)
|
{
|
// 添加到over90DaysGroups中
|
if (over90DaysGroups.ContainsKey(proWarehouseId))
|
{
|
over90DaysGroups[proWarehouseId].AddRange(proGroup.Materials);
|
}
|
else
|
{
|
over90DaysGroups[proWarehouseId] = proGroup.Materials;
|
}
|
}
|
}
|
|
// 10. 获取所有涉及的仓库ID
|
var allWarehouseIds = expirationLabel3Groups.Keys
|
.Union(over90DaysGroups.Keys)
|
.ToList();
|
|
// 11. 发送钉钉消息
|
using (HttpClient httpClient = new HttpClient())
|
{
|
foreach (var warehouseId in allWarehouseIds)
|
{
|
var warehouseName = warehouseIdToName.TryGetValue(warehouseId, out var name)
|
? name
|
: $"仓库{warehouseId}";
|
|
// 11.1 构建markdown格式消息
|
var markdownContent = new StringBuilder();
|
markdownContent.AppendLine($"## {warehouseName}物料提醒通知\n");
|
|
// 统计信息
|
var expiredCount = expirationLabel3Groups.TryGetValue(warehouseId, out var expList) ?
|
expList.Count : 0;
|
|
var over90Count = over90DaysGroups.TryGetValue(warehouseId, out var over90List) ?
|
over90List.Count : 0;
|
|
// 如果是成品仓,需要过滤出成品仓的数据
|
if (warehouseId == 7)
|
{
|
over90Count = over90List?.Count(m => m.IsProStock) ?? 0;
|
}
|
|
markdownContent.AppendLine($"**统计概览:**");
|
if (warehouseId == 7)
|
{
|
markdownContent.AppendLine($"- 超过90天未使用成品:{over90Count}条");
|
}
|
else
|
{
|
markdownContent.AppendLine($"- 过期物料:{expiredCount}条");
|
markdownContent.AppendLine($"- 超过90天未使用物料:{over90Count}条");
|
}
|
markdownContent.AppendLine();
|
|
// 11.2 添加过期物料表格(如果有数据且不是成品仓)
|
if (expiredCount > 0 && warehouseId != 7)
|
{
|
markdownContent.AppendLine("### 一、过期物料");
|
markdownContent.AppendLine("| 序号 | 物料编码 | 批次号 |");
|
markdownContent.AppendLine("| :--- | :--- | :--- |");
|
|
int index = 1;
|
var expiredToShow = expList.Take(200);
|
foreach (var material in expiredToShow)
|
{
|
markdownContent.AppendLine($"| {index} | {material.MaterielCode} | {material.BatchNo} |");
|
index++;
|
}
|
|
if (expiredCount > 200)
|
{
|
markdownContent.AppendLine($"| ... | 共{expiredCount}条,仅显示前200条 | ... |");
|
}
|
markdownContent.AppendLine();
|
}
|
|
// 11.3 添加超过90天未使用物料表格(如果有数据)
|
if (over90Count > 0)
|
{
|
if (warehouseId == 7)
|
{
|
markdownContent.AppendLine("### 超过90天未使用成品");
|
}
|
else
|
{
|
markdownContent.AppendLine("### 二、超过90天未使用物料");
|
}
|
|
markdownContent.AppendLine("| 序号 | 物料编码 | 批次号 |");
|
markdownContent.AppendLine("| :--- | :--- | :--- |");
|
|
int index = 1;
|
var over90ToShow = warehouseId == 7 ?
|
over90List?.Where(m => m.IsProStock).Take(500) :
|
over90List?.Take(500);
|
|
if (over90ToShow != null)
|
{
|
foreach (var material in over90ToShow)
|
{
|
markdownContent.AppendLine($"| {index} | {material.MaterielCode} | {material.BatchNo} |");
|
index++;
|
}
|
|
if (over90Count > (warehouseId == 7 ? 500 : 200))
|
{
|
markdownContent.AppendLine($"| ... | 共{over90Count}条,仅显示前{(warehouseId == 7 ? 500 : 200)}条 | ... |");
|
}
|
}
|
markdownContent.AppendLine();
|
}
|
|
// 11.4 添加时间戳和提示信息
|
markdownContent.AppendLine($"**报告时间:** {currentTime:yyyy-MM-dd HH:mm:ss}");
|
if (warehouseId == 7)
|
{
|
markdownContent.AppendLine("**备注:** 请成品仓管理人员及时处理超过90天未使用的成品。");
|
}
|
else
|
{
|
markdownContent.AppendLine("**备注:** 请相关仓库管理人员及时处理以上物料。");
|
}
|
|
var messageContent = markdownContent.ToString();
|
|
// 11.5 生成钉钉消息所需的时间戳和签名
|
var timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
var sign = GenerateSign(timestamp, secret);
|
|
// 11.6 构建钉钉请求URL
|
var uri = new Uri(webhookUrl);
|
var token = System.Web.HttpUtility.ParseQueryString(uri.Query)["access_token"];
|
var baseUrl = uri.GetLeftPart(UriPartial.Path);
|
var url = $"{baseUrl}?access_token={token}×tamp={timestamp}&sign={sign}";
|
|
// 11.7 构建请求体(使用markdown格式)
|
var requestBody = new
|
{
|
msgtype = "markdown",
|
markdown = new
|
{
|
title = $"{warehouseName}物料提醒",
|
text = messageContent
|
},
|
at = new
|
{
|
// 可以指定@某些人,如果不需要可以删除这部分
|
// atMobiles = new[] { "138xxxx8888" },
|
// isAtAll = false
|
}
|
};
|
|
var jsonBody = JsonConvert.SerializeObject(requestBody);
|
var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
|
|
// 11.8 发送POST请求并处理响应
|
var response = await httpClient.PostAsync(url, content);
|
if (!response.IsSuccessStatusCode)
|
{
|
var errorContent = await response.Content.ReadAsStringAsync();
|
throw new Exception($"【{warehouseName}】消息发送失败,状态码:{response.StatusCode},错误信息:{errorContent}");
|
}
|
|
// 避免发送频率过快
|
await Task.Delay(1000);
|
}
|
}
|
}
|
catch (Exception ex)
|
{
|
// 捕获异常并补充上下文,便于问题定位
|
throw new Exception($"钉钉消息推送整体失败,错误详情:{ex.Message}", ex);
|
}
|
}
|
|
// 钉钉签名生成方法
|
private string GenerateSign(long timestamp, string secret)
|
{
|
var stringToSign = $"{timestamp}\n{secret}";
|
using (var hmac = new System.Security.Cryptography.HMACSHA256(Encoding.UTF8.GetBytes(secret)))
|
{
|
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
|
return Convert.ToBase64String(hash);
|
}
|
}
|
|
}
|
}
|