using DS.Module.Core; using DS.Module.Core.Attributes; using DS.Module.Core.Helpers; using DS.Module.DjyServiceStatus; using DS.Module.SqlSugar; using DS.Module.UserModule; using DS.WMS.Core.Code.Interface; using DS.WMS.Core.Code.Method; using DS.WMS.Core.Invoice.Dtos; using DS.WMS.Core.Map.Interface; using DS.WMS.Core.Map.Method; using DS.WMS.Core.Op.Dtos; using DS.WMS.Core.Op.Entity; using DS.WMS.Core.TaskPlat.Dtos; using DS.WMS.Core.TaskPlat.Entity; using DS.WMS.Core.TaskPlat.Interface; using Mapster; using Masuit.Tools.Systems; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using NLog; using NPOI.SS.Formula.Functions; using SqlSugar; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.Json.Nodes; using System.Threading.Tasks; namespace DS.WMS.Core.TaskPlat.Method { /// /// /// public class TaskShippingOrderCompareService : ITaskShippingOrderCompareService { private readonly IServiceProvider _serviceProvider; private readonly ISqlSugarClient db; private readonly IUser user; private readonly ISaasDbService saasService; private readonly IDjyServiceStatusService _djyServiceStatusService; private readonly IMappingYardService _mappingYardService; private readonly IMappingCarrierService _mappingCarrierService; private readonly ICodePortService _codePortService; private readonly string shippingOrderCompareUrl; private readonly string shippingOrderCompareResultUrl; const string CONST_MAPPING_YARD_MODULE = "BillTrace"; const string CONST_MAPPING_CARRIER_MODULE = "CarrierBaseMapping"; private static readonly NLog.Logger Logger = LogManager.GetCurrentClassLogger(); public TaskShippingOrderCompareService(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; db = _serviceProvider.GetRequiredService(); user = _serviceProvider.GetRequiredService(); saasService = _serviceProvider.GetRequiredService(); _djyServiceStatusService = _serviceProvider.GetRequiredService(); _mappingYardService = _serviceProvider.GetRequiredService(); _mappingCarrierService = _serviceProvider.GetRequiredService(); _codePortService = _serviceProvider.GetRequiredService(); shippingOrderCompareUrl = AppSetting.app(new string[] { "ShippingOrderCompare", "Url" }); shippingOrderCompareResultUrl = AppSetting.app(new string[] { "ShippingOrderCompare", "ResultUrl" }); } /// /// 执行下货纸比对 /// /// 订舱主键 /// 返回回执 public async Task ExcuteShippingOrderCompareAsync(string bookingId) { string batchNo = GuidHelper.GetSnowflakeId(); Logger.Log(NLog.LogLevel.Info, "批次={no}获取订舱数据请求规则 {id}", batchNo, bookingId); /* 处理逻辑 1、台账触发单票下货纸比对 2、调取订舱的详情。 3、对应请求报文。 4、请求比对接口。 5、返回回执。 */ return await InnerExcuteShippingOrderCompareAsync(bookingId); } #region 执行下货纸比对 /// /// 执行下货纸比对 /// /// 订舱主键 /// 比对模式(MANUAL-手工 AUTO-自动) /// 返回回执 private async Task InnerExcuteShippingOrderCompareAsync(string bookingId, string LstShipOrderCompareMode = "MANUAL") { string batchNo = GuidHelper.GetSnowflakeId(); Logger.Log(NLog.LogLevel.Info, "批次={no}获取订舱数据请求规则 {id}", batchNo, bookingId); /* 处理逻辑 1、台账触发单票下货纸比对 2、调取订舱的详情。 3、对应请求报文。 4、请求比对接口。 5、返回回执。 */ TaskManageExcuteResultDto result = new TaskManageExcuteResultDto(); var tenantDb = saasService.GetBizDbScopeById(user.TenantId); try { DateTime nowDate = DateTime.Now; long id = long.Parse(bookingId); var model = tenantDb.Queryable() .First(a => a.Id == id); if (model == null) throw new Exception($"订舱主键{bookingId}无法获取业务信息"); Logger.Log(NLog.LogLevel.Info, "批次={no}获取订舱数据完成", batchNo); //附主信息 var mainInfo = model.Adapt(); //这里需要对场站进行映射翻译 var mapYardList = _mappingYardService.GetAllList().GetAwaiter().GetResult().Data; if (mapYardList.Count > 0) mapYardList = mapYardList.Where(a => a.Module.Equals(CONST_MAPPING_YARD_MODULE, StringComparison.OrdinalIgnoreCase)).ToList(); if (mapYardList.Any(a => a.LinkId == model.YardId)) { mainInfo.YardCode = mapYardList.FirstOrDefault(a => a.LinkId == model.YardId).MapCode; } else { throw new Exception($"场站:{model.Yard} 没有对应的出口运踪映射代码"); } var codePortList = _codePortService.GetAllList().GetAwaiter().GetResult().Data; if (codePortList.Any(a => a.Id == model.LoadPortId)) mainInfo.PortLoadId = codePortList.FirstOrDefault(a => a.Id == model.LoadPortId).EdiCode; if (codePortList.Any(a => a.Id == model.DischargePortId)) mainInfo.PortDischargeId = codePortList.FirstOrDefault(a => a.Id == model.DischargePortId).EdiCode; if (codePortList.Any(a => a.Id == model.DeliveryPlaceId)) mainInfo.PlaceDeliveryId = codePortList.FirstOrDefault(a => a.Id == model.DeliveryPlaceId).EdiCode; if (codePortList.Any(a => a.Id == model.DestinationId)) mainInfo.DestinationId = codePortList.FirstOrDefault(a => a.Id == model.DestinationId).EdiCode; var mapCarrierList = _mappingCarrierService.GetAllList().GetAwaiter().GetResult().Data; if (mapCarrierList.Count > 0) mapCarrierList = mapCarrierList.Where(a => a.Module.Equals(CONST_MAPPING_CARRIER_MODULE, StringComparison.OrdinalIgnoreCase)).ToList(); if (mapYardList.Any(a => a.LinkId == model.CarrierId)) { mainInfo.CarrierCode = mapYardList.FirstOrDefault(a => a.LinkId == model.CarrierId).MapCode; } mainInfo.BusiPKId = model.Id.ToString(); mainInfo.UserId = user.UserId; mainInfo.UserName = user.UserName; //mainInfo.UserEmail = user.; var contaList = await tenantDb.Queryable() .Where(x => long.Parse(x.BSNO) == model.Id).ToListAsync(); Logger.Log(NLog.LogLevel.Info, "批次={no} 提取箱完成 数量={total}", batchNo, contaList.Count); if (contaList.Count > 0) { mainInfo.ContaList = contaList.Adapt>(); } var msgModel = GetMessageInfo(batchNo, mainInfo); Logger.Log(NLog.LogLevel.Info, "批次={no} 对应请求报文完成 msg={msg}", batchNo, JsonConvert.SerializeObject(msgModel)); DateTime bDate = DateTime.Now; var compareResult = await ExcuteCompare(msgModel); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; Logger.Log(NLog.LogLevel.Info, "批次={no} 请求完成,耗时:{timeDiff}ms. 结果{msg}", batchNo, timeDiff, compareResult.succ ? "成功" : "失败"); if (compareResult == null) throw new Exception($"订舱主键{bookingId}请求下货纸比对失败,返回为空"); var orderInfo = tenantDb.Queryable() .First(x => x.Id == id); if (orderInfo != null) { var oldOrderInfo = orderInfo.Adapt(); orderInfo.LstShipOrderCompareDate = bDate; if (compareResult.succ) { orderInfo.LstShipOrderCompareMode = LstShipOrderCompareMode; /* isComplete标记当票是否满足截至要求 1、场站返回的记录里所有箱都有了返场日期。 2、或者当票的实际开船日期或者预计开船日期已到也可以截至。 */ bool isComplete = false; bool isBefore = false; //需要细分状态,返场前和返场后 if (compareResult.yardStatInfo != null) { var yardStatInfo = compareResult.yardStatInfo as YardStatInfo; //如果场站集装箱都有了返场时间,即认为下货纸自动任务结束 if (yardStatInfo.LstReturnYardDate.HasValue && yardStatInfo.ContaNum >= 1 && yardStatInfo.ContaNum == yardStatInfo.ExistsReturnYardDateCtnNum) { isComplete = true; } //判断 有箱号的条数跟总计箱数量不一致 认为是反场前比对 if ((yardStatInfo.ExistsCtnNo < yardStatInfo.ContaNum)) isBefore = true; } DateTime etd = DateTime.MinValue; if (model.ATD.HasValue) { etd = model.ATD.Value; } else if (model.ETD.HasValue) { etd = model.ETD.Value; } if (etd != DateTime.MinValue && etd <= DateTime.Now) isComplete = true; if (compareResult.extra.IsExistsDiff) { if (isComplete) { orderInfo.LstShipOrderCompareRlt = "DIFF"; orderInfo.LstShipOrderCompareRltName = "有差异"; } else { bool isBeforeEqual = false; var checkList = compareResult.extra.ShowDetailList.Where(b => (b.PCode == null || b.PCode == "") && b.IsDisplay && (b.FieldCode.ToUpper() != "PKGS" && b.FieldCode.ToUpper() != "KGS" && b.FieldCode.ToUpper() != "CBM")) .ToList(); if (!checkList.Any(t => t.IsDiff)) { isBeforeEqual = true; } if (isBefore) { if (isBeforeEqual) { orderInfo.LstShipOrderCompareRlt = "BEFORE_EQUAL"; orderInfo.LstShipOrderCompareRltName = "返场前比对正常"; } else { orderInfo.LstShipOrderCompareRlt = "BEFORE_DIFF"; orderInfo.LstShipOrderCompareRltName = "返场前比对有差异"; } } else { orderInfo.LstShipOrderCompareRlt = "DIFF_U"; orderInfo.LstShipOrderCompareRltName = "有差异未结束"; } } } else { if (isComplete) { orderInfo.LstShipOrderCompareRlt = "NO_DIFF"; orderInfo.LstShipOrderCompareRltName = "正常"; //比对成功后触发下货纸比对状态 var pushModel = new EmbedServiceProjectStatusDto { businessId = bookingId, SourceType = 1, StatusCodes = new List { new EmbedServiceProjectStatusDetailDto{ StatusCode = "XHZBDCHG" } } }; var saveStatusRlt = await _djyServiceStatusService.SaveServiceStatus(pushModel); Logger.Log(NLog.LogLevel.Info, "批次={no} 异步推送下货纸比对状态完成,结果={rlt}", batchNo, JsonConvert.SerializeObject(saveStatusRlt)); } else { if (isBefore) { orderInfo.LstShipOrderCompareRlt = "BEFORE_EQUAL"; orderInfo.LstShipOrderCompareRltName = "返场前比对正常"; } else { orderInfo.LstShipOrderCompareRlt = "NO_DIFF_U"; orderInfo.LstShipOrderCompareRltName = "正常未结束"; //比对成功后触发下货纸比对状态 var pushModel = new EmbedServiceProjectStatusDto { businessId = bookingId, SourceType = 1, StatusCodes = new List { new EmbedServiceProjectStatusDetailDto{ StatusCode = "XHZBDCHG" } } }; var saveStatusRlt = await _djyServiceStatusService.SaveServiceStatus(pushModel); Logger.Log(NLog.LogLevel.Info, "批次={no} 异步推送下货纸比对状态完成,结果={rlt}", batchNo, JsonConvert.SerializeObject(saveStatusRlt)); } } } } else { orderInfo.LstShipOrderCompareRlt = "NO_YARD"; orderInfo.LstShipOrderCompareRltName = "无动态"; } if (compareResult.extra != null) orderInfo.LstShipOrderCompareId = compareResult.extra.TaskCompareId; //更新 await tenantDb.Updateable(orderInfo).UpdateColumns(it => new { it.LstShipOrderCompareId, it.LstShipOrderCompareDate, it.LstShipOrderCompareRlt, it.LstShipOrderCompareRltName, it.LstShipOrderCompareMode, }).ExecuteCommandAsync(); // 记录日志 //await _bookingOrderService.SaveLog(orderInfo, oldOrderInfo, "下货纸比对"); } result.succ = compareResult.succ; result.msg = compareResult.msg; result.extra = compareResult.extra; result.extra2 = compareResult.extra2; result.total = compareResult.total; result.yardStatInfo = compareResult.yardStatInfo; Logger.Log(NLog.LogLevel.Info, "批次={no} 请求下货纸比对返回结果{msg}", batchNo, JsonConvert.SerializeObject(compareResult)); Logger.Log(NLog.LogLevel.Info, "批次={no} 返回结果{msg}", batchNo, JsonConvert.SerializeObject(result)); } catch (Exception ex) { result.succ = false; result.msg = $"请求下货纸比对异常,{ex.Message}"; } return result; } #endregion #region 生成请求规则报文 /// /// 生成请求规则报文 /// /// 批次号 /// 订舱主业务信息 /// 返回请求报文类 private TaskMessageInfoDto GetMessageInfo(string batchNo, TaskMessageMain mainInfo) { DateTime nowDate = DateTime.Now; TaskMessageInfoDto msgModel = new TaskMessageInfoDto(); msgModel.Head = new TaskMessageHead { GID = batchNo, MessageType = "SHIP_ORDER_COMPARE", SenderId = AppSetting.app(new string[] { "ExcuteRuleService", "RulesEngineSender" }), SenderName = AppSetting.app(new string[] { "ExcuteRuleService", "RulesEngineSenderName" }), ReceiverId = "RulesEngine", ReceiverName = "大简云规则引擎", Version = "1.0", RequestDate = nowDate.ToString("yyyy-MM-dd HH:mm:ss"), RequestAction = "Compare", }; msgModel.Main = mainInfo; return msgModel; } #endregion #region 请求下货纸比对 /// /// 请求下货纸比对 /// /// /// private async Task ExcuteCompare(TaskMessageInfoDto info) { TaskManageExcuteResultDto model = null; /* 1、读取配置文件中的规则引擎URL 2、填充请求的类,并生成JSON报文 3、POST请求接口,并记录回执。 4、返回信息。 */ try { //var res = await url.OnClientCreating(client => { // // client 为 HttpClient 对象 // client.Timeout = TimeSpan.FromMinutes(15); // 设置超时时间 15分钟 //}).SetHttpMethod(HttpMethod.Post) // .SetBody(JsonConvert.SerializeObject(info), "application/json") // .SetContentEncoding(Encoding.UTF8) //.PostAsync(); var jsonBody = JsonConvert.SerializeObject(info); var res = RequestHelper.Post(jsonBody, shippingOrderCompareUrl); Logger.Log(NLog.LogLevel.Info, string.Format("批次={0} 对应请求报文完成 res={1}", info.Head.GID, res)); if (!string.IsNullOrWhiteSpace(res)) { //var userResult = await res.Content.ReadAsStringAsync(); model = JsonConvert.DeserializeObject(res); } } catch (Exception ex) { //写日志 if (ex is HttpRequestException) throw new Exception("请求失败"); } return model; } #endregion #region 请求下货纸比对结果 /// /// 请求下货纸比对结果 /// /// 订舱主键 /// /// public async Task GetShippingOrderCompareResult(long bookingId) { string batchNo = GuidHelper.GetSnowflakeId(); var tenantDb = saasService.GetBizDbScopeById(user.TenantId); TaskManageExcuteResultDto result = new TaskManageExcuteResultDto(); try { var model = tenantDb.Queryable() .First(a => a.Id == bookingId); DateTime nowDate = DateTime.Now; TaskMessageInfoDto msgModel = new TaskMessageInfoDto { Head = new TaskMessageHead { GID = batchNo, MessageType = "DRAFT_COMPARE", SenderId = AppSetting.app(new string[] { "ExcuteRuleService", "RulesEngineSender" }), SenderName = AppSetting.app(new string[] { "ExcuteRuleService", "RulesEngineSenderName" }), ReceiverId = "RulesEngine", ReceiverName = "大简云规则引擎", Version = "1.0", RequestDate = nowDate.ToString("yyyy-MM-dd HH:mm:ss"), RequestAction = "Compare", }, Main = new TaskMessageMain { BusiPKId = bookingId.ToString(), TaskCompareId = model.LstShipOrderCompareId, } }; Logger.Log(NLog.LogLevel.Info, $"开始请求查询 msg={JsonConvert.SerializeObject(msgModel)}"); result = await GetCompareResult(msgModel); if (result != null && result.extra != null && !string.IsNullOrWhiteSpace(result.extra.TaskCompareId)) { var feedBack = tenantDb.Queryable().First(a => a.BOOKING_ID == bookingId && a.TASK_COMPARE_ID == result.extra.TaskCompareId); if (feedBack != null) { result.extra.ManualFeedBackResult = new FeedBackResult { OperNote = feedBack.NOTES, OperTime = feedBack.CreateTime, OperUser = feedBack.CreateUserName, Reason = (feedBack.IS_OCR_ERROR ? "识别问题 " : "") + (feedBack.IS_EDIT_ERROR ? "录入问题 " : "") + (feedBack.IS_AGENT_ERROR ? "代理录入问题 " : "") }; } } } catch (Exception ex) { Logger.Log(NLog.LogLevel.Info, "获取Draft比对结果异常,原因:{error}", ex.Message); throw new Exception($"获取Draft比对结果异常,原因:{ex.Message}"); } return result; } #endregion #region 获取Draft比对结果 /// /// 获取Draft比对结果 /// /// 请求报文 /// 返回回执 private async Task GetCompareResult(TaskMessageInfoDto info) { TaskManageExcuteResultDto model = null; try { //var res = await url.SetHttpMethod(HttpMethod.Post) // .SetBody(JSON.Serialize(info), "application/json") // .SetContentEncoding(Encoding.UTF8) //.PostAsync(); var jsonBody = JsonConvert.SerializeObject(info); var res = RequestHelper.Post(jsonBody, shippingOrderCompareResultUrl); Logger.Log(NLog.LogLevel.Info, string.Format("批次={0} 对应请求报文完成 res={1}", info.Head.GID, res)); if (!string.IsNullOrWhiteSpace(res)) { //var userResult = await res.Content.ReadAsStringAsync(); Logger.Log(NLog.LogLevel.Info, "对应请求报文 userResult={userResult}", res); model = JsonConvert.DeserializeObject(res); } } catch (Exception ex) { //写日志 if (ex is HttpRequestException) throw new Exception("请求失败"); } return model; } #endregion public async Task ExcuteShippingOrderCompareBatchAsync([FromBody] string[] bookingIds) { TaskManageExcuteResultDto result = new TaskManageExcuteResultDto(); var tenantDb = saasService.GetBizDbScopeById(user.TenantId); try { var ids = bookingIds.Select(a => long.Parse(a)).Distinct().ToArray(); var list = tenantDb.Queryable().Where(t => ids.Contains(t.Id)).ToList(); if (list.Count != ids.Length) { var noRecord = string.Join(",", ids.GroupJoin(list, l => l, r => r.Id, (l, r) => { var currList = r.ToList(); if (r.Count() > 0) return 0; return l; }).Where(t => t > 0).ToArray()); throw new Exception($"以下主键信息 {noRecord} 检索失败或者已作废过"); } //这里单票下货纸比对时,等待结果返回 if (ids.Length == 1) { result = await InnerExcuteShippingOrderCompareAsync(list.FirstOrDefault().Id.ToString()); } else { List> listOfTasks = new List>(); list.ForEach(entity => { listOfTasks.Add(InnerExcuteShippingOrderCompareAsync(entity.Id.ToString())); }); await Task.WhenAll(listOfTasks); result.succ = true; result.msg = "比对完成"; string rltMsg = string.Empty; result.rows = listOfTasks.Select(a => { var origId = a.Result.extra.OrigPKId; var order = list.FirstOrDefault(x => x.Id == long.Parse(origId)); if (a.Result.succ) { if (a.Result.total > 0) { return new { MblNo = order.MBLNO, Msg = "比对异常", IsDiff = true, rlt = a.Result.succ }; } return new { MblNo = order.MBLNO, Msg = "比对一致", IsDiff = false, rlt = a.Result.succ }; } return new { MblNo = order?.MBLNO, Msg = "比对一致", IsDiff = false, rlt = a.Result.succ }; }).ToList(); } } catch (Exception ex) { result.succ = false; result.msg = $"请求下货纸比对异常,{ex.Message}"; } return result; } #region 下货纸自动比对回写状态 /// /// 下货纸自动比对回写状态 /// /// 比对回写详情 /// 返回回执 public async Task AutoTaskShippingOrderCompareCallBackAsync([FromBody] ShippingOrderCompareCallBackInfo model) { TaskManageExcuteResultDto result = new TaskManageExcuteResultDto(); var tenantDb = saasService.GetBizDbScopeById(user.TenantId); try { if (string.IsNullOrWhiteSpace(model.reqBusiId)) throw new Exception($"订舱主键{model.reqBusiId}不能为空"); if (string.IsNullOrWhiteSpace(model.compareId)) throw new Exception($"比对ID{model.compareId}不能为空"); if (string.IsNullOrWhiteSpace(model.compareMode)) throw new Exception($"下货纸比对方式不能为空{model.compareMode}不能为空"); if (string.IsNullOrWhiteSpace(model.compareRltCode)) throw new Exception($"比对结果代码{model.compareRltCode}不能为空"); if (string.IsNullOrWhiteSpace(model.compareRltName)) throw new Exception($"比对结果名称{model.compareId}不能为空"); var bookingOrder = await tenantDb.Queryable() .FirstAsync(a => a.Id == long.Parse(model.reqBusiId)); if (bookingOrder == null) { throw new Exception($"订舱信息检索失败,ID={model.reqBusiId}"); } var oldBookingOrder = bookingOrder.Adapt(); bookingOrder.LstShipOrderCompareId = model.compareId; bookingOrder.LstShipOrderCompareDate = model.compareDate; bookingOrder.LstShipOrderCompareRlt = model.compareRltCode; bookingOrder.LstShipOrderCompareRltName = model.compareRltName; bookingOrder.LstShipOrderCompareMode = model.compareMode; //更新 await tenantDb.Updateable(bookingOrder).UpdateColumns(it => new { it.LstShipOrderCompareId, it.LstShipOrderCompareDate, it.LstShipOrderCompareRlt, it.LstShipOrderCompareRltName, it.LstShipOrderCompareMode, }).ExecuteCommandAsync(); //await _bookingOrderService.SaveLog(bookingOrder, oldBookingOrder, "下货纸自动比对回写状态"); result.succ = true; result.msg = "更新完成"; } catch (Exception ex) { result.succ = false; result.msg = $"下货纸自动比对回写状态失败,{ex.Message}"; } return result; } #endregion #region 自动执行下货纸比对 /// /// 自动执行下货纸比对 /// /// 订舱主键 /// 返回回执 public async Task ExcuteAutoShippingOrderCompareAsync(string bookingId) { string batchNo = GuidHelper.GetSnowflakeId(); Logger.Log(NLog.LogLevel.Info, "批次={no}获取订舱数据请求规则 {id}", batchNo, bookingId); /* 处理逻辑 1、台账触发单票下货纸比对 2、调取订舱的详情。 3、对应请求报文。 4、请求比对接口。 5、返回回执。 */ return await InnerExcuteShippingOrderCompareAsync(bookingId, "AUTO"); } #endregion } }