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

423 lines
19 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 Myshipping.Core;
using Furion.DependencyInjection;
using Furion.DynamicApiController;
using Mapster;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using System.Linq;
using System.Threading.Tasks;
using Myshipping.Application.Entity;
using Microsoft.Extensions.Logging;
using Furion.FriendlyException;
using Myshipping.Application.Enum;
using System.ComponentModel;
using System.Collections.Generic;
using System.IO;
using MiniExcelLibs;
using NPOI.HSSF.UserModel;
using Myshipping.Core.Helper;
using NPOI.SS.UserModel;
using Furion;
using System;
using System.Web;
using System.Text;
using Myshipping.Application.ConfigOption;
using Myshipping.Core.Service;
using Myshipping.Application.Service.BookingSlot.Dto;
using NPOI.SS.Formula.Functions;
using Furion.EventBus;
using Myshipping.Application.Event;
using Microsoft.AspNetCore.Authorization;
using Furion.DatabaseAccessor;
namespace Myshipping.Application
{
/// <summary>
/// 订舱舱位
/// </summary>
[ApiDescriptionSettings("Application", Name = "BookingSlot", Order = 1)]
public class BookingSlotService : IDynamicApiController, ITransient
{
private readonly SqlSugarRepository<BookingSlotBase> _repBase;
private readonly SqlSugarRepository<BookingSlotCtn> _repCtn;
private readonly SqlSugarRepository<BookingSlotStock> _repStock;
private readonly ILogger<BookingSlotService> _logger;
private readonly ISysCacheService _cache;
private readonly IEventPublisher _publisher;
public BookingSlotService(SqlSugarRepository<BookingSlotBase> repBase,
SqlSugarRepository<BookingSlotCtn> repCtn,
SqlSugarRepository<BookingSlotStock> repStock,
ILogger<BookingSlotService> logger,
ISysCacheService cache,
IEventPublisher publisher)
{
_repBase = repBase;
_repCtn = repCtn;
_repStock = repStock;
_logger = logger;
_cache = cache;
_publisher = publisher;
}
#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();
}
/// <summary>
/// 保存订舱舱位
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("/BookingSlot/save")]
public async Task<BookingSlotBaseSaveDto> Save(BookingSlotBaseSaveDto 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);
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);
}
}
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 _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>
[HttpPost("/BookingSlot/delete")]
public async Task Delete(long id)
{
var entity = await _repBase.FirstOrDefaultAsync(u => u.Id == id);
entity.IsDeleted = true;
await _repBase.UpdateAsync(entity);
var ctns = await _repCtn.Where(x => x.SLOT_ID == id).ToListAsync();
foreach (var item in ctns)
{
item.IsDeleted = true;
}
await _repCtn.UpdateAsync(ctns);
//更新库存
await _publisher.PublishAsync(new ChannelEventSource("BookingSlotStock:Update", new BookingSlotStockUpdateModel
{
BOOKING_SLOT_TYPE = entity.BOOKING_SLOT_TYPE,
CARRIERID = entity.CARRIERID,
CONTRACT_NO = entity.CONTRACT_NO,
VESSEL = entity.VESSEL,
VOYNO = entity.VOYNO
}));
}
/// <summary>
/// 获取订舱舱位
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("/BookingSlot/detail")]
public async Task<BookingSlotBaseSaveDto> 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<BookingSlotBaseSaveDto>();
rtn.CtnList = ctns.Adapt<List<BookingSlotCtnSaveInput>>();
return rtn;
}
#region 对外接口
/// <summary>
/// 舱位接收保存、取消接口
/// </summary>
/// <returns></returns>
[HttpPost("/BookingSlot/ApiReceive"), AllowAnonymous, ApiUser]
public async Task ApiReceive(BookingSlotBaseApiDto dto)
{
//接口方法直接调用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);
foreach (var ctn in dto.DataObj.CtnList)
{
var newCtn = ctn.Adapt<BookingSlotCtn>();
newCtn.SLOT_ID = model.Id;
await _repCtn.InsertAsync(newCtn);
}
}
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} 的数据");
}
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);
}
}
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 _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("操作类型参数有误");
}
}
#endregion
#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));
}
/// <summary>
/// 查询可用的舱位及箱子
/// </summary>
[HttpGet("/BookingSlot/getAvailableSlots")]
public async Task<List<BookingSlotAvailableDto>> GetAvailableSlots([FromQuery] BookingSlotBaseDto input)
{
string[] ctnCodeList = null;
if (!string.IsNullOrEmpty(input.CTN_STAT))
{
ctnCodeList = input.CTN_STAT.Split(',');
}
// 1. 【舱位基础表】与【箱子表】做关联并根据【舱位主键】、【箱型】做分组统计出【总的箱量】作为queryable1
var queryable1 = _repBase.Context.Queryable<BookingSlotBase, BookingSlotCtn>((bas, ctn) => bas.Id == ctn.SLOT_ID)
.WhereIF(!string.IsNullOrEmpty(input.PORTLOAD), bas => bas.PORTLOAD.Contains(input.PORTLOAD))
.WhereIF(!string.IsNullOrEmpty(input.PORTDISCHARGE), bas => bas.PORTLOAD.Contains(input.PORTLOAD))
.WhereIF(!string.IsNullOrEmpty(input.VESSEL), bas => bas.VESSEL.Contains(input.VESSEL))
.WhereIF(!string.IsNullOrEmpty(input.VOYNO), bas => bas.VOYNO.Contains(input.VOYNO))
.WhereIF(!string.IsNullOrEmpty(input.CARRIAGE_TYPE), bas => bas.CARRIAGE_TYPE == input.CARRIAGE_TYPE)
.WhereIF(!string.IsNullOrEmpty(input.BOOKING_SLOT_TYPE), bas => bas.BOOKING_SLOT_TYPE == input.BOOKING_SLOT_TYPE)
.WhereIF(ctnCodeList != null, (bas, ctn) => ctnCodeList.Contains(ctn.CTNCODE))
.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<BookingSlotAvailableDto> result = baseList.Adapt<List<BookingSlotAvailableDto>>();
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;
}
#endregion
}
}