using Furion; using Furion.DependencyInjection; using Furion.DistributedIDGenerator; using Furion.DynamicApiController; using Furion.Extensions; using Furion.FriendlyException; using Furion.JsonSerialization; using Furion.RemoteRequest.Extensions; using Mapster; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Myshipping.Application.ConfigOption; using Myshipping.Application.Entity; using Myshipping.Application.Service.BookingOrder.Dto; using Myshipping.Core; using Myshipping.Core.Entity; using Myshipping.Core.Service; using Newtonsoft.Json.Linq; using SqlSugar; using StackExchange.Profiling.Internal; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; using Yitter.IdGenerator; namespace Myshipping.Application { /// /// 订舱增值类服务 /// [ApiDescriptionSettings("Application", Name = "BookingValueAdded", Order = 9)] public class BookingValueAddedService : IBookingValueAddedService, IDynamicApiController, ITransient { private readonly ISysCacheService _cache; //private readonly IBookingOrderService _bookingOrderService; private readonly ILogger _logger; private readonly SqlSugarRepository _bookingOrderRepository; private readonly SqlSugarRepository _bookingFileRepository; private readonly SqlSugarRepository _djyWebsiteAccountConfigRepository; private readonly SqlSugarRepository _sysUserRepository; private readonly SqlSugarRepository _bookingLetteryardRepository; private readonly SqlSugarRepository _taskBCInfoRepository; private readonly SqlSugarRepository _statuslogRepository; private readonly SqlSugarRepository _statuslogdetailRepository; private readonly SqlSugarRepository _bookinglogRepository; private readonly SqlSugarRepository _bookinglogdetailRepository; private readonly SqlSugarRepository _bookingRemarkRepository; private readonly SqlSugarRepository _bookingCtnVGMRepository; private readonly IServiceWorkFlowBaseService _serviceWorkFlowBaseService; private readonly IServiceWorkFlowManageService _serviceWorkFlowManageService; private readonly IDjyWebsiteAccountConfigService _webAccountConfigService; private readonly INamedServiceProvider _namedBookingOrderServiceProvider; const string CONST_MAPPING_BC_MODULE_ROUTE = "BC_DOWN_RT"; const string CONST_MAPPING_DRAFT_MODULE_ROUTE = "DRAFT_DOWN_RT"; const string CONST_MAPPING_MANIALLO_CHK_MODULE_ROUTE = "MANI_ALLOC_CHK_RT"; const string CONST_FORMAT_BC_URL = "{0}_bc_down_url"; const string CONST_FORMAT_DRAFT_URL = "{0}_draft_down_url"; const string CONST_FORMAT_MANIALLO_CHK_URL = "{0}_manialloc_chk_url"; const string CONST_FORMAT_WEB = "{0}Web"; const string CONST_BC_FILE_PARSE_URL = "bc_file_parse_url"; const string CONST_BC_FILE_READ_URL = "bc_file_pdf_read_url"; public BookingValueAddedService(ISysCacheService cache, ILogger logger, SqlSugarRepository bookingOrderRepository, SqlSugarRepository djyWebsiteAccountConfigRepository, SqlSugarRepository sysUserRepository, SqlSugarRepository bookingLetteryardRepository//,// IBookingOrderService bookingOrderService , SqlSugarRepository taskBCInfoRepository, IServiceWorkFlowBaseService serviceWorkFlowBaseService, SqlSugarRepository statuslogRepository, SqlSugarRepository statuslogdetailRepository, SqlSugarRepository bookinglogRepository, SqlSugarRepository bookinglogdetailRepository, SqlSugarRepository bookingRemarkRepository, SqlSugarRepository bookingFileRepository, INamedServiceProvider namedBookingOrderServiceProvider, IServiceWorkFlowManageService serviceWorkFlowManageService, SqlSugarRepository bookingCtnVGMRepository, IDjyWebsiteAccountConfigService webAccountConfigService) { _cache = cache; _logger = logger; _bookingOrderRepository = bookingOrderRepository; _djyWebsiteAccountConfigRepository = djyWebsiteAccountConfigRepository; _sysUserRepository = sysUserRepository; _bookingLetteryardRepository = bookingLetteryardRepository; //_bookingOrderService = bookingOrderService; _taskBCInfoRepository = taskBCInfoRepository; _serviceWorkFlowBaseService = serviceWorkFlowBaseService; _statuslogRepository = statuslogRepository; _statuslogdetailRepository = statuslogdetailRepository; _bookinglogRepository = bookinglogRepository; _bookinglogdetailRepository = bookinglogdetailRepository; _bookingRemarkRepository = bookingRemarkRepository; _bookingFileRepository = bookingFileRepository; _bookingCtnVGMRepository = bookingCtnVGMRepository; _serviceWorkFlowManageService = serviceWorkFlowManageService; _namedBookingOrderServiceProvider = namedBookingOrderServiceProvider; _webAccountConfigService = webAccountConfigService; } /// /// 批量BC下载 /// /// 订舱主键数组 /// 返回回执 [HttpPost("/BookingValueAdded/DownloadBookingConfirm")] public async Task DownloadBookingConfirm([FromBody] long[] bookingIds) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); List> taskList = new List>(); string batchNo = IDGen.NextID().ToString(); try { /* 1、订舱主键提取提单号、订舱编号,优先去提单号,其次订舱编号 2、根据不同的船公司调取单独的配置账户。 3、不同船公司对应不同的接口。 4、轮询异步调取接口等待返回接口。 5、成功后将文件链接存入附件表。 6、等待所有请求完成返回统计结果。 */ var list = _bookingOrderRepository.AsQueryable() .Where(a => bookingIds.Contains(a.Id)).ToList(); if (list.Count != bookingIds.Length) throw Oops.Oh($"订舱信息获取失败,订舱信息不存在或已作废"); var noList = bookingIds.Select((a, idx) => new { no = idx + 1, id = a }).ToList(); foreach (var bk in list) { var sortNo = noList.FirstOrDefault(a => a.id == bk.Id).no; taskList.Add(Task.Run(() => InnerDownloadBookingConfirm(bk, batchNo, sortNo))); } Task.WaitAll(taskList.ToArray()); result.succ = true; result.msg = "批量下载BC成功"; var downResultList = taskList.Select(x => x.Result).ToList(); if (downResultList.Any(x => !x.succ)) { result.succ = false; result.msg = "BC下载失败"; } else { result.succ = true; result.msg = downResultList.FirstOrDefault().msg; } result.ext = downResultList; var succ = downResultList.Count(x => x.succ); var fail = downResultList.Count(x => !x.succ); if (succ > 0) { result.batchTotal = succ.ToString(); } else { result.batchTotal = "- "; } if (fail > 0) { result.batchTotal += "/" + fail.ToString(); } else { result.batchTotal += " -"; } } catch (Exception ex) { result.succ = false; result.msg = $"批量BC下载异常,原因:{ex.Message}"; } return result; } #region 单票下载BC /// /// 单票下载BC /// /// 订舱详情 /// 批次号 /// 请求顺序号 /// 返回回执 private async Task InnerDownloadBookingConfirm(BookingOrder bookingOrder, string batchNo, int sortNo) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); result.bno = bookingOrder.MBLNO; try { /* 1、根据船公司代码匹配船公司映射。 2、BC和DRAFT是分别2个请求地址。 3、生成请求报文。 4、请求相应的链接。 5、返回成功写入附件。 */ if (string.IsNullOrWhiteSpace(bookingOrder.MBLNO)) { if (!string.IsNullOrWhiteSpace(bookingOrder.CUSTNO)) { result.bno = $"订 {bookingOrder.CUSTNO}"; } else { result.bno = $"NO.{sortNo}"; } throw Oops.Bah($"主提单号不能为空"); } var bcOrDraftRouteCfg = _cache.GetAllMappingCarrier().GetAwaiter().GetResult() .FirstOrDefault(t => t.Module.Equals(CONST_MAPPING_BC_MODULE_ROUTE, StringComparison.OrdinalIgnoreCase) && t.Code.Equals(bookingOrder.CARRIERID?.Trim(), StringComparison.OrdinalIgnoreCase)); _logger.LogInformation("提单号【{mbl}】根据订舱的船公司代码{ca} 提取船公司映射完成,结果={rlt}", bookingOrder.MBLNO, bookingOrder.CARRIERID, bcOrDraftRouteCfg); if (bcOrDraftRouteCfg == null) { _logger.LogInformation("提单号{mbl} 根据订舱的船公司代码{ca} 提取船公司映射失败", bookingOrder.MBLNO, bookingOrder.CARRIERID); throw Oops.Bah($"船公司={bookingOrder.CARRIERID} 暂不支持BC下载"); } string urlKey = string.Format(CONST_FORMAT_BC_URL, bcOrDraftRouteCfg.MapCode.ToLower()); var bcUrl = _cache.GetAllDictData().GetAwaiter().GetResult() .FirstOrDefault(x => x.TypeCode == "url_set" && x.Code.Equals(urlKey, StringComparison.OrdinalIgnoreCase))?.Value; _logger.LogInformation("提单号{mbl} 根据订舱的船公司代码{ca} 提取BC下载URL完成,结果={rlt}", bookingOrder.MBLNO, bookingOrder.CARRIERID, bcUrl); if (string.IsNullOrWhiteSpace(bcUrl)) { _logger.LogInformation("提单号{0} 根据订舱的船公司代码{1} 提取BC下载URL失败,未取到配置key={key}", bookingOrder.MBLNO, bookingOrder.CARRIERID, urlKey); throw Oops.Bah($"船公司={bookingOrder.CARRIERID} 未配置请求地址{urlKey} 请联系管理员"); } string webKey = string.Format(CONST_FORMAT_WEB, bcOrDraftRouteCfg.MapCode); //获取个人对应的账户,这里GetAccountConfig逻辑优先取个人,个人没有配置取公司对应配置 var userWebAccountConfig = GetAccountConfig(webKey, UserManager.UserId, UserManager.TENANT_ID).GetAwaiter() .GetResult(); _logger.LogInformation("批次={no} 获取获取网站的账户完成,result={Num}", batchNo, JSON.Serialize(userWebAccountConfig)); if (userWebAccountConfig == null) throw Oops.Oh($" 未配置个人或公司网站账户,网站{webKey}"); BCOrDraftRequestDto requestDto = new BCOrDraftRequestDto { user_key = App.Configuration["BCOrDraftUserKey"], user_secret = App.Configuration["BCOrDraftUserSecret"], web_user = userWebAccountConfig.Account?.Trim(), web_psw = userWebAccountConfig.Password?.Trim(), bno = bookingOrder.MBLNO, is_parse = false }; _logger.LogInformation("批次={no} json={json} 请求BC远端下载开始", batchNo, JSON.Serialize(requestDto)); DateTime bDate = DateTime.Now; //开始请求BC var rlt = await ExcuteBCDownload(bcUrl, requestDto, batchNo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} result={result} 请求BC远端下载结束 耗时:{timeDiff}ms. ", batchNo, JSON.Serialize(rlt), timeDiff); if (rlt.code == 200) { _logger.LogInformation("批次={no} 下载文件成功,转存本地", batchNo); string currFilePath = rlt.data.FirstOrDefault().path; string fileTypeCode = "bc"; string fileTypeName = "Booking Confirmation"; //读取文件配置 var fileCfg = App.GetOptions(); string relativePath = $"{fileCfg.relativePath}\\bcfiles\\{bookingOrder.Id}"; string filePath = $"{(!string.IsNullOrWhiteSpace(fileCfg.basePath) ? fileCfg.basePath : App.WebHostEnvironment.WebRootPath)}\\{relativePath}"; string fileFullName = $"{filePath}\\{new System.IO.FileInfo(currFilePath).Name}"; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { relativePath = relativePath.Replace("\\", "/"); filePath = filePath.Replace("\\", "/"); fileFullName = fileFullName.Replace("\\", "/"); } _logger.LogInformation("批次={no} 生成文件保存路径完成 路由={filePath} 服务器系统={system}", batchNo, filePath, RuntimeInformation.OSDescription); //预先创建目录 if (!Directory.Exists(filePath)) { Directory.CreateDirectory(filePath); } var bcStream = await currFilePath.GetAsStreamAsync(); using (var fileStream = File.Create(fileFullName)) { await bcStream.CopyToAsync(fileStream); } _logger.LogInformation("批次={no} 完成文件保存 filepath={path}", batchNo, fileFullName); string bookFilePath = string.Empty; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { bookFilePath = System.Text.RegularExpressions.Regex.Match(fileFullName, relativePath.Replace("/", "\\/") + ".*").Value; } else { bookFilePath = System.Text.RegularExpressions.Regex.Match(fileFullName, relativePath.Replace("\\", "\\\\") + ".*").Value; } //这里先写入附件表 await SaveEDIFile(bookingOrder.Id, bookFilePath, new System.IO.FileInfo(currFilePath).Name, fileTypeCode, fileTypeName); result.succ = true; result.msg = "BC下载成功"; } else { result.succ = false; result.msg = $"BC下载失败,{rlt.msg}"; } } catch (Exception ex) { result.succ = false; result.msg = $"BC下载失败,{ex.Message}"; } return result; } #endregion #region 异步写入订舱附件表 /// /// 异步写入订舱附件表 /// /// 订舱ID /// 文件路径 /// 文件名 /// 附件类型代码 /// 附件类型名称 /// [NonAction] private async Task SaveEDIFile(long boookId, string FilePath, string fileName, string fileTypeCode = "bc", string fileTypeName = "Booking Confirmation") { /* 直接将附件信息写入附件表 */ //EDI文件 var bookFile = new BookingFile { Id = YitIdHelper.NextId(), FileName = fileName, FilePath = FilePath, TypeCode = fileTypeCode, TypeName = fileTypeName, BookingId = boookId, }; await _bookingFileRepository.InsertAsync(bookFile); } #endregion /// /// 批量Draft下载 /// /// 订舱主键数组 /// [HttpPost("/BookingValueAdded/DownloadDraft")] public async Task DownloadDraft([FromBody] long[] bookingIds) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); List> taskList = new List>(); string batchNo = IDGen.NextID().ToString(); try { /* 1、订舱主键提取提单号、订舱编号,优先去提单号,其次订舱编号 2、根据不同的船公司调取单独的配置账户。 3、不同船公司对应不同的接口。 4、轮询异步调取接口等待返回接口。 5、成功后将文件链接存入附件表。 6、等待所有请求完成返回统计结果。 */ var list = _bookingOrderRepository.AsQueryable() .Where(a => bookingIds.Contains(a.Id)).ToList(); if (list.Count != bookingIds.Length) throw Oops.Oh($"订舱信息获取失败,订舱信息不存在或已作废"); var noList = bookingIds.Select((a, idx) => new { no = idx + 1, id = a }).ToList(); foreach (var bk in list) { var sortNo = noList.FirstOrDefault(a => a.id == bk.Id).no; taskList.Add(Task.Run(() => InnerDownloadDraft(bk, batchNo, sortNo))); } Task.WaitAll(taskList.ToArray()); result.succ = true; result.msg = "下载Draft成功"; var downResultList = taskList.Select(x => x.Result).ToList(); if (downResultList.Any(x => !x.succ)) { result.succ = false; result.msg = "Draft下载失败"; } else { result.succ = true; result.msg = downResultList.FirstOrDefault().msg; } result.ext = downResultList; var succ = downResultList.Count(x => x.succ); var fail = downResultList.Count(x => !x.succ); if (succ > 0) { result.batchTotal = succ.ToString(); } else { result.batchTotal = "- "; } if (fail > 0) { result.batchTotal += "/" + fail.ToString(); } else { result.batchTotal += " -"; } } catch (Exception ex) { result.succ = false; result.msg = $"下载Draft异常,原因:{ex.Message}"; } return result; } #region 单票下载BC /// /// 单票下载BC /// /// 订舱详情 /// 批次号 /// 请求顺序号 /// 返回回执 private async Task InnerDownloadDraft(BookingOrder bookingOrder, string batchNo, int sortNo) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); result.bno = bookingOrder.MBLNO; try { /* 1、根据船公司代码匹配船公司映射。 2、BC和DRAFT是分别2个请求地址。 3、生成请求报文。 4、请求相应的链接。 5、返回成功写入附件。 */ if (bookingOrder.CARRIERID.Equals("ESL", StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(bookingOrder.TMBLNO)) { if (!string.IsNullOrWhiteSpace(bookingOrder.CUSTNO)) { result.bno = $"订 {bookingOrder.CUSTNO}"; } else if (string.IsNullOrWhiteSpace(bookingOrder.MBLNO)) { result.bno = $"NO.{sortNo}"; } throw Oops.Bah($"EP号不能为空"); } } else { if (string.IsNullOrWhiteSpace(bookingOrder.MBLNO)) { if (!string.IsNullOrWhiteSpace(bookingOrder.CUSTNO)) { result.bno = $"订 {bookingOrder.CUSTNO}"; } else { result.bno = $"NO.{sortNo}"; } throw Oops.Bah($"主提单号不能为空"); } } var bcOrDraftRouteCfg = _cache.GetAllMappingCarrier().GetAwaiter().GetResult() .FirstOrDefault(t => t.Module.Equals(CONST_MAPPING_DRAFT_MODULE_ROUTE, StringComparison.OrdinalIgnoreCase) && t.Code.Equals(bookingOrder.CARRIERID?.Trim(), StringComparison.OrdinalIgnoreCase)); _logger.LogInformation("提单号{mbl} 根据订舱的船公司代码{ca} 提取船公司映射完成,结果={rlt}", bookingOrder.MBLNO, bookingOrder.CARRIERID, bcOrDraftRouteCfg); if (bcOrDraftRouteCfg == null) { _logger.LogInformation("提单号{0} 根据订舱的船公司代码{1} 提取船公司映射失败", bookingOrder.MBLNO, bookingOrder.CARRIERID); throw Oops.Bah($"船公司={bookingOrder.CARRIERID} 暂不支持Draft下载"); } string urlKey = string.Format(CONST_FORMAT_DRAFT_URL, bcOrDraftRouteCfg.MapCode.ToLower()); var bcUrl = _cache.GetAllDictData().GetAwaiter().GetResult() .FirstOrDefault(x => x.TypeCode == "url_set" && x.Code.Equals(urlKey, StringComparison.OrdinalIgnoreCase))?.Value; _logger.LogInformation("提单号{mbl} 根据订舱的船公司代码{ca} 提取DRAFT下载URL完成,结果={rlt}", bookingOrder.MBLNO, bookingOrder.CARRIERID, bcUrl); if (string.IsNullOrWhiteSpace(bcUrl)) { _logger.LogInformation("提单号{0} 根据订舱的船公司代码{1} 提取BC下载URL失败,未取到配置key={key}", bookingOrder.MBLNO, bookingOrder.CARRIERID, urlKey); throw Oops.Bah($"船公司={bookingOrder.CARRIERID} 未配置请求地址{urlKey} 请联系管理员"); } string webKey = string.Format(CONST_FORMAT_WEB, bcOrDraftRouteCfg.MapCode); //获取个人对应的账户,这里GetAccountConfig逻辑优先取个人,个人没有配置取公司对应配置 var userWebAccountConfig = GetAccountConfig(webKey, UserManager.UserId, UserManager.TENANT_ID).GetAwaiter() .GetResult(); _logger.LogInformation("批次={no} 获取获取网站的账户完成,result={Num}", batchNo, JSON.Serialize(userWebAccountConfig)); if (userWebAccountConfig == null) throw Oops.Oh($" 未配置个人或公司网站账户,网站{webKey}"); string downloadFilePathRlt = string.Empty; string erroMsg = string.Empty; if (bcOrDraftRouteCfg.MapCode.Trim().Equals("ESL", StringComparison.OrdinalIgnoreCase)) { ESLDraftRequestDto requestDto = new ESLDraftRequestDto { u = userWebAccountConfig.Account?.Trim(), p = userWebAccountConfig.Password?.Trim(), ep_code = bookingOrder.TMBLNO?.Trim().ToUpper(), }; _logger.LogInformation("批次={no} json={json} 请求Draft远端下载开始", batchNo, JSON.Serialize(requestDto)); DateTime bDate = DateTime.Now; //开始请求BC var rlt = await ExcuteESLDraftDownload(bcUrl, requestDto, batchNo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} result={result} 请求Draft远端下载结束 耗时:{timeDiff}ms. ", batchNo, JSON.Serialize(rlt), timeDiff); if (rlt.status == 1) { downloadFilePathRlt = rlt.data.api_path; } else { erroMsg = rlt.message; } } else { BCOrDraftRequestDto requestDto = new BCOrDraftRequestDto { user_key = App.Configuration["BCOrDraftUserKey"], user_secret = App.Configuration["BCOrDraftUserSecret"], web_user = userWebAccountConfig.Account?.Trim(), web_psw = userWebAccountConfig.Password?.Trim(), bno = bookingOrder.MBLNO, is_parse = false }; _logger.LogInformation("批次={no} json={json} 请求Draft远端下载开始", batchNo, JSON.Serialize(requestDto)); DateTime bDate = DateTime.Now; //开始请求BC var rlt = await ExcuteDraftDownload(bcUrl, requestDto, batchNo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} result={result} 请求Draft远端下载结束 耗时:{timeDiff}ms. ", batchNo, JSON.Serialize(rlt), timeDiff); if (rlt.code == 200) { downloadFilePathRlt = rlt.data.FirstOrDefault().path; } else { erroMsg = rlt.msg; } } if (!string.IsNullOrWhiteSpace(downloadFilePathRlt)) { _logger.LogInformation("批次={no} 下载文件成功,转存本地", batchNo); string currFilePath = downloadFilePathRlt; string fileTypeCode = "draft"; string fileTypeName = "Draft"; //读取文件配置 var fileCfg = App.GetOptions(); string relativePath = $"{fileCfg.relativePath}\\draftfiles\\{bookingOrder.Id}"; string filePath = $"{(!string.IsNullOrWhiteSpace(fileCfg.basePath) ? fileCfg.basePath : App.WebHostEnvironment.WebRootPath)}\\{relativePath}"; string fileFullName = $"{filePath}\\{new System.IO.FileInfo(currFilePath).Name}"; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { relativePath = relativePath.Replace("\\", "/"); filePath = filePath.Replace("\\", "/"); fileFullName = fileFullName.Replace("\\", "/"); } _logger.LogInformation("批次={no} 生成文件保存路径完成 路由={filePath} 服务器系统={system}", batchNo, filePath, RuntimeInformation.OSDescription); //预先创建目录 if (!Directory.Exists(filePath)) { Directory.CreateDirectory(filePath); } var bcStream = await currFilePath.GetAsStreamAsync(); using (var fileStream = File.Create(fileFullName)) { await bcStream.CopyToAsync(fileStream); } _logger.LogInformation("批次={no} 完成文件保存 filepath={path}", batchNo, fileFullName); string bookFilePath = string.Empty; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { bookFilePath = System.Text.RegularExpressions.Regex.Match(fileFullName, relativePath.Replace("/", "\\/") + ".*").Value; } else { bookFilePath = System.Text.RegularExpressions.Regex.Match(fileFullName, relativePath.Replace("\\", "\\\\") + ".*").Value; } //这里先写入附件表 await SaveEDIFile(bookingOrder.Id, bookFilePath, new System.IO.FileInfo(currFilePath).Name, fileTypeCode, fileTypeName); result.succ = true; result.msg = "Draft下载成功"; } else { result.succ = false; result.msg = $"Draft下载失败,原因={erroMsg}"; } } catch (Exception ex) { result.succ = false; result.msg = $"Draft下载失败,原因:{ex.Message}"; } return result; } #endregion #region BC请求远端下载 /// /// BC请求远端下载 /// /// 请求URL /// 请求详情 /// 批次号 /// 返回结果 [NonAction] private async Task ExcuteBCDownload(string url, BCOrDraftRequestDto info, string batchNo) { BCAPIBaseResult model = null; /* 1、填充请求的类,并生成JSON报文 2、POST请求接口,并记录回执。 3、返回信息。 */ try { _logger.LogInformation("批次={no} 对应请求报文 request={res}", batchNo, JSON.Serialize(info)); var res = await url.OnClientCreating(client => { // client 为 HttpClient 对象 client.Timeout = TimeSpan.FromMinutes(15); // 设置超时时间 15分钟 }).SetHttpMethod(HttpMethod.Post) .SetBody(JSON.Serialize(info), "application/json") .SetContentEncoding(Encoding.UTF8) .PostAsync(); _logger.LogInformation("批次={no} 对应请求报文完成 res={res}", batchNo, JSON.Serialize(res)); if (res.StatusCode == System.Net.HttpStatusCode.OK) { var userResult = await res.Content.ReadAsStringAsync(); System.Text.Json.JsonSerializerOptions jsonOptions = new JsonSerializerOptions(); jsonOptions.Converters.Add(new DateTimeJsonConverter("yyyy-MM-dd HH:mm:ss")); model = JSON.Deserialize(userResult, jsonOptions); } } catch (Exception ex) { //写日志 if (ex is HttpRequestException) throw Oops.Oh(10000002); } return model; } #endregion #region Draft请求远端下载 /// /// Draft请求远端下载 /// /// 请求URL /// 请求详情 /// 批次号 /// 返回结果 [NonAction] private async Task ExcuteDraftDownload(string url, BCOrDraftRequestDto info, string batchNo) { DraftAPIBaseResult model = null; /* 1、填充请求的类,并生成JSON报文 2、POST请求接口,并记录回执。 3、返回信息。 */ try { _logger.LogInformation("批次={no} 对应请求报文 request={res}", batchNo, JSON.Serialize(info)); var res = await url.OnClientCreating(client => { // client 为 HttpClient 对象 client.Timeout = TimeSpan.FromMinutes(15); // 设置超时时间 15分钟 }).SetHttpMethod(HttpMethod.Post) .SetBody(JSON.Serialize(info), "application/json") .SetContentEncoding(Encoding.UTF8) .PostAsync(); _logger.LogInformation("批次={no} 对应请求报文完成 res={res}", batchNo, JSON.Serialize(res)); if (res.StatusCode == System.Net.HttpStatusCode.OK) { var userResult = await res.Content.ReadAsStringAsync(); System.Text.Json.JsonSerializerOptions jsonOptions = new JsonSerializerOptions(); jsonOptions.Converters.Add(new DateTimeJsonConverter("yyyy-MM-dd HH:mm:ss")); model = JSON.Deserialize(userResult, jsonOptions); } } catch (Exception ex) { //写日志 if (ex is HttpRequestException) throw Oops.Oh(10000002); } return model; } #endregion #region ESL Draft请求远端下载 /// /// ESL Draft请求远端下载 /// 由于现有的ESL和TSL接口不一致,需要提供单独的POST方法 /// /// 请求URL /// 请求详情 /// 批次号 /// 返回结果 [NonAction] private async Task ExcuteESLDraftDownload(string url, ESLDraftRequestDto info, string batchNo) { ESLDraftAPIBaseResult model = null; /* 1、填充请求的类,并生成JSON报文 2、POST请求接口,并记录回执。 3、返回信息。 */ try { _logger.LogInformation("批次={no} 对应请求报文 request={res}", batchNo, JSON.Serialize(info)); var res = await url.OnClientCreating(client => { // client 为 HttpClient 对象 client.Timeout = TimeSpan.FromMinutes(15); // 设置超时时间 15分钟 }).SetHttpMethod(HttpMethod.Post) .SetBody(JSON.Serialize(info), "application/json") .SetContentEncoding(Encoding.UTF8) .PostAsync(); _logger.LogInformation("批次={no} 对应请求报文完成 res={res}", batchNo, JSON.Serialize(res)); if (res.StatusCode == System.Net.HttpStatusCode.OK) { var userResult = await res.Content.ReadAsStringAsync(); model = JSON.Deserialize(userResult); } } catch (Exception ex) { //写日志 if (ex is HttpRequestException) throw Oops.Oh(10000002); } return model; } #endregion #region ESL SO Ref.No搜索ESL号查询 /// /// ESL SO Ref.No搜索ESL号查询 /// /// 请求URL /// 请求详情 /// 批次号 /// 返回结果 [NonAction] private async Task ExcuteManiAllocCheckDownload(string url, ESLManiAlloChkRequestDto info, string batchNo) { ESLManiAlloChkAPIBaseResult model = null; /* 1、填充请求的类,并生成JSON报文 2、POST请求接口,并记录回执。 3、返回信息。 */ try { _logger.LogInformation("批次={no} 对应请求报文 request={res}", batchNo, JSON.Serialize(info)); var res = await url.OnClientCreating(client => { // client 为 HttpClient 对象 client.Timeout = TimeSpan.FromMinutes(15); // 设置超时时间 15分钟 }).SetHttpMethod(HttpMethod.Post) .SetBody(JSON.Serialize(info), "application/json") .SetContentEncoding(Encoding.UTF8) .PostAsync(); _logger.LogInformation("批次={no} 对应请求报文完成 res={res}", batchNo, JSON.Serialize(res)); if (res.StatusCode == System.Net.HttpStatusCode.OK) { var userResult = await res.Content.ReadAsStringAsync(); model = JSON.Deserialize(userResult); } } catch (Exception ex) { //写日志 if (ex is HttpRequestException) throw Oops.Oh(10000002); } return model; } #endregion /// /// 舱位分配查询 /// /// 订舱主键数组 /// 返回回执 [HttpPost("/BookingValueAdded/CheckUpdateManifestNo")] public async Task CheckUpdateManifestNo([FromBody] long[] bookingIds) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); List> taskList = new List>(); string batchNo = IDGen.NextID().ToString(); try { /* 现在只有ESL支持舱位分配查询 1、订舱主键提取提单号,判断提单号如果不是ESL开头的,需要启动流程 2、异步调取ESL接口。 3、返回结果后提取esl_no号 4、将参考号写入订舱编号。 5、将esl_no号写入主提单号。 6、等待所有请求完成返回统计结果。 */ var list = _bookingOrderRepository.AsQueryable() .Where(a => bookingIds.Contains(a.Id)).ToList(); if (list.Count != bookingIds.Length) throw Oops.Oh($"订舱信息获取失败,订舱信息不存在或已作废"); var noList = bookingIds.Select((a, idx) => new { no = idx + 1, id = a }).ToList(); foreach (var bk in list) { var sortNo = noList.FirstOrDefault(a => a.id == bk.Id).no; taskList.Add(Task.Run(() => InnerCheckUpdateManifestNo(bk, batchNo, sortNo))); } Task.WaitAll(taskList.ToArray()); result.succ = true; result.msg = "批量执行成功"; var downResultList = taskList.Select(x => x.Result).ToList(); if (downResultList.Any(x => !x.succ)) { result.succ = false; result.msg = "批量执行失败"; } else { result.succ = true; result.msg = downResultList.FirstOrDefault().msg; } result.ext = downResultList; var succ = downResultList.Count(x => x.succ); var fail = downResultList.Count(x => !x.succ); if (succ > 0) { result.batchTotal = succ.ToString(); } else { result.batchTotal = "- "; } if (fail > 0) { result.batchTotal += "/" + fail.ToString(); } else { result.batchTotal += " -"; } } catch (Exception ex) { result.succ = false; result.msg = $"批量执行失败,原因:{ex.Message}"; } return result; } #region 单票舱位分配查询 /// /// 单票舱位分配查询 /// /// 订舱详情 /// 批次号 /// 请求顺序号 /// 返回回执 private async Task InnerCheckUpdateManifestNo(BookingOrder bookingOrder, string batchNo, int sortNo) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); result.bno = bookingOrder.MBLNO; try { /* 1、根据船公司代码匹配船公司映射。 2、BC和DRAFT是分别2个请求地址。 3、生成请求报文。 4、请求相应的链接。 5、返回成功写入附件。 */ //2023-07-06 按照最新要求,从订舱编号取单号 if (string.IsNullOrWhiteSpace(bookingOrder.CUSTNO)) { if (!string.IsNullOrWhiteSpace(bookingOrder.MBLNO)) { result.bno = $"订 {bookingOrder.MBLNO}"; } else { result.bno = $"NO.{sortNo}"; } throw Oops.Bah($"订舱编号不能为空"); } else { if (Regex.IsMatch(bookingOrder.CUSTNO, "\\bESL\\w+")) { _logger.LogInformation("批次={no} id={id} 订舱编号没填写参考号,无法继续", batchNo, bookingOrder.Id); throw Oops.Oh($"订舱编号不是有效的参考号"); } } var bcOrDraftRouteCfg = _cache.GetAllMappingCarrier().GetAwaiter().GetResult() .FirstOrDefault(t => t.Module.Equals(CONST_MAPPING_MANIALLO_CHK_MODULE_ROUTE, StringComparison.OrdinalIgnoreCase) && t.Code.Equals(bookingOrder.CARRIERID?.Trim(), StringComparison.OrdinalIgnoreCase)); _logger.LogInformation("订舱编号【{mbl}】根据订舱的船公司代码{ca} 提取船公司映射完成,结果={rlt}", bookingOrder.CUSTNO, bookingOrder.CARRIERID, bcOrDraftRouteCfg); if (bcOrDraftRouteCfg == null) { _logger.LogInformation("订舱编号{mbl} 根据订舱的船公司代码{ca} 提取船公司映射失败", bookingOrder.CUSTNO, bookingOrder.CARRIERID); throw Oops.Bah($"船公司={bookingOrder.CARRIERID} 暂不支持舱位分配查询"); } string urlKey = string.Format(CONST_FORMAT_MANIALLO_CHK_URL, bcOrDraftRouteCfg.MapCode.ToLower()); var bcUrl = _cache.GetAllDictData().GetAwaiter().GetResult() .FirstOrDefault(x => x.TypeCode == "url_set" && x.Code.Equals(urlKey, StringComparison.OrdinalIgnoreCase))?.Value; _logger.LogInformation("订舱编号{mbl} 根据订舱的船公司代码{ca} 提取舱位分配查询URL完成,结果={rlt}", bookingOrder.CUSTNO, bookingOrder.CARRIERID, bcUrl); if (string.IsNullOrWhiteSpace(bcUrl)) { _logger.LogInformation("订舱编号{0} 根据订舱的船公司代码{1} 提取舱位分配查询URL失败,未取到配置key={key}", bookingOrder.CUSTNO, bookingOrder.CARRIERID, urlKey); throw Oops.Bah($"船公司={bookingOrder.CARRIERID} 未配置请求地址{urlKey} 请联系管理员"); } string webKey = string.Format(CONST_FORMAT_WEB, bcOrDraftRouteCfg.MapCode); //获取个人对应的账户,这里GetAccountConfig逻辑优先取个人,个人没有配置取公司对应配置 var userWebAccountConfig = GetAccountConfig(webKey, UserManager.UserId, UserManager.TENANT_ID).GetAwaiter() .GetResult(); _logger.LogInformation("批次={no} 获取获取网站的账户完成,result={Num}", batchNo, JSON.Serialize(userWebAccountConfig)); if (userWebAccountConfig == null) throw Oops.Oh($" 未配置个人或公司网站账户,网站{webKey}"); ESLManiAlloChkRequestDto requestDto = new ESLManiAlloChkRequestDto { u = userWebAccountConfig.Account?.Trim(), p = userWebAccountConfig.Password?.Trim(), so_no = bookingOrder.CUSTNO, }; _logger.LogInformation("批次={no} json={json} 请求舱位分配查询远端下载开始", batchNo, JSON.Serialize(requestDto)); DateTime bDate = DateTime.Now; //开始请求BC var rlt = await ExcuteManiAllocCheckDownload(bcUrl, requestDto, batchNo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} result={result} 请求舱位分配查询远端下载结束 耗时:{timeDiff}ms. ", batchNo, JSON.Serialize(rlt), timeDiff); if (rlt.status == 1) { if (rlt.data == null || string.IsNullOrWhiteSpace(rlt.data.esl_no)) { _logger.LogInformation("批次={no} 舱位分配查询失败,ESL号不存在", batchNo); throw Oops.Oh($"查询ESL号失败"); } _logger.LogInformation("批次={no} 舱位分配查询成功", batchNo); /* 提单号写入订舱编号,并把esl_no写入主提单号 */ var bkInfo = _bookingOrderRepository.AsQueryable().First(a => a.Id == bookingOrder.Id); if (bkInfo == null) { _logger.LogInformation("批次={no} id={id} 订舱信息获取失败,无法更新", batchNo, bookingOrder.Id); throw Oops.Oh($"订舱信息获取失败,无法更新"); } var oldBkInfo = bkInfo.Adapt(); /* //2023-07-06 按照最新要求,从订舱编号取单号,所以这里不判断了 if(Regex.IsMatch(bkInfo.MBLNO,"\\bESL\\w+")) { _logger.LogInformation("批次={no} id={id} 主提单号已变更,无法更新", batchNo, bookingOrder.Id); throw Oops.Oh($"主提单号已变更,无法更新"); }*/ _logger.LogInformation("批次={no} 变更前记录 id={id} MBLNO={MBLNO} CUSTNO={CUSTNO} esl_no={eslno}", batchNo, bookingOrder.Id, bkInfo.MBLNO, bkInfo.CUSTNO, rlt.data.esl_no); //bkInfo.CUSTNO = bkInfo.MBLNO; bkInfo.MBLNO = rlt.data.esl_no.Trim().ToUpper(); bkInfo.UpdatedTime = DateTime.Now; bkInfo.UpdatedUserId = UserManager.UserId; bkInfo.UpdatedUserName = UserManager.Name; await _bookingOrderRepository.AsUpdateable(bkInfo).UpdateColumns(it => new { it.MBLNO, it.UpdatedTime, it.UpdatedUserId, it.UpdatedUserName }).ExecuteCommandAsync(); var _bookingOrderService = _namedBookingOrderServiceProvider.GetService(nameof(BookingOrderService)); // 记录日志 await _bookingOrderService.SaveLog(bkInfo, oldBkInfo, $"{bookingOrder.CARRIERID}舱位分配查询回写提单号"); result.succ = true; result.msg = "舱位分配查询成功"; } else { result.succ = false; result.msg = $"舱位分配查询失败,{rlt.message}"; } } catch (Exception ex) { result.succ = false; result.msg = $"舱位分配查询失败,{ex.Message}"; } return result; } #endregion /// /// 提交规则意见 /// /// 规则意见详情 /// 返回回执 [HttpPost("/BookingValueAdded/SubmitRuleOpinion")] public async Task SubmitRuleOpinion([FromBody] RulesEngineUserFeedBackDto model) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); string batchNo = IDGen.NextID().ToString(); DateTime nowDate = DateTime.Now; try { if (string.IsNullOrWhiteSpace(model.ruleName) && model.opinionType == "MODIFY") throw Oops.Oh($"规则名称不能为空"); if (string.IsNullOrWhiteSpace(model.opinionType)) throw Oops.Oh($"意见类型不能为空"); if (string.IsNullOrWhiteSpace(model.opinionContent)) throw Oops.Oh($"意见内容不能为空"); RulesEngineUserFeedBackMessageInfo msgModel = new RulesEngineUserFeedBackMessageInfo(); msgModel.Head = new RulesEngineUserFeedBackHead { GID = batchNo, MessageType = "BUSI_RULE", SenderId = App.Configuration["RulesEngineSender"], SenderName = App.Configuration["RulesEngineSenderName"], SenderKey = App.Configuration["RulesEngineAuthKey"], ReceiverId = "RulesEngine", ReceiverName = "大简云规则引擎", Version = "1.0", RequestDate = nowDate.ToString("yyyy-MM-dd HH:mm:ss"), RequestAction = "Add", }; msgModel.Main = new RulesEngineUserFeedBackMain { ruleName = model.ruleName, opinionType = model.opinionType, opinionContent = model.opinionContent, ruleNotice = model.ruleNotice, submitUser = UserManager.Name }; DateTime bDate = DateTime.Now; var rlt = await ExcuteRuleOpinion(App.Configuration["RulesEngineOpinionUrl"], msgModel, batchNo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} 请求完成,耗时:{timeDiff}ms. 结果{msg}", batchNo, timeDiff, rlt.succ ? "成功" : "失败"); if (!rlt.succ) { throw Oops.Oh($"请求失败,原因={rlt.msg}"); } result.succ = true; result.msg = "提交成功"; } catch (Exception ex) { result.succ = false; result.msg = $"提交规则意见失败,{ex.Message}"; _logger.LogInformation("批次={no} 异常,{msg}", batchNo, ex.Message); } return result; } /// /// 查询规则意见历史 /// /// 规则意见详情 /// 返回回执 [HttpPost("/BookingValueAdded/GetRuleOpinionLog")] public async Task GetRuleOpinionLog([FromBody] RulesEngineUserFeedBackDto model) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); string batchNo = IDGen.NextID().ToString(); DateTime nowDate = DateTime.Now; try { RulesEngineUserFeedBackMessageInfo msgModel = new RulesEngineUserFeedBackMessageInfo(); msgModel.Head = new RulesEngineUserFeedBackHead { GID = batchNo, MessageType = "BUSI_RULE", SenderId = App.Configuration["RulesEngineSender"], SenderName = App.Configuration["RulesEngineSenderName"], SenderKey = App.Configuration["RulesEngineAuthKey"], ReceiverId = "RulesEngine", ReceiverName = "大简云规则引擎", Version = "1.0", RequestDate = nowDate.ToString("yyyy-MM-dd HH:mm:ss"), RequestAction = "Add", }; msgModel.Main = new RulesEngineUserFeedBackMain { ruleName = model.ruleName, opinionType = model.opinionType, opinionContent = model.opinionContent, ruleNotice = model.ruleNotice, submitUser = UserManager.Name }; DateTime bDate = DateTime.Now; var rlt = await ExcuteRuleOpinion(App.Configuration["RulesEngineQueryOpinionUrl"], msgModel, batchNo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} 请求完成,耗时:{timeDiff}ms. 结果{msg}", batchNo, timeDiff, rlt.succ ? "成功" : "失败"); if (!rlt.succ) { throw Oops.Oh($"请求失败,原因={rlt.msg}"); } result.succ = true; result.msg = "查询成功"; if (rlt.rows != null) result.rows = JSON.Deserialize>(JSON.Serialize(rlt.rows)); } catch (Exception ex) { result.succ = false; result.msg = $"查询规则意见历史失败,{ex.Message}"; _logger.LogInformation("批次={no} 异常,{msg}", batchNo, ex.Message); } return result; } #region 规则用户反馈请求远端 /// /// 规则用户反馈请求远端 /// /// 请求URL /// 请求详情 /// 批次号 /// 返回结果 [NonAction] private async Task ExcuteRuleOpinion(string url, RulesEngineUserFeedBackMessageInfo info, string batchNo) { RulesEngineWebApiResult model = null; /* 1、填充请求的类,并生成JSON报文 2、POST请求接口,并记录回执。 3、返回信息。 */ try { _logger.LogInformation("批次={no} 对应请求报文 request={res}", batchNo, JSON.Serialize(info)); var res = await url.OnClientCreating(client => { // client 为 HttpClient 对象 client.Timeout = TimeSpan.FromMinutes(15); // 设置超时时间 15分钟 }).SetHttpMethod(HttpMethod.Post) .SetBody(JSON.Serialize(info), "application/json") .SetContentEncoding(Encoding.UTF8) .PostAsync(); _logger.LogInformation("批次={no} 对应请求报文完成 res={res}", batchNo, JSON.Serialize(res)); if (res.StatusCode == System.Net.HttpStatusCode.OK) { var userResult = await res.Content.ReadAsStringAsync(); _logger.LogInformation("批次={no} 对应请求报文完成 userResult={userResult}", batchNo, userResult); model = JSON.Deserialize(userResult); } } catch (Exception ex) { //写日志 if (ex is HttpRequestException) throw Oops.Oh(10000002); } return model; } #endregion /// /// 获取个人或公司网站账户配置 /// /// 账户类型代码 /// 用户ID /// 租户ID /// 返回账户配置 private async Task GetAccountConfig(string typeCode, long userId, long tendId) { DjyWebsiteAccountConfig accountConfig = new DjyWebsiteAccountConfig(); accountConfig = await _djyWebsiteAccountConfigRepository.EntityContext.CopyNew().Queryable() .FirstAsync(x => x.TypeCode == typeCode && x.CreatedUserId == userId); if (accountConfig == null) { accountConfig = await _djyWebsiteAccountConfigRepository.EntityContext.CopyNew().Queryable() .FirstAsync(x => x.TypeCode == typeCode && x.TenantId == tendId && x.IsTenant == true); } return accountConfig; } /// /// 单票BC文件解析 /// /// 上传文件 /// 订舱主键 /// 返回回执 [HttpPost("/BookingValueAdded/SingleBCFileRead")] public async Task SingleBCFileRead(IFormFile file, [FromForm] long bookingOrderId) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); string batchNo = IDGen.NextID().ToString(); SingleBCDto singleBCDto = null; try { /* 1、推送附件到识别接口,等待识别结果。 2、解析识别结果,并返回BC的解析明细。 */ if (bookingOrderId < 0) throw Oops.Oh($"订舱主键不能为空"); var bookingOrder = _bookingOrderRepository.AsQueryable().First(a => a.Id == bookingOrderId); if (bookingOrder == null) throw Oops.Oh($"订舱信息获取失败,订舱信息不存在或已作废"); //获取解析BC文件链接 var bcUrl = _cache.GetAllDictData().GetAwaiter().GetResult() .FirstOrDefault(x => x.TypeCode == "url_set" && x.Code.Equals(CONST_BC_FILE_PARSE_URL, StringComparison.OrdinalIgnoreCase))?.Value; _logger.LogInformation("提单号{mbl} 获取解析BC文件URL完成,结果={rlt}", bookingOrder.MBLNO, bcUrl); if (string.IsNullOrWhiteSpace(bcUrl)) { _logger.LogInformation("提单号{0} 获取解析BC文件URL失败,未取到配置key={key}", bookingOrder.MBLNO, CONST_BC_FILE_PARSE_URL); throw Oops.Bah($"未配置请求地址{CONST_BC_FILE_PARSE_URL} 请联系管理员"); } byte[] bytes = file.ToByteArray(); DateTime bDate = DateTime.Now; //请求BC解析 var bcParseRlt = await TransmitFile(bcUrl, new { file = "file", fileName = file.FileName, fileBytes = bytes }); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} result={result} 获取解析BC文件URL结束 耗时:{timeDiff}ms. ", batchNo, JSON.Serialize(bcParseRlt), timeDiff); if (bcParseRlt.status == 1) { var currBC = bcParseRlt.data.BCList.FirstOrDefault(); if (!string.IsNullOrWhiteSpace(currBC.Vessel) && Regex.IsMatch(currBC.Vessel, "\\(|(|\\)|)|\\n")) { if (Regex.IsMatch(currBC.Vessel, "\\(|(|\\)|)")) { currBC.Vessel = Regex.Match(currBC.Vessel, "\\w+\\s?\\n?\\s?\\w+(?=\\()").Value; } if (Regex.IsMatch(currBC.Vessel, "\\n")) { currBC.Vessel = Regex.Replace(Regex.Replace(currBC.Vessel, "\\n", " "), "\\s{2,}", " "); } } singleBCDto = new SingleBCDto { BookingOrderId = bookingOrderId, BLNo = currBC.BLNo?.Trim().ToUpper(), Vessel = currBC.Vessel?.Trim().ToUpper(), Voyage = currBC.Voyage?.Trim().ToUpper(), ClosingDate = currBC.CutSingleTime, CYCutOffTime = currBC.CYCutOffTime, VGMCutOffTime = currBC.VGMCutOffTime, ETD = currBC.ETD }; singleBCDto.BookingOrderBCDto = new BookingOrderBCDto { MBLNO = bookingOrder.MBLNO, CUSTNO = bookingOrder.CUSTNO, CLOSEDOCDATE = bookingOrder.CLOSEDOCDATE, CLOSEVGMDATE = bookingOrder.CLOSEVGMDATE, CLOSINGDATE = bookingOrder.CLOSINGDATE, ETD = bookingOrder.ETD, VESSEL = bookingOrder.VESSEL, VOYNO = bookingOrder.VOYNO, }; /* 这里考虑后面会有将文件写入附件表的动作,在识别成功后将文件写入暂存路径,后续完成放舱讲文件写入正式链接 */ //读取文件配置 var fileFullName = await FileAttachHelper.TempSaveWebFile(bookingOrder.Id.ToString(), file, batchNo); _logger.LogInformation("批次={no} 完成文件保存 filepath={path}", batchNo, fileFullName); singleBCDto.FileTempPath = fileFullName; result.succ = true; result.ext = singleBCDto; } else { result.succ = false; result.msg = $"解析BC失败,原因:{bcParseRlt.message}"; } } catch (Exception ex) { _logger.LogInformation("批次={no} 解析BC异常,原因:{error} ", batchNo, ex.Message); result.succ = false; result.msg = $"解析BC异常,原因:{ex.Message}"; } return result; } #region 请求BC解析 /// /// 请求BC解析 /// /// 请求地址 /// 文件详情 /// 返回BC解析详情 private async Task TransmitFile(string requestUrl, dynamic fileInfo) { BCAPIBaseDataParse model = null; try { var response = await requestUrl.SetContentType("multipart/form-data") .SetBodyBytes((fileInfo.file.ToString(), fileInfo.fileBytes, HttpUtility.UrlEncode(fileInfo.fileName.ToString()))) .PostAsync(); if (response.StatusCode == System.Net.HttpStatusCode.OK) { var result = response.Content.ReadAsStringAsync().Result; if (string.IsNullOrWhiteSpace(result)) { throw Oops.Bah($"获取解析BC文件失败,未获取到有效信息"); } System.Text.Json.JsonSerializerOptions jsonOptions = new JsonSerializerOptions(); jsonOptions.Converters.Add(new DateTimeJsonConverter("yyyy-MM-dd HH:mm:ss")); jsonOptions.Converters.Add(new IntegerJsonConverter()); jsonOptions.Converters.Add(new DecimalJsonConverter()); model = JSON.Deserialize(result, jsonOptions); } } catch (Exception ex) { _logger.LogInformation("{name} 发送BC文件解析请求 url={url} 异常,原因={error}", nameof(TransmitFile), requestUrl, ex.Message); throw Oops.Bah($"{nameof(TransmitFile)} {requestUrl} 发送BC文件解析请求异常,{ex.Message}"); } return model; } #endregion #region 单票BC更新订舱 /// /// 单票BC更新订舱 /// /// 单票BC详情 /// 返回回执 [HttpPost("/BookingValueAdded/SingleBCUpdateBookingOrder")] public async Task SingleBCUpdateBookingOrder(SingleBCDto model) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); string batchNo = IDGen.NextID().ToString(); try { /* 1、根据BC的数据更新订舱相关信息(提单号、截港时间、截单时间、船名航次、开船日期) 2、如果选择了转为入货通知,则先保存入货通知。 3、调取入货通知的发送。 */ var bookingOrder = _bookingOrderRepository.AsQueryable().First(a => a.Id == model.BookingOrderId); if (bookingOrder == null) throw Oops.Bah($"订舱信息获取失败,订舱信息不存在或已作废"); var updateRlt = InnerBCUpdateBookingOrder(model, bookingOrder, batchNo, 1).GetAwaiter().GetResult(); if (!updateRlt.succ) throw Oops.Bah($"订舱信息更新失败,{updateRlt.msg}"); if (model.IsLetterYard) { _logger.LogInformation("批次={no} id={id} 单票BC更新订舱后,用户选择转为入货通知,开始执行入货通知", batchNo , bookingOrder.Id); if (string.IsNullOrWhiteSpace(model.FileTempPath)) { _logger.LogInformation("批次={no} id={id} 未提交文件路径,请求失败", batchNo , bookingOrder.Id); throw Oops.Bah($"未提交文件路径,执行放舱失败"); } var letterYardDto = new UpdateBookingLetteryardInput(); letterYardDto = model.LetteryardDto.Adapt(); if (model.LetterYardId.HasValue && model.LetterYardId.Value > 0) { letterYardDto.Id = model.LetterYardId.Value; _logger.LogInformation("批次={no} id={id} 单票BC更新订舱后,存在放舱记录 LetterYardId={LetterYardId}", batchNo , bookingOrder.Id, letterYardDto.Id); } else { var letterYardModel = _bookingLetteryardRepository.AsQueryable() .First(x => x.BookingId == model.BookingOrderId); if (letterYardModel != null) letterYardDto.Id = letterYardModel.Id; } var _bookingOrderService = _namedBookingOrderServiceProvider.GetService(nameof(BookingOrderService)); //放舱保存 var letterYardId = await _bookingOrderService.LetteryardSave(letterYardDto); _logger.LogInformation("批次={no} id={id} 已完放舱记录保存 返回结果{rlt}", batchNo , bookingOrder.Id, letterYardId); _logger.LogInformation("批次={no} id={id} templateid={tempid} 开始发送放舱", batchNo , bookingOrder.Id, model.TemplateId); //发送放舱 await _bookingOrderService.SendLetterYard(model.BookingOrderId, model.TemplateId); _logger.LogInformation("批次={no} id={id} templateid={tempid} 完成发送放舱", batchNo , bookingOrder.Id, model.TemplateId); string fileTypeCode = "bc"; string fileTypeName = "Booking Confirmation"; //重新将暂存文件写入正式路径 //读取文件配置 var bookFilePath = await FileAttachHelper.MoveFile(bookingOrder.Id.ToString(), model.FileTempPath, batchNo); //将BC引入的文件写入订舱的附件 await SaveEDIFile(bookingOrder.Id, bookFilePath, new System.IO.FileInfo(bookFilePath).Name, fileTypeCode, fileTypeName); _logger.LogInformation("批次={no} id={id} 完成写入附件表 {filepath}", batchNo , bookingOrder.Id, model.FileTempPath); } result.succ = true; result.msg = "执行成功"; } catch (Exception ex) { result.succ = false; result.msg = ex.Message; } return result; } #endregion #region 单票BC更新订舱信息 /// /// 单票BC更新订舱信息 /// /// BC详情 /// 订舱详情 /// 批次号 /// 顺序号 /// 返回回执 private async Task InnerBCUpdateBookingOrder(SingleBCDto model, BookingOrder bookingOrder, string batchNo, int sortNo) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); result.bno = bookingOrder.MBLNO; var oldBookingOrder = bookingOrder.Adapt(); try { if (string.IsNullOrWhiteSpace(result.bno)) { if (!string.IsNullOrWhiteSpace(bookingOrder.CUSTNO)) { result.bno = $"订 {bookingOrder.CUSTNO}"; } else { result.bno = $"NO.{sortNo}"; } } //截单日期 if (model.ClosingDate.HasValue) { _logger.LogInformation("批次={no} id={id} 更新截单日期 原:{date1} 变更为 {date2}", batchNo, bookingOrder.Id, bookingOrder.CLOSEDOCDATE, model.ClosingDate); bookingOrder.CLOSEDOCDATE = model.ClosingDate; } //截VGM时间 if (model.VGMCutOffTime.HasValue) { _logger.LogInformation("批次={no} id={id} 更新截VGM日期 原:{date1} 变更为 {date2}", batchNo, bookingOrder.Id, bookingOrder.CLOSEVGMDATE, model.VGMCutOffTime); bookingOrder.CLOSEVGMDATE = model.VGMCutOffTime; } //截港日期 if (model.CYCutOffTime.HasValue) { _logger.LogInformation("批次={no} id={id} 更新截港日期 原:{date1} 变更为 {date2}", batchNo, bookingOrder.Id, bookingOrder.CLOSINGDATE, model.CYCutOffTime); bookingOrder.CLOSINGDATE = model.CYCutOffTime; } //开船日期 if (model.ETD.HasValue) { _logger.LogInformation("批次={no} id={id} 更新截港日期 原:{date1} 变更为 {date2}", batchNo, bookingOrder.Id, bookingOrder.ETD, model.ETD); bookingOrder.ETD = model.ETD; } //船名 if (!string.IsNullOrWhiteSpace(model.Vessel)) { _logger.LogInformation("批次={no} id={id} 更新船名 原:{date1} 变更为 {date2}", batchNo, bookingOrder.Id, bookingOrder.VESSEL, model.Vessel); bookingOrder.VESSEL = model.Vessel; } //航次 if (!string.IsNullOrWhiteSpace(model.Voyage)) { _logger.LogInformation("批次={no} id={id} 更新航次 原:{date1} 变更为 {date2}", batchNo, bookingOrder.Id, bookingOrder.VOYNO, model.Voyage); bookingOrder.VOYNO = model.Voyage; } //提单号 if (!string.IsNullOrWhiteSpace(model.BLNo)) { _logger.LogInformation("批次={no} id={id} 更新提单号 原:{date1} 变更为 {date2}", batchNo, bookingOrder.Id, bookingOrder.MBLNO, model.BLNo); bookingOrder.MBLNO = model.BLNo; } bookingOrder.UpdatedTime = DateTime.Now; bookingOrder.UpdatedUserId = UserManager.UserId; bookingOrder.UpdatedUserName = UserManager.Name; await _bookingOrderRepository.AsUpdateable(bookingOrder).UpdateColumns(it => new { it.UpdatedTime, it.UpdatedUserId, it.UpdatedUserName, it.CLOSEDOCDATE, it.CLOSEVGMDATE, it.CLOSINGDATE, it.ETD, it.VESSEL, it.VOYNO, it.MBLNO }).ExecuteCommandAsync(); var _bookingOrderService = _namedBookingOrderServiceProvider.GetService(nameof(BookingOrderService)); // 记录日志 await _bookingOrderService.SaveLog(bookingOrder, oldBookingOrder, "引入BC更新订舱信息"); _logger.LogInformation("批次={no} id={id} BC引入更新订舱完成", batchNo, bookingOrder.Id); result.succ = true; result.msg = "执行成功"; } catch (Exception ex) { result.succ = false; result.msg = ex.Message; } return result; } #endregion #region 批量BC更新订舱 /// /// 批量BC更新订舱 /// /// 批量BC更新列表 /// 返回回执 [HttpPost("/BookingValueAdded/BatchBCUpdate")] public async Task BatchBCUpdate([FromBody] List batchBCList) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); List> taskList = new List>(); string batchNo = IDGen.NextID().ToString(); try { var bcTaskList = batchBCList.Select(a => a.bcPKId).Distinct().ToList(); var bkOrderList = batchBCList.Select(a => a.bkOrderId.Value).Distinct().ToList(); var bklist = _bookingOrderRepository.AsQueryable() .Where(a => bkOrderList.Contains(a.Id)).ToList(); var bclist = _taskBCInfoRepository.AsQueryable() .Where(a => bcTaskList.Contains(a.PK_ID)).ToList(); if (bklist.Count != bkOrderList.Count) throw Oops.Oh($"订舱信息获取失败,订舱信息不存在或已作废"); var noList = bklist.Select((a, idx) => new { no = idx + 1, id = a.Id }).ToList(); foreach (var bk in bklist) { var sortNo = noList.FirstOrDefault(a => a.id == bk.Id).no; var currBC = batchBCList.Join(bclist, l => l.bcPKId, r => r.PK_ID, (l, r) => { return r; }).FirstOrDefault(); SingleBCDto singleBCDto = new SingleBCDto { BLNo = currBC.MBL_NO, Vessel = currBC.VESSEL, Voyage = currBC.VOYNO, ETD = currBC.ETD, CYCutOffTime = currBC.CY_CUTOFF_TIME, VGMCutOffTime = currBC.VGM_CUTOFF_TIME, ClosingDate = currBC.CUT_SINGLE_TIME }; taskList.Add(Task.Run(() => InnerBCUpdateBookingOrder(singleBCDto, bk, batchNo, sortNo))); } Task.WaitAll(taskList.ToArray()); result.succ = true; result.msg = "批量执行成功"; var downResultList = taskList.Select(x => x.Result).ToList(); if (downResultList.Any(x => !x.succ)) { result.succ = false; result.msg = "批量执行失败"; } else { result.succ = true; result.msg = downResultList.FirstOrDefault().msg; } result.ext = downResultList; var succ = downResultList.Count(x => x.succ); var fail = downResultList.Count(x => !x.succ); if (succ > 0) { result.batchTotal = succ.ToString(); } else { result.batchTotal = "- "; } if (fail > 0) { result.batchTotal += "/" + fail.ToString(); } else { result.batchTotal += " -"; } } catch (Exception ex) { result.succ = false; result.msg = $"批量执行失败,原因:{ex.Message}"; } return result; } #endregion /// /// 获取订舱数据接口 备注 、 文件 、服务项目 /// /// 订舱ID /// 返回服务状态列表 [HttpGet("/BookingValueAdded/GetAllDataVNTWO")] public async Task GetAllDataVNTWO([FromQuery] long bookingId) { BookingAttachedDataDto resultDto = new BookingAttachedDataDto(); if (bookingId == 0) { throw Oops.Oh($"订舱ID不能为空"); } var orderInfo = _bookingOrderRepository.AsQueryable().Filter(null, true).First(a => a.Id == bookingId); //订舱备注列表 var remarklist = await _bookingRemarkRepository.AsQueryable().Where(u => u.PId == bookingId) .ToListAsync(); resultDto.remark = remarklist; //订舱附件列表 var filelist = await _bookingFileRepository.AsQueryable().Filter(null, true) .Where(u => u.BookingId == bookingId).ToListAsync(); resultDto.file = filelist; var projectList = await _serviceWorkFlowBaseService.GetEnableProjectList(orderInfo.TenantId.Value.ToString()); if (projectList != null && projectList.Count > 0) { //resultDto = BookingServiceItem } return resultDto; } #region 获取订舱数据接口 日志、状态日志 /// /// 获取订舱数据接口 日志、状态日志 /// /// 订舱ID /// 返回日志详情 [HttpGet("/BookingValueAdded/GetAllLogDataVNTWO")] public async Task GetAllLogDataVNTWO([FromQuery] long bookingId) { BookingLogDataDto resultDto = new BookingLogDataDto(); var statusLogList = _statuslogRepository.AsQueryable().Filter(null, true) .LeftJoin((a, b) => a.Id == b.PId) .Where((a, b) => a.BookingId == bookingId) .Select((a, b) => new { Base = a, Detail = b }) .ToList(); if (statusLogList.Count > 0) { resultDto.statuslog = statusLogList.OrderByDescending(a => a.Base.OpTime) .GroupBy(a => a.Base.Id) .Select(a => { var currList = a.ToList(); var baseDto = currList.FirstOrDefault().Base.Adapt(); if (currList.Any(b => b.Detail != null)) { baseDto.detail = currList.Where(b => b.Detail != null) .Select(b => b.Detail.Adapt()).ToList(); } return baseDto; }).ToList(); } var logList = _bookinglogRepository.AsQueryable() .LeftJoin((a, b) => a.Id == b.PId) .Where((a, b) => a.BookingId == bookingId) .Select((a, b) => new { Base = a, Detail = b }) .ToList(); if (logList.Count > 0) { resultDto.log = logList.GroupBy(a => a.Base.Id) .Select(a => { var currList = a.ToList(); var baseDto = currList.FirstOrDefault().Base.Adapt(); if (currList.Any(b => b.Detail != null)) { baseDto.details = currList.Where(b => b.Detail != null) .Select(b => b.Detail).ToList(); } return baseDto; }).ToList(); } return resultDto; } #endregion #region 保存服务项目 /// /// 保存服务项目 /// /// 修改服务项目详情 /// 返回回执 [HttpPost("/BookingValueAdded/SaveServiceProject")] public async Task SaveServiceProject([FromBody] ModifyServiceProjectDto model) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); string batchNo = IDGen.NextID().ToString(); try { var bookingOrder = _bookingOrderRepository.AsQueryable().Filter(null, true) .First(a => a.Id == model.BookingId); if (bookingOrder == null) throw Oops.Oh($"订舱信息获取失败,订舱信息不存在或已作废"); _logger.LogInformation("批次={no} 请求保存服务项目 modifyjson={msg}", batchNo, JSON.Serialize(model)); TrackingMessageInfo msgInfo = new TrackingMessageInfo { Head = new TrackingMessageHeadInfo { GID = IDGen.NextID().ToString(), MessageType = "PROJECT", ReceiverId = "ServiceProjectStatus", ReceiverName = "服务项目和状态", SenderId = "BookingOrder", SenderName = "海运订舱", RequestDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), Version = "2.0", RequestAction = "AddOrModify", }, Main = new TrackingMessageMainInfo { BusiId = model.BookingId.ToString(), BusiSystemCode = "BOOKING_ORDER", MBlNo = bookingOrder.MBLNO, VesselVoyno = $"{bookingOrder.VESSEL}/{bookingOrder.VOYNO}", OrderNo = bookingOrder.BSNO, PushType = TrackingPushTypeEnum.Project, OperTenantId = bookingOrder.TenantId.Value, OperTenantName = bookingOrder.TenantName, OpertType = TrackingOperTypeEnum.MANUAL, OperUserId = UserManager.UserId.ToString(), OperUserName = UserManager.Name, SourceType = TrackingSourceTypeEnum.MANUAL, ProjectList = model.ProjectCodes.Select(a => new TrackingMessageMainProjectInfo { ServiceProjectCode = a, }).ToList() } }; DateTime bDate = DateTime.Now; _logger.LogInformation("批次={no} 推送保存服务项目 msg={msg}", batchNo, JSON.Serialize(msgInfo)); var rlt = await _serviceWorkFlowManageService.PushStatus(msgInfo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} 请求完成,耗时:{timeDiff}ms. 结果{msg} result={rlt}", batchNo, timeDiff, (rlt.succ ? "成功" : "失败") , JSON.Serialize(rlt)); if (!rlt.succ) { result = rlt; } else { result.succ = true; result.msg = "保存成功"; } } catch (Exception ex) { result.succ = false; result.msg = $"服务项目保存失败,原因:{ex.Message}"; } return result; } #endregion #region 取消服务项目 /// /// 取消服务项目 /// /// 修改服务项目详情 /// 返回回执 [HttpPost("/BookingValueAdded/CancelServiceProject")] public async Task CancelServiceProject([FromBody] ModifyServiceProjectDto model) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); string batchNo = IDGen.NextID().ToString(); try { var bookingOrder = _bookingOrderRepository.AsQueryable().Filter(null, true) .First(a => a.Id == model.BookingId); if (bookingOrder == null) throw Oops.Oh($"订舱信息获取失败,订舱信息不存在或已作废"); _logger.LogInformation("批次={no} 请求保存服务项目 modifyjson={msg}", batchNo, JSON.Serialize(model)); TrackingMessageInfo msgInfo = new TrackingMessageInfo { Head = new TrackingMessageHeadInfo { GID = IDGen.NextID().ToString(), MessageType = "PROJECT", ReceiverId = "ServiceProjectStatus", ReceiverName = "服务项目和状态", SenderId = "BookingOrder", SenderName = "海运订舱", RequestDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), Version = "2.0", RequestAction = "AddOrModify", }, Main = new TrackingMessageMainInfo { BusiId = model.BookingId.ToString(), BusiSystemCode = "BOOKING_ORDER", MBlNo = bookingOrder.MBLNO, VesselVoyno = $"{bookingOrder.VESSEL}/{bookingOrder.VOYNO}", OrderNo = bookingOrder.BSNO, PushType = TrackingPushTypeEnum.Project, OperTenantId = bookingOrder.TenantId.Value, OperTenantName = bookingOrder.TenantName, OpertType = TrackingOperTypeEnum.MANUAL, OperUserId = UserManager.UserId.ToString(), OperUserName = UserManager.Name, SourceType = TrackingSourceTypeEnum.MANUAL, ProjectList = model.ProjectCodes.Select(a => new TrackingMessageMainProjectInfo { ServiceProjectCode = a, }).ToList() } }; DateTime bDate = DateTime.Now; _logger.LogInformation("批次={no} 推送取消服务项目 msg={msg}", batchNo, JSON.Serialize(msgInfo)); var rlt = await _serviceWorkFlowManageService.CancelStatus(msgInfo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} 请求完成,耗时:{timeDiff}ms. 结果{msg} result={rlt}", batchNo, timeDiff, (rlt.succ ? "成功" : "失败") , JSON.Serialize(rlt)); if (!rlt.succ) { result = rlt; } else { result.succ = true; result.msg = "取消成功"; } } catch (Exception ex) { result.succ = false; result.msg = $"服务项目取消失败,原因:{ex.Message}"; } return result; } #endregion /// /// 保存服务状态 /// /// 修改服务状态详情 /// 返回回执 [HttpPost("/BookingValueAdded/SaveServiceStatus")] public async Task SaveServiceStatus([FromBody] ModifyServiceProjectStatusDto model) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); string batchNo = IDGen.NextID().ToString(); try { var bookingOrder = _bookingOrderRepository.AsQueryable().Filter(null, true) .First(a => a.Id == model.BookingId); if (bookingOrder == null) throw Oops.Oh($"订舱信息获取失败,订舱信息不存在或已作废"); _logger.LogInformation("批次={no} 请求保存服务项目 modifyjson={msg}", batchNo, JSON.Serialize(model)); TrackingMessageInfo msgInfo = new TrackingMessageInfo { Head = new TrackingMessageHeadInfo { GID = IDGen.NextID().ToString(), MessageType = "PROJECT", ReceiverId = "ServiceProjectStatus", ReceiverName = "服务项目和状态", SenderId = "BookingOrder", SenderName = "海运订舱", RequestDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), Version = "2.0", RequestAction = "AddOrModify", }, Main = new TrackingMessageMainInfo { BusiId = model.BookingId.ToString(), BusiSystemCode = "BOOKING_ORDER", MBlNo = bookingOrder.MBLNO, VesselVoyno = $"{bookingOrder.VESSEL}/{bookingOrder.VOYNO}", OrderNo = bookingOrder.CUSTNO, PushType = TrackingPushTypeEnum.Status, OperTenantId = bookingOrder.TenantId.Value, OperTenantName = bookingOrder.TenantName, OpertType = TrackingOperTypeEnum.MANUAL, OperUserId = UserManager.UserId.ToString(), OperUserName = UserManager.Name, SourceType = model.SourceType, StatusList = model.StatusCodes.Select(a => new TrackingMessageMainStatusInfo { StatusCode = a.StatusCode, StatusDate = a.SetActDate, StatusVal = a.SetActVal, Remark = a.ActRemark }).ToList() } }; DateTime bDate = DateTime.Now; _logger.LogInformation("批次={no} 推送保存服务项目 msg={msg}", batchNo, JSON.Serialize(msgInfo)); var rlt = await _serviceWorkFlowManageService.PushStatus(msgInfo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} 请求完成,耗时:{timeDiff}ms. 结果{msg} result={rlt}", batchNo, timeDiff, (rlt.succ ? "成功" : "失败") , JSON.Serialize(rlt)); if (!rlt.succ) { result = rlt; } else { result.succ = true; result.msg = "推送成功"; var _bookingOrderService = _namedBookingOrderServiceProvider.GetService(nameof(BookingOrderService)); await _bookingOrderService.SetBookingGoodsStatus(bookingOrder.Id, true, "手动更新货物状态"); } } catch (Exception ex) { result.succ = false; result.msg = $"服务项目状态推送失败,原因:{ex.Message}"; } return result; } /// /// 取消服务状态 /// /// 修改服务状态详情 /// 返回回执 [HttpPost("/BookingValueAdded/CancelServiceStatus")] public async Task CancelServiceStatus([FromBody] ModifyServiceProjectStatusDto model) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); string batchNo = IDGen.NextID().ToString(); try { var bookingOrder = _bookingOrderRepository.AsQueryable().Filter(null, true) .First(a => a.Id == model.BookingId); if (bookingOrder == null) throw Oops.Oh($"订舱信息获取失败,订舱信息不存在或已作废"); _logger.LogInformation("批次={no} 请求保存服务项目 modifyjson={msg}", batchNo, JSON.Serialize(model)); TrackingMessageInfo msgInfo = new TrackingMessageInfo { Head = new TrackingMessageHeadInfo { GID = IDGen.NextID().ToString(), MessageType = "PROJECT", ReceiverId = "ServiceProjectStatus", ReceiverName = "服务项目和状态", SenderId = "BookingOrder", SenderName = "海运订舱", RequestDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), Version = "2.0", RequestAction = "AddOrModify", }, Main = new TrackingMessageMainInfo { BusiId = model.BookingId.ToString(), BusiSystemCode = "BOOKING_ORDER", MBlNo = bookingOrder.MBLNO, VesselVoyno = $"{bookingOrder.VESSEL}/{bookingOrder.VOYNO}", OrderNo = bookingOrder.BSNO, PushType = TrackingPushTypeEnum.Status, OperTenantId = bookingOrder.TenantId.Value, OperTenantName = bookingOrder.TenantName, OpertType = TrackingOperTypeEnum.MANUAL, OperUserId = UserManager.UserId.ToString(), OperUserName = UserManager.Name, SourceType = model.SourceType, StatusList = model.StatusCodes.Select(a => new TrackingMessageMainStatusInfo { StatusCode = a.StatusCode, StatusDate = a.SetActDate, StatusVal = a.SetActVal, Remark = a.ActRemark }).ToList() } }; DateTime bDate = DateTime.Now; _logger.LogInformation("批次={no} 推送保存服务项目 msg={msg}", batchNo, JSON.Serialize(msgInfo)); var rlt = await _serviceWorkFlowManageService.CancelStatus(msgInfo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} 请求完成,耗时:{timeDiff}ms. 结果{msg} result={rlt}", batchNo, timeDiff, (rlt.succ ? "成功" : "失败") , JSON.Serialize(rlt)); if (!rlt.succ) { result = rlt; } else { result.succ = true; result.msg = "推送成功"; var _bookingOrderService = _namedBookingOrderServiceProvider.GetService(nameof(BookingOrderService)); await _bookingOrderService.SetBookingGoodsStatus(bookingOrder.Id, true, "手动取消货物状态"); } } catch (Exception ex) { result.succ = false; result.msg = $"服务项目状态推送失败,原因:{ex.Message}"; } return result; } #region 获取服务项目列表 /// /// 获取服务项目列表 /// /// 查询服务项目和状态详情 /// 返回回执 [HttpPost("/BookingValueAdded/GetServiceProjectList")] public async Task GetServiceProjectList([FromBody] QueryServiceProjectWithStatus model) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); string batchNo = IDGen.NextID().ToString(); try { var bookingOrder = _bookingOrderRepository.AsQueryable().Filter(null, true) .First(a => a.Id == model.BookingId); if (bookingOrder == null) throw Oops.Oh($"订舱信息获取失败,订舱信息不存在或已作废"); DateTime bDate = DateTime.Now; QueryServiceProjectWithStatus queryInfo = new QueryServiceProjectWithStatus { BookingId = model.BookingId, QueryType = TrackingQueryTypeEnum.QUERY_SERVICE_PROJECT, TenantId = bookingOrder.TenantId.Value }; result = await _serviceWorkFlowManageService.GetEnableProjectList(queryInfo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} id={id} 单票请求服务项目结果 耗时:{timeDiff}ms. ", batchNo, model.BookingId, timeDiff); } catch (Exception ex) { result.succ = false; result.msg = $"获取服务项目列表失败,原因:{ex.Message}"; } return result; } #endregion /// /// 获取服务项目下的状态列表 /// /// 查询服务项目和状态详情 /// 返回回执 [HttpPost("/BookingValueAdded/GetServiceStatusList")] public async Task GetServiceStatusList([FromBody] QueryServiceProjectWithStatus model) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); string batchNo = IDGen.NextID().ToString(); try { //查询所有服务服务项目和状态时,需要先获取订舱详情 if (model.QueryType == TrackingQueryTypeEnum.QUERY_SERVICE_ALL) { DateTime bDate = DateTime.Now; var bookingOrder = _bookingOrderRepository.AsQueryable().Filter(null, true) .First(a => a.Id == model.BookingId); if (bookingOrder == null) throw Oops.Oh($"订舱信息获取失败,订舱信息不存在或已作废"); QueryServiceProjectWithStatus queryInfo = new QueryServiceProjectWithStatus { BookingId = model.BookingId, QueryType = TrackingQueryTypeEnum.QUERY_SERVICE_PROJECT, TenantId = bookingOrder.TenantId.Value }; result = await _serviceWorkFlowManageService.GetEnableStatusListByBusiness(queryInfo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} id={id} 单票请求服务状态结果 耗时:{timeDiff}ms. ", batchNo, model.BookingId, timeDiff); } else { if (model.ProjectCodes == null || (model.ProjectCodes != null && model.ProjectCodes.Length == 0)) throw Oops.Oh($"服务项目代码不能为空"); model.TenantId = UserManager.TENANT_ID; result = await _serviceWorkFlowManageService.GetEnableStatusListByProject(model); } } catch (Exception ex) { result.succ = false; result.msg = $"获取服务项目下的状态列表失败,原因:{ex.Message}"; } return result; } #region 推送东胜同步 /// /// 推送东胜同步 /// /// 订舱主键组 /// 返回回执 [HttpPost("/BookingValueAdded/SinglePushBKOrderSyncDS")] public async Task SinglePushBKOrderSyncDS([FromBody] long[] ids) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); string batchNo = IDGen.NextID().ToString(); try { var _bookingOrderService = _namedBookingOrderServiceProvider.GetService(nameof(BookingOrderService)); var rlt = await _bookingOrderService.SendBookingOrder(ids); if (rlt != null) { result.succ = true; result.msg = "同步完成"; } else { result.succ = false; result.msg = "同步失败"; } } catch (Exception ex) { result.succ = false; result.msg = $"推送东胜同步失败,原因:{ex.Message}"; } return result; } #endregion #region 获取订舱对应的集装箱VGM回执详情 /// /// 获取订舱对应的集装箱VGM回执详情 /// /// 订舱ID /// 返回VGM明细 [HttpGet("/BookingValueAdded/GetBookingVGMResult")] public async Task> GetBookingVGMResult(long id) { List list = new List(); if (id < 1) throw Oops.Oh($"订舱ID为空"); var vgmList = _bookingCtnVGMRepository.AsQueryable().Where(t => t.BILLID == id).ToList(); if (vgmList.Count > 0) list = vgmList.Select(t => new BookingCtnVGMDto { id = t.Id, cntrNo = t.CNTRNO, vgmWeight = t.VGM_WEIGHT, vgmWeightMethod = t.VGM_METHOD, vgmWeightUnit = t.VGM_WEIGHT_UNIT, recvDate = t.REC_TIME, isMatch = t.IS_MATCH, isMissing = t.IS_MISSING, submissionDeadLine = t.SUBMISSION_DEADLINE }).ToList(); return list; } #endregion /// /// 单票正本下载 /// 1=正本下载 2=副本下载 /// [HttpGet("/BookingValueAdded/DownloadOriginal")] public async Task DownloadOriginal(long id, bool? isCheck, int downloadType = 1) { string fileTypeCode = downloadType switch { 1 => "zhengben", 2 => "fuben", _ => throw new NotImplementedException() }; string fileTypeName = downloadType switch { 1 => "正本", 2 => "副本", _ => throw new NotImplementedException() }; TaskManageOrderResultDto result = new TaskManageOrderResultDto(); if (isCheck == true) { if (await _bookingFileRepository.IsExistsAsync(x => x.BookingId == id && x.TypeCode == fileTypeCode)) { result.status = -1; result.succ = false; result.msg = "文件已存在"; return result; } } try { if (id <= 0) throw Oops.Oh($"订舱ID为空"); var order = await _bookingOrderRepository.AsQueryable().FirstAsync(a => a.Id == id); if (order == null) throw Oops.Oh($"订舱信息获取失败(订舱信息不存在或已作废)"); result.bno = order.MBLNO; var allSysConfig = _cache.GetAllSysConfig().Result; var url = allSysConfig.FirstOrDefault(x => x.Code == "DraftDownloadServiceUrl")?.Value; var key = allSysConfig.FirstOrDefault(x => x.Code == "DraftDownloadServiceKey")?.Value; var token = allSysConfig.FirstOrDefault(x => x.Code == "DraftDownloadServiceToken")?.Value; if (string.IsNullOrWhiteSpace(url)) { throw Oops.Bah("单据下载服务接口地址未配置,请联系管理员"); } if (string.IsNullOrWhiteSpace(key)) { throw Oops.Bah("单据下载服务接口Key未配置,请联系管理员"); } if (string.IsNullOrWhiteSpace(token)) { throw Oops.Bah("单据下载服务接口Token未配置,请联系管理员"); } var webAccount = _webAccountConfigService.GetAccountConfig("MSKWeb", UserManager.UserId).Result; if (webAccount == null) { throw Oops.Bah("未配置网站账户,类型:MSKWeb"); } if (string.IsNullOrWhiteSpace(order.ISSUETYPE)) { throw Oops.Bah("签单方式为空"); } if (string.IsNullOrWhiteSpace(order.CARRIERID)) { throw Oops.Bah("船公司为空"); } var mappingCarrier = _cache.GetAllMappingCarrier().Result; var mappingIssueType = _cache.GetAllMappingIssueType().Result; // 判断船公司是否符合条件 string carrierId = mappingCarrier.FirstOrDefault(x => x.Module == "ORIGINAL_DOWN_RT" && x.Code == order.CARRIERID)?.MapCode; carrierId = (carrierId ?? order.CARRIERID).ToUpper(); if (carrierId != "MSK") { throw Oops.Bah($"船公司=[{order.CARRIER}]暂不支持{fileTypeName}下载"); } // 判断签单方式是否符合条件 string issueType = mappingIssueType.FirstOrDefault(x => x.Module == "ORIGINAL_DOWN_RT" && x.Code == order.ISSUETYPE)?.MapCode; issueType = issueType ?? order.ISSUETYPE; if (!issueType.Equals("original", StringComparison.OrdinalIgnoreCase)) { throw Oops.Bah($"此单签单方式非[正本],无法下载"); } // 实时下载接口 url += downloadType switch { 1 => "/documents_server/api/bill_lading/v1/download_realtime", 2 => "/documents_server/api/copy/v1/download_realtime", _ => throw new NotImplementedException() }; var body = new { web_code = carrierId, web_user = webAccount.Account, web_pwd = webAccount.Password, bill_no = order.MBLNO, consumer_key = key, iscontent = false, }; var header = new Dictionary() { { "Token",token }, { "Content-Type", "application/json" } }; _logger.LogInformation("请求fileTypeName={fileTypeName}下载接口开始,mblno={mblno}, url={url}, body={body}", fileTypeName, order.MBLNO, url, body); DateTime bDate = DateTime.Now; var rtn = await url.OnClientCreating(client => { client.Timeout = TimeSpan.FromMinutes(3); // 设置超时时间 3分钟 }).SetBody(body).SetHeaders(header).PostAsStringAsync(); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("请求fileTypeName={fileTypeName}下载接口结束,mblno={mblno}, rtn={rtn}, 耗时:{timeDiff}ms", fileTypeName, order.MBLNO, rtn, timeDiff); var jsonRtn = JObject.Parse(rtn); if (jsonRtn.GetIntValue("status") == 1) { _logger.LogInformation("请求fileTypeName={fileTypeName}下载接口成功,转存本地,mblno={mblno}", fileTypeName, order.MBLNO); var data = jsonRtn.GetJObjectValue("data"); // 远程文件路径 var remoteFilePath = data.GetStringValue("proxy_path"); // 真实文件名 var reallyFileName = data.GetStringValue("file_name"); // 读取文件保存配置 var fileCfg = App.GetOptions(); // 实时下载接口 string floderName = downloadType switch { 1 => "originalfiles", 2 => "copyfiles", _ => throw new NotImplementedException() }; string relativePath = $"{fileCfg.relativePath}\\{floderName}\\{order.Id}"; string relativeDic = $"{(!string.IsNullOrWhiteSpace(fileCfg.basePath) ? fileCfg.basePath : App.WebHostEnvironment.WebRootPath)}\\{relativePath}"; string fileSavePath = $"{relativeDic}\\{new System.IO.FileInfo(remoteFilePath).Name}"; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { relativePath = relativePath.Replace("\\", "/"); relativeDic = relativeDic.Replace("\\", "/"); fileSavePath = fileSavePath.Replace("\\", "/"); } _logger.LogInformation("mblno={mblno}, 生成文件保存路径完成 路由={relativeDic} 服务器系统={system}", order.MBLNO, relativeDic, RuntimeInformation.OSDescription); //预先创建目录 if (!Directory.Exists(relativeDic)) { Directory.CreateDirectory(relativeDic); } var bcStream = await remoteFilePath.GetAsStreamAsync(); using (var fileStream = File.Create(fileSavePath)) { await bcStream.CopyToAsync(fileStream); } _logger.LogInformation("mblno={mblno} 完成文件保存 filepath={path}", order.MBLNO, fileSavePath); string bookFilePath = string.Empty; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { bookFilePath = System.Text.RegularExpressions.Regex.Match(fileSavePath, relativePath.Replace("/", "\\/") + ".*").Value; } else { bookFilePath = System.Text.RegularExpressions.Regex.Match(fileSavePath, relativePath.Replace("\\", "\\\\") + ".*").Value; } result.succ = true; result.msg = $"{fileTypeName}下载成功"; await _bookingFileRepository.DeleteAsync(x => x.TypeCode == fileTypeCode && x.BookingId == order.Id); await SaveEDIFile(order.Id, bookFilePath, reallyFileName, fileTypeCode, fileTypeName); } else { var msg = jsonRtn.GetStringValue("message")?.Replace("爬虫", ""); _logger.LogInformation("请求fileTypeName={fileTypeName}下载接口失败,mblno={mblno}, 服务返回:{msg}", fileTypeName, order.MBLNO, msg); throw Oops.Bah(msg); } } catch (Exception ex) { result.succ = false; result.status = -2; result.msg = $"{fileTypeName}下载失败,{ex.Message}"; } return result; } } public class DateTimeJsonConverter : System.Text.Json.Serialization.JsonConverter> { private readonly string _dateFormatString; public DateTimeJsonConverter() { _dateFormatString = "yyyy-MM-dd HH:mm:ss"; } public DateTimeJsonConverter(string dateFormatString) { _dateFormatString = dateFormatString; } public override Nullable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { DateTime currDate = DateTime.MinValue; if (DateTime.TryParse(reader.GetString(), out currDate)) return currDate; return null; } public override void Write(Utf8JsonWriter writer, Nullable value, JsonSerializerOptions options) { if (value.HasValue) writer.WriteStringValue(value.Value.ToUniversalTime().ToString(_dateFormatString)); else writer.WriteNullValue(); } } public class IntegerJsonConverter : System.Text.Json.Serialization.JsonConverter { public IntegerJsonConverter() { } public override Int32 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.String) { var curStr = reader.GetString(); Int32 curVal = 0; Int32.TryParse(curStr, out curVal); return curVal; } else { return reader.GetInt32(); } } public override void Write(Utf8JsonWriter writer, Int32 value, JsonSerializerOptions options) { writer.WriteNumberValue(value); } } public class DecimalJsonConverter : System.Text.Json.Serialization.JsonConverter { public DecimalJsonConverter() { } public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.String) { var curStr = reader.GetString(); decimal curVal = 0; decimal.TryParse(curStr, out curVal); return curVal; } else { return reader.GetDecimal(); } } public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) { writer.WriteNumberValue(value); } } }