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, 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; } /// /// 查询订单立库库存视图 /// /// /// /// public List GetStockSelectViews(int orderId, string materielCode) { try { Dt_OutboundOrder outboundOrder = _outboundRepository.OutboundOrderRepository.QueryFirst(x => x.Id == orderId); if (outboundOrder == null) { throw new Exception($"未找到出库单信息"); } List locationCodes = _basicRepository.LocationInfoRepository.PPGetCanOutLocationCodes(outboundOrder.WarehouseId); return BaseDal.QueryTabs((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; } } /// /// 查询订单平库库存视图 /// /// /// /// public List 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((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; } } /// /// 查询订单PP立库库存视图 /// /// /// /// public List PPGetStockSelectViews(int orderId, string materielCode) { try { Dt_MesPPCutOutboundOrder mesPPCutOutboundOrder = _outboundRepository.MesPPCutOutboundOrderRepository.QueryFirst(x => x.Id == orderId); if (mesPPCutOutboundOrder == null) { throw new Exception($"未找到出库单信息"); } List locationCodes = _basicRepository.LocationInfoRepository.PPGetCanOutLocationCodes(mesPPCutOutboundOrder.WarehouseId); return BaseDal.QueryTabs((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; } } /// /// 查询订单PP平库库存视图 /// /// /// /// public List 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((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().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); } } /// /// /// /// /// /// /// /// public List GetOutboundStocks(List stockInfos, string materielCode, float needQuantity, out float residueQuantity) { List outStocks = new List(); 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 GetUseableStocks(string materielCode, string batchNo, int warehoseId) { List locationCodes = _basicRepository.LocationInfoRepository.GetCanOutLocationCodes(warehoseId); return BaseDal.GetStockInfos(materielCode, batchNo, locationCodes); } public List GetUseableStocks(string materielCode, string batchNo, string palletcode, int warehoseId) { Dt_StockInfo stockInfo = BaseDal.Db.Queryable().Where(x => x.PalletCode == palletcode && x.WarehouseId == warehoseId).Includes(x => x.Details).First(); List 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() .InnerJoin((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(); 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(); 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("更新失败,请联系管理员"); } } /// /// 钉钉机器人消息推送测试 /// /// public async Task T0DingTalkText(string webhookUrl, string secret) { try { if (webhookUrl == null || secret == null) { webhookUrl = "https://oapi.dingtalk.com/robot/send?access_token=fbc3aaf4133ea650d8116fb86b3ebfd0c5e0d46775966ce87893a41886bdf9dc"; secret = "SECf221842b26356f22ccac84c4e60714e5287408ee8332a8f63503791382c3f5fb"; } var warehouseIdToName = new Dictionary { { 2, "油墨仓" }, { 3, "板材仓" }, { 4, "PP仓" }, { 6, "测试架仓" }, { 11, "干膜仓" }, { 12, "阻焊仓" } }; var currentTime = DateTime.Now; var ninetyDaysAgo = currentTime.AddDays(-90); var expirationLabel3Stocks = BaseDal.Db.Queryable() .Where(s => s.Expirationlabel == 3 && s.WarehouseId != 5) .ToList(); var over90DaysStocks = BaseDal.Db.Queryable() .Where(s => s.ModifyDate <= ninetyDaysAgo && s.Expirationlabel != 3 && s.WarehouseId != 5) .ToList(); if (!expirationLabel3Stocks.Any() && !over90DaysStocks.Any()) { return; } var expirationLabel3StockIds = expirationLabel3Stocks.Select(s => s.Id).ToList(); var over90DaysStockIds = over90DaysStocks.Select(s => s.Id).ToList(); var expirationLabel3Details = expirationLabel3StockIds.Any() ? BaseDal.Db.Queryable() .Where(d => expirationLabel3StockIds.Contains(d.StockId)) .ToList() : new List(); var over90DaysDetails = over90DaysStockIds.Any() ? BaseDal.Db.Queryable() .Where(d => over90DaysStockIds.Contains(d.StockId)) .ToList() : new List(); var stockIdToWarehouseId = expirationLabel3Stocks .Concat(over90DaysStocks) .ToDictionary(s => s.Id, s => s.WarehouseId); var expirationLabel3Groups = expirationLabel3Details .GroupBy(d => stockIdToWarehouseId[d.StockId]) .ToDictionary(g => g.Key, g => g.Select(d => new { d.MaterielCode, d.BatchNo }).ToList()); var over90DaysGroups = over90DaysDetails .GroupBy(d => stockIdToWarehouseId[d.StockId]) .ToDictionary(g => g.Key, g => g.Select(d => new { d.MaterielCode, d.BatchNo }).ToList()); var allWarehouseIds = expirationLabel3Groups.Keys .Union(over90DaysGroups.Keys) .ToList(); using (HttpClient httpClient = new HttpClient()) { foreach (var warehouseId in allWarehouseIds) { // 9.1 处理仓库名称显示:优先用映射名称,无映射时显示原始ID var warehouseName = warehouseIdToName.TryGetValue(warehouseId, out var name) ? name : $"仓库{warehouseId}"; var messageParts = new List { $"【{warehouseName}】物料提醒" }; // 添加过期物料信息 if (expirationLabel3Groups.TryGetValue(warehouseId, out var label3Materials) && label3Materials.Any()) { var label3Details = label3Materials .Select(m => $"• 物料编码:{m.MaterielCode} | 批次号:{m.BatchNo}") .Aggregate((current, next) => $"{current}\n{next}"); messageParts.Add($"一、过期物料\n{label3Details}"); } // 添加超过90天未使用物料信息 if (over90DaysGroups.TryGetValue(warehouseId, out var over90Materials) && over90Materials.Any()) { var over90Details = over90Materials .Select(m => $"• 物料编码:{m.MaterielCode} | 批次号:{m.BatchNo}") .Aggregate((current, next) => $"{current}\n{next}"); messageParts.Add($"二、超过90天未使用物料\n{over90Details}"); } var messageContent = string.Join("\n\n", messageParts); var timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); var sign = GenerateSign(timestamp, secret); var url = $"{webhookUrl.Split('?')[0]}?access_token={new Uri(webhookUrl).Query.Split('=')[1]}×tamp={timestamp}&sign={sign}"; // 构建请求体(符合钉钉text类型消息格式) var requestBody = new { msgtype = "text", text = new { content = messageContent } }; var jsonBody = JsonConvert.SerializeObject(requestBody); var content = new StringContent(jsonBody, Encoding.UTF8, "application/json"); var response = await httpClient.PostAsync(url, content); if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync(); throw new Exception($"【{warehouseName}】消息发送失败,状态码:{response.StatusCode},错误信息:{errorContent}"); } } } } catch (Exception ex) { throw new Exception($"钉钉消息推送整体失败,错误详情:{ex.Message}", ex); } } /// /// 生成加签签名 /// /// 时间戳 /// 签名 private string GenerateSign(long timestamp,string secret) { var stringToSign = $"{timestamp}\n{secret}"; using (var hmacsha256 = new System.Security.Cryptography.HMACSHA256(Encoding.UTF8.GetBytes(secret))) { var hashBytes = hmacsha256.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)); return Convert.ToBase64String(hashBytes).Replace("+", "%2B").Replace("/", "%2F"); } } } }