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/BookingSlot/BookingSlotService.cs

1166 lines
51 KiB
C#

using Furion;
using Furion.DependencyInjection;
using Furion.DistributedIDGenerator;
11 months ago
using Furion.DynamicApiController;
using Furion.EventBus;
using Furion.Extensions;
using Furion.FriendlyException;
using Furion.JsonSerialization;
using Furion.RemoteRequest.Extensions;
11 months ago
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
11 months ago
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Myshipping.Application.Entity;
11 months ago
using Myshipping.Application.Event;
11 months ago
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;
11 months ago
namespace Myshipping.Application
{
/// <summary>
/// 订舱舱位
/// </summary>
[ApiDescriptionSettings("Application", Name = "BookingSlot", Order = 1)]
public class BookingSlotService : IDynamicApiController, ITransient, IBookingSlotService
11 months ago
{
private readonly SqlSugarRepository<BookingSlotBase> _repBase;
private readonly SqlSugarRepository<BookingSlotCtn> _repCtn;
private readonly SqlSugarRepository<BookingSlotStock> _repStock;
private readonly SqlSugarRepository<BookingSlotAllocation> _repAllocation;
private readonly SqlSugarRepository<BookingSlotAllocationCtn> _repAllocationCtn;
private readonly SqlSugarRepository<BookingFile> _bookingFileRepository;
10 months ago
private readonly SqlSugarRepository<BookingSlotCompare> _bookingSlotCompareRepository;
11 months ago
private readonly SqlSugarRepository<BookingLog> _repBookingLog;
private readonly SqlSugarRepository<BookingLogDetail> _repBookingLogDetail;
private readonly SqlSugarRepository<BookingFile> _bookingfile;
11 months ago
11 months ago
private readonly ILogger<BookingSlotService> _logger;
private readonly ISysCacheService _cache;
11 months ago
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";
11 months ago
public BookingSlotService(SqlSugarRepository<BookingSlotBase> repBase,
SqlSugarRepository<BookingSlotCtn> repCtn,
SqlSugarRepository<BookingSlotStock> repStock,
11 months ago
SqlSugarRepository<BookingLog> repBookingLog,
SqlSugarRepository<BookingLogDetail> repBookingLogDetail,
SqlSugarRepository<BookingFile> bookingfile,
ILogger<BookingSlotService> logger,
11 months ago
ISysCacheService cache,
IEventPublisher publisher,
SqlSugarRepository<BookingSlotAllocation> repAllocation,
SqlSugarRepository<BookingSlotAllocationCtn> repAllocationCtn,
10 months ago
SqlSugarRepository<BookingFile> bookingFileRepository,
SqlSugarRepository<BookingSlotCompare> bookingSlotCompareRepository)
11 months ago
{
_repBase = repBase;
_repCtn = repCtn;
_repStock = repStock;
11 months ago
_repBookingLog = repBookingLog;
_repBookingLogDetail = repBookingLogDetail;
_repAllocation = repAllocation;
_repAllocationCtn = repAllocationCtn;
11 months ago
11 months ago
_logger = logger;
_cache = cache;
11 months ago
_publisher = publisher;
_bookingfile = bookingfile;
_bookingFileRepository = bookingFileRepository;
10 months ago
_bookingSlotCompareRepository = bookingSlotCompareRepository;
11 months ago
}
#region 舱位
/// <summary>
/// 保存订舱舱位
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("/BookingSlot/save")]
11 months ago
public async Task<BookingSlotBaseSaveOutput> Save(BookingSlotBaseSaveInput input)
11 months ago
{
BookingSlotBase model = null;
11 months ago
if (input.Id > 0) //修改
11 months ago
{
11 months ago
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("订舱编号已存在");
}
11 months ago
model = _repBase.FirstOrDefault(x => x.Id == input.Id);
11 months ago
var oldObj = model.Adapt<BookingSlotBaseSaveInput>();
11 months ago
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<BookingSlotCtn>();
newCtn.SLOT_ID = model.Id;
await _repCtn.InsertAsync(newCtn);
}
11 months ago
await InsLog("Update", model.Id, typeof(BookingSlotBaseSaveInput), oldObj, input, "CtnList");
11 months ago
}
else
{
11 months ago
var c = _repBase.AsQueryable().Where(x => x.SLOT_BOOKING_NO == input.SLOT_BOOKING_NO).Count();
if (c > 0)
{
throw Oops.Bah("订舱编号已存在");
}
11 months ago
model = input.Adapt<BookingSlotBase>();
await _repBase.InsertAsync(model);
foreach (var ctn in input.CtnList)
{
var newCtn = ctn.Adapt<BookingSlotCtn>();
newCtn.SLOT_ID = model.Id;
await _repCtn.InsertAsync(newCtn);
}
11 months ago
await InsLog("Add", model.Id, "新增舱位");
11 months ago
}
11 months ago
//更新库存
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,
PLACERECEIPT = model.PLACERECEIPT,
PLACEDELIVERY = model.PLACEDELIVERY
11 months ago
}));
11 months ago
return await Detail(model.Id);
}
/// <summary>
/// 获取订舱舱位
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("/BookingSlot/detail")]
11 months ago
public async Task<BookingSlotBaseSaveOutput> Detail(long id)
11 months ago
{
var slotBase = await _repBase.FirstOrDefaultAsync(u => u.Id == id);
var ctns = await _repCtn.Where(x => x.SLOT_ID == id).ToListAsync();
11 months ago
var rtn = slotBase.Adapt<BookingSlotBaseSaveOutput>();
11 months ago
rtn.CtnList = ctns.Adapt<List<BookingSlotCtnSaveInput>>();
11 months ago
List<BookingLogDto> list = new List<BookingLogDto>();
var main = await _repBookingLog.AsQueryable().Where(u => u.BookingId == slotBase.Id).ToListAsync();
var mailidlist = main.Select(x => x.Id).ToList();
list = main.Adapt<List<BookingLogDto>>();
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;
11 months ago
return rtn;
}
11 months ago
#region 对外接口
/// <summary>
/// 舱位接收保存、取消接口
/// </summary>
/// <returns></returns>
[HttpPost("/BookingSlot/ApiReceive"), AllowAnonymous, ApiUser]
public async Task<TaskManageOrderResultDto> ApiReceive(string jsonData, IFormFile file = null, IFormFile modifyFile = null)
{
TaskManageOrderResultDto result = new TaskManageOrderResultDto();
try
{
BookingSlotBaseApiDto dto = JSON.Deserialize<BookingSlotBaseApiDto>(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
{
10 months ago
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
/// <summary>
/// 舱位接收保存、取消接口
/// </summary>
/// <param name="dto"></param>
/// <param name="file"></param>
/// <param name="modifyFile"></param>
/// <returns></returns>
[HttpPost("/BookingSlot/InnerApiReceive")]
public async Task<long> InnerApiReceive(BookingSlotBaseApiDto dto, DynameFileInfo file = null, DynameFileInfo modifyFile = null)
11 months ago
{
long id = 0;
11 months ago
//接口方法直接调用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;
}
});
}
11 months ago
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<BookingSlotBase>();
await _repBase.InsertAsync(model);
id = model.Id;
11 months ago
foreach (var ctn in dto.DataObj.CtnList)
{
var newCtn = ctn.Adapt<BookingSlotCtn>();
newCtn.SLOT_ID = model.Id;
await _repCtn.InsertAsync(newCtn);
}
11 months ago
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))
{
//将格式单附件写入订舱的附件
10 months ago
SaveEDIFile(id, fileFullPath, modifyFile.FileName, UserManager.TENANT_ID,
CONST_BC_NOTICE_FILE_CODE, CONST_BC_NOTICE_FILE_NAME).GetAwaiter();
}
}
11 months ago
}
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>();
TaskBCInfoDto bcTargetDto = dto.DataObj.Adapt<TaskBCInfoDto>();
//提取箱信息
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();
}
11 months ago
var oldObj = model.Adapt<BookingSlotBaseApiSaveDto>();
11 months ago
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<BookingSlotCtn>();
newCtn.SLOT_ID = model.Id;
await _repCtn.InsertAsync(newCtn);
}
11 months ago
11 months ago
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,需要与舱位进行数据比对
10 months ago
await PushCompareBCInfo(bcSrcDto, bcTargetDto,id, dto.BatchNo);
11 months ago
}
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;
11 months ago
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);
}
11 months ago
await InsLog("Del", model.Id, "取消舱位");
11 months ago
}
//更新库存
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,
PLACERECEIPT = model.PLACERECEIPT,
PLACEDELIVERY = model.PLACEDELIVERY
11 months ago
}));
}
else
{
throw Oops.Bah("操作类型参数有误");
}
return id;
11 months ago
}
11 months ago
/// <summary>
/// 插入日志(仅显示一条文本信息)
/// </summary>
/// <param name="type"></param>
/// <param name="slotId"></param>
/// <param name="status"></param>
/// <returns></returns>
[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,
});
}
}
/// <summary>
/// 插入日志(比对修改内容)
/// </summary>
/// <param name="type"></param>
/// <param name="id"></param>
/// <param name="objType"></param>
/// <param name="objOld"></param>
/// <param name="objNew"></param>
/// <param name="excepProp"></param>
/// <returns></returns>
[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}",
});
}
}
}
11 months ago
#endregion
#region 库存
11 months ago
/// <summary>
/// 库存查询
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("/BookingSlot/pageStock")]
public async Task<dynamic> 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<SqlSugarPagedList<BookingSlotStockListOutput>>();
return result.XnPagedResult();
}
/// <summary>
/// 刷新库存统计
/// </summary>
/// <returns></returns>
[HttpPost("/BookingSlot/refreshStock")]
public async Task RefreshStock(BookingSlotStockUpdateModel input)
{
//更新库存
await _publisher.PublishAsync(new ChannelEventSource("BookingSlotStock:Update", input));
}
#endregion
#region 舱位引入
/// <summary>
/// 查询可用的舱位及箱子
/// </summary>
[HttpGet("/BookingSlot/getAvailableSlots")]
public async Task<List<BookingSlotBaseWithCtnDto>> GetAvailableSlots([FromQuery] BookingSlotBaseDto input)
{
return await GetAvailableSlots(input, null);
}
/// <summary>
/// 查询可用的舱位及箱子列表
/// </summary>
/// <param name="slotInput">筛选条件1舱位信息、箱型</param>
/// <param name="slotIdListInput">筛选条件2舱位主键列表</param>
/// <returns>可用的舱位列表(含可用的箱子列表)</returns>
[NonAction]
public async Task<List<BookingSlotBaseWithCtnDto>> GetAvailableSlots(BookingSlotBaseDto slotInput = null, List<long> 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<BookingSlotBase, BookingSlotCtn>((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<BookingSlotAllocation, BookingSlotAllocationCtn>((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<BookingSlotBase> baseList = await _repBase.AsQueryable()
.Where(u => baseIdList.Contains(u.Id))
.ToListAsync();
List<Core.Entity.CodeCtn> ctnCodeCache = await _cache.GetAllCodeCtn();
// 构建结果
List<BookingSlotBaseWithCtnDto> result = baseList.Adapt<List<BookingSlotBaseWithCtnDto>>();
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;
}
/// <summary>
/// 检查指定订舱记录,是否可以引入舱位列表
/// </summary>
/// <param name="slots">待引入的舱位列表</param>
/// <param name="bookingOrderId">待关联的订舱记录</param>
/// <returns>isExists指定订舱记录是否已经引入过舱位数据isEnough现有舱位及箱子是否满足需求message提示信息</returns>
[NonAction]
public async Task<(bool isExists, bool isEnough, string message)> CheckImportSlots(List<BookingSlotBaseWithCtnDto> slots, long bookingOrderId)
{
slots ??= new List<BookingSlotBaseWithCtnDto>();
// 判断是否已存在引用关系
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();
/// <summary>
/// 为指定订舱记录引入舱位信息
/// </summary>
/// <param name="slots">待引入的舱位列表</param>
/// <param name="bookingOrderId">待关联的订舱记录</param>
/// <param name="isCheck">是否进行剩余量检查</param>
/// <returns>isSuccess检查余量及已引用检查是否成功通过message提示信息</returns>
[NonAction]
public async Task<(bool isSuccess, string message)> ImportSlots(List<BookingSlotBaseWithCtnDto> slots, long bookingOrderId, bool isCheck)
{
slots ??= new List<BookingSlotBaseWithCtnDto>();
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<BookingSlotBase> 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<BookingSlotBase, BookingSlotAllocation>()
.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<BookingSlotAllocation>(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,
PLACERECEIPT = latestSlot.PLACERECEIPT,
PLACEDELIVERY = latestSlot.PLACEDELIVERY
}));
}
}
finally
{
Monitor.Exit(ImportLockObj);
}
return (true, "引入成功");
}
11 months ago
#endregion
#region 获取附件
/// <summary>
/// 获取附件
/// </summary>
/// <param name="id">舱位主键</param>
/// <returns>返回附件列表</returns>
[HttpGet("/BookingSlot/GetFile")]
public async Task<List<BookingFile>> GetFile(long id)
{
var list = await _bookingfile.AsQueryable().Filter(null, true)
.Where(u => u.BookingId == id && u.Moudle == "BookingSlot").ToListAsync();
return list;
}
#endregion
#region 舱位
/// <summary>
/// 分页查询订舱舱位
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("/BookingSlot/page")]
public async Task<dynamic> 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<SqlSugarPagedList<BookingSlotBaseListOutput>>();
return result.XnPagedResult();
}
#endregion
#region 异步写入附件表
/// <summary>
/// 异步写入附件表
/// </summary>
/// <param name="boookId">订舱ID</param>
/// <param name="FilePath">文件路径</param>
/// <param name="fileName">文件名</param>
/// <param name="tenantId">租户ID</param>
/// <param name="fileTypeCode">附件类型代码</param>
/// <param name="fileTypeName">附件类型名称</param>
/// <param name="moudle">附件模块代码</param>
/// <returns></returns>
[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
10 months ago
#region 推送BC变更比对
/// <summary>
/// 推送BC变更比对
/// </summary>
/// <param name="bcSrcDto">原舱位详情</param>
/// <param name="bcTargetDto">变更后舱位详情</param>
/// <param name="slotId">舱位主键</param>
/// <param name="reqBatchNo">请求批次号用来区分对应的哪个批次任务</param>
/// <returns></returns>
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比对失败返回为空");
10 months ago
DateTime nowDate = DateTime.Now;
10 months ago
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),
};
10 months ago
await _bookingSlotCompareRepository.InsertAsync(entity);
}
10 months ago
#endregion
#region 请求BC比对
/// <summary>
/// 请求BC比对
/// </summary>
/// <param name="bcSrcDto">BC详情</param>
/// <param name="bcTargetDto">BC变更后详情</param>
/// <returns>返回回执</returns>
[NonAction]
private async Task<TaskManageExcuteResultDto> ExcuteCompare(TaskBCInfoDto bcSrcDto, TaskBCInfoDto bcTargetDto)
{
TaskManageExcuteResultDto model = null;
/*
1URL
2JSON
3POST
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<TaskManageExcuteResultDto>(result);
}
}
catch (Exception ex)
{
_logger.LogInformation("推送BC比对异常原因{error}", ex.Message);
throw Oops.Oh($"推送BC比对异常原因{ex.Message}");
}
}
return model;
}
#endregion
10 months ago
#region 获取舱位变更比对结果
/// <summary>
/// 获取舱位变更比对结果
/// </summary>
/// <param name="id">舱位主键</param>
/// <param name="batchNo">批次号</param>
/// <returns>返回舱位变更比对结果</returns>
[HttpGet("/BookingSlot/GetSlotCompareResult")]
public async Task<List<CompareResultDetailInfo>> GetSlotCompareResult([FromQuery] long id, [FromQuery] string batchNo)
10 months ago
{
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<List<CompareResultDetailInfo>>(compareInfo.COMPARE_RLT);
}
#endregion
#region 导入舱位
/// <summary>
/// 导入舱位
/// </summary>
/// <param name="file">导入舱位文件</param>
/// <returns>返回回执</returns>
[HttpPost("/BookingSlot/ImportSlotFromFile")]
public async Task<TaskManageOrderResultDto> ImportSlotFromFile(IFormFile file)
{
TaskManageOrderResultDto result = new TaskManageOrderResultDto();
try
{
/*
1Excel
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
{
/// <summary>
/// 文件名称
/// </summary>
public string FileName { get; set; }
/// <summary>
/// 文件二进制流
/// </summary>
public byte[] FileBytes { get; set; }
11 months ago
}
}