using AngleSharp.Dom;
|
using Mapster;
|
using Masuit.Tools.Models;
|
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Mvc;
|
using OfficeOpenXml.ConditionalFormatting;
|
using System.Threading.Tasks;
|
using WIDESEA_Common;
|
using WIDESEA_Core;
|
using WIDESEA_Core.HttpContextUser;
|
using WIDESEA_DTO;
|
using WIDESEA_Model.Models;
|
|
namespace WIDESEA_StorageBasicService;
|
|
public class LocationInfoService : ServiceBase<DtLocationInfo, ILocationInfoRepository>, ILocationInfoService
|
{
|
private readonly LogFactory LogFactory = new LogFactory();
|
private readonly IUnitOfWorkManage _unitOfWorkManage;
|
private readonly IDt_TaskRepository _taskRepository;
|
private readonly IDt_TaskService _taskService;
|
private readonly IStockInfoRepository _stockInfoRepository;
|
private readonly IStockInfoDetailRepository _stockInfoDetailRepository;
|
private readonly IDt_WareAreaInfoRepository _wareAreaInfoRepository;
|
private readonly ITaskExecuteDetailRepository _taskExecuteDetailRepository;
|
private readonly ILocationStatusChangeRecordRepository _locationStatusChangeRecordRepository;
|
private readonly IMapper _mapper;
|
private readonly IDt_PalletStockInfoRepository _palletStockInfoRepository;
|
|
public LocationInfoService(ILocationInfoRepository BaseDal,
|
IUnitOfWorkManage unitOfWorkManage,
|
IDt_TaskRepository taskRepository,
|
IStockInfoRepository stockInfoRepository,
|
IDt_WareAreaInfoRepository wareAreaInfoRepository,
|
ITaskExecuteDetailRepository taskExecuteDetailRepository,
|
ILocationStatusChangeRecordRepository locationStatusChangeRecordRepository,
|
IStockInfoDetailRepository stockInfoDetailRepository,
|
IMapper mapper,
|
IDt_TaskService taskService,
|
IDt_PalletStockInfoRepository palletStockInfoRepository) : base(BaseDal)
|
{
|
_unitOfWorkManage = unitOfWorkManage;
|
_taskRepository = taskRepository;
|
_stockInfoRepository = stockInfoRepository;
|
_wareAreaInfoRepository = wareAreaInfoRepository;
|
_taskExecuteDetailRepository = taskExecuteDetailRepository;
|
_locationStatusChangeRecordRepository = locationStatusChangeRecordRepository;
|
_stockInfoDetailRepository = stockInfoDetailRepository;
|
_mapper = mapper;
|
_taskService = taskService;
|
_palletStockInfoRepository = palletStockInfoRepository;
|
}
|
|
/// <summary>
|
/// 检查并生成移库任务或返回出库任务
|
/// </summary>
|
/// <param name="locationID">任务号</param>
|
/// <returns>任务对象</returns>
|
public virtual async Task<Dt_Task> TransferCheckAsync(RequestTaskDto input)
|
{
|
try
|
{
|
int taskNum = input.taskNum;
|
|
// 根据任务号获取任务
|
var outboundTask = await _taskRepository.QueryFirstAsync(x => x.TaskNum == taskNum);
|
if (outboundTask == null)
|
return null;
|
LogFactory.GetLog("检查是否需要移库").InfoFormat(true, "根据任务号获取任务", $"任务号:{taskNum},任务数据:{outboundTask}");
|
|
var location = await BaseDal.QueryFirstAsync(x => x.LocationCode == outboundTask.SourceAddress);
|
LogFactory.GetLog("检查是否需要移库").InfoFormat(true, "获取指定库位的货位数据", $"货位号:{outboundTask.SourceAddress},货位数据:{location}");
|
|
// 检查是否需要进行移库
|
if (CheckForInternalTransfer(location))
|
{
|
LogFactory.GetLog("检查是否需要移库").InfoFormat(true, "需要移库", $"货位号:{outboundTask.SourceAddress},货位数据:{location}");
|
// 计算对应位置的相对库位 (奇数行的下一行或者偶数行的上一行)
|
var newLocationID = GetRelativeLocationID(location);
|
LogFactory.GetLog("检查是否需要移库").InfoFormat(true, "需要移库,组建需要移库货位号", $"新的库位ID:{newLocationID}");
|
|
var relocationTask = await _taskRepository.QueryFirstAsync(x => x.SourceAddress == newLocationID && x.Roadway == outboundTask.Roadway && x.TaskType == (int)TaskTypeEnum.Relocation);
|
if (relocationTask != null) return relocationTask;
|
|
// 获取新的库位的任务
|
var internalTransferTask = await _taskRepository.QueryFirstAsync(x => x.SourceAddress == newLocationID && x.Roadway == outboundTask.Roadway && x.TaskState == (int)TaskOutStatusEnum.OutNew);
|
|
LogFactory.GetLog("检查是否需要移库").InfoFormat(true, "需要移库,获取新库位的任务", $"新库位任务:{internalTransferTask}");
|
// 如果新的库位没有找到对应的任务
|
if (internalTransferTask == null)
|
{
|
LogFactory.GetLog("检查是否需要移库").InfoFormat(true, "需要移库,未获取到新库位任务", $"");
|
return await HandleNoTaskAtLocation(outboundTask.SourceAddress, newLocationID, outboundTask);
|
}
|
LogFactory.GetLog("检查是否需要移库").InfoFormat(true, "需要移库,获取到新库位任务直接返回", $"新库位任务:{internalTransferTask}");
|
// 直接返回一深位出库任务
|
return internalTransferTask;
|
}
|
|
// 返回当前库位的出库任务
|
return outboundTask;
|
}
|
catch (Exception)
|
{
|
return null;
|
}
|
}
|
|
public override WebResponseContent UpdateData(SaveModel saveModel)
|
{
|
int id = saveModel.MainData["id"].ObjToInt();
|
int status = saveModel.MainData["locationStatus"].ObjToInt();
|
var location = BaseDal.QueryFirst(x => x.Id == id);
|
|
LocationChangeRecordDto changeRecordDto = new LocationChangeRecordDto()
|
{
|
AfterStatus = status,
|
BeforeStatus = location.LocationStatus,
|
TaskNum = 0,
|
LocationId = id,
|
LocationCode = location.LocationCode,
|
ChangeType = (int)StatusChangeTypeEnum.ManualOperation
|
};
|
|
_locationStatusChangeRecordRepository.AddStatusChangeRecord(changeRecordDto);
|
|
return base.UpdateData(saveModel);
|
}
|
|
public override WebResponseContent AddData(SaveModel saveModel)
|
{
|
WebResponseContent content = new();
|
try
|
{
|
int line = saveModel.MainData["row"].ObjToInt();
|
int column = saveModel.MainData["column"].ObjToInt();
|
int layer = saveModel.MainData["layer"].ObjToInt();
|
int locationType = saveModel.MainData["locationType"].ObjToInt();
|
//int status = saveModel.MainData["locationStatus"].ObjToInt();
|
//string roadwayNo = saveModel.MainData["roadwayNo"].ToString();
|
|
string locationCode = line.ToString().PadLeft(3, '0') + '-' + column.ToString().PadLeft(3, '0') + '-' + layer.ToString().PadLeft(3, '0');
|
var location = BaseDal.QueryFirst(x => x.LocationCode == locationCode);
|
if (location != null) throw new Exception($"{locationCode}库位已存在,请核查");
|
|
DtLocationInfo locationInfo = new DtLocationInfo
|
{
|
Row = line,
|
Column = column,
|
Layer = layer,
|
LocationCode = line.ToString().PadLeft(3, '0') + '-' + column.ToString().PadLeft(3, '0') + '-' + layer.ToString().PadLeft(3, '0'),
|
LocationName = ConvertToFormattedString(line, column, layer),
|
LocationType = locationType,
|
Remark = "",
|
Depth = 0,
|
RoadwayNo = "AGV",
|
LocationStatus = LocationEnum.Free.ObjToInt(),
|
AreaId = 0,
|
Creater = App.User.UserName,
|
EnalbeStatus = 2, //默认禁用
|
CreateDate = DateTime.Now,
|
};
|
|
if (BaseDal.AddData(locationInfo) > 0)
|
{
|
return content.OK("添加成功!");
|
}
|
else
|
{
|
return content.Error("添加失败");
|
}
|
}
|
catch (Exception ex)
|
{
|
return content.Error($"添加失败:{ex.Message}");
|
}
|
|
}
|
|
#region 初始化库位
|
public async Task<WebResponseContent> initializeLocation(int locationID)
|
{
|
WebResponseContent content = new WebResponseContent();
|
try
|
{
|
DtLocationInfo? location = BaseDal.QueryData(x => x.Id == locationID).FirstOrDefault();
|
int LastStatus = location.LocationStatus;
|
if (location == null)
|
{
|
return content.Error("未找到货位信息!");
|
}
|
DtStockInfo stock = _stockInfoRepository.QueryFirst(x => x.LocationId == location.Id);
|
if (stock == null)
|
{
|
location.LocationStatus = (int)LocationEnum.Lock;
|
BaseDal.UpdateData(location);
|
}
|
else
|
{
|
_unitOfWorkManage.BeginTran();
|
DtStockInfo_Hty stockInfo_Hty = stock.Adapt<DtStockInfo_Hty>();
|
stockInfo_Hty.ModifyDate = DateTime.Now;
|
await DeleteStockInfoAsync(stock.Id);
|
List<DtStockInfoDetail> detail = _stockInfoDetailRepository.QueryData(x => x.StockId == stock.Id).ToList();
|
if (detail != null && detail.Count() > 0)
|
{
|
List<DtStockInfoDetail_Hty> details = detail.Adapt<List<DtStockInfoDetail_Hty>>();
|
await DeleteStockInfoDetailsAsync(detail);
|
AddStockInfoDetailHty(details);
|
}
|
await AddStockInfoHtyAsync(stockInfo_Hty);
|
|
|
location.LocationStatus = (int)LocationEnum.Lock;
|
BaseDal.UpdateData(location);
|
|
_locationStatusChangeRecordRepository.AddLocationStatusChangeRecord(location, LastStatus, (int)StatusChangeTypeEnum.ManualOperation, 0);
|
_unitOfWorkManage.CommitTran();
|
}
|
return content.OK();
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return content.Error(ex.Message);
|
}
|
}
|
#endregion
|
|
#region 创建原始货位数据
|
|
/// <summary>
|
/// 创建原始货位数据
|
/// </summary>
|
/// <param name="x">行</param>
|
/// <param name="y">列</param>
|
/// <param name="z">层</param>
|
/// <param name="locType">货位类型(1、单深,2、双深)</param>
|
/// <param name="areaId">库区编码</param>
|
/// <returns></returns>
|
public WebResponseContent CreateLocation(int x, int y, int z, int locType, int areaId)
|
{
|
string[] chineseNumbers = new string[] { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九" };
|
var locationList = new List<DtLocationInfo>();
|
for (int line = 1; line <= x; line++)
|
{
|
for (int column = 1; column <= y; column++)
|
{
|
for (int layer = 1; layer <= z; layer++)
|
{
|
locationList.Add(new DtLocationInfo()
|
{
|
Row = line,
|
Column = column,
|
Layer = layer,
|
LocationCode = line.ToString().PadLeft(3, '0') + '-' + column.ToString().PadLeft(3, '0') + '-' + layer.ToString().PadLeft(3, '0'),
|
LocationName = ConvertToFormattedString(line, column, layer),
|
LocationType = 1,
|
Remark = "",
|
Depth = locType > 1 ? (((line - 1) % 4) + 1) == 2 || (((line - 1) % 4) + 1) == 3 ? 1 : 2 : 1,
|
RoadwayNo = locType > 1 ? $"SC{((line - 1) / 4) + 1}" : $"SC{((line - 1) / 2) + 1}",
|
LocationStatus = LocationEnum.Free.ObjToInt(),
|
AreaId = areaId,
|
Creater = "systeam",
|
EnalbeStatus = 2,
|
});
|
}
|
}
|
}
|
var isResult = BaseDal.AddData(locationList) > 0;
|
if (isResult)
|
return new WebResponseContent().OK();
|
else
|
return new WebResponseContent().Error();
|
}
|
|
#endregion 创建原始货位数据
|
|
#region 启用禁用货位
|
|
public WebResponseContent LocationEnable(SaveModel saveModel)
|
{
|
WebResponseContent content = new WebResponseContent();
|
try
|
{
|
List<DtLocationInfo> locations = new List<DtLocationInfo>();
|
int enable = Convert.ToBoolean(saveModel.Extra) ? 1 : 2;
|
for (int i = 0; i < saveModel.DelKeys.Count; i++)
|
{
|
DtLocationInfo location = BaseDal.QueryData(x => x.Id == int.Parse(saveModel.DelKeys[i].ToString())).FirstOrDefault();
|
location.EnalbeStatus = enable;
|
locations.Add(location);
|
}
|
BaseDal.UpdateData(locations);
|
content = WebResponseContent.Instance.OK();
|
}
|
catch (Exception ex)
|
{
|
content = WebResponseContent.Instance.Error(ex.Message);
|
}
|
finally
|
{
|
//日志记录
|
}
|
return content;
|
}
|
|
#endregion 启用禁用货位
|
|
#region 内部方法
|
|
#region 获取货位状态
|
/// <summary>
|
/// 获取货位状态
|
/// </summary>
|
/// <param name="area"></param>
|
/// <returns></returns>
|
public object GetLocationStatu([FromBody] Area area)
|
{
|
var data = BaseDal.QueryData(x => x.AreaId == Convert.ToInt32(area.areaCode));
|
|
List<LocationLayer> layers = new List<LocationLayer>();
|
foreach (var layer in data.GroupBy(t => t.Layer))
|
{
|
var rows = new List<LocationRow>();
|
var data_rows = layer.GroupBy(t => t.Row);
|
foreach (var data_row in data_rows)
|
{
|
var cols = new List<LocationCol>();
|
foreach (var data_col in data_row)
|
{
|
cols.Add(new LocationCol()
|
{
|
//列
|
index = data_col.Column,
|
location_state = data_col.LocationStatus.ToString(),
|
location_lock = data_col.LocationStatus.ToString(),
|
});
|
}
|
cols = cols.OrderBy(t => t.index).ToList();
|
rows.Add(new LocationRow()
|
{
|
//行
|
index = data_row.Key,
|
cols = cols
|
});
|
}
|
rows = rows.OrderBy(t => t.index).ToList();
|
layers.Add(new LocationLayer()
|
{
|
//层
|
index = layer.Key,
|
rows = rows
|
});
|
}
|
layers = layers.OrderBy(t => t.index).ToList();
|
return layers;
|
}
|
#endregion
|
|
#region 移库方法
|
|
/// <summary>
|
/// 计算相对的库位ID
|
/// </summary>
|
/// <param name="locationID">当前库位ID</param>
|
/// <returns>相对的库位ID</returns>
|
private string GetRelativeLocationID(DtLocationInfo locationInfo)
|
{
|
int line = locationInfo.Row;
|
|
// 计算相对的货位行值,奇数行的下一行或者偶数行的上一行
|
int relativeLine = line % 2 == 1 ? line + 1 : line - 1;
|
LogFactory.GetLog("检查是否需要移库").InfoFormat(true, "需要移库,计算相对行", $"原始货位行:{line},计算相对货位行:{relativeLine}");
|
|
// 构建新的库位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);
|
}
|
|
/// <summary>
|
/// 处理没有任务的库位情况
|
/// </summary>
|
/// <param name="originalLocationID">原始库位ID</param>
|
/// <param name="newLocationID">新的库位ID</param>
|
/// <param name="outboundTask">出库任务</param>
|
/// <returns>生成的移库任务或原始出库任务</returns>
|
private async Task<Dt_Task> HandleNoTaskAtLocation(string originalLocationID, string newLocationID, Dt_Task outboundTask)
|
{
|
// 判断该位置是否有库存
|
var stockInfo = await _palletStockInfoRepository.QueryFirstAsync(x => x.PalletCode == newLocationID);
|
|
LogFactory.GetLog("检查是否需要移库").InfoFormat(true, "需要移库,未获取到新库位任务", $"该位置是否有库存:{stockInfo}");
|
if (stockInfo == null)
|
{
|
LogFactory.GetLog("检查是否需要移库").InfoFormat(true, "需要移库,未获取到库存数据", $"直接返回原先出库任务:{outboundTask}");
|
// 如果没有库存,直接返回当前出库任务
|
return outboundTask;
|
}
|
else
|
{
|
// 如果有库存,生成移库任务
|
var emptyLocation = RequestLocation("SC1");
|
if (emptyLocation == null) throw new Exception("暂无空库");
|
|
LogFactory.GetLog("检查是否需要移库").InfoFormat(true, "需要移库,查找能移库货位", $"货位数据:{emptyLocation}");
|
var taskNo = await _taskRepository.GetTaskNo();
|
Dt_Task newTransferTask = new Dt_Task()
|
{
|
CreateDate = DateTime.Now,
|
Creater = App.User.UserName == null ? "admin" : App.User.UserName,
|
CurrentAddress = stockInfo.LocationCode,
|
Grade = 99,
|
MaterialNo = stockInfo.PalletCode,
|
NextAddress = emptyLocation.LocationCode,
|
PalletCode = stockInfo.PalletCode,
|
Remark = "移库",
|
Roadway = "SC1",
|
SourceAddress = stockInfo.LocationCode,
|
TaskNum = taskNo,
|
TargetAddress = emptyLocation.LocationCode,
|
TaskState = (int)TaskStatus.Created,
|
TaskType = (int)TaskTypeEnum.Relocation,
|
Dispatchertime = DateTime.Now,
|
};
|
LogFactory.GetLog("检查是否需要移库").InfoFormat(true, "需要移库,新建移库任务", $"移库任务数据:{newTransferTask}");
|
|
//修改库位状态
|
emptyLocation.LocationStatus = (int)LocationEnum.FreeDisable;
|
BaseDal.UpdateData(emptyLocation);
|
|
return await _taskRepository.Create(newTransferTask);
|
}
|
}
|
|
/// <summary>
|
/// 根据货位是否需要移库
|
/// </summary>
|
/// <param name="locationID">货位ID</param>
|
/// <returns>是否需要移库</returns>
|
private bool CheckForInternalTransfer(DtLocationInfo location)
|
{
|
return location.Depth == 2 ? true : false;
|
}
|
|
|
|
#region 库位分配
|
#region 获取货位
|
object objLOCK = new object();
|
/// <summary>
|
/// 双升库位分配
|
/// </summary>
|
/// <param name="RoadWayNo"></param>
|
/// <param name="locationInfos"></param>
|
/// <returns></returns>
|
public DtLocationInfo RequestLocation(string RoadWayNo, List<DtLocationInfo> locationInfos = null)
|
{
|
lock (objLOCK)
|
{
|
try
|
{
|
//List<DtLocationInfo> locations = new List<DtLocationInfo>();
|
if (locationInfos == null || locationInfos.Count == 0)
|
{
|
locationInfos = BaseDal.QueryData(x => x.LocationStatus == (int)LocationEnum.Free && x.RoadwayNo == "SC1" && x.EnalbeStatus == 1 && x.LocationType == 1);
|
}
|
|
var location = GetEmptyLocation(locationInfos);
|
|
if (location != null)
|
{
|
if (location.Depth == 2)
|
{
|
int row = location.Row;
|
int relativeLine = row % 2 == 1 ? row + 1 : row - 1;
|
|
var insideLocation = BaseDal.QueryFirst(x => x.Row == relativeLine && x.Layer == location.Layer && x.Column == location.Column);
|
|
if (insideLocation.LocationStatus != (int)LocationEnum.Free /*|| insideLocation.EnalbeStatus ==*/ )
|
{
|
locationInfos.Remove(location);
|
if (locationInfos.Count == 0) return null;
|
RequestLocation(RoadWayNo, locationInfos);
|
}
|
}
|
}
|
|
if (location == null)
|
{
|
return null;
|
}
|
|
return location;
|
}
|
catch (Exception err)
|
{
|
Console.WriteLine(err.Message.ToString());
|
return null;
|
}
|
}
|
}
|
private DtLocationInfo GetEmptyLocation(List<DtLocationInfo> dtLocationInfos)
|
{
|
var locationinfo = dtLocationInfos.Where(x => x.LocationStatus == (int)LocationEnum.Free && x.RoadwayNo == "SC1" && x.EnalbeStatus == 1 && x.LocationType == 1).OrderBy(x => x.Layer).ThenByDescending(x => x.Depth).ThenBy(x => x.Row).ThenBy(x => x.Column).FirstOrDefault();
|
|
return locationinfo;
|
}
|
|
#endregion 获取货位
|
|
|
#endregion 库位分配
|
|
/// <summary>
|
/// 根据巷道获取二深位的空库位
|
/// </summary>
|
/// <param name="roadway">巷道</param>
|
/// <returns>货位对象</returns>
|
private DtLocationInfo GetTransferLocationEmpty(string roadway)
|
{
|
var locationInfo = BaseDal.QueryFirst(x => x.EnalbeStatus == (int)EnableEnum.Enable && x.LocationStatus == (int)LocationEnum.Free && x.RoadwayNo == roadway);
|
return locationInfo;
|
|
/* x.Depth == 2 &&*/
|
//Db.Queryable<Dt_LocationInfo>()
|
//.Where(x => x.Status == LocationEnum.Free.ObjToInt())
|
//.Where(x => x.Depth == 2.ToString())
|
//.Where(x => x.Roadway == roadway)
|
//.First();
|
}
|
|
#endregion 移库方法
|
|
#region 创建初始货位方法
|
|
public static string ConvertToFormattedString(int line, int column, int layer)
|
{
|
// 将每个部分转换为目标格式
|
string lineString = ConvertNumberToChineseString(line);
|
string columnString = ConvertNumberToChineseString(column);
|
string layerString = ConvertNumberToChineseString(layer);
|
|
// 格式化输出
|
return $"{lineString}行{columnString}列{layerString}层";
|
}
|
|
public static string ConvertNumberToChineseString(int number)
|
{
|
string[] chineseNumbers = { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" };
|
|
if (number <= 10)
|
{
|
return chineseNumbers[number];
|
}
|
|
// 处理大于10的数字
|
string result = string.Empty;
|
if (number / 10 > 1)
|
{
|
result += chineseNumbers[number / 10] + "十";
|
}
|
else
|
{
|
result += "十";
|
}
|
|
if (number % 10 > 0)
|
{
|
result += chineseNumbers[number % 10];
|
}
|
|
return result;
|
}
|
|
#endregion 创建初始货位方法
|
|
#region 库存移入历史
|
private async Task DeleteStockInfoAsync(int stockId)
|
{
|
var isStockUpdated = await _stockInfoRepository.DeleteDataByIdAsync(stockId);
|
if (!isStockUpdated)
|
{
|
throw new Exception("库存信息更新失败");
|
}
|
}
|
|
private async Task AddStockInfoHtyAsync(DtStockInfo_Hty dtStock)
|
{
|
var isStockAdd = await SqlSugarHelper.DbWMS.InsertNav(dtStock).IncludesAllFirstLayer().ExecuteCommandAsync();
|
if (!isStockAdd)
|
{
|
throw new Exception("库存历史信息添加失败");
|
}
|
}
|
|
private async Task DeleteStockInfoDetailsAsync(IEnumerable<DtStockInfoDetail> details)
|
{
|
var ids = details.Select(x => (object)x.Id).ToArray();
|
var isStockDetailUpdated = await _stockInfoDetailRepository.DeleteDataByIdsAsync(ids);
|
if (!isStockDetailUpdated)
|
{
|
throw new Exception("库存详情信息更新失败");
|
}
|
}
|
private void AddStockInfoDetailHty(List<DtStockInfoDetail_Hty> details)
|
{
|
|
var isStockAdd = SqlSugarHelper.DbWMS.Insertable(details).ExecuteCommand();
|
if (isStockAdd == 0)
|
{
|
throw new Exception("库存明细历史信息添加失败");
|
}
|
}
|
|
#endregion
|
|
#endregion 内部方法
|
}
|