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