using Furion; using Furion.DependencyInjection; using Furion.DistributedIDGenerator; using Furion.DynamicApiController; using Furion.EventBus; using Furion.Extensions; using Furion.FriendlyException; using Furion.JsonSerialization; using Furion.RemoteRequest.Extensions; using Mapster; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Myshipping.Application.Entity; using Myshipping.Application.Event; using Myshipping.Application.Service.BookingOrder.Dto; using Myshipping.Application.Service.BookingSlot.Dto; using Myshipping.Core; using Myshipping.Core.Service; using MySqlX.XDevAPI.Common; using Org.BouncyCastle.Asn1.Tsp; using SqlSugar; using System; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; using Yitter.IdGenerator; using static Aliyun.OSS.Model.CreateSelectObjectMetaInputFormatModel; namespace Myshipping.Application { /// /// 订舱舱位 /// [ApiDescriptionSettings("Application", Name = "BookingSlot", Order = 1)] public class BookingSlotService : IDynamicApiController, ITransient, IBookingSlotService { private readonly SqlSugarRepository _repBase; private readonly SqlSugarRepository _repCtn; private readonly SqlSugarRepository _repStock; private readonly SqlSugarRepository _repAllocation; private readonly SqlSugarRepository _repAllocationCtn; private readonly SqlSugarRepository _bookingFileRepository; private readonly SqlSugarRepository _bookingSlotCompareRepository; private readonly SqlSugarRepository _repBookingLog; private readonly SqlSugarRepository _repBookingLogDetail; private readonly SqlSugarRepository _bookingfile; private readonly ILogger _logger; private readonly ISysCacheService _cache; private readonly IEventPublisher _publisher; const string CONST_BC_FILE_CODE = "bc"; const string CONST_BC_FILE_NAME = "Booking Confirmation"; const string CONST_BC_NOTICE_FILE_CODE = "bc_notice"; const string CONST_BC_NOTICE_FILE_NAME = "Booking Confirmation Notice"; const string CONST_BC_MODIFY_FILE_CODE = "bc_modify"; const string CONST_BC_MODIFY_FILE_NAME = "Booking Amendment"; const string CONST_BC_MODIFY_NOTICE_FILE_CODE = "bc_moidfynotice"; const string CONST_BC_MODIFY_NOTICE_FILE_NAME = "Booking Amendment Notice"; public BookingSlotService(SqlSugarRepository repBase, SqlSugarRepository repCtn, SqlSugarRepository repStock, SqlSugarRepository repBookingLog, SqlSugarRepository repBookingLogDetail, SqlSugarRepository bookingfile, ILogger logger, ISysCacheService cache, IEventPublisher publisher, SqlSugarRepository repAllocation, SqlSugarRepository repAllocationCtn, SqlSugarRepository bookingFileRepository, SqlSugarRepository bookingSlotCompareRepository) { _repBase = repBase; _repCtn = repCtn; _repStock = repStock; _repBookingLog = repBookingLog; _repBookingLogDetail = repBookingLogDetail; _repAllocation = repAllocation; _repAllocationCtn = repAllocationCtn; _logger = logger; _cache = cache; _publisher = publisher; _bookingfile = bookingfile; _bookingFileRepository = bookingFileRepository; _bookingSlotCompareRepository = bookingSlotCompareRepository; } #region 舱位 /// /// 保存订舱舱位 /// /// /// [HttpPost("/BookingSlot/save")] public async Task Save(BookingSlotBaseSaveInput input) { BookingSlotBase model = null; if (input.Id > 0) //修改 { var c = _repBase.AsQueryable().Where(x => x.SLOT_BOOKING_NO == input.SLOT_BOOKING_NO && input.Id != input.Id).Count(); if (c > 0) { throw Oops.Bah("订舱编号已存在"); } model = _repBase.FirstOrDefault(x => x.Id == input.Id); var oldObj = model.Adapt(); input.Adapt(model); await _repBase.UpdateAsync(model); await _repCtn.DeleteAsync(x => x.SLOT_ID == model.Id); foreach (var ctn in input.CtnList) { var newCtn = ctn.Adapt(); newCtn.SLOT_ID = model.Id; await _repCtn.InsertAsync(newCtn); } await InsLog("Update", model.Id, typeof(BookingSlotBaseSaveInput), oldObj, input, "CtnList"); } else { var c = _repBase.AsQueryable().Where(x => x.SLOT_BOOKING_NO == input.SLOT_BOOKING_NO).Count(); if (c > 0) { throw Oops.Bah("订舱编号已存在"); } model = input.Adapt(); await _repBase.InsertAsync(model); foreach (var ctn in input.CtnList) { var newCtn = ctn.Adapt(); newCtn.SLOT_ID = model.Id; await _repCtn.InsertAsync(newCtn); } await InsLog("Add", model.Id, "新增舱位"); } //更新库存 await _publisher.PublishAsync(new ChannelEventSource("BookingSlotStock:Update", new BookingSlotStockUpdateModel { BOOKING_SLOT_TYPE = model.BOOKING_SLOT_TYPE, CARRIERID = model.CARRIERID, CONTRACT_NO = model.CONTRACT_NO, VESSEL = model.VESSEL, VOYNO = model.VOYNO })); return await Detail(model.Id); } /// /// 获取订舱舱位 /// /// /// [HttpGet("/BookingSlot/detail")] public async Task Detail(long id) { var slotBase = await _repBase.FirstOrDefaultAsync(u => u.Id == id); var ctns = await _repCtn.Where(x => x.SLOT_ID == id).ToListAsync(); var rtn = slotBase.Adapt(); rtn.CtnList = ctns.Adapt>(); List list = new List(); var main = await _repBookingLog.AsQueryable().Where(u => u.BookingId == slotBase.Id).ToListAsync(); var mailidlist = main.Select(x => x.Id).ToList(); list = main.Adapt>(); if (list != null) { var bookinglogdetail = await _repBookingLogDetail.AsQueryable().Where(x => mailidlist.Contains(x.PId)).ToListAsync(); foreach (var item in list) { var details = bookinglogdetail.Where(x => x.PId == item.Id).ToList(); item.details = details; } } rtn.LogList = list; return rtn; } #region 对外接口 /// /// 舱位接收保存、取消接口 /// /// [HttpPost("/BookingSlot/ApiReceive"), AllowAnonymous, ApiUser] public async Task ApiReceive(string jsonData, IFormFile file = null, IFormFile modifyFile = null) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); try { BookingSlotBaseApiDto dto = JSON.Deserialize(jsonData); DynameFileInfo bcFile = null; DynameFileInfo bcNoticeFile = null; if (file != null) { bcFile = new DynameFileInfo { FileBytes = file.ToByteArray(), FileName = file.FileName }; } if (modifyFile != null) { bcNoticeFile = new DynameFileInfo { FileBytes = modifyFile.ToByteArray(), FileName = modifyFile.FileName }; } var id = InnerApiReceive(dto, bcFile, bcNoticeFile).GetAwaiter().GetResult(); result.succ = true; result.msg = "成功"; result.ext = id; } catch(Exception ex) { result.succ = false; result.msg = $"失败,原因:{ex.Message}"; } return result; } #endregion /// /// 舱位接收保存、取消接口 /// /// /// /// /// [HttpPost("/BookingSlot/InnerApiReceive")] public async Task InnerApiReceive(BookingSlotBaseApiDto dto, DynameFileInfo file = null, DynameFileInfo modifyFile = null) { long id = 0; //接口方法直接调用save、delete等方法会报错,可能因为非token授权登录导致,故重写一遍保存、删除代码 if (dto.OpType == "add" || dto.OpType == "update" || dto.OpType == "del") { //翻译船公司 if (!string.IsNullOrWhiteSpace(dto.DataObj.CARRIERID) && string.IsNullOrWhiteSpace(dto.DataObj.CARRIER)) { var carrierInfo = _cache.GetAllCodeCarrier().GetAwaiter().GetResult() .Where(t => t.Code.Equals(dto.DataObj.CARRIERID, StringComparison.OrdinalIgnoreCase) || t.EnName.Equals(dto.DataObj.CARRIERID, StringComparison.OrdinalIgnoreCase) || t.CnName.Equals(dto.DataObj.CARRIERID, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); if(carrierInfo != null) { dto.DataObj.CARRIER = carrierInfo.CnName?.Trim(); } } //翻译箱型代码 if (dto.DataObj.CtnList != null && dto.DataObj.CtnList.Count > 0 && dto.DataObj.CtnList.Any(t => string.IsNullOrWhiteSpace(t.CTNCODE))) { var ctnCodeList = _cache.GetAllCodeCtn().GetAwaiter().GetResult().ToList(); dto.DataObj.CtnList.ForEach(t => { if(!string.IsNullOrWhiteSpace(t.CTNALL) && string.IsNullOrWhiteSpace(t.CTNCODE)) { var ctnCode = ctnCodeList.FirstOrDefault(a => !string.IsNullOrWhiteSpace(a.Name) && a.Name.Equals(t.CTNALL, StringComparison.OrdinalIgnoreCase)); if (ctnCode != null) t.CTNCODE = ctnCode?.Code; } }); } BookingSlotBase model = null; if (dto.OpType == "add") { var c = _repBase.AsQueryable().Filter(null, true).Where(x => x.IsDeleted == false && x.TenantId == UserManager.TENANT_ID && x.SLOT_BOOKING_NO == dto.DataObj.SLOT_BOOKING_NO).Count(); if (c > 0) { throw Oops.Bah("订舱编号已存在"); } model = dto.DataObj.Adapt(); await _repBase.InsertAsync(model); id = model.Id; foreach (var ctn in dto.DataObj.CtnList) { var newCtn = ctn.Adapt(); newCtn.SLOT_ID = model.Id; await _repCtn.InsertAsync(newCtn); } await InsLog("Add", model.Id, "新增舱位"); string batchNo = IDGen.NextID().ToString(); //处理附件 if (file != null) { _logger.LogInformation($"请求文件名:{file.FileName}"); var fileFullPath = await FileAttachHelper.SaveFileDirect(model.Id.ToString(), file.FileBytes, batchNo, file.FileName, "bcfiles"); _logger.LogInformation($"保存文件路径:{fileFullPath}"); if (!string.IsNullOrWhiteSpace(fileFullPath)) { //将格式单附件写入订舱的附件 SaveEDIFile(id, fileFullPath, file.FileName, UserManager.TENANT_ID, CONST_BC_FILE_CODE, CONST_BC_FILE_NAME).GetAwaiter(); } } if (modifyFile != null) { _logger.LogInformation($"请求文件名(变更文件):{modifyFile.FileName}"); var fileFullPath = await FileAttachHelper.SaveFileDirect(model.Id.ToString(), modifyFile.FileBytes, batchNo, modifyFile.FileName, "bcnoticefiles"); _logger.LogInformation($"保存文件路径(变更文件):{fileFullPath}"); if (!string.IsNullOrWhiteSpace(fileFullPath)) { //将格式单附件写入订舱的附件 SaveEDIFile(id, fileFullPath, modifyFile.FileName, UserManager.TENANT_ID, CONST_BC_NOTICE_FILE_CODE, CONST_BC_NOTICE_FILE_NAME).GetAwaiter(); } } } else if (dto.OpType == "update") { model = await _repBase.AsQueryable().Filter(null, true).FirstAsync(x => x.IsDeleted == false && x.TenantId == UserManager.TENANT_ID && x.SLOT_BOOKING_NO == dto.DataObj.SLOT_BOOKING_NO); if (model == null) { throw Oops.Bah($"未找到订舱编号为 {dto.DataObj.SLOT_BOOKING_NO} 的数据"); } id = model.Id; //生成待比对详情 TaskBCInfoDto bcSrcDto = model.Adapt(); TaskBCInfoDto bcTargetDto = dto.DataObj.Adapt(); //提取箱信息 var ctnList = _repCtn.AsQueryable() .Where(x => x.IsDeleted == false && x.TenantId == UserManager.TENANT_ID && x.SLOT_ID == model.Id).ToList(); if(ctnList != null) { bcSrcDto.CtnList = ctnList.GroupBy(x => x.CTNALL) .Select(x => { return new TaskBCCTNInfoDto { CtnALL = x.Key, CTNNUM = x.ToList() .Sum(a => a.CTNNUM) }; }).ToList(); } if (dto.DataObj.CtnList != null && dto.DataObj.CtnList.Count > 0) { bcTargetDto.CtnList = dto.DataObj.CtnList.GroupBy(x => x.CTNALL) .Select(x => { return new TaskBCCTNInfoDto { CtnALL = x.Key, CTNNUM = x.ToList() .Sum(a => a.CTNNUM) }; }).ToList(); } var oldObj = model.Adapt(); dto.DataObj.Adapt(model); await _repBase.UpdateAsync(model); await _repCtn.DeleteAsync(x => x.SLOT_ID == model.Id); foreach (var ctn in dto.DataObj.CtnList) { var newCtn = ctn.Adapt(); newCtn.SLOT_ID = model.Id; await _repCtn.InsertAsync(newCtn); } await InsLog("Update", model.Id, typeof(BookingSlotBaseApiSaveDto), oldObj, dto.DataObj, "CtnList"); string batchNo = IDGen.NextID().ToString(); //处理附件 if (file != null) { _logger.LogInformation($"请求文件名:{file.FileName}"); var fileFullPath = await FileAttachHelper.SaveFileDirect(model.Id.ToString(), file.FileBytes, batchNo, file.FileName, "bcmoidfyfiles"); _logger.LogInformation($"保存文件路径:{fileFullPath}"); if (!string.IsNullOrWhiteSpace(fileFullPath)) { //将格式单附件写入订舱的附件 SaveEDIFile(id, fileFullPath, file.FileName, UserManager.TENANT_ID, CONST_BC_MODIFY_FILE_CODE, CONST_BC_MODIFY_FILE_NAME).GetAwaiter(); } } if (modifyFile != null) { _logger.LogInformation($"请求文件名(变更文件):{modifyFile.FileName}"); var fileFullPath = await FileAttachHelper.SaveFileDirect(model.Id.ToString(), modifyFile.FileBytes, batchNo, modifyFile.FileName, "bcmoidfynoticefiles"); _logger.LogInformation($"保存文件路径(变更文件):{fileFullPath}"); if (!string.IsNullOrWhiteSpace(fileFullPath)) { //将格式单附件写入订舱的附件 SaveEDIFile(id, fileFullPath, modifyFile.FileName, UserManager.TENANT_ID, CONST_BC_MODIFY_NOTICE_FILE_CODE, CONST_BC_MODIFY_NOTICE_FILE_NAME).GetAwaiter(); } } //一般更新数据指的是Booking Amendment,需要与舱位进行数据比对 await PushCompareBCInfo(bcSrcDto, bcTargetDto,id, dto.BatchNo); } else if (dto.OpType == "del") { var slotNO = dto.DataObj.SLOT_BOOKING_NO; model = await _repBase.AsQueryable().Filter(null, true).FirstAsync(x => x.IsDeleted == false && x.TenantId == UserManager.TENANT_ID && x.SLOT_BOOKING_NO == slotNO); if (model == null) { throw Oops.Bah($"未找到订舱编号为 {slotNO} 的数据"); } id = model.Id; model.IsDeleted = true; await _repBase.UpdateAsync(model); var ctns = await _repCtn.AsQueryable().Filter(null, true).Where(x => x.IsDeleted == false && x.TenantId == UserManager.TENANT_ID && x.SLOT_ID == model.Id).ToListAsync(); foreach (var ctn in ctns) { ctn.IsDeleted = true; await _repCtn.UpdateAsync(ctn); } await InsLog("Del", model.Id, "取消舱位"); } //更新库存 await _publisher.PublishAsync(new ChannelEventSource("BookingSlotStock:Update", new BookingSlotStockUpdateModel { BOOKING_SLOT_TYPE = model.BOOKING_SLOT_TYPE, CARRIERID = model.CARRIERID, CONTRACT_NO = model.CONTRACT_NO, VESSEL = model.VESSEL, VOYNO = model.VOYNO })); } else { throw Oops.Bah("操作类型参数有误"); } return id; } /// /// 插入日志(仅显示一条文本信息) /// /// /// /// /// [NonAction] private async Task InsLog(string type, long slotId, string status) { var bid = await _repBookingLog.InsertReturnSnowflakeIdAsync(new BookingLog { Type = type, BookingId = slotId, TenantId = Convert.ToInt64(UserManager.TENANT_ID), CreatedTime = DateTime.Now, CreatedUserId = UserManager.UserId, CreatedUserName = UserManager.Name, Module = "Slot" }); if (!string.IsNullOrEmpty(status)) { await _repBookingLogDetail.InsertReturnSnowflakeIdAsync(new BookingLogDetail { PId = bid, Field = "", OldValue = "", NewValue = status, }); } } /// /// 插入日志(比对修改内容) /// /// /// /// /// /// /// /// [NonAction] private async Task InsLog(string type, long id, Type objType, object objOld, object objNew, params string[] excepProp) { var bid = await _repBookingLog.InsertReturnSnowflakeIdAsync(new BookingLog { Type = type, BookingId = id, TenantId = Convert.ToInt64(UserManager.TENANT_ID), CreatedTime = DateTime.Now, CreatedUserId = UserManager.UserId, CreatedUserName = UserManager.Name, Module = "Slot" }); foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(objType)) { if (excepProp.Contains(descriptor.Name)) { continue; } var compResult = false; var oldValue = descriptor.GetValue(objOld); var newValue = descriptor.GetValue(objNew); if (oldValue != null && newValue != null) { if (descriptor.PropertyType == typeof(string)) { compResult = oldValue.ToString() == newValue.ToString(); } else if (descriptor.PropertyType == typeof(DateTime) || descriptor.PropertyType == typeof(DateTime?)) { compResult = Convert.ToDateTime(oldValue) == Convert.ToDateTime(newValue); } else if (descriptor.PropertyType == typeof(decimal) || descriptor.PropertyType == typeof(float) || descriptor.PropertyType == typeof(double) || descriptor.PropertyType == typeof(decimal?) || descriptor.PropertyType == typeof(float?) || descriptor.PropertyType == typeof(double?)) { compResult = Convert.ToDecimal(oldValue) == Convert.ToDecimal(newValue); } else if (descriptor.PropertyType == typeof(int) || descriptor.PropertyType == typeof(long) || descriptor.PropertyType == typeof(int?) || descriptor.PropertyType == typeof(long?)) { compResult = Convert.ToInt64(oldValue) == Convert.ToInt64(newValue); } } else { compResult = oldValue == newValue; } if (!compResult) { var fieldName = descriptor.Name; if (!string.IsNullOrWhiteSpace(descriptor.Description)) { fieldName = descriptor.Description; } await _repBookingLogDetail.InsertReturnSnowflakeIdAsync(new BookingLogDetail { PId = bid, Field = fieldName, OldValue = $"{oldValue}", NewValue = $"{newValue}", }); } } } #endregion #region 库存 /// /// 库存查询 /// /// /// [HttpPost("/BookingSlot/pageStock")] public async Task PageStock(BookingSlotStockPageInput input) { var entities = await _repStock.AsQueryable() .WhereIF(!string.IsNullOrEmpty(input.VESSEL), u => u.VESSEL.Contains(input.VESSEL)) .WhereIF(!string.IsNullOrEmpty(input.VOYNO), u => u.VOYNO.Contains(input.VOYNO)) .WhereIF(!string.IsNullOrEmpty(input.CARRIER), u => u.CARRIER.Contains(input.CARRIER)) .WhereIF(!string.IsNullOrEmpty(input.CTN_STAT), u => u.CTN_STAT.Contains(input.CTN_STAT)) .WhereIF(input.ETD_START.HasValue, u => u.ETD >= input.ETD_START.Value) .WhereIF(input.ETD_END.HasValue, u => u.ETD < input.ETD_END.Value.AddDays(1)) .WhereIF(input.ETA_START.HasValue, u => u.ETA >= input.ETA_START.Value) .WhereIF(input.ETA_END.HasValue, u => u.ETA < input.ETA_END.Value.AddDays(1)) .ToPagedListAsync(input.PageNo, input.PageSize); var result = entities.Adapt>(); return result.XnPagedResult(); } /// /// 刷新库存统计 /// /// [HttpPost("/BookingSlot/refreshStock")] public async Task RefreshStock(BookingSlotStockUpdateModel input) { //更新库存 await _publisher.PublishAsync(new ChannelEventSource("BookingSlotStock:Update", input)); } #endregion #region 舱位引入 /// /// 查询可用的舱位及箱子 /// [HttpGet("/BookingSlot/getAvailableSlots")] public async Task> GetAvailableSlots([FromQuery] BookingSlotBaseDto input) { return await GetAvailableSlots(input, null); } /// /// 查询可用的舱位及箱子列表 /// /// 筛选条件1:舱位信息、箱型 /// 筛选条件2:舱位主键列表 /// 可用的舱位列表(含可用的箱子列表) [NonAction] public async Task> GetAvailableSlots(BookingSlotBaseDto slotInput = null, List slotIdListInput = null) { slotInput ??= new(); slotIdListInput ??= new(); string[] ctnCodeList = null; if (!string.IsNullOrEmpty(slotInput.CTN_STAT)) { ctnCodeList = slotInput.CTN_STAT.Split(','); } // 1. 【舱位基础表】与【箱子表】做关联,并根据【舱位主键】、【箱型】做分组,统计出【总的箱量】,作为queryable1 var queryable1 = _repBase.Context.Queryable((bas, ctn) => bas.Id == ctn.SLOT_ID) .WhereIF(!string.IsNullOrEmpty(slotInput.PORTLOAD), bas => bas.PORTLOAD.Contains(slotInput.PORTLOAD)) .WhereIF(!string.IsNullOrEmpty(slotInput.PORTDISCHARGE), bas => bas.PORTLOAD.Contains(slotInput.PORTLOAD)) .WhereIF(!string.IsNullOrEmpty(slotInput.VESSEL), bas => bas.VESSEL.Contains(slotInput.VESSEL)) .WhereIF(!string.IsNullOrEmpty(slotInput.VOYNO), bas => bas.VOYNO.Contains(slotInput.VOYNO)) .WhereIF(!string.IsNullOrEmpty(slotInput.CARRIAGE_TYPE), bas => bas.CARRIAGE_TYPE == slotInput.CARRIAGE_TYPE) .WhereIF(!string.IsNullOrEmpty(slotInput.BOOKING_SLOT_TYPE), bas => bas.BOOKING_SLOT_TYPE == slotInput.BOOKING_SLOT_TYPE) .WhereIF(ctnCodeList != null, (bas, ctn) => ctnCodeList.Contains(ctn.CTNCODE)) .WhereIF(slotIdListInput.Any(), (bas) => slotIdListInput.Contains(bas.Id)) .GroupBy((bas, ctn) => new { bas.Id, ctn.CTNCODE }) .Select((bas, ctn) => new { id = bas.Id, ctnCode = ctn.CTNCODE, numAll = SqlFunc.AggregateSum(ctn.CTNNUM) }) .MergeTable(); // 2. 【已引入舱位表】与【已使用的箱子表】做关联,并根据【舱位主键】、【箱型】做分组,统计出【已使用的箱量】,作为queryable2 var queryable2 = _repBase.Context.Queryable((alc, ctn) => alc.Id == ctn.SLOT_ALLOC_ID) .GroupBy((alc, ctn) => new { alc.BOOKING_SLOT_ID, ctn.CTNCODE }) .Select((alc, ctn) => new { id = alc.BOOKING_SLOT_ID, ctnCode = ctn.CTNCODE, numUse = SqlFunc.AggregateSum(ctn.CTNNUM) }) .MergeTable(); // 3. 将queryable1 左连接 queryable2,使用【总的箱量】减去【已使用的箱量】,得到【剩余的箱量】,添加【剩余的箱量】> 0 的条件,作为queryable3 var queryable3 = queryable1.LeftJoin(queryable2, (q1, q2) => q1.id == q2.id && q1.ctnCode == q2.ctnCode) .Select((q1, q2) => new { q1.id, q1.ctnCode, numResidue = SqlFunc.IsNull(q1.numAll - q2.numUse, q1.numAll) }) .MergeTable() .Where(r => r.numResidue > 0); // 4. 执行ToList(),得到可用的【舱位主键】、【箱型】、【箱量】列表 var canUselist = await queryable3.ToListAsync(); // 查询舱位列表 var baseIdList = canUselist.Select(c => c.id); List baseList = await _repBase.AsQueryable() .Where(u => baseIdList.Contains(u.Id)) .ToListAsync(); List ctnCodeCache = await _cache.GetAllCodeCtn(); // 构建结果 List result = baseList.Adapt>(); foreach (var item in result) { var ctnList = canUselist.Where(c => c.id == item.Id).ToList(); if (ctnList?.Any() == true) { item.CtnList = ctnList.Select(c => new BookingSlotCtnDto() { CTNCODE = c.ctnCode, CTNNUM = c.numResidue, CTNALL = ctnCodeCache.FirstOrDefault(e => e.Code == c.ctnCode)?.Name ?? throw new Exception($"舱位信息中存在未收录的箱型:{c.ctnCode},需要在箱型字典中补充"), }).ToList(); } } return result; } /// /// 检查指定订舱记录,是否可以引入舱位列表 /// /// 待引入的舱位列表 /// 待关联的订舱记录 /// isExists:指定订舱记录是否已经引入过舱位数据,isEnough:现有舱位及箱子是否满足需求,message:提示信息 [NonAction] public async Task<(bool isExists, bool isEnough, string message)> CheckImportSlots(List slots, long bookingOrderId) { slots ??= new List(); // 判断是否已存在引用关系 if (bookingOrderId != 0 && await _repAllocation.IsExistsAsync(a => a.BOOKING_ID == bookingOrderId)) { return (true, false, $"订舱主键{bookingOrderId}已引用舱位"); } var slotIdList = slots.Select(s => s.Id).ToList(); // 查询可用舱位及箱子列表 var latestSlotList = await GetAvailableSlots(null, slotIdList); // 判断余量是否满足需求 foreach (var inSlotItem in slots) { var latestSlot = latestSlotList.FirstOrDefault(b => b.Id == inSlotItem.Id); if (latestSlot == null) { return (false, false, $"订舱编号为{inSlotItem.SLOT_BOOKING_NO}的舱位已被占用或取消,请重新引入"); } if (inSlotItem.CtnList?.Any() == false) { return (false, false, $"每个舱位至少选择一个箱子,订舱编号:{inSlotItem.SLOT_BOOKING_NO}"); } foreach (var inCtnItem in inSlotItem.CtnList) { var latestCtn = latestSlot.CtnList.FirstOrDefault(c => c.CTNCODE == inCtnItem.CTNCODE); if (latestCtn == null) { return (false, false, $"订舱编号为{latestSlot.SLOT_BOOKING_NO}的舱位中,箱型为{inCtnItem.CTNALL}的箱子已被占用或取消,请重新引入"); } if (latestCtn.CTNNUM < inCtnItem.CTNNUM) { return (false, false, $"订舱编号为{latestSlot.SLOT_BOOKING_NO}的舱位中,箱型为{inCtnItem.CTNALL}的箱子当前剩余{latestCtn.CTNNUM}个,少于所需的{inCtnItem.CTNNUM}个,请重新引入"); } } } return (false, true, $"可以引入"); } public static object ImportLockObj = new object(); /// /// 为指定订舱记录引入舱位信息 /// /// 待引入的舱位列表 /// 待关联的订舱记录 /// 是否进行剩余量检查 /// isSuccess:检查(余量及已引用检查)是否成功通过,message:提示信息 [NonAction] public async Task<(bool isSuccess, string message)> ImportSlots(List slots, long bookingOrderId, bool isCheck) { slots ??= new List(); Monitor.Enter(ImportLockObj); try { if (isCheck) { (bool isExists, bool isEnough, string message) checkResult = await CheckImportSlots(slots, bookingOrderId); if (checkResult.isExists || !checkResult.isEnough) return (false, checkResult.message); } var slotIdList = slots.Select(s => s.Id).ToList(); List latestSlotList = await _repBase.AsQueryable().Where(b => slotIdList.Contains(b.Id)).ToListAsync(); foreach (var inSlotItem in slots) { var latestSlot = latestSlotList.First(b => b.Id == inSlotItem.Id); var config = new TypeAdapterConfig(); config.ForType() .Ignore(dest => dest.CreatedTime) .Ignore(dest => dest.UpdatedTime) .Ignore(dest => dest.CreatedUserId) .Ignore(dest => dest.UpdatedUserId) .Ignore(dest => dest.CreatedUserName) .Ignore(dest => dest.UpdatedUserName) .Ignore(dest => dest.TenantId) .Ignore(dest => dest.TenantName); var newSlotAllocation = latestSlot.Adapt(config); newSlotAllocation.Id = 0; newSlotAllocation.BOOKING_SLOT_ID = latestSlot.Id; newSlotAllocation.BOOKING_ID = bookingOrderId; newSlotAllocation.ALLO_BILL_NO = latestSlot.SLOT_BOOKING_NO; newSlotAllocation.FINAL_BILL_NO = latestSlot.SLOT_BOOKING_NO; await _repAllocation.InsertAsync(newSlotAllocation); var insertCtnList = inSlotItem.CtnList.Select(c => new BookingSlotAllocationCtn() { SLOT_ALLOC_ID = newSlotAllocation.Id, CTNCODE = c.CTNCODE, CTNALL = c.CTNALL, CTNNUM = c.CTNNUM }); await _repAllocationCtn.InsertAsync(insertCtnList); // 更新库存 await _publisher.PublishAsync(new ChannelEventSource("BookingSlotStock:Update", new Event.BookingSlotStockUpdateModel { BOOKING_SLOT_TYPE = latestSlot.BOOKING_SLOT_TYPE, CARRIERID = latestSlot.CARRIERID, CONTRACT_NO = latestSlot.CONTRACT_NO, VESSEL = latestSlot.VESSEL, VOYNO = latestSlot.VOYNO })); } } finally { Monitor.Exit(ImportLockObj); } return (true, "引入成功"); } #endregion #region 获取附件 /// /// 获取附件 /// /// 舱位主键 /// 返回附件列表 [HttpGet("/BookingSlot/GetFile")] public async Task> GetFile(long id) { var list = await _bookingfile.AsQueryable().Filter(null, true) .Where(u => u.BookingId == id && u.Moudle == "BookingSlot").ToListAsync(); return list; } #endregion #region 舱位 /// /// 分页查询订舱舱位 /// /// /// [HttpPost("/BookingSlot/page")] public async Task Page(BookingSlotBasePageInput input) { var entities = await _repBase.AsQueryable() .WhereIF(!string.IsNullOrEmpty(input.SLOT_BOOKING_NO), u => u.SLOT_BOOKING_NO.Contains(input.SLOT_BOOKING_NO)) .WhereIF(!string.IsNullOrEmpty(input.VESSEL), u => u.VESSEL.Contains(input.VESSEL)) .WhereIF(!string.IsNullOrEmpty(input.VOYNO), u => u.VOYNO.Contains(input.VOYNO)) .WhereIF(!string.IsNullOrEmpty(input.PORTLOAD), u => u.PORTLOAD.Contains(input.PORTLOAD)) .WhereIF(!string.IsNullOrEmpty(input.PORTDISCHARGE), u => u.PORTLOAD.Contains(input.PORTLOAD)) .WhereIF(!string.IsNullOrEmpty(input.CARRIER), u => u.CARRIER.Contains(input.CARRIER)) .WhereIF(!string.IsNullOrEmpty(input.LANENAME), u => u.LANENAME.Contains(input.LANENAME)) .WhereIF(!string.IsNullOrEmpty(input.CARRIAGE_TYPE), u => u.CARRIAGE_TYPE == input.CARRIAGE_TYPE) .WhereIF(!string.IsNullOrEmpty(input.BOOKING_SLOT_TYPE), u => u.BOOKING_SLOT_TYPE == input.BOOKING_SLOT_TYPE) .WhereIF(!string.IsNullOrEmpty(input.CTN_STAT), u => u.CTN_STAT.Contains(input.CTN_STAT)) .WhereIF(!string.IsNullOrEmpty(input.VGM_RLT_STAT), u => u.VGM_RLT_STAT == input.VGM_RLT_STAT) .WhereIF(!string.IsNullOrEmpty(input.SI_RLT_STAT), u => u.SI_RLT_STAT == input.SI_RLT_STAT) .WhereIF(input.ETD_START.HasValue, u => u.ETD >= input.ETD_START.Value) .WhereIF(input.ETD_END.HasValue, u => u.ETD < input.ETD_END.Value.AddDays(1)) .WhereIF(input.ETA_START.HasValue, u => u.ETA >= input.ETA_START.Value) .WhereIF(input.ETA_END.HasValue, u => u.ETA < input.ETA_END.Value.AddDays(1)) .ToPagedListAsync(input.PageNo, input.PageSize); var result = entities.Adapt>(); return result.XnPagedResult(); } #endregion #region 异步写入附件表 /// /// 异步写入附件表 /// /// 订舱ID /// 文件路径 /// 文件名 /// 租户ID /// 附件类型代码 /// 附件类型名称 /// 附件模块代码 /// [NonAction] private async Task SaveEDIFile(long boookId, string FilePath, string fileName, long tenantId, string fileTypeCode = "bc", string fileTypeName = "Booking Confirmation", string moudle = "BookingSlot") { /* 直接将附件信息写入附件表 */ //EDI文件 var bookFile = new BookingFile { Id = YitIdHelper.NextId(), FileName = fileName, FilePath = FilePath, TypeCode = fileTypeCode, TypeName = fileTypeName, BookingId = boookId, TenantId = tenantId, Moudle = moudle }; await _bookingFileRepository.InsertAsync(bookFile); } #endregion #region 推送BC变更比对 /// /// 推送BC变更比对 /// /// 原舱位详情 /// 变更后舱位详情 /// 舱位主键 /// 请求批次号用来区分对应的哪个批次任务 /// private async Task PushCompareBCInfo(TaskBCInfoDto bcSrcDto, TaskBCInfoDto bcTargetDto,long slotId,string reqBatchNo) { string batchNo = IDGen.NextID().ToString(); DateTime bDate = DateTime.Now; var compareResult = await ExcuteCompare(bcSrcDto, bcTargetDto); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} 请求完成,耗时:{timeDiff}ms. 结果{msg}", batchNo, timeDiff, compareResult.succ ? "成功" : "失败"); if (compareResult == null) throw Oops.Oh($"舱位主键{slotId}请求BC比对失败,返回为空"); DateTime nowDate = DateTime.Now; BookingSlotCompare entity = new BookingSlotCompare { SLOT_ID = slotId, COMPARE_BATCHNO = reqBatchNo, COMPARE_DIFF_NUM = compareResult.extra.IsExistsDiff? compareResult.extra.ShowDetailList.Count : 0, CreatedTime = nowDate, UpdatedTime = nowDate, CreatedUserId = UserManager.UserId, CreatedUserName = UserManager.Name, UpdatedUserId = UserManager.UserId, UpdatedUserName = UserManager.Name, COMPARE_TYPE = "BC_MODIFY", COMPARE_RLT = JSON.Serialize(compareResult.extra.ShowDetailList), }; await _bookingSlotCompareRepository.InsertAsync(entity); } #endregion #region 请求BC比对 /// /// 请求BC比对 /// /// BC详情 /// BC变更后详情 /// 返回回执 [NonAction] private async Task ExcuteCompare(TaskBCInfoDto bcSrcDto, TaskBCInfoDto bcTargetDto) { TaskManageExcuteResultDto model = null; /* 1、读取配置文件中的规则引擎URL 2、填充请求的类,并生成JSON报文 3、POST请求接口,并记录回执。 4、返回信息。 */ var url = App.Configuration["BCCompareUrl"]; using (var httpClient = new HttpClient()) { try { using (var reduceAttach = new MultipartFormDataContent()) { var dataContent = new ByteArrayContent(Encoding.UTF8.GetBytes(JSON.Serialize(bcSrcDto))); dataContent.Headers.ContentDisposition = new ContentDispositionHeaderValue($"form-data") { Name = "srcJson" }; reduceAttach.Add(dataContent); var dataContent2 = new ByteArrayContent(Encoding.UTF8.GetBytes(JSON.Serialize(bcTargetDto))); dataContent2.Headers.ContentDisposition = new ContentDispositionHeaderValue($"form-data") { Name = "destJson" }; reduceAttach.Add(dataContent2); //请求 var response = httpClient.PostAsync(url, reduceAttach).Result; var result = response.Content.ReadAsStringAsync().Result; model = JSON.Deserialize(result); } } catch (Exception ex) { _logger.LogInformation("推送BC比对异常,原因:{error}", ex.Message); throw Oops.Oh($"推送BC比对异常,原因:{ex.Message}"); } } return model; } #endregion #region 获取舱位变更比对结果 /// /// 获取舱位变更比对结果 /// /// 舱位主键 /// 批次号 /// 返回舱位变更比对结果 [HttpGet("/BookingSlot/GetSlotCompareResult")] public async Task> GetSlotCompareResult([FromQuery] long id, [FromQuery] string batchNo) { var compareInfo = await _bookingSlotCompareRepository.AsQueryable() .FirstAsync(t => t.SLOT_ID == id && t.COMPARE_BATCHNO == batchNo); if (compareInfo == null) { throw Oops.Oh($"舱位变更比对结果不存在"); } if (string.IsNullOrWhiteSpace(compareInfo.COMPARE_RLT)) { throw Oops.Oh($"获取舱位变更比对结果错误,比对内容不存在"); } return JSON.Deserialize>(compareInfo.COMPARE_RLT); } #endregion #region 导入舱位 /// /// 导入舱位 /// /// 导入舱位文件 /// 返回回执 [HttpPost("/BookingSlot/ImportSlotFromFile")] public async Task ImportSlotFromFile(IFormFile file) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); try { /* 1、只支持Excel导入 2、 */ result.succ = true; result.msg = "导入成功"; } catch(Exception ex) { _logger.LogError($"导入舱位异常,原因:{ex.Message}"); result.succ = false; result.msg = $"导入舱位异常,原因:{ex.Message}"; } return result; } #endregion } public class DynameFileInfo { /// /// 文件名称 /// public string FileName { get; set; } /// /// 文件二进制流 /// public byte[] FileBytes { get; set; } } }