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 MathNet.Numerics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Myshipping.Application.ConfigOption; using Myshipping.Application.Entity; using Myshipping.Core; using Myshipping.Core.Entity; using Myshipping.Core.Service; using MySqlX.XDevAPI.Common; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Utilities; using SqlSugar; using StackExchange.Profiling.Internal; using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Net.NetworkInformation; using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Web; using Ubiety.Dns.Core; 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 _bookingfile; private readonly SqlSugarRepository _djyWebsiteAccountConfigRepository; private readonly SqlSugarRepository _sysUserRepository; private readonly SqlSugarRepository _bookingLetteryardRepository; private readonly SqlSugarRepository _taskBCInfoRepository; 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 bookingfile, SqlSugarRepository djyWebsiteAccountConfigRepository, SqlSugarRepository sysUserRepository, SqlSugarRepository bookingLetteryardRepository, IBookingOrderService bookingOrderService , SqlSugarRepository taskBCInfoRepository) { _cache = cache; _logger = logger; _bookingOrderRepository = bookingOrderRepository; _bookingfile = bookingfile; _djyWebsiteAccountConfigRepository = djyWebsiteAccountConfigRepository; _sysUserRepository = sysUserRepository; _bookingLetteryardRepository = bookingLetteryardRepository; _bookingOrderService = bookingOrderService; _taskBCInfoRepository = taskBCInfoRepository; } /// /// 批量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 _bookingfile.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、返回成功写入附件。 */ if (string.IsNullOrWhiteSpace(bookingOrder.MBLNO)) { if (!string.IsNullOrWhiteSpace(bookingOrder.CUSTNO)) { result.bno = $"订 {bookingOrder.CUSTNO}"; } else { result.bno = $"NO.{sortNo}"; } throw Oops.Bah($"主提单号不能为空"); } else { if (Regex.IsMatch(bookingOrder.MBLNO, "\\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.MBLNO, bookingOrder.CARRIERID, bcOrDraftRouteCfg); if (bcOrDraftRouteCfg == null) { _logger.LogInformation("提单号{mbl} 根据订舱的船公司代码{ca} 提取船公司映射失败", bookingOrder.MBLNO, 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.MBLNO, bookingOrder.CARRIERID, bcUrl); if (string.IsNullOrWhiteSpace(bcUrl)) { _logger.LogInformation("提单号{0} 根据订舱的船公司代码{1} 提取舱位分配查询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}"); ESLManiAlloChkRequestDto requestDto = new ESLManiAlloChkRequestDto { u = userWebAccountConfig.Account?.Trim(), p = userWebAccountConfig.Password?.Trim(), so_no = bookingOrder.MBLNO, }; _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($"订舱信息获取失败,无法更新"); } 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.CUSTNO, it.MBLNO, it.UpdatedTime, it.UpdatedUserId, it.UpdatedUserName }).ExecuteCommandAsync(); 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 /// /// 获取个人或公司网站账户配置 /// /// 账户类型代码 /// 用户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 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; 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(); _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 } 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); } } }