using WIDESEA_Common.StockEnum;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_DTO.Stock;
using WIDESEA_IBasicService;
using WIDESEA_IRecordService;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
namespace WIDESEA_StockService
{
///
/// 库存信息服务实现类
///
public partial class StockInfoService : ServiceBase>, IStockInfoService
{
///
/// 获取库存信息仓储接口
///
public IRepository Repository => BaseDal;
///
/// 货位信息服务接口(用于获取仓库货位信息)
///
private readonly ILocationInfoService _locationInfoService;
///
/// 仓库信息服务接口(用于获取仓库基本信息)
///
private readonly IWarehouseService _warehouseService;
private readonly IRecordService _recordService;
///
/// 构造函数
///
/// 基础数据访问对象
public StockInfoService(
IRepository baseDal,
ILocationInfoService locationInfoService,
IWarehouseService warehouseService,
IRecordService recordService) : base(baseDal)
{
_locationInfoService = locationInfoService;
_warehouseService = warehouseService;
_recordService = recordService;
}
///
/// 获取库存信息列表(出库日期小于当前时间且库存状态为入库完成的记录)
///
/// 库存信息列表
public async Task> GetStockInfoAsync()
{
return await BaseDal.QueryDataAsync(x =>
x.OutboundDate < DateTime.Now &&
x.StockStatus == StockStatusEmun.入库完成.GetHashCode());
}
///
/// 获取库存信息列表(出库日期小于当前时间且库存状态为入库完成的记录,且仓库ID匹配)
///
/// 仓库ID
/// 库存信息列表
public async Task> GetStockInfoAsync(int warehouseId)
{
return await BaseDal.QueryDataAsync(x =>
x.OutboundDate < DateTime.Now &&
x.StockStatus == StockStatusEmun.入库完成.GetHashCode() &&
x.WarehouseId == warehouseId);
}
///
/// 获取库存信息(根据托盘码查询)
///
/// 托盘编码
/// 库存信息
public async Task GetStockInfoAsync(string palletCode)
{
return await BaseDal.QueryDataNavFirstAsync(x => x.PalletCode == palletCode);
}
///
/// 更新库存数据
///
/// 库存信息对象
/// 更新是否成功
public async Task UpdateStockAsync(Dt_StockInfo stockInfo)
{
var beforeStock = await BaseDal.QueryDataNavFirstAsync(x => x.Id == stockInfo.Id);
var result = await BaseDal.UpdateDataAsync(stockInfo);
if (!result)
return false;
var afterStock = await BaseDal.QueryDataNavFirstAsync(x => x.Id == stockInfo.Id) ?? stockInfo;
var changeType = ResolveChangeType(beforeStock, afterStock);
return await _recordService.AddStockChangeRecordAsync(beforeStock, afterStock, changeType, remark: "库存更新");
}
public override WebResponseContent AddData(Dt_StockInfo entity)
{
var result = base.AddData(entity);
if (!result.Status)
return result;
var saveRecordResult = _recordService.AddStockChangeRecordAsync(null, entity, StockChangeTypeEnum.Inbound, remark: "库存新增").GetAwaiter().GetResult();
return saveRecordResult ? result : WebResponseContent.Instance.Error("库存变更记录保存失败");
}
public override WebResponseContent UpdateData(Dt_StockInfo entity)
{
var beforeStock = BaseDal.QueryFirst(x => x.Id == entity.Id);
var result = base.UpdateData(entity);
if (!result.Status)
return result;
var changeType = ResolveChangeType(beforeStock, entity);
var saveRecordResult = _recordService.AddStockChangeRecordAsync(beforeStock, entity, changeType, remark: "库存更新").GetAwaiter().GetResult();
return saveRecordResult ? result : WebResponseContent.Instance.Error("库存变更记录保存失败");
}
public override WebResponseContent DeleteData(Dt_StockInfo entity)
{
var beforeStock = CloneStockSnapshot(entity);
var result = base.DeleteData(entity);
if (!result.Status)
return result;
var saveRecordResult = _recordService.AddStockChangeRecordAsync(beforeStock, null, StockChangeTypeEnum.Outbound, remark: "库存删除").GetAwaiter().GetResult();
return saveRecordResult ? result : WebResponseContent.Instance.Error("库存变更记录保存失败");
}
private static StockChangeTypeEnum ResolveChangeType(Dt_StockInfo? beforeStock, Dt_StockInfo? afterStock)
{
if (beforeStock == null)
return StockChangeTypeEnum.Inbound;
if (afterStock == null)
return StockChangeTypeEnum.Outbound;
if (!string.Equals(beforeStock.LocationCode, afterStock.LocationCode, StringComparison.OrdinalIgnoreCase))
return StockChangeTypeEnum.Relocation;
if (beforeStock.StockStatus != afterStock.StockStatus)
return StockChangeTypeEnum.StockLock;
var beforeQuantity = beforeStock.Details?.Sum(x => x.StockQuantity) ?? 0;
var afterQuantity = afterStock.Details?.Sum(x => x.StockQuantity) ?? 0;
return afterQuantity >= beforeQuantity ? StockChangeTypeEnum.Inbound : StockChangeTypeEnum.Outbound;
}
private static Dt_StockInfo CloneStockSnapshot(Dt_StockInfo stockInfo)
{
return new Dt_StockInfo
{
Id = stockInfo.Id,
PalletCode = stockInfo.PalletCode,
PalletType = stockInfo.PalletType,
LocationId = stockInfo.LocationId,
LocationCode = stockInfo.LocationCode,
WarehouseId = stockInfo.WarehouseId,
StockStatus = stockInfo.StockStatus,
Remark = stockInfo.Remark,
OutboundDate = stockInfo.OutboundDate,
Details = stockInfo.Details?.Select(detail => new Dt_StockInfoDetail
{
Id = detail.Id,
StockId = detail.StockId,
MaterielCode = detail.MaterielCode,
MaterielName = detail.MaterielName,
OrderNo = detail.OrderNo,
BatchNo = detail.BatchNo,
ProductionDate = detail.ProductionDate,
EffectiveDate = detail.EffectiveDate,
SerialNumber = detail.SerialNumber,
StockQuantity = detail.StockQuantity,
OutboundQuantity = detail.OutboundQuantity,
Status = detail.Status,
Unit = detail.Unit,
InboundOrderRowNo = detail.InboundOrderRowNo,
Remark = detail.Remark
}).ToList()
};
}
///
/// 检索指定托盘在给定位置的库存详细信息
///
/// 托盘编码
/// 货位编码
/// 库存信息
public async Task GetStockInfoAsync(string palletCode, string locationCode)
{
return await BaseDal.QueryFirstAsync(x => x.PalletCode == palletCode && x.LocationCode == locationCode);
}
///
/// 获取仓库3D布局数据
///
/// 仓库ID
/// 3D布局DTO
public async Task Get3DLayoutAsync(int warehouseId)
{
// 1. 查询仓库信息
var warehouse = await _warehouseService.Repository.QueryFirstAsync(x => x.WarehouseId == warehouseId);
// 2. 查询该仓库所有货位
var locations = await _locationInfoService.Repository.QueryDataAsync(x => x.WarehouseId == warehouseId);
// 3. 查询该仓库所有库存信息(包含Details导航属性)
var stockInfos = await Repository.QueryDataNavAsync(x => x.WarehouseId == warehouseId && x.LocationId != 0);
// 4. 提取物料编号和批次号列表(去重)
var materielCodeList = stockInfos
.Where(s => s.Details != null)
.SelectMany(s => s.Details)
.Select(d => d.MaterielCode)
.Where(c => !string.IsNullOrEmpty(c))
.Distinct()
.ToList();
var batchNoList = stockInfos
.Where(s => s.Details != null)
.SelectMany(s => s.Details)
.Select(d => d.BatchNo)
.Where(b => !string.IsNullOrEmpty(b))
.Distinct()
.ToList();
// 5. 创建库存字典用于快速查找(以LocationId为键)
var stockDict = stockInfos.ToDictionary(s => s.LocationId, s => s);
// 6. 映射每个货位到Location3DItemDTO
const float defaultMaxCapacity = 100f;
var locationItems = locations.Select(loc =>
{
var item = new Location3DItemDTO
{
LocationId = loc.Id,
LocationCode = loc.LocationCode,
Row = loc.Row,
Column = loc.Column,
Layer = loc.Layer,
LocationStatus = loc.LocationStatus,
MaxCapacity = defaultMaxCapacity
};
// 尝试从库存字典中获取库存信息
if (stockDict.TryGetValue(loc.Id, out var stockInfo))
{
// 空托盘也有库存记录,只是不包含明细
item.PalletCode = stockInfo.PalletCode;
item.StockStatus = stockInfo.StockStatus; // 直接使用后端库存状态
// 只有当Details不为null且有数据时才处理库存明细
if (stockInfo.Details != null && stockInfo.Details.Any())
{
item.StockQuantity = stockInfo.Details.Sum(d => d.StockQuantity);
// 获取第一个明细的物料信息(如果存在)
var firstDetail = stockInfo.Details.FirstOrDefault();
if (firstDetail != null)
{
item.MaterielCode = firstDetail.MaterielCode;
item.MaterielName = firstDetail.MaterielName;
item.BatchNo = firstDetail.BatchNo;
}
// 填充库存明细列表
item.Details = stockInfo.Details.Select(d => new StockDetailItemDTO
{
Id = d.Id,
MaterielCode = d.MaterielCode,
MaterielName = d.MaterielName,
BatchNo = d.BatchNo,
StockQuantity = d.StockQuantity,
Unit = d.Unit,
ProductionDate = d.ProductionDate,
EffectiveDate = d.EffectiveDate,
OrderNo = d.OrderNo,
Status = d.Status
}).ToList();
}
else
{
// 空托盘(无明细)
item.StockQuantity = 0;
item.Details = new List(); // 确保是空列表而非null
}
}
else
{
// 无库存记录,货位为空
item.StockStatus = 0; // 空闲
item.StockQuantity = 0;
}
return item;
}).ToList();
// 7. 计算仓库尺寸
var maxRow = locations.Any() ? locations.Max(l => l.Row) : 0;
var maxColumn = locations.Any() ? locations.Max(l => l.Column) : 0;
var maxLayer = locations.Any() ? locations.Max(l => l.Layer) : 0;
// 8. 构建返回结果
return new Stock3DLayoutDTO
{
WarehouseId = warehouseId,
WarehouseName = warehouse?.WarehouseName ?? string.Empty,
MaxRow = maxRow,
MaxColumn = maxColumn,
MaxLayer = maxLayer,
MaterielCodeList = materielCodeList,
BatchNoList = batchNoList,
Locations = locationItems
};
}
}
}