You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
BookingHeChuan/Myshipping.Application/Service/BookingOrder/BookingValueAddedService.cs

1804 lines
73 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
{
/// <summary>
/// 订舱增值类服务
/// </summary>
[ApiDescriptionSettings("Application", Name = "BookingValueAdded", Order = 9)]
public class BookingValueAddedService : IBookingValueAddedService, IDynamicApiController, ITransient
{
private readonly ISysCacheService _cache;
private readonly IBookingOrderService _bookingOrderService;
private readonly ILogger<BookingTruckService> _logger;
private readonly SqlSugarRepository<BookingOrder> _bookingOrderRepository;
private readonly SqlSugarRepository<BookingFile> _bookingfile;
private readonly SqlSugarRepository<DjyWebsiteAccountConfig> _djyWebsiteAccountConfigRepository;
private readonly SqlSugarRepository<SysUser> _sysUserRepository;
private readonly SqlSugarRepository<BookingLetteryard> _bookingLetteryardRepository;
private readonly SqlSugarRepository<TaskBCInfo> _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<BookingTruckService> logger,
SqlSugarRepository<BookingOrder> bookingOrderRepository, SqlSugarRepository<BookingFile> bookingfile,
SqlSugarRepository<DjyWebsiteAccountConfig> djyWebsiteAccountConfigRepository, SqlSugarRepository<SysUser> sysUserRepository,
SqlSugarRepository<BookingLetteryard> bookingLetteryardRepository, IBookingOrderService bookingOrderService
, SqlSugarRepository<TaskBCInfo> taskBCInfoRepository)
{
_cache = cache;
_logger = logger;
_bookingOrderRepository = bookingOrderRepository;
_bookingfile = bookingfile;
_djyWebsiteAccountConfigRepository = djyWebsiteAccountConfigRepository;
_sysUserRepository = sysUserRepository;
_bookingLetteryardRepository = bookingLetteryardRepository;
_bookingOrderService = bookingOrderService;
_taskBCInfoRepository = taskBCInfoRepository;
}
/// <summary>
/// 批量BC下载
/// </summary>
/// <param name="bookingIds">订舱主键数组</param>
/// <returns>返回回执</returns>
[HttpPost("/BookingValueAdded/DownloadBookingConfirm")]
public async Task<TaskManageOrderResultDto> DownloadBookingConfirm([FromBody]long[] bookingIds)
{
TaskManageOrderResultDto result = new TaskManageOrderResultDto();
List<Task<TaskManageOrderResultDto>> taskList = new List<Task<TaskManageOrderResultDto>>();
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
/// <summary>
/// 单票下载BC
/// </summary>
/// <param name="bookingOrder">订舱详情</param>
/// <param name="batchNo">批次号</param>
/// <param name="sortNo">请求顺序号</param>
/// <returns>返回回执</returns>
private async Task<TaskManageOrderResultDto> 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<BookingAttachOptions>();
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 异步写入订舱附件表
/// <summary>
/// 异步写入订舱附件表
/// </summary>
/// <param name="boookId">订舱ID</param>
/// <param name="FilePath">文件路径</param>
/// <param name="fileName">文件名</param>
/// <param name="fileTypeCode">附件类型代码</param>
/// <param name="fileTypeName">附件类型名称</param>
/// <returns></returns>
[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
/// <summary>
/// 批量Draft下载
/// </summary>
/// <param name="bookingIds">订舱主键数组</param>
/// <returns></returns>
[HttpPost("/BookingValueAdded/DownloadDraft")]
public async Task<TaskManageOrderResultDto> DownloadDraft([FromBody]long[] bookingIds)
{
TaskManageOrderResultDto result = new TaskManageOrderResultDto();
List<Task<TaskManageOrderResultDto>> taskList = new List<Task<TaskManageOrderResultDto>>();
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
/// <summary>
/// 单票下载BC
/// </summary>
/// <param name="bookingOrder">订舱详情</param>
/// <param name="batchNo">批次号</param>
/// <param name="sortNo">请求顺序号</param>
/// <returns>返回回执</returns>
private async Task<TaskManageOrderResultDto> 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<BookingAttachOptions>();
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请求远端下载
/// <summary>
/// BC请求远端下载
/// </summary>
/// <param name="url">请求URL</param>
/// <param name="info">请求详情</param>
/// <param name="batchNo">批次号</param>
/// <returns>返回结果</returns>
[NonAction]
private async Task<BCAPIBaseResult> 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<BCAPIBaseResult>(userResult, jsonOptions);
}
}
catch (Exception ex)
{
//写日志
if (ex is HttpRequestException)
throw Oops.Oh(10000002);
}
return model;
}
#endregion
#region Draft请求远端下载
/// <summary>
/// Draft请求远端下载
/// </summary>
/// <param name="url">请求URL</param>
/// <param name="info">请求详情</param>
/// <param name="batchNo">批次号</param>
/// <returns>返回结果</returns>
[NonAction]
private async Task<DraftAPIBaseResult> 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<DraftAPIBaseResult>(userResult, jsonOptions);
}
}
catch (Exception ex)
{
//写日志
if (ex is HttpRequestException)
throw Oops.Oh(10000002);
}
return model;
}
#endregion
#region ESL Draft请求远端下载
/// <summary>
/// ESL Draft请求远端下载
/// 由于现有的ESL和TSL接口不一致需要提供单独的POST方法
/// </summary>
/// <param name="url">请求URL</param>
/// <param name="info">请求详情</param>
/// <param name="batchNo">批次号</param>
/// <returns>返回结果</returns>
[NonAction]
private async Task<ESLDraftAPIBaseResult> 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<ESLDraftAPIBaseResult>(userResult);
}
}
catch (Exception ex)
{
//写日志
if (ex is HttpRequestException)
throw Oops.Oh(10000002);
}
return model;
}
#endregion
#region ESL SO Ref.No搜索ESL号查询
/// <summary>
/// ESL SO Ref.No搜索ESL号查询
/// </summary>
/// <param name="url">请求URL</param>
/// <param name="info">请求详情</param>
/// <param name="batchNo">批次号</param>
/// <returns>返回结果</returns>
[NonAction]
private async Task<ESLManiAlloChkAPIBaseResult> 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<ESLManiAlloChkAPIBaseResult>(userResult);
}
}
catch (Exception ex)
{
//写日志
if (ex is HttpRequestException)
throw Oops.Oh(10000002);
}
return model;
}
#endregion
/// <summary>
/// 舱位分配查询
/// </summary>
/// <param name="bookingIds">订舱主键数组</param>
/// <returns>返回回执</returns>
[HttpPost("/BookingValueAdded/CheckUpdateManifestNo")]
public async Task<TaskManageOrderResultDto> CheckUpdateManifestNo([FromBody] long[] bookingIds)
{
TaskManageOrderResultDto result = new TaskManageOrderResultDto();
List<Task<TaskManageOrderResultDto>> taskList = new List<Task<TaskManageOrderResultDto>>();
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 单票舱位分配查询
/// <summary>
/// 单票舱位分配查询
/// </summary>
/// <param name="bookingOrder">订舱详情</param>
/// <param name="batchNo">批次号</param>
/// <param name="sortNo">请求顺序号</param>
/// <returns>返回回执</returns>
private async Task<TaskManageOrderResultDto> 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
/// <summary>
/// 获取个人或公司网站账户配置
/// </summary>
/// <param name="typeCode">账户类型代码</param>
/// <param name="userId">用户ID</param>
/// <param name="tendId">租户ID</param>
/// <returns>返回账户配置</returns>
private async Task<DjyWebsiteAccountConfig> GetAccountConfig(string typeCode,long userId,long tendId)
{
DjyWebsiteAccountConfig accountConfig = new DjyWebsiteAccountConfig();
accountConfig = await _djyWebsiteAccountConfigRepository.EntityContext.CopyNew().Queryable<DjyWebsiteAccountConfig>()
.FirstAsync(x => x.TypeCode == typeCode && x.CreatedUserId == userId);
if (accountConfig == null)
{
accountConfig = await _djyWebsiteAccountConfigRepository.EntityContext.CopyNew().Queryable<DjyWebsiteAccountConfig>()
.FirstAsync(x => x.TypeCode == typeCode && x.TenantId == tendId && x.IsTenant == true);
}
return accountConfig;
}
/// <summary>
/// 单票BC文件解析
/// </summary>
/// <param name="file">上传文件</param>
/// <param name="bookingOrderId">订舱主键</param>
/// <returns>返回回执</returns>
[HttpPost("/BookingValueAdded/SingleBCFileRead")]
public async Task<TaskManageOrderResultDto> 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解析
/// <summary>
/// 请求BC解析
/// </summary>
/// <param name="requestUrl">请求地址</param>
/// <param name="fileInfo">文件详情</param>
/// <returns>返回BC解析详情</returns>
private async Task<BCAPIBaseDataParse> 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<BCAPIBaseDataParse>(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更新订舱
/// <summary>
/// 单票BC更新订舱
/// </summary>
/// <param name="model">单票BC详情</param>
/// <returns>返回回执</returns>
[HttpPost("/BookingValueAdded/SingleBCUpdateBookingOrder")]
public async Task<TaskManageOrderResultDto> 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<UpdateBookingLetteryardInput>();
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更新订舱信息
/// <summary>
/// 单票BC更新订舱信息
/// </summary>
/// <param name="model">BC详情</param>
/// <param name="bookingOrder">订舱详情</param>
/// <param name="batchNo">批次号</param>
/// <param name="sortNo">顺序号</param>
/// <returns>返回回执</returns>
private async Task<TaskManageOrderResultDto> 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更新订舱
/// <summary>
/// 批量BC更新订舱
/// </summary>
/// <param name="batchBCList">批量BC更新列表</param>
/// <returns>返回回执</returns>
[HttpPost("/BookingValueAdded/BatchBCUpdate")]
public async Task<TaskManageOrderResultDto> BatchBCUpdate([FromBody]List<BatchBCDto> batchBCList)
{
TaskManageOrderResultDto result = new TaskManageOrderResultDto();
List<Task<TaskManageOrderResultDto>> taskList = new List<Task<TaskManageOrderResultDto>>();
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<Nullable<DateTime>>
{
private readonly string _dateFormatString;
public DateTimeJsonConverter()
{
_dateFormatString = "yyyy-MM-dd HH:mm:ss";
}
public DateTimeJsonConverter(string dateFormatString)
{
_dateFormatString = dateFormatString;
}
public override Nullable<DateTime> 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<DateTime> value, JsonSerializerOptions options)
{
if (value.HasValue)
writer.WriteStringValue(value.Value.ToUniversalTime().ToString(_dateFormatString));
else
writer.WriteNullValue();
}
}
public class IntegerJsonConverter : System.Text.Json.Serialization.JsonConverter<Int32>
{
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<decimal>
{
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);
}
}
}