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
|
{
|
/// <summary>
|
/// 公共操作服务
|
/// </summary>
|
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;
|
}
|
|
|
/// <summary>
|
/// 查找空库位
|
/// </summary>
|
/// <param name="materialModel"></param>
|
/// <param name="materialNo"></param>
|
/// <param name="requiredNum"></param>
|
/// <returns></returns>
|
/// <exception cref="UserFriendlyException"></exception>
|
public async Task<Dictionary<WmsPlaceDto, int>> 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 placeList = await _wmsPlaceRepository.GetListAsync(new WmsPlace { StorageTypeNo = Domain.Shared.Enums.PlaceTypeEnum.YUANLIAOKUWEI });
|
var allPlaceList = ObjectMapper.Map<List<WmsPlace>, List<WmsPlaceDto>>(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<WmsPlaceDto, int>();
|
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;
|
}
|
|
/// <summary>
|
/// 查找库存
|
/// </summary>
|
/// <param name="materialModel"></param>
|
/// <param name="requiredNum"></param>
|
/// <param name="materialNo"></param>
|
/// <returns></returns>
|
public async Task<Dictionary<WmsMaterialStockDto, int>> FindStockAsync(string materialModel, int requiredNum, string placeNo = "", PlaceTypeEnum placeType = PlaceTypeEnum.YUANLIAOKUWEI, string materialNo = "")
|
{
|
var allocation = new Dictionary<WmsMaterialStockDto, int>(); // <库位号, 出库数>
|
// 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<WmsMaterialStock, WmsMaterialStockDto>(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<WmsMaterialStock>, List<WmsMaterialStockDto>>(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<Expression<Func<WmsMaterialStock, bool>>> DynamicGetQueryParams(GetWmsMaterialStockInput input)
|
{
|
//动态构造查询条件
|
var whereConditions = WhereConditionsExtensions.GetWhereConditions<WmsMaterialStock, GetWmsMaterialStockInput>(input);
|
if (!whereConditions.IsSuccess)
|
{
|
throw new Exception("动态构造查询条件失败:" + whereConditions.ErrMsg);
|
}
|
|
//也可再次自定义构建查询条件
|
Expression<Func<WmsMaterialStock, bool>> extendExpression = a => a.IsDeleted == false;
|
// 使用 System.Linq.PredicateBuilder 的 And
|
var pres = (System.Linq.Expressions.Expression<Func<WmsMaterialStock, bool>>)(whereConditions.data);
|
whereConditions.data = System.Linq.PredicateBuilder.And(pres, extendExpression);
|
|
return whereConditions;
|
}
|
|
|
/// <summary>
|
/// 扣减库存
|
/// </summary>
|
/// <returns></returns>
|
public async Task<List<WmsMaterialStockDto>> ReduceMaterialStockAsync(ReduceStockInput input)
|
{
|
using var scope = _serviceProvider.CreateScope();
|
var unitOfWorkManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
|
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<WmsInOutStockRecord>();
|
var delStock = new List<WmsMaterialStock>();
|
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<WmsMaterialStock>, List<WmsMaterialStockDto>>(delStock);
|
return result;
|
}
|
|
/// <summary>
|
/// 库存转移
|
/// </summary>
|
/// <param name="input"></param>
|
/// <returns></returns>
|
public async Task<List<WmsMaterialStockDto>> MoveMaterialStock(MoveStockInput input)
|
{
|
using var scope = _serviceProvider.CreateScope();
|
var unitOfWorkManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
|
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<WmsInOutStockRecord>();
|
var result = new List<WmsMaterialStockDto>();
|
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<WmsMaterialStock>, List<WmsMaterialStockDto>>(reduceStockList));
|
}
|
|
await uow.SaveChangesAsync();
|
await uow.CompleteAsync();
|
|
return result;
|
}
|
}
|
}
|