Files
kunshan-bzfm-mes-backend/DOAN.Service/MES/Material/MmRecordOutboundService.cs
git_rabbit cc1fe5f967 feat(工单管理): 新增工单物料查询及生产进度功能
新增工单物料服务接口及实现,包括领料清单、成品入库清单和出货清单查询
添加工单生产进度查询功能及相关DTO定义
完善工单修改日志记录功能
规范工单号字段使用,区分原材料工单号和当前工单号
2026-01-30 18:28:21 +08:00

488 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using DOAN.Model.BZFM;
using DOAN.Model.BZFM.Dto;
using DOAN.Repository;
using DOAN.Service.BZFM.IBZFMService;
using Infrastructure.Attribute;
using Infrastructure.Converter;
using Infrastructure.Extensions;
using Microsoft.AspNetCore.Http;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
namespace DOAN.Service.BZFM
{
/// <summary>
/// 出库记录表Service业务层处理
/// </summary>
[AppService(
ServiceType = typeof(IMmRecordOutboundService),
ServiceLifetime = LifeTime.Transient
)]
public class MmRecordOutboundService : BaseService<MmRecordOutbound>, IMmRecordOutboundService
{
/// <summary>
/// 查询出库记录表列表
/// </summary>
/// <param name="parm"></param>
/// <returns></returns>
public PagedInfo<MmRecordOutboundDto> GetList(MmRecordOutboundQueryDto parm)
{
var predicate = QueryExp(parm);
var response = Queryable()
.Where(predicate.ToExpression())
.OrderByDescending(it => it.CreatedTime)
.ToPage<MmRecordOutbound, MmRecordOutboundDto>(parm);
return response;
}
/// <summary>
/// 获取详情
/// </summary>
/// <param name="Id"></param>
/// <returns></returns>
public MmRecordOutbound GetInfo(int Id)
{
var response = Queryable().Where(x => x.Id == Id).First();
return response;
}
/// <summary>
/// 添加出库记录表
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public MmRecordOutbound AddMmRecordOutbound(MmRecordOutbound model)
{
return Insertable(model).ExecuteReturnEntity();
}
/// <summary>
/// 修改出库记录表
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public int UpdateMmRecordOutbound(MmRecordOutbound model)
{
return Update(model, true);
}
/// <summary>
/// 查询导出表达式
/// </summary>
/// <param name="parm"></param>
/// <returns></returns>
private static Expressionable<MmRecordOutbound> QueryExp(MmRecordOutboundQueryDto parm)
{
if (parm.CreatedTime != null && parm.CreatedTime.Length > 0)
{
parm.CreatedTime[0] = DOANConvertDate.ConvertLocalDate(parm.CreatedTime[0]);
parm.CreatedTime[1] = DOANConvertDate.ConvertLocalDate(parm.CreatedTime[1]);
parm.CreatedTime[0] = parm.CreatedTime[0].Date;
parm.CreatedTime[1] = parm.CreatedTime[1].Date;
}
var predicate = Expressionable
.Create<MmRecordOutbound>()
.AndIF(
!string.IsNullOrEmpty(parm.MaterialCode),
it => it.MaterialCode.Contains(parm.MaterialCode)
)
.AndIF(
!string.IsNullOrEmpty(parm.OutboundNo),
it => it.OutboundNo.Contains(parm.OutboundNo)
)
.AndIF(
!string.IsNullOrEmpty(parm.Workorder),
it => it.Workorder.Contains(parm.Workorder)
)
.AndIF(
!string.IsNullOrEmpty(parm.TransactionType),
it => it.TransactionType.Contains(parm.TransactionType)
)
.AndIF(
!string.IsNullOrEmpty(parm.Operator),
it => it.Operator.Contains(parm.Operator)
)
.AndIF(
parm.CreatedTime != null && parm.CreatedTime[0] > DateTime.MinValue,
it => it.CreatedTime >= parm.CreatedTime[0]
)
.AndIF(
parm.CreatedTime != null && parm.CreatedTime[1] > DateTime.MinValue,
it => it.CreatedTime <= parm.CreatedTime[1]
);
;
return predicate;
}
/// <summary>
/// 导入数据
/// </summary>
/// <param name="formFile"></param>
/// <returns></returns>
public ImportResultDto ImportRecordOutbound(IFormFile formFile, string username)
{
string message = "导出成功";
List<MmRecordOutboundExcelDto> recordoutboundList = new();
using (var stream = formFile.OpenReadStream())
{
try
{
IWorkbook workbook = new XSSFWorkbook(stream);
ISheet sheet = workbook.GetSheetAt(0);
#region excel
// 遍历每一行
for (int row = 1; row <= sheet.LastRowNum; row++)
{
IRow currentRow = sheet.GetRow(row);
if (currentRow != null) // 确保行不为空
{
MmRecordOutboundExcelDto recordoutbound =
new MmRecordOutboundExcelDto();
//00 ID
NPOI.SS.UserModel.ICell currentCell_00 = currentRow.GetCell(0);
recordoutbound.Id = (int)currentCell_00?.NumericCellValue;
//01 创建时间
NPOI.SS.UserModel.ICell currentCell_01 = currentRow.GetCell(1);
recordoutbound.CreatedTime =
currentCell_01?.DateCellValue ?? DateTime.Now;
//02 出库单号
NPOI.SS.UserModel.ICell currentCell_02 = currentRow.GetCell(2);
recordoutbound.OutboundNo = currentCell_02?.ToString();
if (
currentCell_02 == null
|| string.IsNullOrEmpty(recordoutbound.OutboundNo)
)
{
message = $"出库单号不可为空,第{row + 1}行";
break;
}
//03 物料编码
NPOI.SS.UserModel.ICell currentCell_03 = currentRow.GetCell(3);
recordoutbound.MaterialCode = currentCell_03?.ToString();
if (
currentCell_03 == null
|| string.IsNullOrEmpty(recordoutbound.MaterialCode)
)
{
message = $"物料编码不可为空,第{row + 1}行";
break;
}
//04 物料名称
NPOI.SS.UserModel.ICell currentCell_04 = currentRow.GetCell(4);
recordoutbound.MaterialName = currentCell_04?.ToString();
if (
currentCell_04 == null
|| string.IsNullOrEmpty(recordoutbound.MaterialName)
)
{
message = $"物料名称不可为空,第{row + 1}行";
break;
}
//05 仓库编码
NPOI.SS.UserModel.ICell currentCell_05 = currentRow.GetCell(5);
recordoutbound.WarehouseCode = currentCell_05?.ToString();
if (
currentCell_05 == null
|| string.IsNullOrEmpty(recordoutbound.WarehouseCode)
)
{
message = $"仓库编码不可为空,第{row + 1}行";
break;
}
//06 仓库名称
NPOI.SS.UserModel.ICell currentCell_06 = currentRow.GetCell(6);
recordoutbound.WarehouseName = currentCell_06?.ToString();
if (
currentCell_06 == null
|| string.IsNullOrEmpty(recordoutbound.WarehouseName)
)
{
message = $"仓库名称不可为空,第{row + 1}行";
break;
}
//07 库位编码
NPOI.SS.UserModel.ICell currentCell_07 = currentRow.GetCell(7);
recordoutbound.LocationCode = currentCell_05?.ToString();
if (
currentCell_07 == null
|| string.IsNullOrEmpty(recordoutbound.LocationCode)
)
{
message = $"库位编码不可为空,第{row + 1}行";
break;
}
//08 库位名称
NPOI.SS.UserModel.ICell currentCell_08 = currentRow.GetCell(8);
recordoutbound.LocationName = currentCell_06?.ToString();
if (
currentCell_08 == null
|| string.IsNullOrEmpty(recordoutbound.LocationName)
)
{
message = $"库位名称不可为空,第{row + 1}行";
break;
}
//09 出库数量
NPOI.SS.UserModel.ICell currentCell_09 = currentRow.GetCell(9);
string QuantityStr = currentCell_09?.ToString();
if (currentCell_09 == null || string.IsNullOrWhiteSpace(QuantityStr))
{
message = $"出库数量不可为空,第{row + 1}行";
break;
}
// 尝试转换为decimal
if (!decimal.TryParse(QuantityStr, out decimal Quantity))
{
message = $"出库数量格式错误(必须是数字),第{row + 1}行";
break;
}
//验证数值范围(可根据业务需求调整)
if (Quantity < 0)
{
message = $"出库数量不能为负数,第{row + 1}行";
break;
}
recordoutbound.Quantity = Quantity;
//10 出库类型
NPOI.SS.UserModel.ICell currentCell_10 = currentRow.GetCell(10);
recordoutbound.TransactionType = currentCell_08?.ToString();
if (
currentCell_10 == null
|| string.IsNullOrEmpty(recordoutbound.TransactionType)
)
{
message = $"入库类型不可为空,第{row + 1}行";
break;
}
//11关联订单号
NPOI.SS.UserModel.ICell currentCell_11 = currentRow.GetCell(11);
recordoutbound.OrderNo = currentCell_11?.ToString() ?? string.Empty;
//12 操作员
NPOI.SS.UserModel.ICell currentCell_12 = currentRow.GetCell(12);
recordoutbound.Operator = currentCell_12?.ToString() ?? string.Empty;
recordoutboundList.Add(recordoutbound);
}
}
#endregion
}
catch (Exception ex)
{
return null;
}
}
// TODO 3.调用SplitInsert方法实现导入操作,注意主键列的配置(建议优化为ID相同则修改不同则新增)
var x = Context
.Storageable(recordoutboundList)
//.SplitInsert(it => !it.Any())
//.WhereColumns(it => it.Id) //如果不是主键可以这样实现多字段it=>new{it.x1,it.x2}
.ToStorage();
var result = x.AsInsertable.ExecuteCommand();
var result2 = x.AsUpdateable.IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommand(); //插入可插入部分;
var importResult = new ImportResultDto
{
Message = message,
Inserted = x.InsertList.Count,
Updated = x.UpdateList.Count,
ErrorCount = x.ErrorList.Count,
IgnoredCount = x.IgnoreList.Count,
Deleted = x.DeleteList.Count,
Total = x.TotalList.Count,
};
//输出统计
Console.WriteLine(importResult);
// 4.收集错误与忽略信息返回导入结果ImportResultDto 提示需要修改IServer相关返回格式
foreach (var item in x.ErrorList)
{
importResult.Errors.Add(
new ImportErrorDto
{
MaterialCode = item.Item.MaterialCode,
Message = item.StorageMessage,
}
);
}
foreach (var item in x.IgnoreList)
{
importResult.Ignored.Add(
new ImportErrorDto
{
MaterialCode = item.Item.MaterialCode,
Message = item.StorageMessage,
}
);
}
return importResult;
}
/// <summary>
/// 导出入库记录列表
/// </summary>
/// <returns></returns>
public PagedInfo<MmRecordOutboundExcelDto> SelectRecordoutboundList(
MmRecordOutboundQueryDto recordoutbound,
PagerInfo pager
)
{
// Use the same predicate builder as GetList to support consistent filtering
var predicate = QueryExp(recordoutbound);
var query = Queryable()
.Where(predicate.ToExpression())
.Select(it => new MmRecordOutboundExcelDto
{
Id = it.Id,
CreatedTime = it.CreatedTime,
OutboundNo = it.OutboundNo,
MaterialCode = it.MaterialCode,
MaterialName = it.MaterialName,
WarehouseCode = it.WarehouseCode,
WarehouseName = it.WarehouseName,
LocationCode = it.LocationCode,
LocationName = it.LocationName,
Quantity = it.Quantity,
TransactionType = it.TransactionType,
OrderNo = it.OrderNo,
Operator = it.Operator,
WorkorderRaw = it.WorkorderRaw
});
return query.ToPage(pager);
}
/// <summary>
/// 初始化出库记录数据
/// </summary>
/// <param name="outboundRecord">出库记录对象</param>
/// <param name="currentWorkorder">当前操作工单号</param>
/// <param name="rawMaterialWorkorder">原材料工单号(如果有)</param>
/// <returns>初始化后的出库记录对象</returns>
/// <exception cref="ArgumentException">参数验证异常</exception>
public MmRecordOutbound InitializeShipmentRecord(MmRecordOutbound outboundRecord, string currentWorkorder, string rawMaterialWorkorder = null)
{
// 参数验证
if (outboundRecord == null)
{
throw new ArgumentNullException(nameof(outboundRecord), "出库记录对象不能为空");
}
if (string.IsNullOrWhiteSpace(currentWorkorder))
{
throw new ArgumentException("当前操作工单号不能为空", nameof(currentWorkorder));
}
if (string.IsNullOrWhiteSpace(outboundRecord.MaterialCode))
{
throw new ArgumentException("物料编码不能为空");
}
if (string.IsNullOrWhiteSpace(outboundRecord.WarehouseCode))
{
throw new ArgumentException("仓库编码不能为空");
}
if (outboundRecord.Quantity <= 0)
{
throw new ArgumentException("出库数量必须大于0");
}
if (string.IsNullOrWhiteSpace(outboundRecord.TransactionType))
{
throw new ArgumentException("出库类型不能为空");
}
// 设置默认值
if (string.IsNullOrWhiteSpace(outboundRecord.OutboundNo))
{
// 生成出库单号OUT + 年月日时分秒
outboundRecord.OutboundNo = "OUT" + DateTime.Now.ToString("yyyyMMddHHmmss");
}
if (outboundRecord.CreatedTime == null)
{
outboundRecord.CreatedTime = DateTime.Now;
}
if (string.IsNullOrWhiteSpace(outboundRecord.Unit))
{
outboundRecord.Unit = "个";
}
// 字段使用规范:
// WorkorderRaw永久性记录原材料最初关联的工单号信息保持不变
// Workorder动态记录当前操作环节所对应的工单号根据实际业务操作场景实时更新
if (!string.IsNullOrWhiteSpace(rawMaterialWorkorder))
{
// 如果提供了原材料工单号设置为WorkorderRaw
outboundRecord.WorkorderRaw = rawMaterialWorkorder;
}
else if (string.IsNullOrWhiteSpace(outboundRecord.WorkorderRaw))
{
// 如果没有提供原材料工单号且WorkorderRaw为空则使用当前工单号作为初始值
outboundRecord.WorkorderRaw = currentWorkorder;
}
// 注意如果WorkorderRaw已经有值保持不变确保其在整个生命周期中保持恒定
// 更新当前操作工单号
outboundRecord.Workorder = currentWorkorder;
return outboundRecord;
}
/// <summary>
/// 创建并初始化出库记录
/// </summary>
/// <param name="outboundRecord">出库记录对象</param>
/// <param name="currentWorkorder">当前操作工单号</param>
/// <param name="rawMaterialWorkorder">原材料工单号(如果有)</param>
/// <returns>创建的出库记录对象</returns>
public MmRecordOutbound CreateAndInitializeShipmentRecord(MmRecordOutbound outboundRecord, string currentWorkorder, string rawMaterialWorkorder = null)
{
try
{
// 初始化出库记录数据
var initializedRecord = InitializeShipmentRecord(outboundRecord, currentWorkorder, rawMaterialWorkorder);
// 保存到数据库
return Insertable(initializedRecord).ExecuteReturnEntity();
}
catch (ArgumentException ex)
{
// 处理参数验证异常
throw new Exception($"出库记录初始化失败:{ex.Message}", ex);
}
catch (Exception ex)
{
// 处理其他异常
throw new Exception($"创建出库记录失败:{ex.Message}", ex);
}
}
}
}