first commit
This commit is contained in:
@@ -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