using Admin.NET.Core.Service; using Admin.NET.Application.Entity; using Microsoft.AspNetCore.Http; using Admin.NET.Application; using StackExchange.Redis; using System.Linq.Expressions; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Qiniu.Storage; using Mapster; using Elastic.Clients.Elasticsearch; using Furion.DatabaseAccessor; using static SKIT.FlurlHttpClient.Wechat.Api.Models.ChannelsECMerchantAddFreightTemplateRequest.Types.FreightTemplate.Types; using System.ComponentModel; using static SKIT.FlurlHttpClient.Wechat.Api.Models.ChannelsECLogisticsEWaybillOrderCreateRequest.Types; using NewLife.Security; using Admin.NET.Application.CommonHelper; using Admin.NET.Application.TransferDto; namespace Admin.NET.Application; /// /// 上架服务 /// [ApiDescriptionSettings(ApplicationConst.WmsOperationsGroupName, Order = 100)] public class WmsPutawayService : IDynamicApiController, ITransient { private readonly SqlSugarRepository _wmsRecordTransRep; private readonly SqlSugarRepository _wmsLogActionRep; private readonly SqlSugarRepository _wmsContainerRep; private readonly SqlSugarRepository _wmsPlaceRep; private readonly SqlSugarRepository _wmsAreaRep; private readonly SqlSugarRepository _wmsContainerPlaceRep; private readonly SqlSugarRepository _v_wms_containerRep; private readonly SqlSugarRepository _wmsStockQuanRep; private readonly SqlSugarRepository _wmsTask; private readonly SqlSugarRepository _wmsOrderMovementRep; private readonly SqlSugarRepository _wmsOrderMovementDetailsRep; private readonly SqlSugarRepository _WmsNoCreateRuleRep; private readonly SqlSugarRepository _repSNRep; private readonly SqlSugarRepository _wmsMaterialRep; private readonly SqlSugarRepository _wmsContainerPackagingRep; private readonly SqlSugarRepository _v_wms_stock_quanRep; private readonly WmsGroupDiskService _wmsGroupDiskService; private readonly SqlSugarRepository _repWmsContainerPlaceRep; private readonly SqlSugarRepository _wmsBaseBusinessTypeRep; private WmsPlaceService _wmsPlaceService; /// /// 构造函数 /// public WmsPutawayService( SqlSugarRepository wmsBaseBusinessTypeRep, SqlSugarRepository repWmsContainerPlaceRep, WmsGroupDiskService wmsGroupDiskService, SqlSugarRepository v_wms_containerRep, SqlSugarRepository wmsStockQuanRep, SqlSugarRepository wmsRecordTransRep, SqlSugarRepository wmsLogActionRep, SqlSugarRepository wmsContainerPlaceRep, SqlSugarRepository wmsContainerRep, SqlSugarRepository wmsPlaceRep, SqlSugarRepository wmsAreaRep, SqlSugarRepository wmsTaskRep, SqlSugarRepository wmsOrderMovementRep, SqlSugarRepository wmsOrderMovementDetailsRep, SqlSugarRepository wmsNoCreateRuleRep, SqlSugarRepository repSNRep, SqlSugarRepository wmsMaterialRep, SqlSugarRepository wmsContainerPackagingRep, SqlSugarRepository v_wms_stock_quanRep, WmsPlaceService wmsPlaceService) { _wmsBaseBusinessTypeRep = wmsBaseBusinessTypeRep; _repWmsContainerPlaceRep = repWmsContainerPlaceRep; _wmsGroupDiskService = wmsGroupDiskService; _v_wms_containerRep = v_wms_containerRep; _wmsLogActionRep = wmsLogActionRep; _wmsStockQuanRep = wmsStockQuanRep; _wmsRecordTransRep = wmsRecordTransRep; _wmsContainerRep = wmsContainerRep; _wmsPlaceRep = wmsPlaceRep; _wmsAreaRep = wmsAreaRep; _wmsContainerPlaceRep = wmsContainerPlaceRep; _wmsTask = wmsTaskRep; _wmsOrderMovementRep = wmsOrderMovementRep; _wmsOrderMovementDetailsRep = wmsOrderMovementDetailsRep; _WmsNoCreateRuleRep = wmsNoCreateRuleRep; _repSNRep = repSNRep; _wmsMaterialRep = wmsMaterialRep; _wmsContainerPackagingRep = wmsContainerPackagingRep; _v_wms_stock_quanRep = v_wms_stock_quanRep; _wmsLogActionRep = wmsLogActionRep; _wmsPlaceService = wmsPlaceService; } #region PDA上架操作根据容器号查询要上架的库存信息 /// /// PDA上架查询-获取容器上的的库存信息 /// /// /// [HttpGet] [ApiDescriptionSettings(Name = "GetPutawayStockQuan", Description = "PDA上架查询-获取容器上的的库存信息")] [Description("/WmsPutaway/GetPutawayStockQuan")] public async Task GetPutawayStockQuan([FromQuery] WmsPutawayInput input) { var wmsContainerItem = await _wmsContainerRep.GetFirstAsync(w => w.ContainerCode == input.ContainerCode && w.IsDelete == false); if (wmsContainerItem == null) { throw Oops.Oh($"容器{input.ContainerCode}不存在!"); } if (wmsContainerItem.IsDisabled == true) { throw Oops.Oh($"容器{input.ContainerCode}已禁用!"); } //获取容器库位关系 var wmsContainerLocation = await _wmsContainerPlaceRep.GetFirstAsync(w => w.ContainerCode == input.ContainerCode); WmsPutawayOutput result = new WmsPutawayOutput(); if (wmsContainerLocation != null) { result.PlaceCode = wmsContainerLocation.PlaceCode; } result.Details = await GetStockQuansByContainerCode(input.ContainerCode); return result; } /// /// PDA上架查询-获取容器上的的库存信息(空容器组盘、上架专用) /// /// /// [HttpPost] [ApiDescriptionSettings(Name = "GetContainerInfoQuan", Description = "PDA上架查询-获取容器上的的库存信息")] [Description("/Putaway/GetContainerInfoQuan")] public async Task> GetContainerInfoQuan(GeContainerInput input) { var wmsContainerItem = await _wmsContainerRep.GetFirstAsync(w => w.ContainerCode == input.ContainerCode && w.IsDelete == false); if (wmsContainerItem == null) { throw Oops.Oh($"容器{input.ContainerCode}不存在!"); } if (wmsContainerItem.IsDisabled == true) { throw Oops.Oh($"容器{input.ContainerCode}已禁用!"); } //如果该容器上有 物料,则报错 var stockQun = await _wmsStockQuanRep.GetFirstAsync(x => x.ContainerCode == input.ContainerCode && x.MaterialCode != ApplicationConst.DefaultContinerMaterialCode); if (stockQun != null) { throw Oops.Oh($"容器{input.ContainerCode}上有物料,不允许操作!"); } //ly0815-说明有底托了 两个叠托不能再合并组盘了 if (input.FontContainerList != null) { if (input.FontContainerList.Count > 0) { var ItemContainerCodeAdd = await _wmsStockQuanRep.GetFirstAsync(x => x.SNCode == input.ContainerCode); if (ItemContainerCodeAdd != null) { //存在库存中,不是在途虚拟容器,就认为已组盘 var entityWmsContainerList = await _wmsContainerRep.GetListAsync(u => u.ContainerCode.Contains(ApplicationConst.DefaultZTContainerCode_Pre) && ItemContainerCodeAdd.ContainerCode == u.ContainerCode); if (entityWmsContainerList == null || entityWmsContainerList.Count == 0) throw Oops.Oh($"容器{input.ContainerCode}已组盘,请去【容器解绑】!"); } } } //容器必须有库位关系才能组盘 去pda库位绑定中 进行绑定 var entityWmsContainerPlace = await _repWmsContainerPlaceRep.GetFirstAsync(u => u.ContainerCode == input.ContainerCode); ContainerPlaceHelper.ValidateContainerPlaceForGroupDisk(entityWmsContainerPlace, input.ContainerCode); //查询该空容器下的所有有关库存 var dbstockQunList = await _wmsStockQuanRep.GetListAsync(x => x.ContainerCode == input.ContainerCode || x.SNCode == input.ContainerCode); var wmsContainerItemList = await _wmsContainerRep.GetListAsync(w => dbstockQunList.Select(x => x.SNCode).Contains(w.ContainerCode)); var containerInfos = wmsContainerItemList.Adapt>(); if (containerInfos != null) { var curContiner = wmsContainerItem.Adapt(); if (!containerInfos.Exists(x => x.ContainerCode == curContiner.ContainerCode)) { containerInfos.Add(curContiner); } } return containerInfos; } /// /// PDA上架 /// /// /// [HttpPost] [ApiDescriptionSettings(Name = "PutawayConfirm", Description = "PDA上架")] [Description("/Putaway/PutawayConfirm")] [UnitOfWork] public async Task PutawayConfirm(WmsPutawayParamInput input) { var stockQuans = new List(); stockQuans = await _wmsStockQuanRep.GetListAsync(w => w.ContainerCode == input.ContainerCode); return await CommonPutawayConfirm(input, stockQuans); } /// /// PDA组盘+上架 /// /// /// [HttpPost] [ApiDescriptionSettings(Name = "BindPutawayConfirm", Description = "PDA组盘+上架")] [Description("/Putaway/BindPutawayConfirm")] [UnitOfWork] public async Task BindPutawayConfirm(WmsBindPutawayParamInput input) { WmsGroupDiskBaseInput bindInput = input.Adapt(); PdaBindUpdateOutput pdaBindUpdateOutput = await _wmsGroupDiskService.PdaBindUpdate(bindInput); WmsPutawayParamInput putawayInput = input.Adapt(); return await CommonPutawayConfirm(putawayInput, pdaBindUpdateOutput.StockQuans); } /// /// 公共上架功能 /// /// /// /// private async Task CommonPutawayConfirm(WmsPutawayParamInput input, List stockQuans) { List addWmsOrderMovementDetailsList = new List(); var wmsContainerItem = await _wmsContainerRep.GetFirstAsync(w => w.ContainerCode == input.ContainerCode && w.IsDelete == false); if (wmsContainerItem == null) { throw Oops.Oh($"容器{input.ContainerCode}不存在!"); } if (wmsContainerItem.IsDisabled == true) { throw Oops.Oh($"容器{input.ContainerCode}已禁用!"); } if (wmsContainerItem.IsVirtually == true) { throw Oops.Oh($"容器{input.ContainerCode}是虚拟容器!"); } PlaceTransferDto source_placeTransferDto = new PlaceTransferDto(); PlaceTransferDto to_placeTransferDto = new PlaceTransferDto(); //新增操作履历 List addWmsLogActionList = new List(); //校验库存 if (stockQuans?.Count <= 0) { throw Oops.Oh($"容器{input.ContainerCode}没有库存信息!"); } //2.循环库存信息-校验 foreach (var stockQuan in stockQuans) { #region 校验 if (stockQuan.Quantity <= 0) { //暂时注释该验证 【Editby shaocx,2024-07-28】 // 如果存在库存数量为0的库存,则提示用户 //throw Oops.Oh($"容器{input.ContainerCode}物料{stockQuan.MaterialCode}库存数是{stockQuan.Quantity}不能上架!"); } #endregion } var _v_wms_container = await _v_wms_containerRep.GetFirstAsync(u => u.ContainerCode == input.ContainerCode); if (_v_wms_container == null) { throw Oops.Oh($"容器{input.ContainerCode}不存在容器,不可上架"); } else { if (string.IsNullOrEmpty(_v_wms_container.PlaceCode)) { throw Oops.Oh($"容器{input.ContainerCode}不存在容器和库位绑定关系,不可上架"); } source_placeTransferDto = new PlaceTransferDto() { WarehouseCode = _v_wms_container.WarehouseCode, WarehouseName = _v_wms_container.WarehouseName, AreaCode = _v_wms_container.AreaCode, AreaName = _v_wms_container.AreaName, PlaceCode = _v_wms_container.PlaceCode, PlaceName = _v_wms_container.PlaceName }; } //获取库位信息 WmsBasePlace toPlace = null; if (!string.IsNullOrWhiteSpace(input.PlaceCode)) { toPlace = await _wmsPlaceRep.GetFirstAsync(w => w.PlaceCode == input.PlaceCode && w.IsDelete == false); if (toPlace == null) { throw Oops.Oh($"目标库位{input.PlaceCode}不存在!"); } if (toPlace.IsDisabled == true) { throw Oops.Oh($"目标库位{input.PlaceCode}已禁用!"); } if (toPlace.IsVirtually == true) { throw Oops.Oh($"目标库位{input.PlaceCode}是虚拟库位!"); } //库位属性 正常和禁出的可以组盘上架 if (toPlace.PlaceStatus != PlaceStatusEnum.正常 && toPlace.PlaceStatus != PlaceStatusEnum.禁出) { throw Oops.Oh($"目标库位{input.PlaceCode}库位属性是{toPlace.PlaceStatus.GetDescription()}!"); } } else { //ly0802 - 不选择 目标库位,只需要选择目标库区,如果不选择目标库位,就系统自己寻找 //throw Oops.Oh($"目标库位不能为空!"); var WmsPlaceInput = new WmsPlaceInput(); WmsPlaceInput.AreaCode = input.AreaCode; List WmsPlaceList = await _wmsPlaceService.RecommendPlaceList(WmsPlaceInput); if (WmsPlaceList.Count > 0) { toPlace = await _wmsPlaceRep.GetFirstAsync(w => w.PlaceCode == WmsPlaceList[0].PlaceCode && w.IsDelete == false); } else { throw Oops.Oh($"目标库位不能为空!"); } } //查询上架的目标库区信息 WmsBaseArea toArea = await BaseInfoHelper.GetAreaByPlace(toPlace, _wmsAreaRep); //1.1.校验容器号是否已经创建待上架状态的上架单 var isExsit = await _wmsOrderMovementDetailsRep.GetFirstAsync(x => x.ContainerCode == input.ContainerCode && (x.OrderStatus == OrderStatusEnum.处理中 || x.OrderStatus == OrderStatusEnum.新建)); if (isExsit != null) { throw Oops.Oh($"容器{input.ContainerCode}存在未完成的移动单,单号{isExsit.MovementNo}!"); } var snCodeList = stockQuans.Select(s => s.SNCode).ToList(); //获取要盘点的库存视图-根据盘点库区的容器和盘点范围的物料获取盘点库存-包含库位信息、库区信息,新增事务用 var wmsStockQuanViewList = await _v_wms_stock_quanRep.GetListAsync(x => x.IsDelete == false && snCodeList.Contains(x.SNCode)); int lineNumber = 0;//移动单明细行号 var hearId = Yitter.IdGenerator.YitIdHelper.NextId();//上架单ID //update by liuwq 20240725 string orderNo = await OrderHelper.CreateOrderNoByRuleCommon(OrderTypeEnum.上架单, (int)input.ActionType, _wmsOrderMovementRep, _WmsNoCreateRuleRep, _repSNRep); //4 创建上架单 var addWmsOrderMovement = new WmsOrderMovement() { Id = hearId, OrderNo = orderNo, //按照单号规则生成单号-ly update by liuwq 20240725 OrderType = OrderTypeEnum.上架单, OrderTypeName = OrderTypeEnum.上架单.GetDescription(), BusinessType = (int)input.ActionType, BusinessTypeName = input.ActionType.GetDescription(), OrderStatus = OrderStatusEnum.新建, OrderStatusName = OrderStatusEnum.新建.GetDescription(), SourceWarehouseCode = source_placeTransferDto.WarehouseCode, SourceWarehouseName = source_placeTransferDto.WarehouseName, ToAreaCode = toArea.AreaCode, ToAreaName = toArea.AreaName, ToPlaceCode = toPlace.PlaceCode, ToPlaceName = toPlace.PlaceName, OrderSocure = SourceByEnum.系统 }; addWmsOrderMovement.BusinessType = (int)input.ActionType; addWmsOrderMovement.BusinessTypeName = input.ActionType.GetDescription(); //3.循环库存信息-创建上架单明细 foreach (var stockQuan in stockQuans) { lineNumber++; #region 创建上架单明细 var currentStockQuanView = wmsStockQuanViewList.FirstOrDefault(f => f.SNCode.Equals(stockQuan.SNCode)); if (currentStockQuanView == null) { throw Oops.Oh($"跟踪码{stockQuan.SNCode}没有获取到库存信息"); } //3.根据容器的库存创建上架单明细 var wmsOrderMovementDetails = new WmsOrderMovementDetails() { MovementId = hearId, MovementNo = addWmsOrderMovement.OrderNo, LineNumber = OrderHelper.AutoCompleEBELP(lineNumber.ToString(), 4), SNCode = stockQuan.SNCode, SupplierCode = stockQuan.SupplierCode, SupplierName = stockQuan.SupplierName, Batch = stockQuan.Batch, ErpOrderNo = stockQuan.ErpOrderNo, ErpCode = stockQuan.ErpCode, Unit = currentStockQuanView.MaterialUnit, SupplierBatch = stockQuan.SupplierBatch, OrderStatus = OrderStatusEnum.新建, OrderStatusName = OrderStatusEnum.新建.GetDescription(), //ToAreaCode = input.ToAreaCode, //ToPlaceCode = input.ToPlaceCode, ContainerCode = wmsContainerItem.ContainerCode, ContainerName = wmsContainerItem.ContainerName, SourceWarehouseCode = source_placeTransferDto.WarehouseCode, SourceWarehouseName = source_placeTransferDto.WarehouseName, SourceAreaCode = source_placeTransferDto.AreaCode, SourceAreaName = source_placeTransferDto.AreaName, SourcePlaceCode = source_placeTransferDto.PlaceCode, SourcePlaceName = source_placeTransferDto.PlaceName, ToPlaceCode = toPlace.PlaceCode, ToPlaceName = toPlace.PlaceName, ToAreaCode = toPlace.AreaCode, ToAreaName = toPlace.AreaName, MaterialCode = stockQuan.MaterialCode, MaterialName = stockQuan.MaterialName, Quantity = stockQuan.Quantity, ActionRemark = "PDA上架创建上架单", ActionTime = DateTime.Now }; addWmsOrderMovementDetailsList.Add(wmsOrderMovementDetails); #endregion #region 转移库存添加事务、操作日志 string recordTransRemarks = string.Empty; recordTransRemarks = $"容器{wmsContainerItem.ContainerCode}跟踪码{stockQuan.SNCode}"; //上架操作仅产生 操作日志,事务在调度任务中产生 //新增操作日志 WmsLogAction wareActionLog = LogActionHelper.CreateWmsLogAction(stockQuan.Id, input.ActionType.GetDescription(), recordTransRemarks); addWmsLogActionList.Add(wareActionLog); #endregion } BusinessTypeEnum wmsTaskBusinessTypeEnum = input.ActionType; var businessTypeInfo = BusinessTypeHelper.GetBusinessTypeInfoFromDB((int)wmsTaskBusinessTypeEnum, _wmsBaseBusinessTypeRep); //5.创建调度任务 var wmsTask = new WmsTask() { MoveType = businessTypeInfo.MoveType, MoveTypeName = businessTypeInfo.MoveTypeName, BusinessType = businessTypeInfo.BusinessTypeValue, BusinessTypeName = businessTypeInfo.BusinessTypeName, ContainerCode = input.ContainerCode, IsFlagFinish = false, OrderNo = addWmsOrderMovement.OrderNo, TaskDescribe = "PDA上架", TaskStatus = TaskStatusEnum.新建, TaskStatusName = TaskStatusEnum.新建.GetDescription(), TaskNo = Yitter.IdGenerator.YitIdHelper.NextId().ToString(), TaskPriority = 0, TaskName = wmsTaskBusinessTypeEnum.GetDescription(), SourcePlaceCode = source_placeTransferDto.PlaceCode,//源库位 ToAreaCode = toArea.AreaCode, ToPlaceCode = toPlace.PlaceCode }; try { #region 事务内执行操作 // 1、事务日志 //2、操作日志 //新增操作日志 await _wmsLogActionRep.InsertRangeAsync(addWmsLogActionList); //新增任务调度 await _wmsTask.InsertAsync(wmsTask); if (addWmsOrderMovementDetailsList?.Count > 0) { //新增上架单 await _wmsOrderMovementRep.InsertAsync(addWmsOrderMovement); await _wmsOrderMovementDetailsRep.InsertRangeAsync(addWmsOrderMovementDetailsList); } else { throw Oops.Oh($"上架单明细为空!"); } #endregion } catch { throw; } return 1; } /// /// 根据容器号获取库存信息 /// /// /// private async Task> GetStockQuansByContainerCode(string containerCode) { var query = _wmsStockQuanRep.AsQueryable() .WhereIF(!string.IsNullOrWhiteSpace(containerCode), u => u.ContainerCode == containerCode.Trim()) .Select().OrderBy(o => o.MaterialCode); var list = await query.ToListAsync(); return list; } #endregion }