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;
}
}
}