first commit
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.Business.Common.Models;
|
||||
|
||||
namespace JSMachine.WMS.Business.Common.IService
|
||||
{
|
||||
/// <summary>
|
||||
/// WMS 公共业务服务,协调库位、ERP 任务和 AGV 任务之间的调度关系。
|
||||
/// </summary>
|
||||
public interface ICommonService
|
||||
{
|
||||
/// <summary>
|
||||
/// 库位所在列是否被锁定(有待完成的任务)
|
||||
/// </summary>
|
||||
/// <param name="storageRack"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> IsColLock(StorageRackDto storageRack);
|
||||
/// <summary>
|
||||
/// 跟据erp任务获取已经生成的agv任务列表(不包含已经取消的任务)
|
||||
/// </summary>
|
||||
/// <param name="erpTask"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<AGVTaskDto>> GetCreatedAgvTasksFromErpTask(ERPTaskDto erpTask,TaskTypeEnum taskType);
|
||||
/// <summary>
|
||||
/// 生成中转任务。指定起点,终点为中转区
|
||||
/// </summary>
|
||||
/// <param name="startPosition"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> CreateAgvTaskToTransferArea(StorageRackDto startPosition, TaskTypeEnum taskType, ERPTaskDto erpTask);
|
||||
/// <summary>
|
||||
/// 中转区作业调度 起点为中转区域 终点需计算
|
||||
/// </summary>
|
||||
/// <param name="startPosition"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> CreateAgvTaskFromTransferArea(StorageRackDto startPosition, AGVTaskDto lastAgvTaskRelated, ERPTaskDto erpTask);
|
||||
/// <summary>
|
||||
/// 生成agv任务并插入数据库
|
||||
/// </summary>
|
||||
/// <param name="startPosition"></param>
|
||||
/// <param name="endPosition"></param>
|
||||
/// <param name="erpTask"></param>
|
||||
/// <returns></returns>
|
||||
Task CreateAndInsertAgvTask(AgvTaskCreationParam agvTaskCreationParam);
|
||||
/// <summary>
|
||||
/// 获取可用提升机库位
|
||||
/// </summary>
|
||||
/// <param name="floor">楼层</param>
|
||||
/// <param name="taskType">任务类型 1-入库 2-出库</param>
|
||||
/// <returns></returns>
|
||||
Task<StorageRackDto> GetUsableElevator(FloorEnum floor, int taskType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.Common.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// AGV任务创建参数模型
|
||||
/// </summary>
|
||||
public class AgvTaskCreationParam
|
||||
{
|
||||
/// <summary>
|
||||
/// 起点
|
||||
/// </summary>
|
||||
public StorageRackDto StartPosition { get; set; }
|
||||
/// <summary>
|
||||
/// 终点
|
||||
/// </summary>
|
||||
public StorageRackDto EndPosition { get; set; }
|
||||
/// <summary>
|
||||
/// 关联的erp任务
|
||||
/// </summary>
|
||||
public Guid ErpTaskId { get; set; }
|
||||
/// <summary>
|
||||
/// 任务类型
|
||||
/// </summary>
|
||||
public TaskTypeEnum TaskType { get; set; }
|
||||
/// <summary>
|
||||
/// 最终出库码头(仅针对出库有效)
|
||||
/// </summary>
|
||||
public TransportEnum StockOutWharf { get; set; }
|
||||
/// <summary>
|
||||
/// erp上传状态
|
||||
/// </summary>
|
||||
public ErpUploadStatusEnum ErpUploadStatus { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.App.ServiceImpl;
|
||||
using JSMachine.WMS.Business.Common.IService;
|
||||
using JSMachine.WMS.Business.Common.Models;
|
||||
using JSMachine.WMS.Business.SSE;
|
||||
using JSMachine.WMS.Business.StockIn.IService;
|
||||
using JSMachine.WMS.Business.StockIn.ServiceImpl.Strategy;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.PLC.ModbusImpl.Elevator.ModbusIO.Read;
|
||||
using JSMachine.WMS.PLC.ModbusImpl.Elevator.Model;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace JSMachine.WMS.Business.Common.ServiceImpl
|
||||
{
|
||||
/// <summary>
|
||||
/// WMS 通用业务服务,协调 ERP 任务、库位和 AGV 任务,
|
||||
/// 负责中转区及提升机相关任务的生成。
|
||||
/// </summary>
|
||||
public class CommonService : ICommonService
|
||||
{
|
||||
private IERPTaskService _erpTaskService;
|
||||
private IStorageRackService _storageRackService;
|
||||
private IAGVTaskService _agvTaskService;
|
||||
public CommonService(
|
||||
IERPTaskService erpTaskService,
|
||||
IStorageRackService storageRackService,
|
||||
IAGVTaskService agvTaskService)
|
||||
{
|
||||
_erpTaskService = erpTaskService;
|
||||
_storageRackService = storageRackService;
|
||||
_agvTaskService = agvTaskService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查指定库位所在列是否存在锁定库位。
|
||||
/// 该判断通过查询同列全部库位完成,任一库位被锁定即视为整列锁定。
|
||||
/// </summary>
|
||||
/// <param name="storageRack">用于确定库位列的库位信息。</param>
|
||||
/// <returns>同列存在锁定库位时返回 <see langword="true"/>。</returns>
|
||||
public async Task<bool> IsColLock(StorageRackDto storageRack)
|
||||
{
|
||||
List<StorageRackDto> storageRacksThisCol = await _storageRackService
|
||||
.GetListByExpression(p => p.ReservoirAreaColumnId == storageRack.ReservoirAreaColumnId);
|
||||
|
||||
if (storageRacksThisCol.IsNullOrEmpty())
|
||||
return false;
|
||||
|
||||
if (storageRacksThisCol.Any(p => p.IsLock))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 跟据erp任务获取已经生成的agv任务列表(不包含已经取消的任务)
|
||||
/// </summary>
|
||||
/// <param name="materialBatch"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<List<AGVTaskDto>> GetCreatedAgvTasksFromErpTask(ERPTaskDto erpTask, TaskTypeEnum taskType)
|
||||
{
|
||||
List<AGVTaskDto> agvTasks = await _agvTaskService
|
||||
.GetListByExpression(p => p.ErpTaskId == erpTask.Id &&
|
||||
p.TaskType == taskType &&
|
||||
p.TaskStatus != TaskStatusEnum.Cancelled &&
|
||||
p.TaskStatus != TaskStatusEnum.Failed);
|
||||
|
||||
agvTasks = agvTasks
|
||||
.GroupBy(p => p.BarCode)
|
||||
.Select(p => p.First())
|
||||
.ToList();
|
||||
|
||||
return agvTasks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成到中转区域的任务。指定起点,终点为中转区
|
||||
/// </summary>
|
||||
/// <param name="startPosition"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> CreateAgvTaskToTransferArea(StorageRackDto startPosition, TaskTypeEnum taskType, ERPTaskDto erpTask)
|
||||
{
|
||||
StorageRackDto endPosition = await _storageRackService.GetUsableTransferStorageRack();
|
||||
|
||||
if (endPosition == null)
|
||||
{
|
||||
LogHelper.Info("中转区域库位已满");
|
||||
return false;
|
||||
}
|
||||
|
||||
await CreateAndInsertAgvTask(new AgvTaskCreationParam
|
||||
{
|
||||
StartPosition = startPosition,
|
||||
EndPosition = endPosition,
|
||||
ErpTaskId = erpTask.Id,
|
||||
TaskType = taskType,
|
||||
StockOutWharf = erpTask.Transport,
|
||||
ErpUploadStatus = ErpUploadStatusEnum.Unnecessary
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 中转区作业调度 起点为中转区域 终点需计算
|
||||
/// </summary>
|
||||
/// <param name="startPosition">要调度的中转区物料</param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> CreateAgvTaskFromTransferArea(StorageRackDto startPosition, AGVTaskDto lastAgvTaskRelated, ERPTaskDto erpTask)
|
||||
{
|
||||
// 中转区任务按出库、入库、清理出库阻塞物的顺序生成;任一环节失败即停止后续调度。
|
||||
bool bRet = await CreateTransferStockOutAgvTask(startPosition, lastAgvTaskRelated, erpTask);
|
||||
if (!bRet)
|
||||
return false;
|
||||
|
||||
bRet = await CreateTransferStockInAgvTask(startPosition, lastAgvTaskRelated, erpTask);
|
||||
if (!bRet)
|
||||
return false;
|
||||
|
||||
bRet = await CreateRemoveStockOutObstructionAgvTask(startPosition, lastAgvTaskRelated, erpTask);
|
||||
if (!bRet)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> CreateTransferStockOutAgvTask(
|
||||
StorageRackDto startPosition,
|
||||
AGVTaskDto lastAgvTaskRelated,
|
||||
ERPTaskDto erpTask)
|
||||
{
|
||||
if (lastAgvTaskRelated.TaskType != TaskTypeEnum.TransferStockOut)
|
||||
return true;
|
||||
|
||||
StorageRackDto elevator = await GetUsableElevator(FloorEnum.SecondFloor, 2);
|
||||
|
||||
if (elevator == null)
|
||||
{
|
||||
LogHelper.Warn($"中转区出库业务 所有提升机出库库位均被WMS或者PLC锁定,erpTask {lastAgvTaskRelated.ErpTaskId} 无法生成AgvTask");
|
||||
return false;
|
||||
}
|
||||
|
||||
await CreateAndInsertAgvTask(new AgvTaskCreationParam
|
||||
{
|
||||
StartPosition = startPosition,
|
||||
EndPosition = elevator,
|
||||
ErpTaskId = erpTask.Id,
|
||||
TaskType = TaskTypeEnum.TransferStockOut,
|
||||
StockOutWharf = erpTask.Transport,
|
||||
ErpUploadStatus = ErpUploadStatusEnum.Unnecessary
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> CreateTransferStockInAgvTask(StorageRackDto startPosition, AGVTaskDto lastAgvTaskRelated, ERPTaskDto erpTask)
|
||||
{
|
||||
if (lastAgvTaskRelated.TaskType != TaskTypeEnum.TransferStockIn)
|
||||
return true;
|
||||
|
||||
IStockInStrategy stockInStrategy = StrategyFactory.CreateStockInStrategy(
|
||||
TransportEnum.TransitArea,
|
||||
FloorEnum.SecondFloor,
|
||||
startPosition.MateriaBatch);
|
||||
|
||||
StorageRackDto endPosition = await stockInStrategy.CalCulateWarehouse();
|
||||
if (endPosition == null)
|
||||
return false;
|
||||
|
||||
await CreateAndInsertAgvTask(new AgvTaskCreationParam
|
||||
{
|
||||
StartPosition = startPosition,
|
||||
EndPosition = endPosition,
|
||||
ErpTaskId = erpTask.Id,
|
||||
TaskType = lastAgvTaskRelated.TaskType,
|
||||
ErpUploadStatus =
|
||||
(erpTask.TaskType == TaskTypeEnum.PdaIn || erpTask.TaskType == TaskTypeEnum.TailIn) ?
|
||||
ErpUploadStatusEnum.WaittingToUpload :
|
||||
ErpUploadStatusEnum.Unnecessary
|
||||
});
|
||||
return true;
|
||||
}
|
||||
private async Task<bool> CreateRemoveStockOutObstructionAgvTask(
|
||||
StorageRackDto startPosition,
|
||||
AGVTaskDto lastAgvTaskRelated,
|
||||
ERPTaskDto erpTask)
|
||||
{
|
||||
if (lastAgvTaskRelated.TaskType != TaskTypeEnum.RemoveStockOutObstruction)
|
||||
return true;
|
||||
|
||||
IStockInStrategy stockInStrategy = StrategyFactory.CreateStockInStrategy(
|
||||
TransportEnum.TransitArea,
|
||||
FloorEnum.SecondFloor,
|
||||
startPosition.MateriaBatch);
|
||||
|
||||
StorageRackDto endPosition = await stockInStrategy.CalCulateWarehouse();
|
||||
|
||||
if (endPosition == null)
|
||||
return false;
|
||||
|
||||
await CreateAndInsertAgvTask(new AgvTaskCreationParam
|
||||
{
|
||||
StartPosition = startPosition,
|
||||
EndPosition = endPosition,
|
||||
ErpTaskId = erpTask.Id,
|
||||
TaskType = lastAgvTaskRelated.TaskType,
|
||||
ErpUploadStatus =
|
||||
(erpTask.TaskType == TaskTypeEnum.PdaIn || erpTask.TaskType == TaskTypeEnum.TailIn) ?
|
||||
ErpUploadStatusEnum.WaittingToUpload :
|
||||
ErpUploadStatusEnum.Unnecessary
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取可用提升机库位
|
||||
/// </summary>
|
||||
/// <param name="floor">楼层</param>
|
||||
/// <param name="taskType">任务类型 1-入库 2-出库</param>
|
||||
/// <returns></returns>
|
||||
public async Task<StorageRackDto> GetUsableElevator(FloorEnum floor, int taskType)
|
||||
{
|
||||
List<StorageRackDto> elevatorWarehouse = await _storageRackService.GetListByExpression(p =>
|
||||
p.Transport == TransportEnum.Elevator &&
|
||||
p.Floor == floor &&
|
||||
!p.IsLock
|
||||
);
|
||||
|
||||
if (elevatorWarehouse.IsNullOrEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ElevatorPlcStatus> elevatorPlcStatuses = await ElevatorStatusRead.ReadAllElevatorStatus();
|
||||
|
||||
StorageRackDto elevator = null;
|
||||
foreach (StorageRackDto elvatorWarehouse in elevatorWarehouse)
|
||||
{
|
||||
int elevatorNo = elvatorWarehouse.StorageRackName.Contains("1号") ? 1 : 2;
|
||||
|
||||
ElevatorPlcStatus elevatorPlcStatus = elevatorPlcStatuses.First(p => p.ElevatorNo == elevatorNo);
|
||||
|
||||
//入库
|
||||
if (taskType == 1)
|
||||
{
|
||||
if (elvatorWarehouse.ElevatorChannel == 1001 && elevatorPlcStatus.StockInPutdownChanelTwo ||
|
||||
elvatorWarehouse.ElevatorChannel == 1011 && elevatorPlcStatus.StockInPutdownChanelOne)
|
||||
{
|
||||
elevator = elvatorWarehouse;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//出库
|
||||
if (taskType == 2)
|
||||
{
|
||||
if (elvatorWarehouse.ElevatorChannel == 2001 && elevatorPlcStatus.StockOutPutdownChanelTwo ||
|
||||
elvatorWarehouse.ElevatorChannel == 2011 && elevatorPlcStatus.StockOutPutdownChanelOne)
|
||||
{
|
||||
elevator = elvatorWarehouse;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return elevator;
|
||||
}
|
||||
|
||||
public async Task CreateAndInsertAgvTask(AgvTaskCreationParam agvTaskCreationParam)
|
||||
{
|
||||
AGVTaskDto agvTask = new()
|
||||
{
|
||||
ErpTaskId = agvTaskCreationParam.ErpTaskId,
|
||||
BarCode = agvTaskCreationParam.StartPosition.BarCode,
|
||||
OrderNo = agvTaskCreationParam.StartPosition.OrderNo,
|
||||
CustomName = agvTaskCreationParam.StartPosition.CustomName,
|
||||
MateriaName = agvTaskCreationParam.StartPosition.MateriaName,
|
||||
MateriaType = agvTaskCreationParam.StartPosition.MateriaType,
|
||||
MateriaNum = agvTaskCreationParam.StartPosition.MateriaNum,
|
||||
MateriaBatch = agvTaskCreationParam.StartPosition.MateriaBatch,
|
||||
MateriaCode = agvTaskCreationParam.StartPosition.MateriaCode,
|
||||
TaskType = agvTaskCreationParam.TaskType,
|
||||
TaskStatus = TaskStatusEnum.Initial,
|
||||
Priority = agvTaskCreationParam.TaskType == TaskTypeEnum.SaleOut ? PriorityEnum.High : PriorityEnum.Middle,
|
||||
Floor = agvTaskCreationParam.StartPosition.Floor,
|
||||
|
||||
StartPositionCode = agvTaskCreationParam.StartPosition.StorageRackNo,
|
||||
EndPositionCode = agvTaskCreationParam.EndPosition.StorageRackNo,
|
||||
|
||||
Transport = agvTaskCreationParam.StockOutWharf,
|
||||
|
||||
ErpUploadStatus = agvTaskCreationParam.ErpUploadStatus
|
||||
};
|
||||
|
||||
await _storageRackService.Lock(agvTaskCreationParam.StartPosition);
|
||||
await _storageRackService.Lock(agvTaskCreationParam.EndPosition);
|
||||
await _agvTaskService.Add(agvTask);
|
||||
|
||||
//发送广播通知前端网站刷新页面
|
||||
SseEngine.BrodCast(agvTaskCreationParam.StartPosition.Floor.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Exstension\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\JSMachine.WMS.Common\JSMachine.WMS.Common.csproj" />
|
||||
<ProjectReference Include="..\JSMachine.WMS.PLC\JSMachine.WMS.PLC.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
using JSMachine.WMS.RPC.RcsRPC.Dto.In;
|
||||
using JSMachine.WMS.RPC.RcsRPC.Dto;
|
||||
using JSMachine.WMS.RPC.ErpRPC.Dto.Out;
|
||||
using JSMachine.WMS.App.Dto;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.IService
|
||||
{
|
||||
public interface IRcsCallBackService
|
||||
{
|
||||
/// <summary>
|
||||
/// 给Rcs调用,获取AGV上报的任务状态
|
||||
/// </summary>
|
||||
/// <param name="callBackWmsParam"></param>
|
||||
/// <returns></returns>
|
||||
Task<RcsApiResult> DealwithExcutionStatus(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask);
|
||||
|
||||
/// <summary>
|
||||
/// 处理PDA上报的agv报警处理结果
|
||||
/// </summary>
|
||||
/// <param name="agvTaskId"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> DealWithAlarmHandleResult(Guid agvTaskId);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.RPC.RcsRPC.Dto.In;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.IService.StaePattern
|
||||
{
|
||||
/// <summary>
|
||||
/// agv任务状态处理器 状态设计模式
|
||||
/// </summary>
|
||||
public interface IAgvTaskStateHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理一次 AGV 状态回调,并按状态推进任务或更新任务结果。
|
||||
/// </summary>
|
||||
/// <param name="agvExcutionInfo">RCS 返回的 AGV 执行信息。</param>
|
||||
/// <param name="agvTask">系统中的 AGV 任务。</param>
|
||||
/// <param name="destWarehouse">任务目标库位。</param>
|
||||
/// <returns>状态处理是否成功。</returns>
|
||||
Task<bool> HandleAsync(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask, StorageRackDto destWarehouse);
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.Domain.Entity;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.IService.StrategyPattern
|
||||
{
|
||||
/// <summary>
|
||||
/// agv任务结束策略(放货完成即代表agv任务结束)
|
||||
/// </summary>
|
||||
public interface ITaskCompletionStrategy
|
||||
{
|
||||
Task<bool> CompleteTaskAsync(AGVTaskDto agvTask, StorageRackDto destWarehouse);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.Business.RcsCallBack.IService;
|
||||
using JSMachine.WMS.Business.RcsCallBack.IService.StaePattern;
|
||||
using JSMachine.WMS.Business.RcsCallBack.IService.StrategyPattern;
|
||||
using JSMachine.WMS.Business.RcsCallBack.ServiceImpl.StatePattern;
|
||||
using JSMachine.WMS.Business.RcsCallBack.ServiceImpl.StrategyPatternImpl;
|
||||
using JSMachine.WMS.Common.Dto.Business;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.RPC.RcsRPC.Dto;
|
||||
using JSMachine.WMS.RPC.RcsRPC.Dto.In;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.ServiceImpl
|
||||
{
|
||||
/// <summary>
|
||||
/// RCS 回调业务,将外部 AGV 状态转换为本地任务、库存及报警状态。
|
||||
/// </summary>
|
||||
public class RcsCallBackService : IRcsCallBackService
|
||||
{
|
||||
private IAGVTaskService _agvTaskService;
|
||||
private IElevatorPlcQueueService _elevatorPlcQueueService;
|
||||
private IStorageRackService _storageRackService;
|
||||
private IERPTaskService _erptaskService;
|
||||
private IElevatorCountService _elevatorCountService;
|
||||
|
||||
public RcsCallBackService(
|
||||
IAGVTaskService agvTaskService,
|
||||
IElevatorPlcQueueService elevatorPlcQueueService,
|
||||
IStorageRackService storageRackService,
|
||||
IERPTaskService erpTaskService,
|
||||
IElevatorCountService elevatorCountService)
|
||||
{
|
||||
_agvTaskService = agvTaskService;
|
||||
_elevatorPlcQueueService = elevatorPlcQueueService;
|
||||
_storageRackService = storageRackService;
|
||||
_erptaskService = erpTaskService;
|
||||
_elevatorCountService = elevatorCountService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 给Rcs调用,获取AGV上报的任务状态
|
||||
/// </summary>
|
||||
/// <param name="callBackWmsParam"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<RcsApiResult> DealwithExcutionStatus(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask)
|
||||
{
|
||||
// 先处理失败和取消状态,再处理成功状态;各处理器根据回调状态自行决定是否生效。
|
||||
//处理agv执行失败的回调,推送pda报警
|
||||
await HandleFailed(agvExcutionInfo, agvTask);
|
||||
|
||||
await HandleCanceled(agvExcutionInfo, agvTask);
|
||||
//处理agv执行成功的回调
|
||||
RcsApiResult rcsApiResult = await HandleSuccess(agvExcutionInfo, agvTask);
|
||||
|
||||
return rcsApiResult;
|
||||
}
|
||||
|
||||
public async Task<bool> DealWithAlarmHandleResult(Guid agvTaskId)
|
||||
{
|
||||
AGVTaskDto agvTask = await _agvTaskService.GetSingalByExpression(p => p.Id == agvTaskId);
|
||||
|
||||
if (agvTask == null)
|
||||
{
|
||||
LogHelper.Warn($"PDA上传的 {agvTaskId} 任务不存在,该任务告警可能已经处理?");
|
||||
return true;
|
||||
}
|
||||
|
||||
//模拟rcs回调,发送放货完成命令
|
||||
await DealwithExcutionStatus(new AgvExcutionInfo
|
||||
{
|
||||
OrderId = agvTaskId.ToString(),
|
||||
Status = 23
|
||||
}, agvTask);
|
||||
|
||||
//处理完删除缓存中的报警信息
|
||||
GlobalAgvTaskFailedAlarmInfo.RemoveAlarmInfo(agvTaskId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#region 处理入库rcs回调信息
|
||||
|
||||
private async Task HandleCanceled(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask)
|
||||
{
|
||||
if (agvExcutionInfo.Status != 3)
|
||||
return;
|
||||
|
||||
agvTask.TaskStatus = TaskStatusEnum.Cancelled;
|
||||
await _agvTaskService.EditSingal(agvTask);
|
||||
|
||||
//搬运的任务没有对应的erptask
|
||||
if (agvTask.TaskType != TaskTypeEnum.Carry)
|
||||
{
|
||||
ERPTaskDto erpTask = await _erptaskService.GetSingalByExpression(p => p.Id == agvTask.ErpTaskId);
|
||||
erpTask.TaskStatus = TaskStatusEnum.Cancelled;
|
||||
await _erptaskService.UpdateTaskStatus(erpTask);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleFailed(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask)
|
||||
{
|
||||
if (agvExcutionInfo.Status != 7)
|
||||
return;
|
||||
|
||||
//1.一楼 起点在入库库位,终点在提升机,人工搬运托盘到提升机入口,手动提交放货完成信号
|
||||
//2.二楼 起点在提升机出口,终点在存储区库位,人工搬运托盘到库位,手动提交放货完成信号
|
||||
//3.二楼 起点在入库区库位,终点在提升机入口,人工搬运托盘到提升机入口,手动提交放货完成信号
|
||||
//4.一楼 起点在提升机,终点在出货码头,人工搬运托盘到出货码头,无需其他处理
|
||||
|
||||
await HandleStocInFirstFloor(agvExcutionInfo, agvTask);
|
||||
await HandleStocInSecondFloor(agvExcutionInfo, agvTask);
|
||||
await HandleStocOutSecondFloor(agvExcutionInfo, agvTask);
|
||||
await HandleStocOutFirstFloor(agvExcutionInfo, agvTask);
|
||||
}
|
||||
|
||||
#region agv执行失败报警分任务处理
|
||||
private async Task HandleStocInFirstFloor(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask)
|
||||
{
|
||||
if (!(agvTask.TaskType == TaskTypeEnum.PdaIn && agvTask.Floor == FloorEnum.FirstFloor))
|
||||
return;
|
||||
|
||||
StorageRackDto destWarehouse = await _storageRackService.GetSingalByExpression(p => p.StorageRackNo == agvTask.EndPositionCode);
|
||||
|
||||
//终点在存储区,人工搬运获取到指定库位,完成后写放货完成
|
||||
if (destWarehouse.Transport == TransportEnum.NormalArea)
|
||||
{
|
||||
string operatn = $"{agvExcutionInfo.DeviceNum}号小车报警,请将货物从车上取下搬运到 {destWarehouse.StorageRackName} " +
|
||||
$"库区的 {destWarehouse.StorageRackNo}库位上,完成后点击已处理按钮";
|
||||
|
||||
LogHelper.Warn(operatn);
|
||||
|
||||
GlobalAgvTaskFailedAlarmInfo.AddAlarmInfo(new AgvTaskFailedAlarmInfo
|
||||
{
|
||||
AgvTaskId = agvTask.Id,
|
||||
Operation = operatn
|
||||
});
|
||||
|
||||
await UpdateErpTaskStatus(agvTask, TaskStatusEnum.Failed);
|
||||
}
|
||||
|
||||
//终点在提升机入口,人工搬运获取到提升机入口,完成后写放货完成
|
||||
if (destWarehouse.Transport == TransportEnum.Elevator)
|
||||
{
|
||||
string operatn = $"{agvExcutionInfo.DeviceNum}号小车报警,请将货物从车上(或库位{agvTask.StartPositionCode})搬运到 {destWarehouse.StorageRackName} 上," +
|
||||
$"完成后点击已处理按钮";
|
||||
|
||||
LogHelper.Warn($"任务 id {agvTask.Id} 操作信息:{operatn}");
|
||||
|
||||
GlobalAgvTaskFailedAlarmInfo.AddAlarmInfo(new AgvTaskFailedAlarmInfo
|
||||
{
|
||||
AgvTaskId = agvTask.Id,
|
||||
Operation = operatn
|
||||
});
|
||||
}
|
||||
}
|
||||
private async Task HandleStocInSecondFloor(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask)
|
||||
{
|
||||
if (!(agvTask.TaskType == TaskTypeEnum.PdaIn && agvTask.Floor == FloorEnum.SecondFloor))
|
||||
return;
|
||||
|
||||
StorageRackDto startWarehouse = await _storageRackService.GetByStorageRackNo(agvTask.StartPositionCode);
|
||||
StorageRackDto destWarehouse = await _storageRackService.GetByStorageRackNo(agvTask.EndPositionCode);
|
||||
|
||||
//终点在存储区,人工搬运获取到指定库位,完成后写放货完成
|
||||
|
||||
string operatn = $"{agvExcutionInfo.DeviceNum}号小车报警,请将货物从 {startWarehouse.StorageRackName} 搬运到 " +
|
||||
$"{destWarehouse.StorageRackNo}库位上,完成后点击已处理按钮";
|
||||
|
||||
LogHelper.Warn($"任务 id {agvTask.Id} 操作信息:{operatn}");
|
||||
|
||||
GlobalAgvTaskFailedAlarmInfo.AddAlarmInfo(new AgvTaskFailedAlarmInfo
|
||||
{
|
||||
AgvTaskId = agvTask.Id,
|
||||
Operation = operatn
|
||||
});
|
||||
|
||||
await UpdateErpTaskStatus(agvTask, TaskStatusEnum.Failed);
|
||||
}
|
||||
private async Task HandleStocOutSecondFloor(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask)
|
||||
{
|
||||
if (!(agvTask.TaskType == TaskTypeEnum.SaleOut && agvTask.Floor == FloorEnum.SecondFloor))
|
||||
return;
|
||||
|
||||
StorageRackDto destWarehouse = await _storageRackService.GetByStorageRackNo(agvTask.EndPositionCode);
|
||||
|
||||
//终点在存储区,人工搬运获取到指定库位,完成后写放货完成
|
||||
|
||||
string operatn = $"{agvExcutionInfo.DeviceNum}号小车报警,请将货物从车上(或者{agvTask.StartPositionCode})放到 {destWarehouse.StorageRackNo} 上,完成后点击已处理按钮";
|
||||
|
||||
LogHelper.Warn($"任务 id {agvTask.Id} 操作信息:{operatn}");
|
||||
|
||||
GlobalAgvTaskFailedAlarmInfo.AddAlarmInfo(new AgvTaskFailedAlarmInfo
|
||||
{
|
||||
AgvTaskId = agvTask.Id,
|
||||
Operation = operatn
|
||||
});
|
||||
}
|
||||
private async Task HandleStocOutFirstFloor(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask)
|
||||
{
|
||||
if (!(agvTask.TaskType == TaskTypeEnum.SaleOut && agvTask.Floor == FloorEnum.SecondFloor))
|
||||
return;
|
||||
|
||||
StorageRackDto startWarehouse = await _storageRackService.GetByStorageRackNo(agvTask.StartPositionCode);
|
||||
StorageRackDto destWarehouse = await _storageRackService.GetByStorageRackNo(agvTask.EndPositionCode);
|
||||
|
||||
if (startWarehouse.Transport == TransportEnum.Elevator)
|
||||
{
|
||||
string operatn = $"{agvExcutionInfo.DeviceNum}号小车报警,请将货物从 {startWarehouse.StorageRackName} 搬运到 " +
|
||||
$"{(destWarehouse.Transport == TransportEnum.NorthArea ? "北出库码头" : "南出库码头")} " +
|
||||
$"的 {destWarehouse.StorageRackNo} 库位上,完成后点击已处理按钮";
|
||||
|
||||
LogHelper.Warn($"任务 id {agvTask.Id} 操作信息:{operatn}");
|
||||
|
||||
GlobalAgvTaskFailedAlarmInfo.AddAlarmInfo(new AgvTaskFailedAlarmInfo
|
||||
{
|
||||
AgvTaskId = agvTask.Id,
|
||||
Operation = operatn
|
||||
});
|
||||
}
|
||||
|
||||
if (startWarehouse.Transport == TransportEnum.NorthArea)
|
||||
{
|
||||
string operatn = $"{agvExcutionInfo.DeviceNum}号小车报警,请将货物从 " +
|
||||
$"{startWarehouse} 库位上搬运到北出库码头的" +
|
||||
$"{destWarehouse.StorageRackNo} 库位上,完成后点击已处理按钮";
|
||||
|
||||
LogHelper.Warn($"任务 id {agvTask.Id} 操作信息:{operatn}");
|
||||
|
||||
GlobalAgvTaskFailedAlarmInfo.AddAlarmInfo(new AgvTaskFailedAlarmInfo
|
||||
{
|
||||
AgvTaskId = agvTask.Id,
|
||||
Operation = operatn
|
||||
});
|
||||
}
|
||||
|
||||
if (startWarehouse.Transport == TransportEnum.SouthArea)
|
||||
{
|
||||
string operatn = $"{agvExcutionInfo.DeviceNum}号小车报警,请将货物从" +
|
||||
$"{startWarehouse} 库位搬运到南出库码头" +
|
||||
$"的 {destWarehouse.StorageRackNo} 库位上,完成后点击已处理按钮";
|
||||
|
||||
LogHelper.Warn($"任务 id {agvTask.Id} 操作信息:{operatn}");
|
||||
|
||||
GlobalAgvTaskFailedAlarmInfo.AddAlarmInfo(new AgvTaskFailedAlarmInfo
|
||||
{
|
||||
AgvTaskId = agvTask.Id,
|
||||
Operation = operatn
|
||||
});
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private async Task<RcsApiResult> HandleSuccess(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask)
|
||||
{
|
||||
if (!new int[] { 21, 23 }.Contains(agvExcutionInfo.Status))
|
||||
{
|
||||
LogHelper.Warn("rcs回调状态不是21,23,不处理");
|
||||
return new RcsApiResult { code = 1000 };
|
||||
}
|
||||
|
||||
StorageRackDto destWarehouse = await _storageRackService.GetSingalByExpression(p => p.StorageRackNo == agvTask.EndPositionCode);
|
||||
if (destWarehouse == null)
|
||||
{
|
||||
LogHelper.Error($"AgvTask终点库位 {agvTask.EndPositionCode} 不存在");
|
||||
return new RcsApiResult { code = 1000 }; ;
|
||||
}
|
||||
|
||||
IAgvTaskStateHandler handler = new RcsCallbackHandlerFactory().CreateHandler(agvExcutionInfo.Status, destWarehouse);
|
||||
if (handler == null)
|
||||
{
|
||||
LogHelper.Error("状态模式 没有匹配的模式可以处理");
|
||||
return new RcsApiResult { code = 1000 };
|
||||
}
|
||||
await handler.HandleAsync(agvExcutionInfo, agvTask, destWarehouse);
|
||||
return new RcsApiResult { code = 1000 };
|
||||
}
|
||||
|
||||
|
||||
private async Task UpdateErpTaskStatus(AGVTaskDto agvTask, TaskStatusEnum taskStatus)
|
||||
{
|
||||
ERPTaskDto erpTask = await _erptaskService.GetSingalByExpression(p => p.Id == agvTask.ErpTaskId);
|
||||
erpTask.TaskStatus = taskStatus;
|
||||
//更新erp任务状态为完成
|
||||
bool bRet = await _erptaskService.EditSingal(erpTask);
|
||||
LogHelper.Info($"更新erptask状态为 {taskStatus} ,数据库操作结果 {bRet}");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 处理销售出库/生产退库,Rcs回调
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 解锁中区库位并绑定合格证
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task UnlockAndSetMiddleWarehouse(StorageRackDto storageRack)
|
||||
{
|
||||
if (storageRack.Transport != TransportEnum.MiddleOutArea)
|
||||
return;
|
||||
|
||||
storageRack.IsLock = false;
|
||||
await _storageRackService.EditSingalWithSpecificCols(storageRack, ["IsLock", "BarCode"]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新AgvTask任务状态
|
||||
/// </summary>
|
||||
/// <param name="agvTask">库位编号</param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> UpdateAgvTaskStatus(AGVTaskDto agvTask)
|
||||
{
|
||||
var updateResult = false;
|
||||
|
||||
if (agvTask != null)
|
||||
{
|
||||
updateResult = await _agvTaskService.EditSingal(agvTask);
|
||||
if (!updateResult)
|
||||
{
|
||||
LogHelper.Error($"销售出库:更新AgvTask {agvTask.Id} 的,完成状态失败");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Info($"销售出库:更新AgvTask {agvTask.Id} 的,完成状态成功");
|
||||
}
|
||||
}
|
||||
return updateResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空起点库位上的物料信息
|
||||
/// </summary>
|
||||
/// <param name="agvTask"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> ClearMateriaInfo(AGVTaskDto agvTask)
|
||||
{
|
||||
var clearResult = false;
|
||||
var typeMsg = "";
|
||||
if (agvTask.TaskType == TaskTypeEnum.SaleOut)
|
||||
{
|
||||
typeMsg = "销售出库,";
|
||||
}
|
||||
else if (agvTask.TaskType == TaskTypeEnum.Carry)
|
||||
{
|
||||
typeMsg = "搬运物料,";
|
||||
}
|
||||
|
||||
var clearWarehouse = await _storageRackService.GetSingalByExpression(p => p.StorageRackNo == agvTask.StartPositionCode);
|
||||
if (clearWarehouse != null)
|
||||
{
|
||||
clearWarehouse.MateriaBatch = "";
|
||||
clearWarehouse.MateriaNum = 0;
|
||||
|
||||
clearWarehouse.OrderNo = "";
|
||||
clearWarehouse.MateriaName = "";
|
||||
clearWarehouse.MateriaType = "";
|
||||
clearWarehouse.MateriaCode = "";
|
||||
clearWarehouse.BarCode = "";
|
||||
clearWarehouse.IsLock = false;
|
||||
|
||||
clearResult = await _storageRackService.EditSingal(clearWarehouse);
|
||||
if (!clearResult)
|
||||
{
|
||||
LogHelper.Error($"{typeMsg}清空库位{clearWarehouse.StorageRackNo}上的物料信息{clearWarehouse.MateriaCode}失败");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Info($"{typeMsg}清空库位{clearWarehouse.StorageRackNo}上的物料信息{clearWarehouse.MateriaCode}成功");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Error($"{typeMsg}清空库位时,未查询到库位{agvTask.StartPositionCode}上的物料信息");
|
||||
}
|
||||
return clearResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新终点库位上的物料信息
|
||||
/// </summary>
|
||||
/// <param name="agvTask"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> UpdateMateriaInfo(AGVTaskDto agvTask)
|
||||
{
|
||||
var updateResult = false;
|
||||
|
||||
var updateWarehouse = await _storageRackService.GetSingalByExpression(p => p.StorageRackNo == agvTask.EndPositionCode);
|
||||
if (updateWarehouse != null)
|
||||
{
|
||||
updateWarehouse.MateriaBatch = agvTask.MateriaBatch;
|
||||
updateWarehouse.MateriaNum = agvTask.MateriaNum;
|
||||
|
||||
updateWarehouse.OrderNo = agvTask.OrderNo;
|
||||
updateWarehouse.MateriaName = agvTask.MateriaName;
|
||||
updateWarehouse.MateriaType = agvTask.MateriaType;
|
||||
updateWarehouse.MateriaCode = agvTask.MateriaCode;
|
||||
updateWarehouse.BarCode = agvTask.BarCode;
|
||||
updateWarehouse.IsLock = false;
|
||||
|
||||
updateResult = await _storageRackService.EditSingal(updateWarehouse);
|
||||
if (!updateResult)
|
||||
{
|
||||
LogHelper.Error($"销售出库:更新库位{updateWarehouse.StorageRackNo}上的物料信息{updateWarehouse.MateriaCode}失败");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Info($"销售出库:更新库位{updateWarehouse.StorageRackNo}上的物料信息{updateWarehouse.MateriaCode}成功");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Error($"销售出库:更新库位时,未查询到库位{agvTask.EndPositionCode}上的物料信息");
|
||||
}
|
||||
return updateResult;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.App.ServiceImpl;
|
||||
using JSMachine.WMS.Business.RcsCallBack.IService.StaePattern;
|
||||
using JSMachine.WMS.RPC.RcsRPC.Dto.In;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.ServiceImpl.StatePattern
|
||||
{
|
||||
/// <summary>
|
||||
/// agv任务执行失败处理器
|
||||
/// </summary>
|
||||
public class FailedStateHandler : IAgvTaskStateHandler
|
||||
{
|
||||
private readonly IAGVTaskService _agvTaskService;
|
||||
/// <summary>创建失败状态处理器。</summary>
|
||||
/// <param name="agvTaskService">AGV 任务持久化服务。</param>
|
||||
public FailedStateHandler(IAGVTaskService agvTaskService)
|
||||
{
|
||||
_agvTaskService=agvTaskService;
|
||||
}
|
||||
|
||||
/// <summary>将当前 AGV 任务标记为失败并保存状态。</summary>
|
||||
/// <param name="agvExcutionInfo">RCS 执行信息。</param>
|
||||
/// <param name="agvTask">待更新的 AGV 任务。</param>
|
||||
/// <param name="destWarehouse">目标库位。</param>
|
||||
/// <returns>状态更新是否成功。</returns>
|
||||
public async Task<bool> HandleAsync(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask, StorageRackDto destWarehouse)
|
||||
{
|
||||
agvTask.TaskStatus = TaskStatusEnum.Failed;
|
||||
return await _agvTaskService.UpdateAgvTaskStatus(agvTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.Business.RcsCallBack.IService.StaePattern;
|
||||
using JSMachine.WMS.Business.SSE;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.RPC.RcsRPC.Dto.In;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.ServiceImpl.StatePattern
|
||||
{
|
||||
/// <summary>
|
||||
/// agv取货完成处理器
|
||||
/// </summary>
|
||||
public class PickupCompletedStateHandler : IAgvTaskStateHandler
|
||||
{
|
||||
private readonly IStorageRackService _storageRackService;
|
||||
private readonly IAGVTaskService _agvTaskService;
|
||||
|
||||
public PickupCompletedStateHandler(IStorageRackService storageRackService, IAGVTaskService agvTaskService)
|
||||
{
|
||||
_storageRackService = storageRackService;
|
||||
_agvTaskService = agvTaskService;
|
||||
}
|
||||
|
||||
public async Task<bool> HandleAsync(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask, StorageRackDto destWarehouse)
|
||||
{
|
||||
// 清空出库库位上的物料、解锁库位
|
||||
await ClearMateriaInfo(agvTask);
|
||||
|
||||
//更新AgvTask的状态为 取货完成
|
||||
agvTask.TaskStatus = TaskStatusEnum.PickupCompleted;
|
||||
return await _agvTaskService.UpdateAgvTaskStatus(agvTask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空起点库位上的物料信息
|
||||
/// </summary>
|
||||
/// <param name="agvTask"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> ClearMateriaInfo(AGVTaskDto agvTask)
|
||||
{
|
||||
var clearResult = false;
|
||||
var typeMsg = "";
|
||||
if (agvTask.TaskType == TaskTypeEnum.SaleOut)
|
||||
{
|
||||
typeMsg = "销售出库,";
|
||||
}
|
||||
else if (agvTask.TaskType == TaskTypeEnum.Carry)
|
||||
{
|
||||
typeMsg = "搬运物料,";
|
||||
}
|
||||
|
||||
var clearWarehouse = await _storageRackService.GetSingalByExpression(p => p.StorageRackNo == agvTask.StartPositionCode);
|
||||
if (clearWarehouse != null)
|
||||
{
|
||||
clearWarehouse.MateriaBatch = "";
|
||||
clearWarehouse.MateriaNum = 0;
|
||||
|
||||
clearWarehouse.OrderNo = "";
|
||||
clearWarehouse.MateriaName = "";
|
||||
clearWarehouse.MateriaType = "";
|
||||
clearWarehouse.MateriaCode = "";
|
||||
clearWarehouse.BarCode = "";
|
||||
clearWarehouse.IsLock = false;
|
||||
|
||||
clearResult = await _storageRackService.EditSingal(clearWarehouse);
|
||||
if (!clearResult)
|
||||
{
|
||||
LogHelper.Error($"{typeMsg}清空库位{clearWarehouse.StorageRackNo}上的物料信息{clearWarehouse.MateriaCode}失败");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Info($"{typeMsg}清空库位{clearWarehouse.StorageRackNo}上的物料信息{clearWarehouse.MateriaCode}成功");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Error($"{typeMsg}清空库位时,未查询到库位{agvTask.StartPositionCode}上的物料信息");
|
||||
}
|
||||
|
||||
//发送广播通知前端网站刷新页面
|
||||
SseEngine.BrodCast(agvTask.Floor.ToString());
|
||||
|
||||
return clearResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.App.ServiceImpl;
|
||||
using JSMachine.WMS.Business.SSE;
|
||||
using JSMachine.WMS.Infrastructure;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.PLC.ModbusImpl.Elevator.Model;
|
||||
using JSMachine.WMS.PLC.ModbusImpl.PaperFeedGroundRack.ModbusIO.Write;
|
||||
using JSMachine.WMS.RPC.RcsRPC.Dto.In;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.ServiceImpl.StatePattern.PutdownCompleted
|
||||
{
|
||||
/// <summary>
|
||||
/// 提升机处理器
|
||||
/// </summary>
|
||||
public class ElevatorDeliveryHandler : PutdownCompletedStateHandler
|
||||
{
|
||||
public ElevatorDeliveryHandler
|
||||
(IStorageRackService storageRackService,
|
||||
IAGVTaskService agvTaskService,
|
||||
IElevatorPlcQueueService elevatorPlcQueueService,
|
||||
IElevatorCountService elevatorCountService,
|
||||
IERPTaskService erpTaskService) :
|
||||
base(storageRackService, agvTaskService, elevatorPlcQueueService, elevatorCountService, erpTaskService)
|
||||
{
|
||||
}
|
||||
|
||||
public async override Task<bool> HandleAsync(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask, StorageRackDto destWarehouse)
|
||||
{
|
||||
var elevatorPlcStatus = new ElevatorPlcStatus();
|
||||
|
||||
//2.1 写PLC放货完成
|
||||
int elevatorNo = destWarehouse.StorageRackName.Contains("1号") ? 1 : 2;
|
||||
elevatorPlcStatus.ElevatorNo = elevatorNo;
|
||||
|
||||
if (destWarehouse.ElevatorChannel == 1001)
|
||||
{
|
||||
elevatorPlcStatus.StockInPutdownChanelTwo = true;
|
||||
}
|
||||
if (destWarehouse.ElevatorChannel == 1011)
|
||||
{
|
||||
elevatorPlcStatus.StockInPutdownChanelOne = true;
|
||||
}
|
||||
if (destWarehouse.ElevatorChannel == 2001)
|
||||
{
|
||||
elevatorPlcStatus.StockOutPutdownChanelTwo = true;
|
||||
}
|
||||
if (destWarehouse.ElevatorChannel == 2011)
|
||||
{
|
||||
elevatorPlcStatus.StockOutPutdownChanelOne = true;
|
||||
}
|
||||
bool bRet = await ElevatorWrite.WriteElevator(elevatorPlcStatus);
|
||||
if (!bRet)
|
||||
{
|
||||
LogHelper.Error($"{agvTask.TaskType.GetDescription()}: " +
|
||||
$"向 {elevatorPlcStatus.ElevatorNo} 号提升机 {destWarehouse.ElevatorChannel} 网带写放货完成信号失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(agvTask.TaskType!=TaskTypeEnum.Carry)
|
||||
{
|
||||
//2.2 写PLC队列信息
|
||||
ElevatorPlcQueueDto elevatorPlcQueue = new()
|
||||
{
|
||||
ElevatorNo = elevatorNo,
|
||||
ElevatorChannel = destWarehouse.ElevatorChannel.Value,
|
||||
AgvTaskId = agvTask.Id,
|
||||
IsLock = false
|
||||
};
|
||||
bRet = await _elevatorPlcQueueService.Add(elevatorPlcQueue);
|
||||
if (!bRet)
|
||||
{
|
||||
LogHelper.Error($"{agvTask.TaskType.GetDescription()}:写PLC队列失败");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Info($"{agvTask.TaskType.GetDescription()}:" +
|
||||
$"写PLC队列成功,提升机编号 {elevatorPlcQueue.ElevatorNo} 通道号 {elevatorPlcQueue.ElevatorChannel}");
|
||||
}
|
||||
|
||||
//提升机计数
|
||||
await _elevatorCountService.IncrementCount(elevatorNo, elevatorPlcQueue.ElevatorChannel);
|
||||
}
|
||||
|
||||
//2.3 更新agvtask的状态为完成
|
||||
await base.CompleteTask(agvTask);
|
||||
|
||||
//2.4 解锁提升机库位
|
||||
await _storageRackService.Unlock(destWarehouse);
|
||||
|
||||
//发送广播通知前端网站刷新页面
|
||||
SseEngine.BrodCast(agvTask.Floor.ToString());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.App.ServiceImpl;
|
||||
using JSMachine.WMS.Business.RcsCallBack.IService.StaePattern;
|
||||
using JSMachine.WMS.RPC.RcsRPC.Dto.In;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.ServiceImpl.StatePattern.PutdownCompleted
|
||||
{
|
||||
/// <summary>
|
||||
/// 放货完成处理器
|
||||
/// </summary>
|
||||
public abstract class PutdownCompletedStateHandler : IAgvTaskStateHandler
|
||||
{
|
||||
protected readonly IStorageRackService _storageRackService;
|
||||
protected readonly IAGVTaskService _agvTaskService;
|
||||
protected readonly IElevatorPlcQueueService _elevatorPlcQueueService;
|
||||
protected readonly IElevatorCountService _elevatorCountService;
|
||||
protected readonly IERPTaskService _erpTaskService;
|
||||
|
||||
public PutdownCompletedStateHandler(
|
||||
IStorageRackService storageRackService,
|
||||
IAGVTaskService agvTaskService,
|
||||
IElevatorPlcQueueService elevatorPlcQueueService,
|
||||
IElevatorCountService elevatorCountService,
|
||||
IERPTaskService erpTaskService
|
||||
)
|
||||
{
|
||||
_storageRackService = storageRackService;
|
||||
_agvTaskService = agvTaskService;
|
||||
_elevatorPlcQueueService = elevatorPlcQueueService;
|
||||
_elevatorCountService= elevatorCountService;
|
||||
_erpTaskService = erpTaskService;
|
||||
}
|
||||
|
||||
public abstract Task<bool> HandleAsync(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask, StorageRackDto destWarehouse);
|
||||
|
||||
protected async Task CompleteTask(AGVTaskDto agvTask)
|
||||
{
|
||||
agvTask.TaskStatus = TaskStatusEnum.Completed;
|
||||
await _agvTaskService.UpdateAgvTaskStatus(agvTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.App.ServiceImpl;
|
||||
using JSMachine.WMS.Business.Common.IService;
|
||||
using JSMachine.WMS.Business.RcsCallBack.IService.StaePattern;
|
||||
using JSMachine.WMS.Business.SSE;
|
||||
using JSMachine.WMS.RPC.RcsRPC.Dto.In;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static JSMachine.WMS.App.Dto.StorageRackDto;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.ServiceImpl.StatePattern.PutdownCompleted
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储区(包括中转区)放货完成处理器
|
||||
/// </summary>
|
||||
public class StockAreaHandler : PutdownCompletedStateHandler
|
||||
{
|
||||
public StockAreaHandler(
|
||||
IStorageRackService storageRackService,
|
||||
IAGVTaskService agvTaskService,
|
||||
IElevatorPlcQueueService elevatorPlcQueueService,
|
||||
IElevatorCountService elevatorCountService,
|
||||
IERPTaskService erpTaskService) :
|
||||
base(storageRackService, agvTaskService, elevatorPlcQueueService, elevatorCountService, erpTaskService)
|
||||
{
|
||||
}
|
||||
|
||||
public async override Task<bool> HandleAsync(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask, StorageRackDto destWarehouse)
|
||||
{
|
||||
//1.将物料绑定到目标库位
|
||||
//2.解锁目标库位
|
||||
//3.更改agv任务为完成
|
||||
|
||||
destWarehouse.BingdingMaterial(new MateriaInfo
|
||||
{
|
||||
BarCode = agvTask.BarCode,
|
||||
MateriaBatch = agvTask.MateriaBatch,
|
||||
MateriaCode = agvTask.MateriaCode,
|
||||
MateriaName = agvTask.MateriaName,
|
||||
MateriaNum = agvTask.MateriaNum.ToString(),
|
||||
MateriaType = agvTask.MateriaType,
|
||||
OrderNo = agvTask.OrderNo
|
||||
});
|
||||
destWarehouse.IsLock = false;
|
||||
|
||||
await _storageRackService.EditSingal(destWarehouse);
|
||||
|
||||
await CompleteTask(agvTask);
|
||||
|
||||
//更新erp任务为完成状态
|
||||
if (agvTask.TaskType == TaskTypeEnum.PdaIn ||
|
||||
agvTask.TaskType == TaskTypeEnum.TailIn ||
|
||||
agvTask.TaskType == TaskTypeEnum.TransferStockIn)
|
||||
{
|
||||
ERPTaskDto erpTask = await _erpTaskService.GetSingalByExpression(p => p.Id == agvTask.ErpTaskId);
|
||||
if (erpTask != null)
|
||||
{
|
||||
erpTask.TaskStatus = TaskStatusEnum.Completed;
|
||||
erpTask.TaskMsg = "";
|
||||
await _erpTaskService.EditSingalWithSpecificCols(erpTask, ["TaskStatus", "TaskMsg"]);
|
||||
}
|
||||
}
|
||||
|
||||
//发送广播通知前端网站刷新页面
|
||||
SseEngine.BrodCast(agvTask.Floor.ToString());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.RPC.RcsRPC.Dto.In;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.ServiceImpl.StatePattern.PutdownCompleted
|
||||
{
|
||||
/// <summary>
|
||||
/// 出库码头处理器
|
||||
/// </summary>
|
||||
public class StockOutWharfHandler : PutdownCompletedStateHandler
|
||||
{
|
||||
public StockOutWharfHandler
|
||||
(IStorageRackService storageRackService,
|
||||
IAGVTaskService agvTaskService,
|
||||
IElevatorPlcQueueService elevatorPlcQueueService,
|
||||
IElevatorCountService elevatorCountService,
|
||||
IERPTaskService erpTaskService) :
|
||||
base(storageRackService, agvTaskService, elevatorPlcQueueService, elevatorCountService, erpTaskService)
|
||||
{
|
||||
}
|
||||
|
||||
public async override Task<bool> HandleAsync(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask, StorageRackDto destWarehouse)
|
||||
{
|
||||
destWarehouse.BarCode = agvTask.BarCode;
|
||||
|
||||
agvTask.TaskStatus = TaskStatusEnum.Completed;
|
||||
|
||||
await UnlockAndSetMiddleWarehouse(destWarehouse);
|
||||
|
||||
return await _agvTaskService.UpdateAgvTaskStatus(agvTask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解锁中区库位并绑定合格证
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task UnlockAndSetMiddleWarehouse(StorageRackDto storageRack)
|
||||
{
|
||||
if (storageRack.Transport != TransportEnum.MiddleOutArea)
|
||||
return;
|
||||
|
||||
storageRack.IsLock = false;
|
||||
await _storageRackService.EditSingalWithSpecificCols(storageRack, ["IsLock", "BarCode"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.Business.SSE;
|
||||
using JSMachine.WMS.RPC.RcsRPC.Dto.In;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static JSMachine.WMS.App.Dto.StorageRackDto;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.ServiceImpl.StatePattern.PutdownCompleted
|
||||
{
|
||||
public class TransitAreaHandler : PutdownCompletedStateHandler
|
||||
{
|
||||
public TransitAreaHandler(
|
||||
IStorageRackService storageRackService,
|
||||
IAGVTaskService agvTaskService,
|
||||
IElevatorPlcQueueService elevatorPlcQueueService,
|
||||
IElevatorCountService elevatorCountService,
|
||||
IERPTaskService erpTaskService) :
|
||||
base(storageRackService, agvTaskService, elevatorPlcQueueService, elevatorCountService, erpTaskService)
|
||||
{
|
||||
}
|
||||
|
||||
public async override Task<bool> HandleAsync(AgvExcutionInfo agvExcutionInfo, AGVTaskDto agvTask, StorageRackDto destWarehouse)
|
||||
{
|
||||
//1.将物料绑定到目标库位
|
||||
//2.解锁目标库位
|
||||
//3.更改agv任务为完成
|
||||
|
||||
destWarehouse.BingdingMaterial(new MateriaInfo
|
||||
{
|
||||
BarCode = agvTask.BarCode,
|
||||
MateriaBatch = agvTask.MateriaBatch,
|
||||
MateriaCode = agvTask.MateriaCode,
|
||||
MateriaName = agvTask.MateriaName,
|
||||
MateriaNum = agvTask.MateriaNum.ToString(),
|
||||
MateriaType = agvTask.MateriaType,
|
||||
OrderNo = agvTask.OrderNo
|
||||
});
|
||||
destWarehouse.IsLock = false;
|
||||
|
||||
await _storageRackService.EditSingal(destWarehouse);
|
||||
|
||||
await CompleteTask(agvTask);
|
||||
|
||||
//发送广播通知前端网站刷新页面
|
||||
SseEngine.BrodCast(agvTask.Floor.ToString());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.Business.RcsCallBack.IService.StaePattern;
|
||||
using JSMachine.WMS.Business.RcsCallBack.ServiceImpl.StatePattern.PutdownCompleted;
|
||||
using JSMachine.WMS.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.ServiceImpl.StatePattern
|
||||
{
|
||||
/// <summary>
|
||||
/// rcs回调处理器工厂类
|
||||
/// </summary>
|
||||
public class RcsCallbackHandlerFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据 RCS 返回状态和目标库位运输区域选择状态处理器。
|
||||
/// </summary>
|
||||
/// <param name="status">RCS 任务状态码。</param>
|
||||
/// <param name="destWarehouse">任务目标库位。</param>
|
||||
/// <returns>负责当前状态后续处理的处理器。</returns>
|
||||
public IAgvTaskStateHandler CreateHandler(int status, StorageRackDto destWarehouse)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
21 => GlobalServericeProvidor.GetService<PickupCompletedStateHandler>(),
|
||||
23 => destWarehouse.Transport switch
|
||||
{
|
||||
TransportEnum.NorthArea or TransportEnum.SouthArea or TransportEnum.MiddleOutArea or TransportEnum.ReturnStockArea
|
||||
=> GlobalServericeProvidor.GetService<StockOutWharfHandler>(),
|
||||
TransportEnum.NormalArea => GlobalServericeProvidor.GetService<StockAreaHandler>(),
|
||||
TransportEnum.TransitArea => GlobalServericeProvidor.GetService<TransitAreaHandler>(),
|
||||
TransportEnum.Elevator => GlobalServericeProvidor.GetService<ElevatorDeliveryHandler>(),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.App.ServiceImpl;
|
||||
using JSMachine.WMS.Business.RcsCallBack.IService.StrategyPattern;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.PLC.ModbusImpl.Elevator.Model;
|
||||
using JSMachine.WMS.PLC.ModbusImpl.PaperFeedGroundRack.ModbusIO.Write;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.ServiceImpl.StrategyPatternImpl
|
||||
{
|
||||
public class ElevatorStrategy : ITaskCompletionStrategy
|
||||
{
|
||||
private readonly IElevatorPlcQueueService _elevatorPlcQueueService;
|
||||
private readonly IStorageRackService _storageRackService;
|
||||
private readonly IAGVTaskService _agvTaskService;
|
||||
private readonly IElevatorCountService _elevatorCountService;
|
||||
|
||||
public ElevatorStrategy(
|
||||
IElevatorPlcQueueService elevatorPlcQueueService,
|
||||
IStorageRackService storageRackService,
|
||||
IElevatorCountService elevatorCountService,
|
||||
IAGVTaskService agvTaskService)
|
||||
{
|
||||
_elevatorPlcQueueService = elevatorPlcQueueService;
|
||||
_storageRackService = storageRackService;
|
||||
_elevatorCountService = elevatorCountService;
|
||||
_agvTaskService = agvTaskService;
|
||||
}
|
||||
|
||||
public async Task<bool> CompleteTaskAsync(AGVTaskDto agvTask, StorageRackDto destWarehouse)
|
||||
{
|
||||
ElevatorPlcStatus elevatorPlcStatus = new();
|
||||
|
||||
int elevatorNo = destWarehouse.StorageRackName.Contains("1号") ? 1 : 2;
|
||||
elevatorPlcStatus.ElevatorNo = elevatorNo;
|
||||
|
||||
if (destWarehouse.ElevatorChannel == 1001)
|
||||
{
|
||||
elevatorPlcStatus.StockInPutdownChanelTwo = true;
|
||||
}
|
||||
if (destWarehouse.ElevatorChannel == 1011)
|
||||
{
|
||||
elevatorPlcStatus.StockInPutdownChanelOne = true;
|
||||
}
|
||||
|
||||
bool bRet = await ElevatorWrite.WriteElevator(elevatorPlcStatus);
|
||||
|
||||
if (!bRet)
|
||||
{
|
||||
LogHelper.Error($"向 {elevatorPlcStatus.ElevatorNo} 号提升机 {destWarehouse.ElevatorChannel} 网带写放货完成信号失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
ElevatorPlcQueueDto elevatorPlcQueue = new()
|
||||
{
|
||||
ElevatorNo = elevatorNo,
|
||||
ElevatorChannel = destWarehouse.ElevatorChannel.Value,
|
||||
AgvTaskId = agvTask.Id,
|
||||
IsLock = false
|
||||
};
|
||||
|
||||
bRet = await _elevatorPlcQueueService.Add(elevatorPlcQueue);
|
||||
if (!bRet)
|
||||
{
|
||||
LogHelper.Error($"写PLC队列失败");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Info($"入库:写提升机队列成功 提升机编号 {elevatorPlcQueue.ElevatorNo} 通道号 {elevatorPlcQueue.ElevatorChannel}");
|
||||
}
|
||||
|
||||
agvTask.TaskStatus = TaskStatusEnum.Completed;
|
||||
await _agvTaskService.EditSingalWithSpecificCols(agvTask, ["TaskStatus"]);
|
||||
|
||||
//解锁提升机通道库位
|
||||
await _storageRackService.Unlock(destWarehouse);
|
||||
//提升机计数
|
||||
await _elevatorCountService.IncrementCount(elevatorNo, elevatorPlcQueue.ElevatorChannel);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.App.ServiceImpl;
|
||||
using JSMachine.WMS.Business.RcsCallBack.IService.StrategyPattern;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using Prism;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static JSMachine.WMS.App.Dto.StorageRackDto;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.ServiceImpl.StrategyPatternImpl
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储区策略(普通库位,中转库位)
|
||||
/// </summary>
|
||||
public class StorageAreaStrategy : ITaskCompletionStrategy
|
||||
{
|
||||
private readonly IStorageRackService _storageRackService;
|
||||
private readonly IAGVTaskService _agvTaskService;
|
||||
private readonly IERPTaskService _erpTaskService;
|
||||
public StorageAreaStrategy(
|
||||
IStorageRackService storageRackService,
|
||||
IAGVTaskService agvTaskService,
|
||||
IERPTaskService erpTaskService)
|
||||
{
|
||||
_storageRackService = storageRackService;
|
||||
_agvTaskService = agvTaskService;
|
||||
_erpTaskService = erpTaskService;
|
||||
}
|
||||
|
||||
public async Task<bool> CompleteTaskAsync(AGVTaskDto agvTask, StorageRackDto destWarehouse)
|
||||
{
|
||||
|
||||
|
||||
|
||||
bool bRet = await _agvTaskService.EditSingalWithSpecificCols(agvTask, ["TaskStatus"]);
|
||||
if (!bRet)
|
||||
{
|
||||
LogHelper.Error($"更新agvTask {agvTask.Id} 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
destWarehouse.BingdingMaterial(new MateriaInfo
|
||||
{
|
||||
BarCode = agvTask.BarCode,
|
||||
MateriaBatch = agvTask.MateriaBatch,
|
||||
MateriaCode = agvTask.MateriaCode,
|
||||
MateriaName = agvTask.MateriaName,
|
||||
MateriaNum = agvTask.MateriaNum.ToString(),
|
||||
MateriaType = agvTask.MateriaType,
|
||||
OrderNo = agvTask.OrderNo
|
||||
});
|
||||
//托盘放到位后解锁库位
|
||||
destWarehouse.IsLock = false;
|
||||
await _storageRackService.EditSingal(destWarehouse);
|
||||
|
||||
agvTask.TaskStatus = TaskStatusEnum.Completed;
|
||||
await _agvTaskService.EditSingalWithSpecificCols(agvTask, ["TaskStatus"]);
|
||||
|
||||
//更新erp任务状态为完成
|
||||
await UpdateErpTaskStatus(agvTask, TaskStatusEnum.Completed);
|
||||
|
||||
return true;
|
||||
}
|
||||
private async Task UpdateErpTaskStatus(AGVTaskDto agvTask, TaskStatusEnum taskStatus)
|
||||
{
|
||||
ERPTaskDto erpTask = await _erpTaskService.GetSingalByExpression(p => p.Id == agvTask.ErpTaskId);
|
||||
erpTask.TaskStatus = taskStatus;
|
||||
//更新erp任务状态为完成
|
||||
bool bRet = await _erpTaskService.EditSingal(erpTask);
|
||||
LogHelper.Info($"更新erptask状态为 {taskStatus} ,数据库操作结果 {bRet}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.Business.RcsCallBack.IService.StrategyPattern;
|
||||
using JSMachine.WMS.Domain.Entity;
|
||||
using JSMachine.WMS.Infrastructure;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.RcsCallBack.ServiceImpl.StrategyPatternImpl
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据任务楼层、目标区域和提升机区域选择任务完成处理策略。
|
||||
/// </summary>
|
||||
public class TaskCompletionStrategyFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// 解析 AGV 任务完成后的后续处理策略。
|
||||
/// </summary>
|
||||
/// <param name="agvTask">已完成的 AGV 任务。</param>
|
||||
/// <param name="destWarehouse">任务目标库位。</param>
|
||||
/// <returns>匹配的策略实例;无法匹配时返回空值。</returns>
|
||||
public ITaskCompletionStrategy GetStrategy(AGVTaskDto agvTask, StorageRackDto destWarehouse)
|
||||
{
|
||||
if (agvTask.Floor == FloorEnum.FirstFloor)
|
||||
{
|
||||
if (destWarehouse.Transport == TransportEnum.NormalArea)
|
||||
return GlobalServericeProvidor.GetService<StorageAreaStrategy>();
|
||||
else if (destWarehouse.ReservoirAreaId == 1091 || destWarehouse.ReservoirAreaId == 1092)
|
||||
return GlobalServericeProvidor.GetService<ElevatorStrategy>();
|
||||
}
|
||||
else if (
|
||||
agvTask.Floor == FloorEnum.SecondFloor &&
|
||||
(destWarehouse.Transport == TransportEnum.NormalArea || destWarehouse.Transport == TransportEnum.TransitArea))
|
||||
{
|
||||
return GlobalServericeProvidor.GetService<StorageAreaStrategy>();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace JSMachine.WMS.Business.SSE
|
||||
{
|
||||
public class ClientConnection
|
||||
{
|
||||
public HttpResponse Response { get; set; }
|
||||
public string ClientId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.SSE
|
||||
{
|
||||
public static class SseEngine
|
||||
{
|
||||
private static readonly List<ClientConnection> _clients = [];
|
||||
|
||||
public static void AddClient(ClientConnection client)
|
||||
{
|
||||
_clients.Add(client);
|
||||
}
|
||||
|
||||
public static void RemoveClient(ClientConnection client)
|
||||
{
|
||||
_clients.Remove(client);
|
||||
}
|
||||
|
||||
public static void BrodCast(string message)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
|
||||
foreach (var client in _clients)
|
||||
{
|
||||
var data = $"data: {message}\n\n";
|
||||
var bytes = Encoding.UTF8.GetBytes(data);
|
||||
client.Response.Body.WriteAsync(bytes, 0, bytes.Length);
|
||||
client.Response.Body.FlushAsync();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.Common.Dto.Http.In;
|
||||
using JSMachine.WMS.RPC.ErpRPC.Dto.In.XRBusinessResult;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.StockIn.IService
|
||||
{
|
||||
public interface IStockInService
|
||||
{
|
||||
/// <summary>
|
||||
/// 入库
|
||||
/// </summary>
|
||||
/// <param name="stockInParam"></param>
|
||||
/// <returns></returns>
|
||||
Task<string> StockIn(PDAInParam stockInParam);
|
||||
/// <summary>
|
||||
/// 从ERPTask生成AgvTask(pda入库)
|
||||
/// </summary>
|
||||
/// <param name="erpTask"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> CreateAgvTaskFirstFloor(ERPTaskDto erpTask);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.StockIn.IService
|
||||
{
|
||||
/// <summary>
|
||||
/// 入库策略
|
||||
/// </summary>
|
||||
public interface IStockInStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算目标库位
|
||||
/// </summary>
|
||||
/// <param name="exceptColumnId">要排除的列(出库掏货再回库不可以回原来的列)</param>
|
||||
/// <returns></returns>
|
||||
Task<StorageRackDto> CalCulateWarehouse();
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.ServiceImpl;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using NPOI.OpenXmlFormats.Dml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.StockIn.ServiceImpl.ResponsibilityChain
|
||||
{
|
||||
/// <summary>
|
||||
/// 放置在已有相同批次的列上
|
||||
/// </summary>
|
||||
public class StockInByExist : StockInHandler
|
||||
{
|
||||
public override async Task<StorageRackDto> CalculateWarehouse(
|
||||
TransportEnum startArea,
|
||||
FloorEnum floor,
|
||||
string materialBatch,
|
||||
List<StorageRackDto> storageRacks
|
||||
)
|
||||
{
|
||||
//1.查找有没有同批次待执行的任务
|
||||
//2.有待执行的任务,直接进入中转区域
|
||||
//3.没有待执行的任务,则采用现有逻辑获取一个同批次库位
|
||||
|
||||
//非中转区的任务,需要先判断是否有同批次未完成的任务,如有则需要等待,确保同批次的货物放满同一列
|
||||
|
||||
await _SemaphoreSlim.WaitAsync();
|
||||
StorageRackDto destStorageRack = null;
|
||||
|
||||
try
|
||||
{
|
||||
storageRacks = await base.GetStorageRackExceptStockout(floor);
|
||||
|
||||
if (storageRacks.IsNullOrEmpty())
|
||||
{
|
||||
LogHelper.Info("没有可用库位");
|
||||
return null;
|
||||
}
|
||||
|
||||
List<AGVTaskDto> uncompletedTasks = await _agvTaskService.GetListByExpression(p =>
|
||||
p.CreationTime > DateTime.Now.Date &&
|
||||
p.MateriaBatch == materialBatch &&
|
||||
(p.TaskType == TaskTypeEnum.PdaIn || p.TaskType == TaskTypeEnum.TailIn || p.TaskType == TaskTypeEnum.TransferStockIn) &&
|
||||
(p.TaskStatus != TaskStatusEnum.Completed)
|
||||
);
|
||||
|
||||
List<long> sameBatchColIds = storageRacks
|
||||
.Where(p =>
|
||||
p.MateriaBatch == materialBatch ||
|
||||
(!uncompletedTasks.IsNullOrEmpty() && uncompletedTasks.Select(x => x.EndPositionCode).Contains(p.StorageRackNo))
|
||||
)
|
||||
.Select(p => p.ReservoirAreaColumnId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (sameBatchColIds.IsNullOrEmpty())
|
||||
{
|
||||
destStorageRack = await HandleByNext(startArea, floor, materialBatch, storageRacks);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<StorageRackDto> sameBatchInclude = storageRacks
|
||||
.Where(p => sameBatchColIds.Contains(p.ReservoirAreaColumnId))
|
||||
.ToList();
|
||||
|
||||
var sameBatchIncludeCol = sameBatchInclude.GroupBy(p => p.ReservoirAreaColumnId);
|
||||
|
||||
// 筛选还有空位的列(包含锁定的列)
|
||||
// 有未锁定的列优先取该列
|
||||
// 没有未锁定的列,则任取一列做中转(如果物料所处位置已经是中转区,则不执行)
|
||||
|
||||
var useableStorageCol = sameBatchIncludeCol
|
||||
.FirstOrDefault(p =>
|
||||
p.All(x => !x.IsLock) &&
|
||||
p.Any(x => !x.IsDisabled && string.IsNullOrEmpty(x.BarCode)));
|
||||
|
||||
if (useableStorageCol != null && useableStorageCol.Any())
|
||||
{
|
||||
destStorageRack = useableStorageCol
|
||||
.Where(p => !p.IsDisabled && string.IsNullOrEmpty(p.BarCode))
|
||||
.OrderByDescending(p => p.NumSort)
|
||||
.First();
|
||||
}
|
||||
else
|
||||
{
|
||||
var emptyCols = sameBatchIncludeCol.Where(p => p.Any(x => !x.IsLock && !x.IsDisabled && string.IsNullOrEmpty(x.BarCode)));
|
||||
if (emptyCols.Any())
|
||||
{
|
||||
//如果仅有一个空列,则可以开一个新列,保证最多只开2列
|
||||
if (emptyCols.Count() <= 1)
|
||||
{
|
||||
destStorageRack = await HandleByNext(startArea, floor, materialBatch, storageRacks);
|
||||
}
|
||||
else
|
||||
{
|
||||
//一楼不允许混放或进入中转区。同批次已开启两列后,等待现有任务完成并解锁列。
|
||||
if (floor == FloorEnum.FirstFloor)
|
||||
{
|
||||
LogHelper.Warn("一楼同批次已开启两列且目标列被锁定,入库任务等待列解锁");
|
||||
return null;
|
||||
}
|
||||
|
||||
//物料已在中转区,则不可以继续在中转区生成任务
|
||||
if (startArea == TransportEnum.TransitArea)
|
||||
{
|
||||
LogHelper.Warn("目标库位所在列被锁定,中转入库任调度需要等待列解锁");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
destStorageRack = await _storageRackService.GetUsableTransferStorageRack();
|
||||
|
||||
if (destStorageRack == null)
|
||||
{
|
||||
LogHelper.Warn($"中转区已满,尝试放货到空白列或者和其他货物混放");
|
||||
destStorageRack = await HandleByNext(startArea, floor, materialBatch, storageRacks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
destStorageRack = await HandleByNext(startArea, floor, materialBatch, storageRacks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (destStorageRack != null)
|
||||
{
|
||||
// 先完成数据库锁定,确保数据一致性
|
||||
await _storageRackService.Lock(destStorageRack);
|
||||
}
|
||||
|
||||
return destStorageRack;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"入库责任链处理异常:{ex.Message} \n {ex.StackTrace}");
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_SemaphoreSlim.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.StockIn.ServiceImpl.ResponsibilityChain
|
||||
{
|
||||
/// <summary>
|
||||
/// 和其他不同批次的货物混合放置
|
||||
/// </summary>
|
||||
public class StockInByMixing : StockInHandler
|
||||
{
|
||||
public override async Task<StorageRackDto> CalculateWarehouse(
|
||||
TransportEnum startArea,
|
||||
FloorEnum floor,
|
||||
string materialBatch,
|
||||
List<StorageRackDto> storageRacks)
|
||||
{
|
||||
if (storageRacks.IsNullOrEmpty())
|
||||
{
|
||||
storageRacks = await base.GetStorageRackExceptStockout(floor);
|
||||
}
|
||||
|
||||
if (storageRacks.IsNullOrEmpty())
|
||||
{
|
||||
LogHelper.Info("没有可用库位");
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StorageRackDto> wareHousesSorted = storageRacks.OrderBy(p => p.AreaNumSort).ToList();
|
||||
|
||||
IEnumerable<IGrouping<long, StorageRackDto>> groupByColumn = wareHousesSorted.GroupBy(p => p.ReservoirAreaColumnId);
|
||||
|
||||
StorageRackDto destStorageRack = null;
|
||||
foreach (var columnGroup in groupByColumn)
|
||||
{
|
||||
bool unlockedColumn = columnGroup.All(p => !p.IsLock);
|
||||
if (unlockedColumn)
|
||||
{
|
||||
destStorageRack = columnGroup
|
||||
.Where(p => string.IsNullOrEmpty(p.BarCode))
|
||||
.OrderByDescending(p => p.NumSort)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (destStorageRack != null)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (destStorageRack == null && startArea != TransportEnum.TransitArea)
|
||||
{
|
||||
destStorageRack = await _storageRackService.GetUsableTransferStorageRack();
|
||||
|
||||
if (destStorageRack == null)
|
||||
{
|
||||
LogHelper.Warn($"存储区和中转区均无可用的库位");
|
||||
}
|
||||
}
|
||||
|
||||
return destStorageRack;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.StockIn.ServiceImpl.ResponsibilityChain
|
||||
{
|
||||
/// <summary>
|
||||
/// 放置在一个空白列列上
|
||||
/// </summary>
|
||||
public class StockInByNew : StockInHandler
|
||||
{
|
||||
public override async Task<StorageRackDto> CalculateWarehouse(
|
||||
TransportEnum startArea,
|
||||
FloorEnum floor,
|
||||
string materialBatch,
|
||||
List<StorageRackDto> storageRacks)
|
||||
{
|
||||
if (storageRacks.IsNullOrEmpty())
|
||||
{
|
||||
storageRacks = await base.GetStorageRackExceptStockout(floor);
|
||||
}
|
||||
|
||||
if (storageRacks.IsNullOrEmpty())
|
||||
{
|
||||
LogHelper.Info("没有可用库位");
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StorageRackDto> storageRacksSorted = storageRacks
|
||||
.OrderBy(p => p.AreaNumSort)
|
||||
.ThenBy(p => p.ReservoirAreaColumnId).ToList();
|
||||
|
||||
var groupByColumn = storageRacksSorted.GroupBy(p => p.ReservoirAreaColumnId);
|
||||
|
||||
StorageRackDto destWarehouse = null;
|
||||
foreach (IGrouping<long, StorageRackDto> column in groupByColumn)
|
||||
{
|
||||
if (column.All(p => p.IsEmpty()))
|
||||
{
|
||||
destWarehouse = column.OrderByDescending(p => p.NumSort).First();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (destWarehouse == null)
|
||||
{
|
||||
return await HandleByNext(startArea, floor,materialBatch, storageRacksSorted);
|
||||
}
|
||||
else
|
||||
{
|
||||
return destWarehouse;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.Domain.Entity;
|
||||
using JSMachine.WMS.Infrastructure;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.StockIn.ServiceImpl.ResponsibilityChain
|
||||
{
|
||||
/// <summary>
|
||||
/// 责任链模式。入库处理基类 。责任流动方向:StockInByExist=》StockInByNew=》StockInByMixing
|
||||
/// </summary>
|
||||
public abstract class StockInHandler
|
||||
{
|
||||
protected StockInHandler _nextHandler;
|
||||
protected static IStorageRackService _storageRackService = GlobalServericeProvidor.GetService<IStorageRackService>();
|
||||
protected static IAGVTaskService _agvTaskService = GlobalServericeProvidor.GetService<IAGVTaskService>();
|
||||
protected static IERPTaskService _erpTaskService = GlobalServericeProvidor.GetService<IERPTaskService>();
|
||||
|
||||
protected static IStorageRackService StorageRackService = GlobalServericeProvidor.GetService<IStorageRackService>();
|
||||
protected static readonly SemaphoreSlim _SemaphoreSlim = new(1,1);
|
||||
|
||||
public void SetNextHandler(StockInHandler nextHandler)
|
||||
{
|
||||
_nextHandler = nextHandler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算目标库位
|
||||
/// </summary>
|
||||
/// <param name="startStorageRack">起点</param>
|
||||
/// <param name="materialBatch">批次</param>
|
||||
/// <param name="storageRacks">所有未禁用的库位</param>
|
||||
/// <returns></returns>
|
||||
public abstract Task<StorageRackDto> CalculateWarehouse(
|
||||
TransportEnum startArea, FloorEnum floor, string materialBatch, List<StorageRackDto> storageRacks);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 有下一个处理者,则继续执行下一个处理者的逻辑
|
||||
/// </summary>
|
||||
/// <param name="startStorageRack">起点</param>
|
||||
/// <param name="materialBatch">批次</param>
|
||||
/// <param name="storageRacks">所有未禁用的库位</param>
|
||||
/// <returns></returns>
|
||||
public async Task<StorageRackDto> HandleByNext(
|
||||
TransportEnum startArea,
|
||||
FloorEnum floor,
|
||||
string materialBatch,
|
||||
List<StorageRackDto> storageRacks)
|
||||
{
|
||||
if (_nextHandler != null)
|
||||
{
|
||||
return await _nextHandler.CalculateWarehouse(startArea, floor, materialBatch, storageRacks);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<StorageRackDto>> GetStorageRackExceptStockout(FloorEnum floor)
|
||||
{
|
||||
List<StorageRackDto> storageRacks = await StorageRackService.GetListByExpression(p =>
|
||||
p.Floor == floor &&
|
||||
p.Transport == TransportEnum.NormalArea);
|
||||
|
||||
if(storageRacks.IsNullOrEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ERPTaskDto> erpTaskStockOuts = await _erpTaskService
|
||||
.GetListByExpression(p =>
|
||||
p.CreationTime > DateTime.Now.AddDays(-1) &&
|
||||
p.TaskStatus == TaskStatusEnum.InExecution &&
|
||||
(p.TaskType == TaskTypeEnum.SaleOut || p.TaskType == TaskTypeEnum.ProductionOut)
|
||||
);
|
||||
|
||||
if (!erpTaskStockOuts.IsNullOrEmpty())
|
||||
{
|
||||
List<AGVTaskDto> agvTaks = await _agvTaskService.GetListByExpression(p =>
|
||||
p.CreationTime > DateTime.Now.AddDays(-1) &&
|
||||
erpTaskStockOuts.Select(x => x.Id).Contains(p.ErpTaskId)
|
||||
);
|
||||
|
||||
if (!agvTaks.IsNullOrEmpty())
|
||||
{
|
||||
List<StorageRackDto> startStorageRacks = await StorageRackService.GetListByExpression(p =>
|
||||
agvTaks.Select(x => x.StartPositionCode).Contains(p.StorageRackNo));
|
||||
|
||||
List<long> exceptCols = startStorageRacks.Select(p => p.ReservoirAreaColumnId).Distinct().ToList();
|
||||
|
||||
storageRacks.RemoveAll(p => exceptCols.Contains(p.ReservoirAreaColumnId));
|
||||
}
|
||||
}
|
||||
|
||||
return storageRacks;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.Business.StockIn.IService;
|
||||
using JSMachine.WMS.Business.StockIn.ServiceImpl.Strategy;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.PLC.ModbusImpl.Elevator.Util;
|
||||
using JSMachine.WMS.RPC.ErpRPC.Dto.In.XRBusinessResult;
|
||||
using JSMachine.WMS.RPC.ErpRPC.Dto.Out.XRBusinessResult;
|
||||
using JSMachine.WMS.RPC.ErpRPC.IService;
|
||||
using NPOI.OpenXmlFormats.Dml.Chart;
|
||||
|
||||
namespace JSMachine.WMS.Business.StockIn.ServiceImpl
|
||||
{
|
||||
/// <summary>
|
||||
/// PDA入库业务类
|
||||
/// </summary>
|
||||
public class StockInService : IStockInService
|
||||
{
|
||||
private IPaperStorageService _paperStorageService;
|
||||
private IStorageRackService _storageRackService;
|
||||
private IERPTaskService _erpTaskService;
|
||||
private IAGVTaskService _agvTaskService;
|
||||
private IElevatorPlcQueueService _elevatorPlcQueueService;
|
||||
public StockInService(
|
||||
IPaperStorageService paperStorageService,
|
||||
IStorageRackService storageRackService,
|
||||
IERPTaskService erpTaskService,
|
||||
IAGVTaskService agvTaskService,
|
||||
IElevatorPlcQueueService elevatorPlcQueueService
|
||||
)
|
||||
{
|
||||
_paperStorageService = paperStorageService;
|
||||
_storageRackService = storageRackService;
|
||||
_erpTaskService = erpTaskService;
|
||||
_agvTaskService = agvTaskService;
|
||||
_elevatorPlcQueueService = elevatorPlcQueueService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PDA入库
|
||||
/// </summary>
|
||||
/// <param name="stockInParam"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<string> StockIn(PDAInParam stockInParam)
|
||||
{
|
||||
// 入库流程依次校验重复任务、物料和起点库位,再计算目标库位并创建任务。
|
||||
//1.查询物料信息是否存在
|
||||
//2.查询库位是否存在
|
||||
//3.计算目标库位并锁定
|
||||
//4.写入ErpTask表
|
||||
|
||||
bool existUnCompleteTask = await _erpTaskService.Exist(p =>
|
||||
p.CreationTime > DateTime.Now.Date &&
|
||||
(p.TaskType == TaskTypeEnum.PdaIn || p.TaskType==TaskTypeEnum.TailIn)&&
|
||||
(p.TaskStatus == TaskStatusEnum.Initial || p.TaskStatus == TaskStatusEnum.InExecution) &&
|
||||
p.BarCode == stockInParam.BarCode);
|
||||
|
||||
if (existUnCompleteTask)
|
||||
{
|
||||
LogHelper.Warn($"合格证号 {stockInParam.BarCode} 已有待入库的任务,请检查是否扫错");
|
||||
return $"合格证号 {stockInParam.BarCode} 已有待入库的任务,请检查是否扫错";
|
||||
}
|
||||
|
||||
MateriaInfoDto materiaInfo = await CheckMaterialInfo(stockInParam.BarCode);
|
||||
if (materiaInfo == null)
|
||||
{
|
||||
string msg = $"未在ERP中查询到合格证号为 {stockInParam.BarCode} 的物料信息";
|
||||
|
||||
LogHelper.Warn(msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
StorageRackDto startStoragRack = await _storageRackService.GetSingalByExpression(p =>
|
||||
p.StorageRackNo == stockInParam.StorageRackNo &&
|
||||
!p.IsDisabled);
|
||||
|
||||
if (startStoragRack == null)
|
||||
{
|
||||
string msg = $"入库起点库位 {stockInParam.StorageRackNo} 不可用";
|
||||
|
||||
LogHelper.Warn(msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
//如果是尾托回库,需要自动计算任务楼层
|
||||
if (stockInParam.TaskType == TaskTypeEnum.TailIn)
|
||||
{
|
||||
AGVTaskDto outTask = await _agvTaskService.GetSingalByExpression(p =>
|
||||
p.BarCode == stockInParam.BarCode &&
|
||||
(p.TaskType == TaskTypeEnum.SaleOut || p.TaskType == TaskTypeEnum.TransferStockOut) &&
|
||||
!p.StartPositionCode.Contains("TSJ"),
|
||||
p => p.CreationTime,
|
||||
SqlSugar.OrderByType.Desc);
|
||||
|
||||
if (outTask == null)
|
||||
{
|
||||
string msg = $"合格证号 {stockInParam.BarCode} 批次 {materiaInfo.MateriaBatch} 没有对应的出库任务," +
|
||||
$"因此不知道该物料原来所属楼层,请人工将该货物放置到合适的库位上,然后使用pda更新物料信息";
|
||||
LogHelper.Error(msg);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
stockInParam.Floor = outTask.Floor;
|
||||
}
|
||||
|
||||
//预先计算可用的库位(所有未禁用,空白库位-未完成的入库任务)
|
||||
int usableWarehouseCout = await _storageRackService.Count(p =>
|
||||
p.Floor == stockInParam.Floor &&
|
||||
p.Transport == TransportEnum.NormalArea &&
|
||||
!p.IsDisabled);
|
||||
int unCompleteErpTaskCount = await _erpTaskService.Count(p =>
|
||||
p.CreationTime > DateTime.Now.Date &&
|
||||
(p.TaskType == TaskTypeEnum.PdaIn || p.TaskType == TaskTypeEnum.TailIn) &&
|
||||
!(p.TaskStatus == TaskStatusEnum.Completed || p.TaskStatus == TaskStatusEnum.Cancelled)
|
||||
);
|
||||
|
||||
if (usableWarehouseCout- unCompleteErpTaskCount <=0)
|
||||
{
|
||||
string msg = $"没有空余库位,无法入库";
|
||||
|
||||
LogHelper.Warn(msg);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
ERPTaskDto erpTask = CreateErpTask(stockInParam, materiaInfo);
|
||||
bool bRet = await _erpTaskService.Add(erpTask);
|
||||
if (!bRet)
|
||||
{
|
||||
string msg = $"数据库操作失败";
|
||||
|
||||
LogHelper.Warn(msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从ERPTask生成AgvTask(pda入库)
|
||||
/// </summary>
|
||||
/// <param name="erpTask"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> CreateAgvTaskFirstFloor(ERPTaskDto erpTask)
|
||||
{
|
||||
//1.入库到一楼寻找一楼库位
|
||||
//2.入库到二楼寻找一楼提升机库位
|
||||
|
||||
StorageRackDto destWareHouse = await CalculateDestWarehouse(erpTask, erpTask.MateriaBatch, erpTask.Floor);
|
||||
if (destWareHouse == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AGVTaskDto agvTask = new()
|
||||
{
|
||||
ErpTaskId = erpTask.Id,
|
||||
BarCode = erpTask.BarCode,
|
||||
OrderNo = erpTask.OrderNo,
|
||||
CustomName = erpTask.CustomName,
|
||||
MateriaName = erpTask.MateriaName,
|
||||
MateriaType = erpTask.MateriaType,
|
||||
MateriaNum = erpTask.ErpOrderNum,
|
||||
MateriaBatch = erpTask.MateriaBatch,
|
||||
MateriaCode = erpTask.MateriaCode,
|
||||
|
||||
TaskType = erpTask.TaskType,
|
||||
TaskStatus = TaskStatusEnum.Initial,
|
||||
Priority = PriorityEnum.High,
|
||||
Floor = FloorEnum.FirstFloor,
|
||||
|
||||
StartPositionCode = erpTask.StorageRackNo,
|
||||
EndPositionCode = destWareHouse.StorageRackNo,
|
||||
|
||||
ErpUploadStatus = destWareHouse.Transport == TransportEnum.Elevator ? ErpUploadStatusEnum.Unnecessary : ErpUploadStatusEnum.WaittingToUpload,
|
||||
|
||||
Transport = TransportEnum.NormalArea
|
||||
};
|
||||
|
||||
destWareHouse.IsLock = true;
|
||||
|
||||
await _storageRackService.Lock(destWareHouse);
|
||||
return await _agvTaskService.Add(agvTask);
|
||||
}
|
||||
|
||||
private async Task<MateriaInfoDto> CheckMaterialInfo(string barcode)
|
||||
{
|
||||
XRResultDto xRResult = await _paperStorageService.GetMateriaInfo(barcode);
|
||||
if (xRResult?.status == 200)
|
||||
return xRResult.data;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算目标库位
|
||||
/// </summary>
|
||||
/// <param name="materiaInfo"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<StorageRackDto> CalculateDestWarehouse(ERPTaskDto erpTask, string materiaBatch, FloorEnum floor)
|
||||
{
|
||||
//1.入库到一楼寻找一楼库位
|
||||
//2.入库到二楼寻找一楼提升机库位
|
||||
|
||||
if (floor == FloorEnum.FirstFloor)
|
||||
{
|
||||
IStockInStrategy stockInStrategy = StrategyFactory.CreateStockInStrategy(TransportEnum.ManualArea, floor, materiaBatch);
|
||||
|
||||
StorageRackDto destWarehouse = await stockInStrategy.CalCulateWarehouse();
|
||||
return destWarehouse;
|
||||
}
|
||||
|
||||
StorageRackDto elevatorWarehouse = await ElevatorUtil.GetUsableElevatorChannel(erpTask.Id, FloorEnum.FirstFloor);
|
||||
return elevatorWarehouse;
|
||||
}
|
||||
|
||||
private ERPTaskDto CreateErpTask(PDAInParam pdaInParam, MateriaInfoDto materiaInfoDto)
|
||||
{
|
||||
ERPTaskDto eRPTaskDto = new()
|
||||
{
|
||||
StorageRackNo = pdaInParam.StorageRackNo,
|
||||
OrderNo = materiaInfoDto.OrderNo,
|
||||
BarCode = materiaInfoDto.BarCode,
|
||||
MateriaName = materiaInfoDto.MateriaName,
|
||||
ErpOrderNum = pdaInParam.TaskType == TaskTypeEnum.PdaIn ? int.Parse(materiaInfoDto.MateriaNum) : pdaInParam.MertialNum,
|
||||
MateriaBatch = materiaInfoDto.MateriaBatch,
|
||||
MateriaCode = materiaInfoDto.MateriaCode,
|
||||
|
||||
TaskStatus = TaskStatusEnum.Initial,
|
||||
TaskType = pdaInParam.TaskType,
|
||||
Floor = pdaInParam.Floor
|
||||
};
|
||||
|
||||
return eRPTaskDto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.Business.StockIn.IService;
|
||||
using JSMachine.WMS.Infrastructure;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static JSMachine.WMS.App.Dto.StorageRackDto;
|
||||
|
||||
namespace JSMachine.WMS.Business.StockIn.ServiceImpl.Strategy
|
||||
{
|
||||
/// <summary>
|
||||
/// 完全混放策略
|
||||
/// </summary>
|
||||
public class CompleteMixingStrategy : IStockInStrategy
|
||||
{
|
||||
private static readonly IStorageRackService StorageRackService = GlobalServericeProvidor.GetService<IStorageRackService>();
|
||||
private FloorEnum _floor;
|
||||
private TransportEnum _startArea;
|
||||
private string _materiaBatch;
|
||||
|
||||
public CompleteMixingStrategy(TransportEnum startArea, FloorEnum floor, string materiaBatch)
|
||||
{
|
||||
_startArea = startArea;
|
||||
_materiaBatch = materiaBatch;
|
||||
_floor = floor;
|
||||
}
|
||||
|
||||
public async Task<StorageRackDto> CalCulateWarehouse()
|
||||
{
|
||||
//如果某一列有一个库位被锁定,说明该列有未完成的任务(任务完成会解锁库位),因此该列不可以再生成新的任务
|
||||
|
||||
//获取楼层的所有库位
|
||||
List<StorageRackDto> wareHouses = await StorageRackService.GetListByExpression(p =>
|
||||
p.Floor == _floor && p.Transport == TransportEnum.NormalArea);
|
||||
|
||||
if (wareHouses.IsNullOrEmpty())
|
||||
{
|
||||
LogHelper.Warn($"{_floor} 层没有空余库位");
|
||||
return null;
|
||||
}
|
||||
|
||||
wareHouses = wareHouses.OrderBy(p => p.AreaNumSort).ToList();
|
||||
|
||||
IEnumerable<IGrouping<long, StorageRackDto>> groupByColumn = wareHouses.GroupBy(p => p.ReservoirAreaColumnId);
|
||||
|
||||
StorageRackDto destStorageRack = null;
|
||||
foreach (var columnGroup in groupByColumn)
|
||||
{
|
||||
bool emptyColumn = columnGroup.All(p => !p.IsLock && !p.IsDisabled && string.IsNullOrEmpty(p.BarCode));
|
||||
if (emptyColumn)
|
||||
{
|
||||
destStorageRack = columnGroup.OrderByDescending(p => p.NumSort).First();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return destStorageRack;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.Business.StockIn.IService;
|
||||
using JSMachine.WMS.Business.StockIn.ServiceImpl.ResponsibilityChain;
|
||||
|
||||
namespace JSMachine.WMS.Business.StockIn.ServiceImpl.Strategy
|
||||
{
|
||||
/// <summary>
|
||||
/// 混合模式策略。同批次的优先放同一列,如果没有库位则混放
|
||||
/// </summary>
|
||||
public class MixedModeStrategy : IStockInStrategy
|
||||
{
|
||||
private FloorEnum _floor;
|
||||
private TransportEnum _startArea;
|
||||
private string _materiaBatch;
|
||||
public MixedModeStrategy(TransportEnum startArea, FloorEnum floor, string materiaBatch)
|
||||
{
|
||||
_floor = floor;
|
||||
_startArea = startArea;
|
||||
_materiaBatch = materiaBatch;
|
||||
}
|
||||
|
||||
public async Task<StorageRackDto> CalCulateWarehouse()
|
||||
{
|
||||
StockInByExist stockInByExist = new();
|
||||
StockInByNew stockInByNew = new();
|
||||
StockInByMixing stockInByMixing = new();
|
||||
|
||||
stockInByExist.SetNextHandler(stockInByNew);
|
||||
stockInByNew.SetNextHandler(stockInByMixing);
|
||||
|
||||
return await stockInByExist.CalculateWarehouse(_startArea, _floor, _materiaBatch, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.Business.StockIn.IService;
|
||||
using JSMachine.WMS.Business.StockIn.ServiceImpl.ResponsibilityChain;
|
||||
using JSMachine.WMS.Infrastructure;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static JSMachine.WMS.App.Dto.StorageRackDto;
|
||||
|
||||
namespace JSMachine.WMS.Business.StockIn.ServiceImpl.Strategy
|
||||
{
|
||||
/// <summary>
|
||||
/// 不混放策略
|
||||
/// </summary>
|
||||
public class NoMixingStrategy : IStockInStrategy
|
||||
{
|
||||
private static readonly IStorageRackService StorageRackService = GlobalServericeProvidor.GetService<IStorageRackService>();
|
||||
private FloorEnum _floor;
|
||||
private TransportEnum _startArea;
|
||||
private string _materiaBatch;
|
||||
public NoMixingStrategy(TransportEnum startArea, FloorEnum floor, string materiaBatch)
|
||||
{
|
||||
_floor = floor;
|
||||
_startArea = startArea;
|
||||
_materiaBatch = materiaBatch;
|
||||
}
|
||||
|
||||
public async Task<StorageRackDto> CalCulateWarehouse()
|
||||
{
|
||||
StockInByExist stockInByExist = new();
|
||||
StockInByNew stockInByNew = new();
|
||||
|
||||
stockInByExist.SetNextHandler(stockInByNew);
|
||||
|
||||
return await stockInByExist.CalculateWarehouse(_startArea, _floor, _materiaBatch, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.Business.StockIn.IService;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using JSMachine.WMS.Common;
|
||||
using JSMachine.WMS.Infrastructure;
|
||||
using JSMachine.WMS.App.IService;
|
||||
|
||||
namespace JSMachine.WMS.Business.StockIn.ServiceImpl.Strategy
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据楼层和配置的混放模式选择入库库位计算策略。
|
||||
/// </summary>
|
||||
public class StrategyFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// 0-完全不混放 1-混合模式,优先同批次放一列,没有空库位再混放 2-完全混放模式,不用批次货物放一起
|
||||
/// </summary>
|
||||
/// <param name="startArea">入库起始区域。</param>
|
||||
/// <param name="floor">目标楼层。</param>
|
||||
/// <param name="materiaBatch">物料批次。</param>
|
||||
/// <returns>与当前入库条件匹配的库位计算策略。</returns>
|
||||
public static IStockInStrategy CreateStockInStrategy(TransportEnum startArea, FloorEnum floor, string materiaBatch)
|
||||
{
|
||||
switch (floor == FloorEnum.FirstFloor ? 0 : Global.AppSettings.PlcaementMod)
|
||||
{
|
||||
case 0:
|
||||
return new NoMixingStrategy(startArea, floor, materiaBatch);
|
||||
case 1:
|
||||
return new MixedModeStrategy(startArea, floor, materiaBatch);
|
||||
case 2:
|
||||
return new CompleteMixingStrategy(startArea, floor, materiaBatch);
|
||||
default:
|
||||
return new NoMixingStrategy(startArea, floor, materiaBatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.RPC.ErpRPC.Dto.In;
|
||||
using JSMachine.WMS.RPC.ErpRPC.Dto.In.XRBusinessResult;
|
||||
using JSMachine.WMS.RPC.ErpRPC.Dto.Out;
|
||||
using JSMachine.WMS.RPC.ErpRPC.Dto.Out.XRBusinessResult;
|
||||
|
||||
namespace JSMachine.WMS.Business.StockOut.IService
|
||||
{
|
||||
public interface IStockOutService
|
||||
{
|
||||
/// <summary>
|
||||
/// 出库(包含销售出库和生产退库)
|
||||
/// </summary>
|
||||
/// <param name="erpSaleOutParam"></param>
|
||||
/// <returns></returns>
|
||||
Task<string> Stockout(StockOutParam erpSaleOutParam);
|
||||
|
||||
/// <summary>
|
||||
/// 销售出库,根据ERPTask生成AgvTask
|
||||
/// </summary>
|
||||
/// <param name="erpTask"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> CreateAgvTaskSecondFloor_SaleOut(ERPTaskDto erpTask);
|
||||
|
||||
/// <summary>
|
||||
/// 销售出库,计算目标库位
|
||||
/// </summary>
|
||||
/// <param name="transport"></param>
|
||||
/// <returns></returns>
|
||||
Task<StorageRackDto> CalculateDestWarehouse_SaleOut(TransportEnum transport);
|
||||
|
||||
/// <summary>
|
||||
/// 搬运物料
|
||||
/// 同楼层的存储区,才能搬运
|
||||
/// </summary>
|
||||
/// <param name="carryInParam"></param>
|
||||
/// <returns></returns>
|
||||
Task<ApiResult<string>> CarryMaterialInfo(CarryInParam carryInParam);
|
||||
|
||||
/// <summary>
|
||||
/// 物料搬运,起点库位验证
|
||||
/// </summary>
|
||||
/// <param name="startRankNo"></param>
|
||||
/// <returns></returns>
|
||||
Task<ApiResult<string>> CheckCarryStartRankNo(string startRankNo);
|
||||
|
||||
/// <summary>
|
||||
/// 物料搬运,终点库位验证
|
||||
/// </summary>
|
||||
/// <param name="endRankNo"></param>
|
||||
/// <returns></returns>
|
||||
Task<ApiResult<string>> CheckCarryEndRankNo(string endRankNo);
|
||||
|
||||
/// <summary>
|
||||
/// 出库撤单
|
||||
/// </summary>
|
||||
/// <param name="startRankNo"></param>
|
||||
/// <returns></returns>
|
||||
Task<XRNewResultDto<int>> ErpCancelOrder(ErpCancelOrderParam cancelOrderParam);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Business.StockOut.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 出库任务生成详情
|
||||
/// </summary>
|
||||
public class StockOutCreationDetail
|
||||
{
|
||||
/// <summary>
|
||||
/// 要出库的物料
|
||||
/// </summary>
|
||||
public StorageRackDto StockOutWarehouse { get; set; }
|
||||
/// <summary>
|
||||
/// 异常类型
|
||||
/// </summary>
|
||||
public StockOutCreationExceptionType ExceptionType { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 出库任务生成异常类型
|
||||
/// </summary>
|
||||
public enum StockOutCreationExceptionType
|
||||
{
|
||||
/// <summary>
|
||||
/// 正常无异常
|
||||
/// </summary>
|
||||
[Description("正常无异常")]
|
||||
NoException = 0,
|
||||
/// <summary>
|
||||
/// 没有库存
|
||||
/// </summary>
|
||||
[Description("没有库存")]
|
||||
OutOfStock = 1,
|
||||
/// <summary>
|
||||
/// 列被锁定
|
||||
/// </summary>
|
||||
[Description("列被锁定")]
|
||||
Locked = 2,
|
||||
/// <summary>
|
||||
/// 等待前序物料中转掏货
|
||||
/// </summary>
|
||||
[Description("等待前序物料中转掏货")]
|
||||
WaittingTransfer = 3,
|
||||
/// <summary>
|
||||
/// 等待前序物料任务执行完解锁
|
||||
/// </summary>
|
||||
[Description("等待前序物料任务执行完解锁")]
|
||||
WaittingUnlock = 4,
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,946 @@
|
||||
using JSMachine.WMS.App.Dto;
|
||||
using JSMachine.WMS.App.Dto.Enum;
|
||||
using JSMachine.WMS.App.IService;
|
||||
using JSMachine.WMS.Business.Common.IService;
|
||||
using JSMachine.WMS.Business.StockOut.IService;
|
||||
using JSMachine.WMS.Business.StockOut.Models;
|
||||
using JSMachine.WMS.Domain.Entity;
|
||||
using JSMachine.WMS.Infrastructure;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.PLC.ModbusImpl.Elevator.ModbusIO.Read;
|
||||
using JSMachine.WMS.PLC.ModbusImpl.Elevator.Model;
|
||||
using JSMachine.WMS.RPC.ErpRPC.Dto.In;
|
||||
using JSMachine.WMS.RPC.ErpRPC.Dto.In.XRBusinessResult;
|
||||
using JSMachine.WMS.RPC.ErpRPC.Dto.Out;
|
||||
using JSMachine.WMS.RPC.ErpRPC.Dto.Out.XRBusinessResult;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using SqlSugar;
|
||||
|
||||
namespace JSMachine.WMS.Business.StockOut.ServiceImpl
|
||||
{
|
||||
/// <summary>
|
||||
/// 出库服务类
|
||||
/// </summary>
|
||||
public class StockOutService : IStockOutService
|
||||
{
|
||||
private IERPTaskService _erpTaskService;
|
||||
private IStorageRackService _storageRackService;
|
||||
private IAGVTaskService _agvTaskService;
|
||||
private ICommonService _commonService;
|
||||
public StockOutService(
|
||||
IERPTaskService erpTaskService,
|
||||
IStorageRackService storageRackService,
|
||||
IAGVTaskService agvTaskService,
|
||||
ICommonService commonService)
|
||||
{
|
||||
_erpTaskService = erpTaskService;
|
||||
_storageRackService = storageRackService;
|
||||
_agvTaskService = agvTaskService;
|
||||
_commonService = commonService;
|
||||
}
|
||||
|
||||
#region 销售出库
|
||||
/// <summary>
|
||||
/// 销售出库
|
||||
/// </summary>
|
||||
/// <param name="erpSaleOutParam"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<string> Stockout(StockOutParam stockOutParam)
|
||||
{
|
||||
//1.验证参数,检查销售订单号是否重复
|
||||
//(同批次物料库存(排除锁定禁用)+同批次erp任务(状态为未完结且一天内的)关联的agv任务总数+同批次撤单总数)-erp出库命令总数<=本次出库数
|
||||
//3.生成erp任务
|
||||
|
||||
//1.查询物料信息是否存在 (批次、数量)
|
||||
//2.退库区库位是否有货(若全部有货,则全部更新成无货)
|
||||
//3.生成ErpTask
|
||||
string errMsg = string.Empty;
|
||||
|
||||
errMsg = await ValidateStockOutParam(stockOutParam);
|
||||
if(!string.IsNullOrEmpty(errMsg))
|
||||
{
|
||||
LogHelper.Warn(errMsg);
|
||||
return errMsg;
|
||||
}
|
||||
|
||||
errMsg = await CalStockQty(stockOutParam);
|
||||
if (!string.IsNullOrEmpty(errMsg))
|
||||
{
|
||||
LogHelper.Warn($"出库业务:批次号为{stockOutParam.materiaBatch}的库存不足,出库失败!");
|
||||
return errMsg;
|
||||
}
|
||||
|
||||
//生成ErpTask
|
||||
bool res = await CreateStockOutErpTask(stockOutParam);
|
||||
if (!res)
|
||||
{
|
||||
errMsg = "出库业务,生成Erp任务,数据库操作失败!";
|
||||
LogHelper.Warn(errMsg);
|
||||
return errMsg;
|
||||
}
|
||||
return errMsg;
|
||||
}
|
||||
|
||||
private TaskTypeEnum CalStockOutTaskType(StockOutParam stockOutParam)
|
||||
{
|
||||
TaskTypeEnum taskType = 0;
|
||||
|
||||
if (stockOutParam.transport == (int)TransportEnum.SouthArea ||
|
||||
stockOutParam.transport == (int)TransportEnum.NorthArea ||
|
||||
stockOutParam.transport == (int)TransportEnum.MiddleOutArea)
|
||||
{
|
||||
taskType = TaskTypeEnum.SaleOut;
|
||||
}
|
||||
|
||||
if (stockOutParam.transport == (int)TransportEnum.ReturnStockArea)
|
||||
{
|
||||
if(string.IsNullOrEmpty(stockOutParam.produceReturnCode))
|
||||
stockOutParam.produceReturnCode = stockOutParam.saleNo;
|
||||
|
||||
stockOutParam.saleNo = null;
|
||||
taskType = TaskTypeEnum.ProductionOut;
|
||||
}
|
||||
|
||||
return taskType;
|
||||
}
|
||||
|
||||
private async Task<string> ValidateStockOutParam(StockOutParam stockOutParam)
|
||||
{
|
||||
TaskTypeEnum taskType = CalStockOutTaskType(stockOutParam);
|
||||
|
||||
if (taskType == 0)
|
||||
return "出库码头值错误";
|
||||
|
||||
string errMsg = string.Empty;
|
||||
if (stockOutParam.num <= 0)
|
||||
{
|
||||
errMsg = "出库数要大于0!";
|
||||
LogHelper.Warn(errMsg);
|
||||
}
|
||||
if (string.IsNullOrEmpty(stockOutParam.materiaBatch))
|
||||
{
|
||||
errMsg = "批次号不能为空!";
|
||||
LogHelper.Warn(errMsg);
|
||||
}
|
||||
|
||||
if (taskType == TaskTypeEnum.SaleOut && string.IsNullOrEmpty(stockOutParam.saleNo))
|
||||
{
|
||||
errMsg = "销售单号不能为空!";
|
||||
LogHelper.Warn(errMsg);
|
||||
}
|
||||
|
||||
if (taskType == TaskTypeEnum.ProductionOut && string.IsNullOrEmpty(stockOutParam.produceReturnCode))
|
||||
{
|
||||
errMsg = "生产退库单号不能为空!";
|
||||
LogHelper.Warn(errMsg);
|
||||
}
|
||||
|
||||
if (taskType == TaskTypeEnum.SaleOut)
|
||||
{
|
||||
bool exist = await _erpTaskService.Exist(p => p.SaleNo == stockOutParam.saleNo);
|
||||
if (exist)
|
||||
{
|
||||
errMsg = $"销售单号{stockOutParam.saleNo}重复!";
|
||||
LogHelper.Warn(errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
if (taskType == TaskTypeEnum.ProductionOut)
|
||||
{
|
||||
bool exist = await _erpTaskService.Exist(p => p.ProduceReturnCode == stockOutParam.produceReturnCode);
|
||||
if (exist)
|
||||
{
|
||||
errMsg = $"生产退库单号{stockOutParam.produceReturnCode}重复!";
|
||||
LogHelper.Warn(errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
return errMsg;
|
||||
}
|
||||
private async Task<string> CalStockQty(StockOutParam stockOutParam)
|
||||
{
|
||||
List<StorageRackDto> storageRacks = await _storageRackService.GetListByExpression(p =>
|
||||
p.MateriaBatch == stockOutParam.materiaBatch &&
|
||||
!p.IsLock &&
|
||||
!p.IsDisabled &&
|
||||
p.Transport == TransportEnum.NormalArea);
|
||||
|
||||
if(storageRacks.IsNullOrEmpty())
|
||||
{
|
||||
return "无可用库存";
|
||||
}
|
||||
|
||||
List<ERPTaskDto> erpTaks = await _erpTaskService.GetListByExpression(p =>
|
||||
p.CreationTime >= DateTime.Now.AddDays(-1) &&
|
||||
(p.TaskType == TaskTypeEnum.SaleOut || p.TaskType == TaskTypeEnum.ProductionOut) &&
|
||||
p.TaskStatus == TaskStatusEnum.InExecution &&
|
||||
p.MateriaBatch == stockOutParam.materiaBatch);
|
||||
|
||||
int stockQty= storageRacks.Sum(p=>p.MateriaNum);
|
||||
int taskTotalNum = 0;
|
||||
int cancelTotalNum = 0;
|
||||
|
||||
if (!erpTaks.IsNullOrEmpty())
|
||||
{
|
||||
cancelTotalNum = erpTaks.Sum(p=>p.ErpCancelNum);
|
||||
|
||||
List<AGVTaskDto> agvtTasks = await _agvTaskService
|
||||
.GetListByExpression(p => erpTaks.Select(x => x.Id).Contains(p.ErpTaskId));
|
||||
|
||||
taskTotalNum = agvtTasks.GroupBy(p=>p.BarCode).Select(p=>p.First()).Sum(p=>p.MateriaNum);
|
||||
}
|
||||
|
||||
bool enough = stockQty + taskTotalNum + cancelTotalNum - erpTaks.Sum(p => p.ErpOrderNum) >= stockOutParam.num;
|
||||
return enough ? "" : "库存不足";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据销售出库单,扣减库存
|
||||
/// </summary>
|
||||
/// <param name="saleNo"></param>
|
||||
/// <param name="taskType"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<SaleOutStockDto> GetSumStockNum(string saleNo, TaskTypeEnum taskType)
|
||||
{
|
||||
var stockResult = new SaleOutStockDto();
|
||||
var erpTask = new ERPTaskDto();
|
||||
|
||||
//销售出库
|
||||
if (taskType == TaskTypeEnum.SaleOut)
|
||||
{
|
||||
erpTask = await _erpTaskService.GetSingalByExpression(p => p.SaleNo == saleNo);
|
||||
}
|
||||
|
||||
//生产退库
|
||||
else if (taskType == TaskTypeEnum.ProductionOut)
|
||||
{
|
||||
erpTask = await _erpTaskService.GetSingalByExpression(p => p.ProduceReturnCode == saleNo);
|
||||
}
|
||||
|
||||
if (erpTask != null)
|
||||
{
|
||||
var erpOrderNum = erpTask.ErpOrderNum;
|
||||
stockResult.SumCancelNum = erpTask.ErpCancelNum;
|
||||
|
||||
stockResult.SumReulstNum = erpOrderNum - erpTask.ErpCancelNum;
|
||||
}
|
||||
return stockResult;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 销售出库,计算目标库位
|
||||
/// </summary>
|
||||
/// <param name="transport"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<StorageRackDto> CalculateDestWarehouse_SaleOut(TransportEnum transport)
|
||||
{
|
||||
//1 出库区是否有同批次货物,有则查看其列上是否有空余库位
|
||||
//2 锁定目标库位
|
||||
StorageRackDto destStorageRack = null;
|
||||
if (transport==TransportEnum.MiddleOutArea)
|
||||
{
|
||||
destStorageRack = await CalculateDestWarehouse_SaleOutMiddle();
|
||||
return destStorageRack;
|
||||
}
|
||||
|
||||
//所有出库区库位
|
||||
List<StorageRackDto> wareHouses = await _storageRackService.GetListByExpression(p =>
|
||||
p.Floor == FloorEnum.FirstFloor &&
|
||||
p.Transport == transport);
|
||||
|
||||
//没有空库位
|
||||
if (wareHouses.All(p => p.IsLock || p.IsDisabled))
|
||||
{
|
||||
|
||||
var clearWardHoses = wareHouses.OrderBy(p => p.AreaNumSort).ToList();
|
||||
foreach (var item in clearWardHoses)
|
||||
{
|
||||
item.MateriaBatch = "";
|
||||
item.MateriaNum = 0;
|
||||
|
||||
item.OrderNo = "";
|
||||
item.MateriaName = "";
|
||||
item.MateriaType = "";
|
||||
item.MateriaCode = "";
|
||||
item.BarCode = "";
|
||||
item.IsLock = false;
|
||||
}
|
||||
await _storageRackService.EditBatch(clearWardHoses);
|
||||
|
||||
//锁定库位
|
||||
var tempStorageRack = clearWardHoses.OrderBy(p => p.AreaNumSort).ThenByDescending(p => p.NumSort).FirstOrDefault();
|
||||
|
||||
destStorageRack = tempStorageRack;
|
||||
destStorageRack.IsLock = true;
|
||||
await _storageRackService.EditSingal(destStorageRack);
|
||||
}
|
||||
else
|
||||
{
|
||||
//有空库位,按照AreaNumSort、NumSort排序,依次放
|
||||
var tempWareHouses = wareHouses.Where(p => !p.IsLock && !p.IsDisabled).ToList();
|
||||
var sortWareHoses = tempWareHouses.OrderBy(p => p.AreaNumSort).ThenByDescending(p => p.NumSort).ToList();
|
||||
foreach (var item in sortWareHoses)
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.MateriaBatch))
|
||||
{
|
||||
//锁定库位
|
||||
item.IsLock = true;
|
||||
destStorageRack = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//锁定库位
|
||||
await _storageRackService.EditSingal(destStorageRack);
|
||||
}
|
||||
return destStorageRack;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算中间出库区的出库目标库位
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<StorageRackDto> CalculateDestWarehouse_SaleOutMiddle()
|
||||
{
|
||||
//获取楼层的所有库位
|
||||
List<StorageRackDto> wareHouses = await _storageRackService.GetListByExpression(p =>
|
||||
p.Floor == FloorEnum.FirstFloor && p.Transport == TransportEnum.MiddleOutArea);
|
||||
|
||||
if (wareHouses.IsNullOrEmpty())
|
||||
{
|
||||
LogHelper.Warn($"中间出库区没有空余库位");
|
||||
return null;
|
||||
}
|
||||
|
||||
wareHouses = wareHouses.OrderBy(p => p.AreaNumSort).OrderBy(p => p.ReservoirAreaColumnId).ToList();
|
||||
|
||||
if (wareHouses.IsNullOrEmpty())
|
||||
return null;
|
||||
|
||||
var groupByColumn = wareHouses.GroupBy(p => p.ReservoirAreaColumnId);
|
||||
StorageRackDto destStorageRack = null;
|
||||
foreach (var columnGroup in groupByColumn)
|
||||
{
|
||||
bool unlokedColumn = columnGroup.All(p => !p.IsLock && !p.IsDisabled);
|
||||
if (unlokedColumn)
|
||||
{
|
||||
destStorageRack = columnGroup.OrderByDescending(p => p.NumSort).FirstOrDefault(p => string.IsNullOrEmpty(p.BarCode));
|
||||
if (destStorageRack != null)
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return destStorageRack;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///出库,生成ErpTask
|
||||
/// </summary>
|
||||
/// <param name="erpSaleOutParam"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> CreateStockOutErpTask(StockOutParam stockOutParam)
|
||||
{
|
||||
TaskTypeEnum taskType = CalStockOutTaskType(stockOutParam);
|
||||
|
||||
var erpTask = new ERPTaskDto
|
||||
{
|
||||
BarCode = stockOutParam.barCode,
|
||||
SaleNo = stockOutParam.saleNo,
|
||||
ProduceReturnCode = stockOutParam.produceReturnCode,
|
||||
CustomName = stockOutParam.customName,
|
||||
MateriaName = stockOutParam.materiaName,
|
||||
MateriaType = stockOutParam.materiaType,
|
||||
MateriaBatch = stockOutParam.materiaBatch,
|
||||
MateriaCode = stockOutParam.materiaCode,
|
||||
Transport = (TransportEnum)stockOutParam.transport,
|
||||
ErpOrderNum = stockOutParam.num,
|
||||
|
||||
Priority = PriorityEnum.High,
|
||||
TaskStatus = TaskStatusEnum.Initial,
|
||||
TaskType = taskType,
|
||||
};
|
||||
|
||||
bool res = await _erpTaskService.Add(erpTask);
|
||||
return res;
|
||||
}
|
||||
|
||||
private async Task<StorageRackDto> CalculateOutMaterial(ERPTaskDto erpTask)
|
||||
{
|
||||
//1. 筛选出该批次的所有物料库位
|
||||
//2. 按照列号分组,并按照同一列的拖数从小到大排序(优先出拖数少的列)
|
||||
//3. 存在锁定列(同批次物料),先判断锁定列是否足够
|
||||
//3.1 如果锁定列足够,则返回空,等待解锁
|
||||
//3.2 如果锁定列不够,取第一个未锁定列
|
||||
//3.3 未锁定列前面存在不同批次物料,则生成中转掏货任务
|
||||
//3.4 未锁定列前面不存在不同批次物料,则直接出库
|
||||
List<StorageRackDto> storageRacksAll = await _storageRackService
|
||||
.GetListByExpression(p => p.Transport == TransportEnum.NormalArea);
|
||||
|
||||
if (storageRacksAll.IsNullOrEmpty())
|
||||
return null;
|
||||
|
||||
List<StorageRackDto> storageRacksSameBatch = storageRacksAll
|
||||
.Where(p => p.MateriaBatch == erpTask.MateriaBatch && !p.IsDisabled)
|
||||
.ToList();
|
||||
|
||||
if (storageRacksSameBatch.IsNullOrEmpty())
|
||||
return null;
|
||||
|
||||
var groupByCol = storageRacksSameBatch
|
||||
.GroupBy(p => p.ReservoirAreaColumnId)
|
||||
.OrderBy(p => p.Count());
|
||||
|
||||
var groupByColInclude = storageRacksAll
|
||||
.Where(p => storageRacksSameBatch.Select(x => x.ReservoirAreaColumnId).Distinct().Contains(p.ReservoirAreaColumnId))
|
||||
.GroupBy(p => p.ReservoirAreaColumnId)
|
||||
.OrderBy(p => p.Count());
|
||||
|
||||
List<AGVTaskDto> agvTasks = await _commonService.GetCreatedAgvTasksFromErpTask(erpTask, erpTask.TaskType);
|
||||
//已生成任务的数量
|
||||
int createdNum = agvTasks.Sum(p => p.MateriaNum);
|
||||
//还需要的数量
|
||||
int stillNeedNum = erpTask.ErpOrderNum - erpTask.ErpCancelNum - createdNum;
|
||||
|
||||
var lockedCols = groupByColInclude.Where(p => p.Any(x => x.IsLock));
|
||||
int unlockedInLockedCol = lockedCols.SelectMany(p => p).Where(p => !p.IsLock && !p.IsDisabled).Sum(p => p.MateriaNum);
|
||||
if (unlockedInLockedCol >= stillNeedNum)
|
||||
{
|
||||
LogHelper.Info($"出库业务,已锁定的列上物料足够,等待列解锁后继续出库");
|
||||
return null;
|
||||
}
|
||||
|
||||
var unlockedCol = groupByColInclude.Where(p => p.All(x => !x.IsLock)).OrderBy(p => p.Count()).FirstOrDefault();
|
||||
|
||||
if (unlockedCol == null)
|
||||
{
|
||||
LogHelper.Info($"出库业务,已锁定列上物料不够,但不存在未锁定列物料,确认出库命令是否有误");
|
||||
return null;
|
||||
}
|
||||
|
||||
//判断该列上是否存在不同批次未锁定的物料,有则掏货
|
||||
//判断该列上是否存在不同批次被锁定的物料,有则等待解锁
|
||||
|
||||
StorageRackDto destMaterial = unlockedCol.OrderBy(p => p.NumSort).First(p=>!string.IsNullOrEmpty(p.BarCode));
|
||||
List<StorageRackDto> allInThisCol = unlockedCol.ToList();
|
||||
|
||||
if (allInThisCol.Any(p => p.IsLock))
|
||||
{
|
||||
LogHelper.Info($"出库业务 列 {destMaterial.ReservoirAreaColumnId} 上存在被锁定的物料,将等待解锁后继续取后续的物料");
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StorageRackDto> diffrentBatchInThisCol = allInThisCol
|
||||
.Where(p => !string.IsNullOrEmpty(p.MateriaBatch) && p.MateriaBatch != destMaterial.MateriaBatch)
|
||||
.ToList();
|
||||
|
||||
if (diffrentBatchInThisCol.IsNullOrEmpty())
|
||||
return destMaterial;
|
||||
|
||||
List<StorageRackDto> diffrentBatchInThisColFront = diffrentBatchInThisCol
|
||||
.Where(p => p.NumSort < destMaterial.NumSort)
|
||||
.ToList();
|
||||
|
||||
if (diffrentBatchInThisColFront.IsNullOrEmpty())
|
||||
return destMaterial;
|
||||
|
||||
StorageRackDto diffrentBatchInThisColFirst = diffrentBatchInThisColFront.OrderBy(p => p.NumSort).First();
|
||||
if (diffrentBatchInThisColFirst.IsLock)
|
||||
{
|
||||
LogHelper.Info($"出库业务 列 {diffrentBatchInThisColFirst.ReservoirAreaColumnId} 上存在不同批次的前序物料,且该前序物料被锁定," +
|
||||
$"将等待物料解锁后继续取后续的物料");
|
||||
return null;
|
||||
}
|
||||
|
||||
//掏货
|
||||
return diffrentBatchInThisColFirst;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 销售出库,根据ERPTask生成二楼/一楼 AgvTask
|
||||
/// </summary>
|
||||
/// <param name="erpTask">erp任务</param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> CreateAgvTaskSecondFloor_SaleOut(ERPTaskDto erpTask)
|
||||
{
|
||||
//1.计算要出库的物料
|
||||
//2.如果物料在一楼,直接生成到出库码头的任务
|
||||
//3.如果物料在二楼
|
||||
//3.1 有可用提升机库位,生成到提升机的任务
|
||||
//3.2 没有可用提升机库位,生成中转任务
|
||||
|
||||
StorageRackDto stockOutMaterial = await CalculateOutMaterial(erpTask);
|
||||
if (stockOutMaterial == null)
|
||||
{
|
||||
LogHelper.Warn($"出库业务,批次号 {erpTask.MateriaBatch} 暂无可直接出库的物料");
|
||||
_ = _erpTaskService.UpdateTaskMsg(erpTask.Id, $"暂无可直接出库的物料");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stockOutMaterial.MateriaBatch == erpTask.MateriaBatch)
|
||||
{
|
||||
if (stockOutMaterial.Floor == FloorEnum.FirstFloor)
|
||||
return await CreateTaskToWharf(stockOutMaterial, erpTask);
|
||||
else
|
||||
return await CreateTaskToElevator(stockOutMaterial, erpTask);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return await CreateTaskToTransfer(stockOutMaterial, TaskTypeEnum.RemoveStockOutObstruction, erpTask);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成直接去出库码头的任务
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> CreateTaskToWharf(StorageRackDto stockOutWarehouse, ERPTaskDto erpTask)
|
||||
{
|
||||
if(stockOutWarehouse.Floor!=FloorEnum.FirstFloor)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//2.计算目标库位
|
||||
StorageRackDto wareHouse = await CalculateDestWarehouse_SaleOut(erpTask.Transport);
|
||||
if (wareHouse == null)
|
||||
{
|
||||
LogHelper.Warn($"批次号 {erpTask.MateriaBatch} 暂无合适的码头库位");
|
||||
_ = _erpTaskService.UpdateTaskMsg(erpTask.Id, "暂无合适的码头库位");
|
||||
return false;
|
||||
}
|
||||
|
||||
AGVTaskDto agvTask = new()
|
||||
{
|
||||
ErpTaskId = erpTask.Id,
|
||||
BarCode = stockOutWarehouse.BarCode,
|
||||
OrderNo = stockOutWarehouse.OrderNo,
|
||||
CustomName = stockOutWarehouse.CustomName,
|
||||
MateriaName = stockOutWarehouse.MateriaName,
|
||||
MateriaType = stockOutWarehouse.MateriaType,
|
||||
MateriaNum = stockOutWarehouse.MateriaNum,
|
||||
MateriaBatch = stockOutWarehouse.MateriaBatch,
|
||||
MateriaCode = stockOutWarehouse.MateriaCode,
|
||||
TaskType = erpTask.TaskType,
|
||||
TaskStatus = TaskStatusEnum.Initial,
|
||||
Priority = PriorityEnum.High,
|
||||
Floor = FloorEnum.FirstFloor,
|
||||
|
||||
StartPositionCode = stockOutWarehouse.StorageRackNo,
|
||||
EndPositionCode = wareHouse.StorageRackNo,
|
||||
|
||||
Transport = erpTask.Transport
|
||||
};
|
||||
|
||||
//锁定终点库位
|
||||
await _storageRackService.Lock(wareHouse);
|
||||
|
||||
//锁定起点库位
|
||||
await _storageRackService.Lock(stockOutWarehouse);
|
||||
|
||||
_ = _erpTaskService.UpdateTaskMsg(erpTask.Id, "");
|
||||
|
||||
//生成agvTask
|
||||
return await _agvTaskService.Add(agvTask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成去提升机的任务
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> CreateTaskToElevator(StorageRackDto stockOutWarehouse, ERPTaskDto erpTask)
|
||||
{
|
||||
if (stockOutWarehouse.Floor != FloorEnum.SecondFloor)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StorageRackDto elevator = await _commonService.GetUsableElevator(FloorEnum.SecondFloor,2);
|
||||
if (elevator == null)
|
||||
{
|
||||
LogHelper.Warn("无可用提升机库位");
|
||||
return false;
|
||||
//return await CreateTaskToTransfer(stockOutWarehouse, TaskTypeEnum.TransferStockOut, erpTask);
|
||||
}
|
||||
|
||||
await _commonService.CreateAndInsertAgvTask(new Common.Models.AgvTaskCreationParam
|
||||
{
|
||||
StartPosition = stockOutWarehouse,
|
||||
EndPosition = elevator,
|
||||
TaskType = erpTask.TaskType,
|
||||
ErpTaskId = erpTask.Id,
|
||||
StockOutWharf= erpTask.Transport,
|
||||
|
||||
ErpUploadStatus=ErpUploadStatusEnum.Unnecessary
|
||||
});
|
||||
_ = _erpTaskService.UpdateTaskMsg(erpTask.Id, "");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成去中转区的任务
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> CreateTaskToTransfer(StorageRackDto stockOutWarehouse, TaskTypeEnum taskType, ERPTaskDto erpTask)
|
||||
{
|
||||
bool bRet = await _commonService.CreateAgvTaskToTransferArea(stockOutWarehouse, taskType, erpTask);
|
||||
if (!bRet)
|
||||
{
|
||||
LogHelper.Error($"出库业务 erpTaskId {erpTask.Id} 生成中转任务失败," +
|
||||
$"批次 {stockOutWarehouse.MateriaBatch}" +
|
||||
$"合格证号 {stockOutWarehouse.BarCode}");
|
||||
|
||||
_ = _erpTaskService.UpdateTaskMsg(erpTask.Id, $"{taskType.GetDescription()}任务生成失败");
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = _erpTaskService.UpdateTaskMsg(erpTask.Id, "中转进行中");
|
||||
|
||||
await _storageRackService.Lock(stockOutWarehouse);
|
||||
}
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region 搬运物料
|
||||
|
||||
/// <summary>
|
||||
/// 物料搬运,起点库位验证
|
||||
/// </summary>
|
||||
/// <param name="startRankNo"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<ApiResult<string>> CheckCarryStartRankNo(string startRankNo)
|
||||
{
|
||||
var result = new ApiResult<string>() { ResultCode = "500" };
|
||||
if (string.IsNullOrEmpty(startRankNo))
|
||||
{
|
||||
result.ErrorMsg = $"请扫描起点库位码!";
|
||||
return result;
|
||||
}
|
||||
//起点物料信息
|
||||
StorageRackDto startMaterial = await _storageRackService.GetSingalByExpression(p => p.StorageRackNo == startRankNo);
|
||||
if (startMaterial == null)
|
||||
{
|
||||
result.ErrorMsg = $"起点库位 {startRankNo}不存在,无法搬运!";
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (startMaterial.IsLock)
|
||||
{
|
||||
result.ErrorMsg = $"起点库位 {startRankNo}被锁定,无法搬运!";
|
||||
return result;
|
||||
}
|
||||
if (startMaterial.IsDisabled)
|
||||
{
|
||||
result.ErrorMsg = $"起点库位 {startRankNo}被禁用,无法搬运!";
|
||||
return result;
|
||||
}
|
||||
if (startMaterial.Transport != TransportEnum.NormalArea)
|
||||
{
|
||||
result.ErrorMsg = $"起点库位 {startRankNo}不在存储区,无法搬运!";
|
||||
return result;
|
||||
}
|
||||
if (startMaterial.MateriaNum <= 0)
|
||||
{
|
||||
result.ErrorMsg = $"起点库位 {startRankNo}没有物料信息,无法搬运!";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result.ResultCode = "200";
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 物料搬运,终点库位验证
|
||||
/// </summary>
|
||||
/// <param name="endRankNo"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<ApiResult<string>> CheckCarryEndRankNo(string endRankNo)
|
||||
{
|
||||
var result = new ApiResult<string>() { ResultCode = "500" };
|
||||
if (string.IsNullOrEmpty(endRankNo))
|
||||
{
|
||||
result.ErrorMsg = $"请扫描终点库位码!";
|
||||
return result;
|
||||
}
|
||||
//终点物料信息
|
||||
StorageRackDto endMaterial = await _storageRackService.GetSingalByExpression(p => p.StorageRackNo == endRankNo);
|
||||
if (endMaterial == null)
|
||||
{
|
||||
result.ErrorMsg = $"终点库位 {endRankNo}不存在,无法搬运!";
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (endMaterial.IsLock)
|
||||
{
|
||||
result.ErrorMsg = $"终点库位 {endMaterial}被锁定,无法搬运!";
|
||||
return result;
|
||||
}
|
||||
if (endMaterial.IsDisabled)
|
||||
{
|
||||
result.ErrorMsg = $"终点库位 {endMaterial}被禁用,无法搬运!";
|
||||
return result;
|
||||
}
|
||||
//if (endMaterial.Transport != TransportEnum.NormalArea)
|
||||
//{
|
||||
// result.ErrorMsg = $"终点库位 {endRankNo}不在存储区,无法搬运!";
|
||||
// return result;
|
||||
//}
|
||||
//if (endMaterial.MateriaNum > 0)
|
||||
//{
|
||||
// result.ErrorMsg = $"终点库位 {endRankNo}存在物料信息,无法搬运!";
|
||||
// return result;
|
||||
//}
|
||||
}
|
||||
|
||||
result.ResultCode = "200";
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搬运物料
|
||||
/// 同楼层的存储区,才能搬运
|
||||
/// </summary>
|
||||
/// <param name="carryInParam"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<ApiResult<string>> CarryMaterialInfo(CarryInParam carryInParam)
|
||||
{
|
||||
//1.查询起点物料信息是否存在
|
||||
//2.查询终点物料信息是否存在
|
||||
//3.直接生成AgvTask
|
||||
|
||||
|
||||
var result = new ApiResult<string>() { ResultCode = "500" };
|
||||
|
||||
//起点库位信息
|
||||
StorageRackDto start = await _storageRackService.GetSingalByExpression(p => !p.IsLock && !p.IsDisabled && p.StorageRackNo == carryInParam.StartStorageRackNo);
|
||||
|
||||
//终点库位信息
|
||||
StorageRackDto end = await _storageRackService.GetSingalByExpression(p => !p.IsLock && !p.IsDisabled && p.StorageRackNo == carryInParam.EndStorageRackNo);
|
||||
|
||||
//是否存在,起点、终点正在执行的AgvTask
|
||||
var isExsit = await _agvTaskService.Exist(p => p.StartPositionCode == start.StorageRackNo &&
|
||||
p.EndPositionCode == end.StorageRackNo &&
|
||||
p.TaskType == TaskTypeEnum.Carry &&
|
||||
(p.TaskStatus == TaskStatusEnum.Initial || p.TaskStatus == TaskStatusEnum.InExecution));
|
||||
if (isExsit)
|
||||
{
|
||||
result.ErrorMsg = $"已经存在起点{start.StorageRackNo} 到 终点{end.StorageRackNo}的搬运任务";
|
||||
return result;
|
||||
}
|
||||
|
||||
//直接生成AgvTask
|
||||
bool res = await CreateAgvTask_Carry(start, end);
|
||||
if (!res)
|
||||
{
|
||||
result.ErrorMsg = $"搬运物料,生成Agv任务,数据库操作失败!";
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.ResultCode = "200";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搬运物料,生成AgvTask
|
||||
/// </summary>
|
||||
/// <param name="start"></param>
|
||||
/// <param name="end"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> CreateAgvTask_Carry(StorageRackDto start, StorageRackDto end)
|
||||
{
|
||||
var excuteResult = false;
|
||||
AGVTaskDto agvTask = new()
|
||||
{
|
||||
ErpTaskId = Guid.Empty,
|
||||
BarCode = start.BarCode,
|
||||
OrderNo = start.OrderNo,
|
||||
CustomName = start.CustomName,
|
||||
MateriaName = start.MateriaName,
|
||||
MateriaType = start.MateriaType,
|
||||
MateriaNum = start.MateriaNum,
|
||||
MateriaBatch = start.MateriaBatch,
|
||||
MateriaCode = start.MateriaCode,
|
||||
TaskType = TaskTypeEnum.Carry,
|
||||
TaskStatus = TaskStatusEnum.Initial,
|
||||
Priority = PriorityEnum.Middle,
|
||||
Floor = start.Floor,
|
||||
|
||||
StartPositionCode = start.StorageRackNo,
|
||||
EndPositionCode = end.StorageRackNo,
|
||||
Transport = TransportEnum.NormalArea
|
||||
};
|
||||
|
||||
//锁定起点库位
|
||||
start.IsLock = true;
|
||||
await _storageRackService.EditSingal(start);
|
||||
|
||||
//锁定终点库位
|
||||
end.IsLock = true;
|
||||
await _storageRackService.EditSingal(end);
|
||||
|
||||
//生成agvTask
|
||||
excuteResult = await _agvTaskService.Add(agvTask);
|
||||
|
||||
return excuteResult;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 出库撤单
|
||||
/// <summary>
|
||||
/// 出库撤单
|
||||
/// 只有销售出库,才能撤单
|
||||
/// </summary>
|
||||
/// <param name="cancelOrderParam"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<XRNewResultDto<int>> ErpCancelOrder(ErpCancelOrderParam cancelOrderParam)
|
||||
{
|
||||
var result = new XRNewResultDto<int>() { success = false, status = 500, data = 0 };
|
||||
if (string.IsNullOrEmpty(cancelOrderParam.saleNo))
|
||||
{
|
||||
result.errors = "销售出库单号不能为空!";
|
||||
LogHelper.Warn(result.errors);
|
||||
|
||||
return result;
|
||||
}
|
||||
if (string.IsNullOrEmpty(cancelOrderParam.materiaBatch))
|
||||
{
|
||||
result.errors = "批次号不能为空!";
|
||||
LogHelper.Warn(result.errors);
|
||||
|
||||
return result;
|
||||
}
|
||||
if (string.IsNullOrEmpty(cancelOrderParam.materiaType))
|
||||
{
|
||||
result.errors = "物料类型不能为空!";
|
||||
LogHelper.Warn(result.errors);
|
||||
|
||||
return result;
|
||||
}
|
||||
if (string.IsNullOrEmpty(cancelOrderParam.customName))
|
||||
{
|
||||
result.errors = "客户名称不能为空!";
|
||||
LogHelper.Warn(result.errors);
|
||||
|
||||
return result;
|
||||
}
|
||||
if (string.IsNullOrEmpty(cancelOrderParam.materiaName))
|
||||
{
|
||||
result.errors = "物料名称不能为空!";
|
||||
LogHelper.Warn(result.errors);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
var erpTaskList = await _erpTaskService.GetListByExpression(p => p.SaleNo == cancelOrderParam.saleNo);
|
||||
if (erpTaskList.IsNullOrEmpty())
|
||||
{
|
||||
result.errors = $"销售出库单号{cancelOrderParam.saleNo},不存在任务!";
|
||||
LogHelper.Warn(result.errors);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
var sumCancelNum = erpTaskList == null ? 0 : erpTaskList.Sum(p => p.ErpCancelNum);
|
||||
if (sumCancelNum > 0)
|
||||
{
|
||||
result.errors = $"销售出库单号{cancelOrderParam.saleNo},不能重复撤单!";
|
||||
LogHelper.Warn(result.errors);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
var erpTask = erpTaskList?.FirstOrDefault();
|
||||
|
||||
if (erpTask.TaskType != TaskTypeEnum.SaleOut)
|
||||
{
|
||||
result.errors = $"单号{cancelOrderParam.saleNo},不是销售单号,无法撤单";
|
||||
LogHelper.Warn(result.errors);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (erpTask.TaskStatus == TaskStatusEnum.Completed)
|
||||
{
|
||||
result.errors = $"批次号为{cancelOrderParam.materiaBatch} 销售单号为{cancelOrderParam.saleNo},任务已经完成,无法撤单";
|
||||
LogHelper.Warn(result.errors);
|
||||
|
||||
return result;
|
||||
}
|
||||
var erpCancelNum = await GetErpCancelNum(cancelOrderParam);
|
||||
if (erpCancelNum <= 0)
|
||||
{
|
||||
result.errors = $"批次号为{cancelOrderParam.materiaBatch} 销售单号为{cancelOrderParam.saleNo},没有可以撤回的数量";
|
||||
LogHelper.Warn(result.errors);
|
||||
|
||||
return result;
|
||||
}
|
||||
//更新Erp出库撤单数量
|
||||
if (erpTask != null)
|
||||
{
|
||||
erpTask.ErpCancelNum = erpCancelNum;
|
||||
bool res = await _erpTaskService.EditSingal(erpTask);
|
||||
if (!res)
|
||||
{
|
||||
result.errors = "更新Erp出库撤单数量,数据库操作失败!";
|
||||
LogHelper.Warn(result.errors);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
result.success = true;
|
||||
result.status = 200;
|
||||
result.data = erpTask.ErpCancelNum;
|
||||
result.message = $"撤单成功:批次号为{cancelOrderParam.materiaBatch} 销售单号为{cancelOrderParam.saleNo},撤单数量为{erpTask.ErpCancelNum}";
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据销售出库单,计算可以撤单的数量
|
||||
/// </summary>
|
||||
/// <param name="cancelOrderParam"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<int> GetErpCancelNum(ErpCancelOrderParam cancelOrderParam)
|
||||
{
|
||||
var result = 0;
|
||||
|
||||
var erpTask = await _erpTaskService.GetSingalByExpression(p => p.SaleNo == cancelOrderParam.saleNo);
|
||||
|
||||
if (erpTask != null)
|
||||
{
|
||||
var agvTaskList = await _agvTaskService.GetListByExpression(p => p.ErpTaskId == erpTask.Id &&
|
||||
(p.TaskStatus == TaskStatusEnum.Initial || p.TaskStatus == TaskStatusEnum.InExecution || p.TaskStatus == TaskStatusEnum.Completed) &&
|
||||
(p.TaskType == TaskTypeEnum.SaleOut));
|
||||
|
||||
var sumMateriaNum = 0;
|
||||
if (agvTaskList.Count > 0)
|
||||
{
|
||||
var groupAgvTaskList = agvTaskList.GroupBy(p => p.BarCode);
|
||||
foreach (var groupAgvTask in groupAgvTaskList)
|
||||
{
|
||||
sumMateriaNum += groupAgvTask.FirstOrDefault().MateriaNum;
|
||||
}
|
||||
}
|
||||
|
||||
if (erpTask.ErpOrderNum - sumMateriaNum > 0)
|
||||
{
|
||||
result = erpTask.ErpOrderNum - sumMateriaNum;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user