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 { /// /// 订舱舱位 /// [ApiDescriptionSettings("Application", Name = "BookingSlot", Order = 1)] public class BookingSlotService : IDynamicApiController, ITransient { private readonly SqlSugarRepository _repBase; private readonly SqlSugarRepository _repCtn; private readonly SqlSugarRepository _repStock; private readonly ILogger _logger; private readonly ISysCacheService _cache; private readonly IEventPublisher _publisher; public BookingSlotService(SqlSugarRepository repBase, SqlSugarRepository repCtn, SqlSugarRepository repStock, ILogger logger, ISysCacheService cache, IEventPublisher publisher) { _repBase = repBase; _repCtn = repCtn; _repStock = repStock; _logger = logger; _cache = cache; _publisher = publisher; } #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(); } /// /// 保存订舱舱位 /// /// /// [HttpPost("/BookingSlot/save")] public async Task 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(); 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(); await _repBase.InsertAsync(model); foreach (var ctn in input.CtnList) { var newCtn = ctn.Adapt(); 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); } /// /// 删除订舱舱位 /// /// /// [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 })); } /// /// 获取订舱舱位 /// /// /// [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>(); return rtn; } #region 对外接口 /// /// 舱位接收保存、取消接口 /// /// [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(); await _repBase.InsertAsync(model); foreach (var ctn in dto.DataObj.CtnList) { var newCtn = ctn.Adapt(); 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(); 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 库存 /// /// 库存查询 /// /// /// [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)); } /// /// 查询可用的舱位及箱子 /// [HttpGet("/BookingSlot/getAvailableSlots")] public async Task> 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((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((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; } #endregion } }