using CMS.Plugin.HIAWms.Application.Contracts.Dtos.CommonDto; using CMS.Plugin.HIAWms.Application.Contracts.Dtos.WmsMaterialStocks; using CMS.Plugin.HIAWms.Application.Contracts.Dtos.WmsPlace; using CMS.Plugin.HIAWms.Application.Contracts.Services; using CMS.Plugin.HIAWms.Domain.Shared.Enums; using CMS.Plugin.HIAWms.Domain.WmsInOutStockRecord; using CMS.Plugin.HIAWms.Domain.WmsMaterials; using CMS.Plugin.HIAWms.Domain.WmsMaterialStocks; using CMS.Plugin.HIAWms.Domain.WmsPlaces; using CmsQueryExtensions.Extension; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.Uow; namespace CMS.Plugin.HIAWms.Application.Implements { /// /// 公共操作服务 /// public class WmsCommonAppService : CMSPluginAppService, IWmsCommonAppService { private readonly IWmsMaterialRepository _wmsMaterialRepository; private readonly IWmsPlaceRepository _wmsPlaceRepository; private readonly IWmsMaterialStockRepository _wmsMaterialStockRepository; private readonly IWmsInOutStockRecordRepository _wmsInOutStockRecordRepository; private readonly IServiceProvider _serviceProvider; public WmsCommonAppService(IWmsMaterialRepository wmsMaterialRepository, IWmsPlaceRepository wmsPlaceRepository, IWmsMaterialStockRepository wmsMaterialStockRepository , IServiceProvider serviceProvider, IWmsInOutStockRecordRepository wmsInOutStockRecordRepository) { _wmsMaterialRepository = wmsMaterialRepository; _wmsPlaceRepository = wmsPlaceRepository; _wmsMaterialStockRepository = wmsMaterialStockRepository; _serviceProvider = serviceProvider; _wmsInOutStockRecordRepository = wmsInOutStockRecordRepository; } /// 动态构造查询条件 /// /// 输入参数 /// private FunReturnResultModel>> DynamicGetQueryPlaceParams(GetWmsPlaceInput input) { // 动态构造查询条件 var whereConditions = WhereConditionsExtensions.GetWhereConditions(input); if (!whereConditions.IsSuccess) { throw new Exception("动态构造查询条件失败:" + whereConditions.ErrMsg); } //也可再次自定义构建查询条件 Expression> extendExpression = a => a.IsDeleted == false; // 使用 System.Linq.PredicateBuilder 的 And var pres = (System.Linq.Expressions.Expression>)(whereConditions.data); whereConditions.data = System.Linq.PredicateBuilder.And(pres, extendExpression); return whereConditions; } /// /// 查找空库位 /// /// /// /// /// /// public async Task> FindAvailablePlacesAsync(string materialModel, int requiredNum, string materialNo = "") { var whereConditions = DynamicGetQueryParams(new GetWmsMaterialStockInput { StorageTypeNo = Domain.Shared.Enums.PlaceTypeEnum.YUANLIAOKUWEI }); // 1. 获取所有库存和库位信息 var stockList = await _wmsMaterialStockRepository.GetListAsync(whereConditions); var placeConditions = DynamicGetQueryPlaceParams(new GetWmsPlaceInput { StorageTypeNo = (int)PlaceTypeEnum.YUANLIAOKUWEI }); var placeList = await _wmsPlaceRepository.GetListByFilterAsync(placeConditions.data); var allPlaceList = ObjectMapper.Map, List>(placeList.Where(x => !x.IsDisabled).ToList()); // 2. 查找相同物料型号和编号的库存(按库存量降序) var sameModelStocks = stockList .Where(x => x.MaterialModel == materialModel) .WhereIf(!string.IsNullOrEmpty(materialNo), x => x.MaterialNo == materialNo) .OrderByDescending(x => x.StockNumber) .ToList(); var availablePlaces = new Dictionary(); int remainingNum = requiredNum; // 3. 优先检查已有库存的库位是否能存放(相同 MaterialNo) foreach (var stock in sameModelStocks) { if (remainingNum <= 0) break; // 数量已分配完 var placeInfo = allPlaceList.FirstOrDefault(x => x.PlaceNo == stock.PlaceNo); if (placeInfo == null) continue; int availableSpace = placeInfo.MaxStockNumber - stock.StockNumber; if (availableSpace <= 0) continue; int allocatedNum = Math.Min(availableSpace, remainingNum); availablePlaces.Add(placeInfo, allocatedNum); remainingNum -= allocatedNum; } // 4. 如果仍有剩余,查找空库位 if (remainingNum > 0) { var usedPlaceNos = stockList.Select(x => x.PlaceNo).Distinct().ToList(); var emptyPlaces = allPlaceList .Where(x => !usedPlaceNos.Contains(x.PlaceNo)) .ToList(); foreach (var place in emptyPlaces) { if (remainingNum <= 0) break; int allocatedNum = Math.Min(place.MaxStockNumber, remainingNum); availablePlaces.Add(place, allocatedNum); remainingNum -= allocatedNum; } } // 5. 如果仍有剩余,说明库位不足 if (remainingNum > 0) { throw new UserFriendlyException($"库位不足,还差 {remainingNum} 个无法存放!"); } return availablePlaces; } /// /// 查找库存 /// /// /// /// /// public async Task> FindStockAsync(string materialModel, int requiredNum, string placeNo = "", PlaceTypeEnum placeType = PlaceTypeEnum.YUANLIAOKUWEI, string materialNo = "") { var allocation = new Dictionary(); // <库位号, 出库数> // 1. 获取所有库存(排除锁定库存) var input = new GetWmsMaterialStockInput(); var whereConditions = DynamicGetQueryParams(new GetWmsMaterialStockInput()); var stockList = (await _wmsMaterialStockRepository.GetListAsync(whereConditions)) .Where(x => x.IsLock == Domain.Shared.Enums.YesNoEnum.N) .Where(x => x.StorageTypeNo == placeType) .ToList(); if (!string.IsNullOrEmpty(placeNo)) { var stock = stockList.Where(x => x.PlaceNo == placeNo).FirstOrDefault(); if (stock.StockNumber < requiredNum) { throw new UserFriendlyException($"库位{placeNo}库存不足,可用: {stock.StockNumber}, 缺: {requiredNum - stock.StockNumber}"); } allocation.Add(ObjectMapper.Map(stock), requiredNum); return allocation; } // 2. 筛选匹配物料 var availableStocklist = stockList .Where(x => x.MaterialModel == materialModel) .WhereIf(!string.IsNullOrEmpty(materialNo), x => x.MaterialNo == materialNo) .OrderBy(x => x.StockNumber) // 优先从库存少的库位出 .ToList(); var availableStocks = ObjectMapper.Map, List>(availableStocklist); // 3. 检查总库存是否足够 int totalAvailable = availableStocks.Sum(x => x.StockNumber); if (totalAvailable < requiredNum) { throw new UserFriendlyException( $"库存不足!需求: {requiredNum}, 可用: {totalAvailable}, 缺: {requiredNum - totalAvailable}"); } // 4. 计算各库位出库数量 int remaining = requiredNum; foreach (var stock in availableStocks) { if (remaining <= 0) break; int deductAmount = Math.Min(stock.StockNumber, remaining); allocation.Add(stock, deductAmount); remaining -= deductAmount; } return allocation; } private FunReturnResultModel>> DynamicGetQueryParams(GetWmsMaterialStockInput input) { //动态构造查询条件 var whereConditions = WhereConditionsExtensions.GetWhereConditions(input); if (!whereConditions.IsSuccess) { throw new Exception("动态构造查询条件失败:" + whereConditions.ErrMsg); } //也可再次自定义构建查询条件 Expression> extendExpression = a => a.IsDeleted == false; // 使用 System.Linq.PredicateBuilder 的 And var pres = (System.Linq.Expressions.Expression>)(whereConditions.data); whereConditions.data = System.Linq.PredicateBuilder.And(pres, extendExpression); return whereConditions; } /// /// 扣减库存 /// /// public async Task> ReduceMaterialStockAsync(ReduceStockInput input) { using var scope = _serviceProvider.CreateScope(); var unitOfWorkManager = scope.ServiceProvider.GetRequiredService(); using var uow = unitOfWorkManager.Begin(requiresNew: true); var materialModel = await _wmsMaterialRepository.FindByModelAsync(input.MaterialModel); if (materialModel == null) { throw new UserFriendlyException("物料型号不存在"); } var placeInfo = await _wmsPlaceRepository.FindByNameAsync(input.PlaceNo); if (placeInfo == null) { throw new UserFriendlyException("库位信息不存在"); } var stockresult = await FindStockAsync(input.MaterialModel, input.StockNumber, input.PlaceNo, placeInfo.StorageTypeNo); if (stockresult == null) { throw new UserFriendlyException("当前无库存"); } var recordList = new List(); var delStock = new List(); foreach (var kvp in stockresult) { var stock = kvp.Key; // WmsPlace对象 var quantity = kvp.Value; // 分配数量 // 扣减库存 var stockList = await _wmsMaterialStockRepository.GetStockListAsync(new WmsMaterialStock { MaterialModel = input.MaterialModel, PlaceNo = stock.PlaceNo }); var reduceStockList = stockList.OrderBy(x => x.InStockTime).Take(quantity).ToList(); delStock.AddRange(reduceStockList); // 出入库记录 foreach (var item in reduceStockList) { var record = new WmsInOutStockRecord { TaskNo = string.IsNullOrEmpty(input.OrderNo) ? "Task_" + DateTime.Now.ToString("yyyyMMddHHmmssfff") : input.OrderNo, OrderNo = input.OrderNo, StockType = StockTypeEnum.Move, ContainerNo = item.ContainerNo, MaterialId = item.MaterialId, MaterialModel = item.MaterialModel, MaterialName = item.MaterialName, MaterialNo = item.MaterialNo, MaterialBatch = item.MaterialBatch, SourcePlace = item.PlaceNo, ToPlace = "出库", OperateTime = DateTime.Now, Remark = "出库扣减", }; recordList.Add(record); } } await _wmsMaterialStockRepository.DeleteManyAsync(delStock); await _wmsInOutStockRecordRepository.InsertManyAsync(recordList); await uow.SaveChangesAsync(); await uow.CompleteAsync(); var result = ObjectMapper.Map, List>(delStock); return result; } /// /// 库存转移 /// /// /// public async Task> MoveMaterialStock(MoveStockInput input) { using var scope = _serviceProvider.CreateScope(); var unitOfWorkManager = scope.ServiceProvider.GetRequiredService(); using var uow = unitOfWorkManager.Begin(requiresNew: true); var materialModel = await _wmsMaterialRepository.FindByModelAsync(input.MaterialModel); if (materialModel == null) { throw new UserFriendlyException("物料型号不存在"); } var placeInfo = await _wmsPlaceRepository.FindByNameAsync(input.SourcePlace); if (placeInfo == null) { throw new UserFriendlyException("来源库位信息不存在"); } var toPlace = await _wmsPlaceRepository.FindByNameAsync(input.ToPlace); if (toPlace == null) { throw new UserFriendlyException("目标库位信息不存在"); } var stockresult = await FindStockAsync(input.MaterialModel, input.StockNumber, input.SourcePlace, placeInfo.StorageTypeNo); if (stockresult == null) { throw new UserFriendlyException("当前无库存"); } var recordList = new List(); var result = new List(); foreach (var kvp in stockresult) { var stock = kvp.Key; // WmsPlace对象 var quantity = kvp.Value; // 分配数量 // 扣减转移 var stockList = await _wmsMaterialStockRepository.GetStockListAsync(new WmsMaterialStock { MaterialModel = input.MaterialModel, PlaceNo = stock.PlaceNo }); var reduceStockList = stockList.OrderBy(x => x.InStockTime).Take(quantity).ToList(); // 出入库记录 foreach (var item in reduceStockList) { var record = new WmsInOutStockRecord { TaskNo = string.IsNullOrEmpty(input.OrderNo) ? "Task_" + DateTime.Now.ToString("yyyyMMddHHmmssfff") : input.OrderNo, OrderNo = input.OrderNo, StockType = StockTypeEnum.Move, ContainerNo = item.ContainerNo, MaterialId = item.MaterialId, MaterialModel = item.MaterialModel, MaterialName = item.MaterialName, MaterialNo = item.MaterialNo, MaterialBatch = item.MaterialBatch, SourcePlace = item.PlaceNo, ToPlace = input.ToPlace, OperateTime = DateTime.Now, Remark = "出库扣减", }; recordList.Add(record); item.PlaceNo = input.ToPlace; } await _wmsMaterialStockRepository.UpdateManyAsync(reduceStockList); await _wmsInOutStockRecordRepository.InsertManyAsync(recordList); result.AddRange(ObjectMapper.Map, List>(reduceStockList)); } await uow.SaveChangesAsync(); await uow.CompleteAsync(); return result; } } }