using Mapster;
using MapsterMapper;
using WIDESEA_Common.CommonEnum;
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.StockEnum;
using WIDESEA_Common.TaskEnum;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Utilities;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Task;
using WIDESEA_IBasicService;
using WIDESEA_IRecordService;
using WIDESEA_Model.Models;
namespace WIDESEA_BasicService
{
///
/// 货位信息服务实现类
///
public partial class LocationInfoService : ServiceBase>, ILocationInfoService
{
private readonly IMapper _mapper;
private readonly IRepository _taskRepository;
private readonly IRepository _stockInfoRepository;
private readonly IRecordService _recordService;
private readonly IRepository _warehouseRepository;
///
/// 构造函数
///
/// 基础数据访问对象
/// 任务仓储
/// 库存信息仓储
public LocationInfoService(
IRepository baseDal,
IRepository taskRepository,
IRepository stockInfoRepository,
IRepository warehouseRepository,
IMapper mapper,
IRecordService recordService) : base(baseDal)
{
_taskRepository = taskRepository;
_stockInfoRepository = stockInfoRepository;
_mapper = mapper;
_recordService = recordService;
_warehouseRepository = warehouseRepository;
}
///
/// 获取货位信息仓储
///
public IRepository Repository => BaseDal;
///
/// 批量启用货位
///
/// 货位主键数组
/// 操作结果
public WebResponseContent LocationEnableStatus(int[] keys)
{
var locationInfos = Repository.QueryData(x => keys.Contains(x.Id));
locationInfos.ForEach(x => x.EnableStatus = EnableStatusEnum.Normal.GetHashCode());
Repository.UpdateData(locationInfos);
return WebResponseContent.Instance.OK();
}
///
/// 批量禁用货位
///
/// 货位主键数组
/// 操作结果
public WebResponseContent LocationDisableStatus(int[] keys)
{
var locationInfos = Repository.QueryData(x => keys.Contains(x.Id));
locationInfos.ForEach(x => x.EnableStatus = EnableStatusEnum.Disable.GetHashCode());
Repository.UpdateData(locationInfos);
return WebResponseContent.Instance.OK();
}
///
/// 单个启用货位
///
/// 货位主键
/// 操作结果
public WebResponseContent LocationEnableStatus(int key) => LocationEnableStatus(new[] { key });
///
/// 单个禁用货位
///
/// 货位主键
/// 操作结果
public WebResponseContent LocationDisableStatus(int key) => LocationDisableStatus(new[] { key });
///
/// 初始化货位
///
/// 初始化货位数据传输对象
/// 操作结果
public WebResponseContent InitializationLocation(InitializationLocationDTO dto)
{
try
{
var (isValid, errorMsg, _) = ModelValidate.ValidateModelData(dto);
if (!isValid) return WebResponseContent.Instance.Error(errorMsg);
var locationInfos = new List();
int depth = dto.Depth;
for (int row = 1; row <= dto.MaxRow; row++)
{
depth = CalculateDepth(row, dto.MaxRow, dto.Depth, depth);
for (int col = 1; col <= dto.MaxColumn; col++)
{
for (int layer = 1; layer <= dto.MaxLayer; layer++)
{
var location = CreateLocationInfo(dto.Roadway, row, col, layer, depth);
locationInfos.Add(location);
}
}
}
BaseDal.AddData(locationInfos);
return WebResponseContent.Instance.OK();
}
catch (Exception ex)
{
return WebResponseContent.Instance.Error(ex.Message);
}
}
///
/// 根据巷道获取空闲货位信息
///
/// 巷道编号
/// 空闲货位信息,如果未找到则返回null
public async Task GetLocationInfo(string roadwayNo)
{
var locations = await BaseDal.QueryDataAsync(x =>
x.EnableStatus == EnableStatusEnum.Normal.GetHashCode() &&
x.RoadwayNo == roadwayNo &&
x.LocationStatus == LocationStatusEnum.Free.GetHashCode());
return locations?
.OrderByDescending(x => x.Depth) // 1. 深度优先(从大到小)
.ThenBy(x => x.Layer) // 2. 层数
.ThenBy(x => x.Column) // 3. 列
.ThenBy(x => x.Row) // 4. 行
.FirstOrDefault();
}
///
/// 根据巷道和货位编码获取空闲货位信息
///
/// 巷道编号
/// 货位编码
/// 货位信息,如果未找到则返回null
public async Task GetLocationInfo(string roadwayNo, string locationCode)
{
return await BaseDal.QueryFirstAsync(x => x.RoadwayNo == roadwayNo && x.LocationCode == locationCode);
}
///
/// 根据货位编码获取货位信息
///
/// 货位编码
/// 货位信息
public async Task GetLocationInfoAsync(string locationCode)
{
return await BaseDal.QueryFirstAsync(x => x.LocationCode == locationCode);
}
///
/// 根据货位ID获取货位信息
///
/// 货位id
/// 货位信息
public async Task GetLocationInfoAsync(int id)
{
return await BaseDal.QueryFirstAsync(x => x.Id == id);
}
///
/// 更新货位信息
///
/// 货位信息对象
/// 更新是否成功
public async Task UpdateLocationInfoAsync(Dt_LocationInfo locationInfo)
{
var beforeLocation = await BaseDal.QueryFirstAsync(x => x.Id == locationInfo.Id);
var result = await BaseDal.UpdateDataAsync(locationInfo);
if (!result)
return false;
return beforeLocation == null
|| await _recordService.AddLocationChangeRecordAsync(beforeLocation, locationInfo, LocationChangeType.HandUpdate, remark: "货位更新");
}
public override WebResponseContent UpdateData(Dt_LocationInfo entity)
{
var beforeLocation = BaseDal.QueryFirst(x => x.Id == entity.Id);
var result = base.UpdateData(entity);
if (!result.Status || beforeLocation == null)
return result;
var saveRecordResult = _recordService.AddLocationChangeRecordAsync(beforeLocation, entity, LocationChangeType.HandUpdate, remark: "货位更新").GetAwaiter().GetResult();
return saveRecordResult ? result : WebResponseContent.Instance.Error("货位状态变更记录保存失败");
}
public override WebResponseContent UpdateData(List entities)
{
var beforeLocations = entities
.Select(entity => BaseDal.QueryFirst(x => x.Id == entity.Id))
.Where(location => location != null)
.ToDictionary(location => location!.Id, location => location!);
var result = base.UpdateData(entities);
if (!result.Status)
return result;
foreach (var entity in entities)
{
if (!beforeLocations.TryGetValue(entity.Id, out var beforeLocation))
continue;
var saveRecordResult = _recordService.AddLocationChangeRecordAsync(beforeLocation, entity, LocationChangeType.HandUpdate, remark: "批量货位更新").GetAwaiter().GetResult();
if (!saveRecordResult)
return WebResponseContent.Instance.Error("货位状态变更记录保存失败");
}
return result;
}
///
/// 检查并生成移库任务或返回出库任务
///
/// 任务号
/// 任务信息
public async Task TransferCheckAsync(int taskNum)
{
var content = new WebResponseContent();
try
{
// 根据任务号获取任务
var outboundTask = await _taskRepository.QueryFirstAsync(x => x.TaskNum == taskNum);
if (outboundTask == null)
return content.Error("任务不存在");
var location = await BaseDal.QueryFirstAsync(x => x.LocationCode == outboundTask.SourceAddress && x.RoadwayNo == outboundTask.Roadway);
// 检查是否需要进行移库
if (CheckForInternalTransfer(location))
{
// 计算对应位置的相对库位(奇数行的下一行或者偶数行的上一行)
var newLocationID = GetRelativeLocationID(location);
// 获取新的库位的任务
var internalTransferTask = await _taskRepository.QueryFirstAsync(x => x.SourceAddress == newLocationID && x.Roadway == outboundTask.Roadway);
// 如果新的库位没有找到对应的任务
if (internalTransferTask == null)
{
return content.OK("获取到移库任务", await HandleNoTaskAtLocation(newLocationID, outboundTask));
}
// 直接返回一深位出库任务
return content.OK("获取到一深位出库任务", internalTransferTask);
}
// 返回当前库位的出库任务
return content.OK("当前出库任务", outboundTask);
}
catch (Exception ex)
{
return content.Error($"发生错误:{ex.Message}");
}
}
#region 私有方法
///
/// 计算相对的库位ID
///
/// 货位信息
/// 相对的库位ID
private static string GetRelativeLocationID(Dt_LocationInfo locationInfo)
{
int line = locationInfo.Row;
// 计算相对的货位行值,奇数行的下一行或者偶数行的上一行
int relativeLine = line % 2 == 1 ? line + 1 : line - 1;
// 构建新的库位ID
string[] newLocationParts = new string[] { relativeLine.ToString().PadLeft(3, '0'), locationInfo.Column.ToString().PadLeft(3, '0'), locationInfo.Layer.ToString().PadLeft(3, '0') };
return string.Join("-", newLocationParts);
}
///
/// 处理没有任务的库位情况
///
/// 新的库位ID
/// 出库任务
/// 生成的移库任务或原始出库任务
private async Task HandleNoTaskAtLocation(string newLocationID, Dt_Task outboundTask)
{
// 判断该位置是否有库存
var stockInfo = await _stockInfoRepository.QueryDataNavFirstAsync(x =>
x.LocationCode == newLocationID &&
x.StockStatus == StockStatusEmun.入库完成.GetHashCode() &&
x.LocationDetails.LocationStatus == LocationStatusEnum.InStock.GetHashCode());
if (stockInfo == null)
{
// 如果没有库存,直接返回当前出库任务
return outboundTask;
}
// 如果有库存,生成移库任务
var emptyLocation = await GetTransferLocationEmptyAsync(outboundTask.Roadway);
var taskNo = await _taskRepository.GetTaskNo();
var newTransferTask = new Dt_Task
{
CreateDate = DateTime.Now,
Creater = App.User.UserName ?? "system",
CurrentAddress = newLocationID,
Grade = 99,
NextAddress = emptyLocation.LocationCode,
PalletCode = stockInfo.PalletCode,
Remark = "移库",
Roadway = stockInfo.LocationDetails.RoadwayNo,
SourceAddress = newLocationID,
TaskNum = taskNo,
TargetAddress = emptyLocation.LocationCode,
TaskType = TaskRelocationTypeEnum.Relocation.GetHashCode(),
TaskStatus = TaskRelocationStatusEnum.RelocationNew.GetHashCode(),
WarehouseId = stockInfo.WarehouseId
};
var createdTask = await _taskRepository.Db.Insertable(newTransferTask).ExecuteReturnEntityAsync();
var beforeStock = CloneStockSnapshot(stockInfo);
var beforeSourceLocation = stockInfo.LocationDetails == null ? null : CloneLocationSnapshot(stockInfo.LocationDetails);
var beforeTargetLocation = CloneLocationSnapshot(emptyLocation);
// 创建移库任务后,立即锁定库存和相关货位,避免并发重复分配
stockInfo.StockStatus = StockStatusEmun.移库锁定.GetHashCode();
var updateStockResult = await _stockInfoRepository.UpdateDataAsync(stockInfo);
var locationsToUpdate = new List();
if (stockInfo.LocationDetails != null)
{
stockInfo.LocationDetails.LocationStatus = LocationStatusEnum.InStockLock.GetHashCode();
locationsToUpdate.Add(stockInfo.LocationDetails);
}
emptyLocation.LocationStatus = LocationStatusEnum.FreeLock.GetHashCode();
locationsToUpdate.Add(emptyLocation);
var updateLocationResult = await BaseDal.UpdateDataAsync(locationsToUpdate);
if (!updateStockResult || !updateLocationResult)
{
throw new Exception("创建移库任务后更新库存状态或货位状态失败");
}
var saveStockRecordResult = await _recordService.AddStockChangeRecordAsync(
beforeStock,
stockInfo,
StockChangeTypeEnum.Relocation,
createdTask.TaskNum,
createdTask.OrderNo,
"移库任务预占库存");
if (!saveStockRecordResult)
{
throw new Exception("创建移库任务后记录库存变更失败");
}
if (beforeSourceLocation != null && stockInfo.LocationDetails != null)
{
var saveSourceLocationRecordResult = await _recordService.AddLocationChangeRecordAsync(
beforeSourceLocation,
stockInfo.LocationDetails,
LocationChangeType.RelocationAssignLocation,
createdTask.TaskNum,
createdTask.OrderNo,
null,
"移库任务锁定源货位");
if (!saveSourceLocationRecordResult)
{
throw new Exception("创建移库任务后记录源货位变更失败");
}
}
var saveTargetLocationRecordResult = await _recordService.AddLocationChangeRecordAsync(
beforeTargetLocation,
emptyLocation,
LocationChangeType.RelocationAssignLocation,
createdTask.TaskNum,
createdTask.OrderNo,
null,
"移库任务锁定目标货位");
if (!saveTargetLocationRecordResult)
{
throw new Exception("创建移库任务后记录目标货位变更失败");
}
return createdTask;
}
private static Dt_LocationInfo CloneLocationSnapshot(Dt_LocationInfo location)
{
return new Dt_LocationInfo
{
Id = location.Id,
WarehouseId = location.WarehouseId,
LocationCode = location.LocationCode,
LocationName = location.LocationName,
RoadwayNo = location.RoadwayNo,
Row = location.Row,
Column = location.Column,
Layer = location.Layer,
Depth = location.Depth,
LocationType = location.LocationType,
LocationStatus = location.LocationStatus,
EnableStatus = location.EnableStatus,
Remark = location.Remark
};
}
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()
};
}
///
/// 检查货位是否需要移库
///
/// 货位信息
/// 是否需要移库
private static bool CheckForInternalTransfer(Dt_LocationInfo location)
{
return location.Depth == 2;
}
///
/// 根据巷道获取二深位的空库位
///
/// 巷道编号
/// 货位对象
private async Task GetTransferLocationEmptyAsync(string roadway)
{
return await BaseDal.QueryFirstAsync(x => x.Depth == 2 && x.LocationStatus == LocationStatusEnum.Free.GetHashCode() && x.RoadwayNo == roadway);
}
///
/// 计算深度
///
/// 行号
/// 最大行数
/// 最大深度
/// 当前深度
/// 计算后的深度
private static int CalculateDepth(int row, int maxRow, int maxDepth, int currentDepth)
{
int mod = row % maxRow;
if (mod == 1) return maxDepth;
if (mod == maxDepth + 1) return 1;
if (mod > 1 && mod <= maxDepth) return currentDepth - 1;
return currentDepth + 1;
}
///
/// 创建货位信息
///
/// 巷道编号
/// 行号
/// 列号
/// 层数
/// 深度
/// 货位信息对象
private Dt_LocationInfo CreateLocationInfo(string roadwayNo, int row, int col, int layer, int depth)
{
var warehouse = _warehouseRepository.QueryData(x => x.WarehouseCode == roadwayNo).FirstOrDefault();
if (warehouse == null)
{
throw new InvalidOperationException($"未找到巷道编号为 {roadwayNo} 的仓库信息");
}
return new Dt_LocationInfo
{
WarehouseId = warehouse.WarehouseId,
Row = row,
Column = col,
Layer = layer,
Depth = depth,
RoadwayNo = roadwayNo,
EnableStatus = EnableStatusEnum.Normal.GetHashCode(),
LocationStatus = LocationStatusEnum.Free.GetHashCode(),
LocationType = LocationTypeEnum.Undefined.GetHashCode(),
//LocationCode = $"{roadwayNo}-{row:D3}-{col:D3}-{layer:D3}",
LocationCode = $"{row:D3}-{col:D3}-{layer:D3}",
LocationName = $"{roadwayNo}巷道{row:D3}行{col:D3}列{layer:D3}层{depth:D2}深"
};
}
#endregion 私有方法
}
}