wangxinhui
2025-10-14 0705cb6170a9ba77ba48bbb6dcebb9cf3d73cbea
´úÂë¹ÜÀí/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs
@@ -1,12 +1,16 @@
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;
@@ -19,6 +23,7 @@
using WIDESEA_IStockRepository;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
using WIDESEA_StockRepository;
namespace WIDESEA_StockService
{
@@ -53,7 +58,7 @@
                    throw new Exception($"未找到出库单信息");
                }
                List<string> locationCodes = _basicRepository.LocationInfoRepository.GetCanOutLocationCodes(outboundOrder.WarehouseId);
                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
                {
@@ -113,6 +118,92 @@
                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)
        {
@@ -141,18 +232,26 @@
        {
            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();
            //stockInfos = stockInfos.OrderBy(x => x.Id).ToList();
            if (stockTotalQuantity >= needQuantity)//库存够
            {
                int index = 0;
                while (needQuantity > 0)
                {
                    Dt_StockInfo stockInfo = stockInfos[index];
                    float useableStockQuantity = stockInfo.Details.Where(x => x.MaterielCode == materielCode).Sum(x => x.StockQuantity - x.OutboundQuantity);
                    if (useableStockQuantity < needQuantity)
                    // è®¡ç®—可用库存时转换为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);
                        needQuantity -= useableStockQuantity;
                        stockInfo.Details.ForEach(x =>
                            x.OutboundQuantity = x.StockQuantity);
                        // ä½¿ç”¨decimal进行计算后再转回float
                        needQuantity = (float)((decimal)needQuantity - useableStockQuantity);
                    }
                    else
                    {
@@ -160,14 +259,20 @@
                        {
                            if (x.StockQuantity > x.OutboundQuantity && x.MaterielCode == materielCode)
                            {
                                if (x.StockQuantity - x.OutboundQuantity >= needQuantity)
                                // å°†ç›¸å…³å€¼è½¬æ¢ä¸ºdecimal进行精确计算
                                decimal currentStock = (decimal)x.StockQuantity;
                                decimal currentOutbound = (decimal)x.OutboundQuantity;
                                decimal currentNeed = (decimal)needQuantity;
                                decimal available = currentStock - currentOutbound;
                                if (available >= currentNeed)
                                {
                                    x.OutboundQuantity += needQuantity;
                                    x.OutboundQuantity = (float)(currentOutbound + currentNeed);
                                    needQuantity = 0;
                                }
                                else
                                {
                                    needQuantity -= (x.StockQuantity - x.OutboundQuantity);
                                    needQuantity = (float)(currentNeed - available);
                                    x.OutboundQuantity = x.StockQuantity;
                                }
                            }
@@ -176,39 +281,11 @@
                    outStocks.Add(stockInfo);
                    index++;
                }
            }
            else
            {
                for (int i = 0; i < stockInfos.Count; i++)
                {
                    Dt_StockInfo stockInfo = stockInfos[i];
                    float useableStockQuantity = stockInfo.Details.Where(x => x.MaterielCode == materielCode).Sum(x => x.StockQuantity - x.OutboundQuantity);
                    if (useableStockQuantity < needQuantity)
                    {
                        stockInfo.Details.ForEach(x => x.OutboundQuantity = x.StockQuantity);
                        needQuantity -= useableStockQuantity;
                    }
                    else
                    {
                        stockInfo.Details.ForEach(x =>
                        {
                            if (x.StockQuantity > x.OutboundQuantity && x.MaterielCode == materielCode)
                            {
                                if (x.StockQuantity - x.OutboundQuantity >= needQuantity)
                                {
                                    x.OutboundQuantity += needQuantity;
                                    needQuantity = 0;
                                }
                                else
                                {
                                    needQuantity -= (x.StockQuantity - x.OutboundQuantity);
                                    x.OutboundQuantity = x.StockQuantity;
                                }
                            }
                        });
                    }
                    outStocks.Add(stockInfo);
                }
                throw new Exception("库存不足");
            }
            residueQuantity = needQuantity;
            return outStocks;
@@ -220,5 +297,270 @@
            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=fbc3aaf4133ea650d8116fb86b3ebfd0c5e0d46775966ce87893a41886bdf9dc";
                    secret = "SECf221842b26356f22ccac84c4e60714e5287408ee8332a8f63503791382c3f5fb";
                }
                var warehouseIdToName = new Dictionary<int, string>
                {
                    { 2, "油墨仓" },
                    { 3, "板材仓" },
                    { 4, "PP仓" },
                    { 6, "测试架仓" },
                    { 11, "干膜仓" },
                    { 12, "阻焊仓" }
                };
                var currentTime = DateTime.Now;
                var ninetyDaysAgo = currentTime.AddDays(-90);
                var expirationLabel3Stocks = BaseDal.Db.Queryable<Dt_StockInfo>()
                    .Where(s => s.Expirationlabel == 3 && s.WarehouseId != 5)
                    .ToList();
                var over90DaysStocks = BaseDal.Db.Queryable<Dt_StockInfo>()
                    .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<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>();
                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<string>
                {
                    $"【{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]}&timestamp={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);
            }
        }
        /// <summary>
        /// ç”ŸæˆåŠ ç­¾ç­¾å
        /// </summary>
        /// <param name="timestamp">时间戳</param>
        /// <returns>签名</returns>
        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");
            }
        }
    }
}