wangxinhui
2025-12-31 6f8b21411a2a56f6a33fe0112c2ed0eeca407e9c
´úÂë¹ÜÀí/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs
@@ -293,6 +293,10 @@
        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);
@@ -417,37 +421,57 @@
            {
                if (webhookUrl == null || secret == null)
                {
                    webhookUrl = "https://oapi.dingtalk.com/robot/send?access_token=fbc3aaf4133ea650d8116fb86b3ebfd0c5e0d46775966ce87893a41886bdf9dc";
                    secret = "SECf221842b26356f22ccac84c4e60714e5287408ee8332a8f63503791382c3f5fb";
                    webhookUrl = "https://oapi.dingtalk.com/robot/send?access_token=c3e05f2c6bcd595383ee02e713446174b9201bad91db216590620fe0acd4e75e";
                    secret = "SEC617f06140fc7cbd8b91d3e203f270826320637af41e7423d756e62df40f62282";
                }
                var warehouseIdToName = new Dictionary<int, string>
                {
                    { 2, "油墨仓" },
                    { 3, "板材仓" },
                    { 4, "PP仓" },
                    { 6, "测试架仓" },
                    { 11, "干膜仓" },
                    { 12, "阻焊仓" }
                };
                // 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)
                    .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)
                    .Where(s => s.ModifyDate <= ninetyDaysAgo && s.Expirationlabel != 3 && s.WarehouseId != 5 && s.WarehouseId != 7 && s.LocationCode != "" && s.LocationCode != null)
                    .ToList();
                if (!expirationLabel3Stocks.Any() && !over90DaysStocks.Any())
                // 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))
@@ -460,107 +484,264 @@
                        .ToList()
                    : new List<Dt_StockInfoDetail>();
                var stockIdToWarehouseId = expirationLabel3Stocks
                    .Concat(over90DaysStocks)
                    .ToDictionary(s => s.Id, s => s.WarehouseId);
                // 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
                    {
                        d.MaterielCode,
                        d.BatchNo
                    }).ToList());
                        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
                    {
                        d.MaterielCode,
                        d.BatchNo
                    }).ToList());
                        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)
                    {
                        // 9.1 å¤„理仓库名称显示:优先用映射名称,无映射时显示原始ID
                        var warehouseName = warehouseIdToName.TryGetValue(warehouseId, out var name)
                            ? name
                            : $"仓库{warehouseId}";
                        var messageParts = new List<string>
                {
                    $"【{warehouseName}】物料提醒"
                };
                        // 11.1 æž„建markdown格式消息
                        var markdownContent = new StringBuilder();
                        markdownContent.AppendLine($"## {warehouseName}物料提醒通知\n");
                        // æ·»åŠ è¿‡æœŸç‰©æ–™ä¿¡æ¯
                        if (expirationLabel3Groups.TryGetValue(warehouseId, out var label3Materials) && label3Materials.Any())
                        // ç»Ÿè®¡ä¿¡æ¯
                        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)
                        {
                            var label3Details = label3Materials
                                .Select(m => $"• ç‰©æ–™ç¼–码:{m.MaterielCode} | æ‰¹æ¬¡å·ï¼š{m.BatchNo}")
                                .Aggregate((current, next) => $"{current}\n{next}");
                            messageParts.Add($"一、过期物料\n{label3Details}");
                            over90Count = over90List?.Count(m => m.IsProStock) ?? 0;
                        }
                        // æ·»åŠ è¶…è¿‡90天未使用物料信息
                        if (over90DaysGroups.TryGetValue(warehouseId, out var over90Materials) && over90Materials.Any())
                        markdownContent.AppendLine($"**统计概览:**");
                        if (warehouseId == 7)
                        {
                            var over90Details = over90Materials
                                .Select(m => $"• ç‰©æ–™ç¼–码:{m.MaterielCode} | æ‰¹æ¬¡å·ï¼š{m.BatchNo}")
                                .Aggregate((current, next) => $"{current}\n{next}");
                            messageParts.Add($"二、超过90天未使用物料\n{over90Details}");
                            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();
                        }
                        var messageContent = string.Join("\n\n", messageParts);
                        // 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);
                        var url = $"{webhookUrl.Split('?')[0]}?access_token={new Uri(webhookUrl).Query.Split('=')[1]}&timestamp={timestamp}&sign={sign}";
                        // 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}&timestamp={timestamp}&sign={sign}";
                        // æž„建请求体(符合钉钉text类型消息格式)
                        // 11.7 æž„建请求体(使用markdown格式)
                        var requestBody = new
                        {
                            msgtype = "text",
                            text = new { content = messageContent }
                            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);
            }
        }
        /// <summary>
        /// ç”ŸæˆåŠ ç­¾ç­¾å
        /// </summary>
        /// <param name="timestamp">时间戳</param>
        /// <returns>签名</returns>
        private string GenerateSign(long timestamp,string secret)
        // é’‰é’‰ç­¾åç”Ÿæˆæ–¹æ³•
        private string GenerateSign(long timestamp, string secret)
        {
            var stringToSign = $"{timestamp}\n{secret}";
            using (var hmacsha256 = new System.Security.Cryptography.HMACSHA256(Encoding.UTF8.GetBytes(secret)))
            using (var hmac = 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");
                var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
                return Convert.ToBase64String(hash);
            }
        }
    }
}