|
|
using Furion.DependencyInjection;
|
|
|
using Furion.DynamicApiController;
|
|
|
using Furion.EventBus;
|
|
|
using Furion.FriendlyException;
|
|
|
using Mapster;
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
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 SqlSugar;
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.ComponentModel;
|
|
|
using System.Linq;
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
namespace Myshipping.Application
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// 订舱舱位
|
|
|
/// </summary>
|
|
|
[ApiDescriptionSettings("Application", Name = "BookingSlot", Order = 1)]
|
|
|
public class BookingSlotService : IDynamicApiController, ITransient, IBookingSlotService
|
|
|
{
|
|
|
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<BookingLog> _repBookingLog;
|
|
|
private readonly SqlSugarRepository<BookingLogDetail> _repBookingLogDetail;
|
|
|
private readonly SqlSugarRepository<BookingFile> _bookingfile;
|
|
|
|
|
|
private readonly ILogger<BookingSlotService> _logger;
|
|
|
private readonly ISysCacheService _cache;
|
|
|
|
|
|
private readonly IEventPublisher _publisher;
|
|
|
|
|
|
public BookingSlotService(SqlSugarRepository<BookingSlotBase> repBase,
|
|
|
SqlSugarRepository<BookingSlotCtn> repCtn,
|
|
|
SqlSugarRepository<BookingSlotStock> repStock,
|
|
|
SqlSugarRepository<BookingLog> repBookingLog,
|
|
|
SqlSugarRepository<BookingLogDetail> repBookingLogDetail,
|
|
|
SqlSugarRepository<BookingFile> bookingfile,
|
|
|
ILogger<BookingSlotService> logger,
|
|
|
ISysCacheService cache,
|
|
|
IEventPublisher publisher,
|
|
|
SqlSugarRepository<BookingSlotAllocation> repAllocation)
|
|
|
{
|
|
|
_repBase = repBase;
|
|
|
_repCtn = repCtn;
|
|
|
_repStock = repStock;
|
|
|
_repBookingLog = repBookingLog;
|
|
|
_repBookingLogDetail = repBookingLogDetail;
|
|
|
_repAllocation = repAllocation;
|
|
|
|
|
|
|
|
|
_logger = logger;
|
|
|
_cache = cache;
|
|
|
|
|
|
_publisher = publisher;
|
|
|
_bookingfile = bookingfile;
|
|
|
}
|
|
|
|
|
|
#region 舱位
|
|
|
|
|
|
/// <summary>
|
|
|
/// 保存订舱舱位
|
|
|
/// </summary>
|
|
|
/// <param name="input"></param>
|
|
|
/// <returns></returns>
|
|
|
[HttpPost("/BookingSlot/save")]
|
|
|
public async Task<BookingSlotBaseSaveOutput> 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<BookingSlotBaseSaveInput>();
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
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<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);
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 获取订舱舱位
|
|
|
/// </summary>
|
|
|
/// <param name="id"></param>
|
|
|
/// <returns></returns>
|
|
|
[HttpGet("/BookingSlot/detail")]
|
|
|
public async Task<BookingSlotBaseSaveOutput> 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<BookingSlotBaseSaveOutput>();
|
|
|
rtn.CtnList = ctns.Adapt<List<BookingSlotCtnSaveInput>>();
|
|
|
|
|
|
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;
|
|
|
|
|
|
return rtn;
|
|
|
}
|
|
|
|
|
|
#region 对外接口
|
|
|
/// <summary>
|
|
|
/// 舱位接收保存、取消接口
|
|
|
/// </summary>
|
|
|
/// <returns></returns>
|
|
|
[HttpPost("/BookingSlot/ApiReceive"), AllowAnonymous, ApiUser]
|
|
|
public async Task<long> ApiReceive(BookingSlotBaseApiDto dto)
|
|
|
{
|
|
|
long id = 0;
|
|
|
//接口方法直接调用save、delete等方法会报错,可能因为非token授权登录导致,故重写一遍保存、删除代码
|
|
|
if (dto.OpType == "add" || dto.OpType == "update" || dto.OpType == "del")
|
|
|
{
|
|
|
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;
|
|
|
|
|
|
foreach (var ctn in dto.DataObj.CtnList)
|
|
|
{
|
|
|
var newCtn = ctn.Adapt<BookingSlotCtn>();
|
|
|
newCtn.SLOT_ID = model.Id;
|
|
|
await _repCtn.InsertAsync(newCtn);
|
|
|
}
|
|
|
|
|
|
await InsLog("Add", model.Id, "新增舱位");
|
|
|
}
|
|
|
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} 的数据");
|
|
|
}
|
|
|
|
|
|
var oldObj = model.Adapt<BookingSlotBaseApiSaveDto>();
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
await InsLog("Update", model.Id, typeof(BookingSlotBaseApiSaveDto), oldObj, dto.DataObj, "CtnList");
|
|
|
}
|
|
|
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} 的数据");
|
|
|
}
|
|
|
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;
|
|
|
}
|
|
|
#endregion
|
|
|
|
|
|
/// <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}",
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endregion
|
|
|
|
|
|
#region 库存
|
|
|
/// <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>(指定订舱记录是否已经引入过舱位数据,现有舱位及箱子是否满足需求,提示信息)</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, $"可以引入");
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 为指定订舱记录引入舱位信息
|
|
|
/// </summary>
|
|
|
/// <param name="slots">待引入的舱位列表</param>
|
|
|
/// <param name="bookingOrderId">待关联的订舱记录</param>
|
|
|
/// <param name="isCheck">是否进行剩余量检查</param>
|
|
|
/// <returns>(是否成功,提示消息)</returns>
|
|
|
[NonAction]
|
|
|
public async Task<(bool isSuccess, string message)> ImportSlots(List<BookingSlotBaseWithCtnDto> slots, long bookingOrderId, bool isCheck)
|
|
|
{
|
|
|
slots ??= new List<BookingSlotBaseWithCtnDto>();
|
|
|
|
|
|
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
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
return (true, "引入成功");
|
|
|
}
|
|
|
#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
|
|
|
}
|
|
|
}
|