Files
kunshan-bzfm-mes-backend/DOAN.Service/MES/Product/ProWorkorderImportService.cs
git_rabbit 9e096214c7 feat(材料管理): 添加炉号字段并优化工单排序逻辑
在材料相关的DTO和服务中添加StoveCode字段以支持炉号记录
优化工单导入服务中的排序逻辑,处理空值情况
调整报表流程服务中的材料查询和更新逻辑
2026-02-11 14:38:04 +08:00

577 lines
25 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 System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using DOAN.Model;
using DOAN.Model.BZFM;
using DOAN.Model.MES.base_;
using DOAN.Model.MES.product;
using DOAN.Service.MES.product.IService;
using Infrastructure;
using Infrastructure.Attribute;
using Microsoft.AspNetCore.Http;
using NLog;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using SqlSugar;
namespace DOAN.Service.MES.product
{
/// <summary>
/// 工单导入服务
/// </summary>
[AppService(
ServiceType = typeof(IProWorkorderImportService),
ServiceLifetime = LifeTime.Transient
)]
public class ProWorkorderImportService : BaseService<ProWorkorder>, IProWorkorderImportService
{
/// <summary>
/// 日志记录器
/// </summary>
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
/// <summary>
/// 生成唯一的工单编号
/// </summary>
/// <param name="workorder">工单对象</param>
/// <param name="dateValue">日期值</param>
/// <param name="currentIndex">当前编号索引</param>
/// <param name="nickCode">昵称代码</param>
/// <param name="maxAttempts">最大尝试次数</param>
/// <returns>生成的工单编号和更新后的索引</returns>
private Tuple<string, int> GenerateUniqueWorkorderNo(ProWorkorder workorder, DateTime dateValue, int currentIndex, string nickCode, int maxAttempts = 100)
{
string workorderNo;
bool isUnique;
int attempts = 0;
do
{
attempts++;
if (attempts > maxAttempts)
{
throw new Exception(
$"生成工单编号失败,超过最大尝试次数({maxAttempts})productionCode: {workorder.productionCode}"
);
}
workorderNo =
dateValue.ToString("yyyyMMdd")
+ "_"
+ workorder.GroupCode
+ workorder.RouteCode
+ "_"
+ nickCode
+ "_"
+ currentIndex.ToString("000");
// 检查编号是否已存在
isUnique = !Context
.Queryable<ProWorkorder>()
.Where(it => it.Workorder == workorderNo)
.Any();
if (!isUnique)
{
currentIndex++;
}
} while (!isUnique);
return new Tuple<string, int>(workorderNo, currentIndex);
}
/// <summary>
/// 获取指定productionCode当天的最大工单编号索引
/// </summary>
/// <param name="productionCode">产品代码</param>
/// <param name="dateValue">日期值</param>
/// <returns>最大编号索引</returns>
private int GetMaxWorkorderIndex(string productionCode, DateTime dateValue)
{
var workorderNumbers = Context
.Queryable<ProWorkorder>()
.Where(it => it.WorkorderDate == dateValue.Date)
.Where(it => it.productionCode == productionCode)
.Select(it => it.Workorder)
.ToList()
.Where(w => w.StartsWith(dateValue.ToString("yyyyMMdd")))
.Select(w =>
{
var parts = w.Split('_');
if (parts.Length >= 4 && int.TryParse(parts[3], out int index))
return index;
return 0;
})
.ToList();
// 如果没有找到记录返回0
return workorderNumbers.Count > 0 ? workorderNumbers.Max() : 0;
}
/// <summary>
/// 从Excel文件读取工单数据
/// </summary>
/// <param name="formFile">Excel文件</param>
/// <param name="username">用户名</param>
/// <param name="dateValue">返回的日期值</param>
/// <returns>工单列表</returns>
private List<ProWorkorder> ReadWorkordersFromExcel(IFormFile formFile, string username, out DateTime dateValue)
{
List<ProWorkorder> workorderList = new List<ProWorkorder>();
dateValue = DateTime.Now;
using (var stream = formFile.OpenReadStream())
{
IWorkbook workbook = new XSSFWorkbook(stream);
ISheet sheet = workbook.GetSheetAt(0);
// 处理第2行 获取日期
IRow secondRow = sheet.GetRow(1);
ICell cell = secondRow.GetCell(0);
// 安全获取日期值
if (cell != null && cell.DateCellValue.HasValue)
{
dateValue = cell.DateCellValue.Value;
}
// 遍历每一行
for (int row = 4; row <= sheet.LastRowNum; row++)
{
IRow currentRow = sheet.GetRow(row);
if (currentRow != null) // 确保行不为空
{
ProWorkorder workorder = new ProWorkorder();
//00 主体品名
ICell currentCell_01 = currentRow.GetCell(0);
workorder.productionName = currentCell_01?.ToString();
if (
currentCell_01 == null
|| string.IsNullOrEmpty(workorder.productionName)
)
{
throw new Exception($"{row + 1}行【主体品名】不可为空");
}
//01 成品型号
ICell currentCell_02 = currentRow.GetCell(1);
workorder.productionCode = currentCell_02?.ToString();
if (
currentCell_02 == null
|| string.IsNullOrEmpty(workorder.productionCode)
)
{
throw new Exception($"{row + 1}行【主体型号】不可为空");
}
//02 单位
ICell currentCell_04 = currentRow.GetCell(2);
workorder.Unit = currentCell_04?.ToString() ?? string.Empty;
//3 计划数量
ICell currentCell_07 = currentRow.GetCell(3);
if (currentCell_07 != null)
{
if (currentCell_07.CellType == CellType.Numeric)
{
workorder.PlanNum = (int)currentCell_07.NumericCellValue;
}
else if (currentCell_07.CellType == CellType.String && int.TryParse(currentCell_07.StringCellValue, out int planNum))
{
workorder.PlanNum = planNum;
}
else
{
workorder.PlanNum = 0;
}
}
else
{
workorder.PlanNum = 0;
}
if (workorder.PlanNum < 0)
{
workorder.PlanNum = 0;
}
//4 原材料名称
ICell currentCell_11 = currentRow.GetCell(4);
workorder.MaterialName = currentCell_11?.ToString();
if (
currentCell_11 == null
|| string.IsNullOrEmpty(workorder.MaterialName)
)
{
throw new Exception($"{row + 1}行【材料型号】不可为空");
}
//5 原材料编号
ICell currentCell_12 = currentRow.GetCell(5);
workorder.MaterialCode = currentCell_12?.ToString();
if (
currentCell_12 == null
|| string.IsNullOrEmpty(workorder.MaterialCode)
)
{
throw new Exception($"{row + 1}行【材料编码】不可为空");
}
//6 原材料材质
ICell currentCell_13 = currentRow.GetCell(6);
workorder.MaterialtextureCode = currentCell_13?.ToString() ?? string.Empty;
//7 炉号
ICell currentCell_14 = currentRow.GetCell(7);
workorder.StoveCode = currentCell_14?.ToString() ?? string.Empty;
//8 图号
ICell currentCell_15 = currentRow.GetCell(8);
workorder.DrawingCode = currentCell_15?.ToString() ?? string.Empty;
//9 版本
ICell currentCell_16 = currentRow.GetCell(9);
workorder.Version = currentCell_16?.ToString() ?? string.Empty;
//10 指导日期
ICell cell17 = currentRow.GetCell(10);
// 安全获取日期值
if (cell17 != null && cell17.DateCellValue.HasValue)
{
workorder.InstructionDate = cell17.DateCellValue.Value;
}
else
{
workorder.InstructionDate = dateValue;
}
// 11 车间code
ICell currentCell_18 = currentRow.GetCell(11);
if (currentCell_18 == null)
{
workorder.WorkshopCode = string.Empty;
}
else
{
if (currentCell_18.CellType == CellType.Numeric)
{
workorder.WorkshopCode = currentCell_18.NumericCellValue.ToString();
}
else if (currentCell_18.CellType == CellType.String)
{
workorder.WorkshopCode = currentCell_18.StringCellValue;
}
else
{
workorder.WorkshopCode = string.Empty;
}
}
//12 组号code
ICell currentCell_19 = currentRow.GetCell(12);
if (currentCell_19 == null)
{
workorder.GroupCode = string.Empty;
}
else
{
if (currentCell_19.CellType == CellType.Numeric)
{
workorder.GroupCode = currentCell_19.NumericCellValue.ToString();
}
else if (currentCell_19.CellType == CellType.String)
{
workorder.GroupCode = currentCell_19.StringCellValue;
}
else
{
workorder.GroupCode = string.Empty;
}
}
//13 班号code
ICell currentCell_20 = currentRow.GetCell(13);
if (currentCell_20 == null)
{
workorder.RouteCode = string.Empty;
}
else
{
if (currentCell_20.CellType == CellType.Numeric)
{
workorder.RouteCode = currentCell_20.NumericCellValue.ToString();
}
else if (currentCell_20.CellType == CellType.String)
{
workorder.RouteCode = currentCell_20.StringCellValue;
}
else
{
workorder.RouteCode = string.Empty;
}
}
//14 优先级
ICell currentCell_21 = currentRow.GetCell(14);
if (currentCell_21 == null)
{
workorder.Priority = 1;
}
else
{
string priorityStr = string.Empty;
if (currentCell_21.CellType == CellType.String)
{
priorityStr = currentCell_21.StringCellValue;
}
else if (currentCell_21.CellType == CellType.Numeric)
{
priorityStr = currentCell_21.NumericCellValue.ToString();
}
if (priorityStr == "优先")
{
workorder.Priority = 3;
}
else if (priorityStr == "插单")
{
workorder.Priority = 2;
}
else if (priorityStr == "正常" || string.IsNullOrEmpty(priorityStr))
{
workorder.Priority = 1;
}
else
{
workorder.Priority = 1;
}
}
//15 节拍
ICell currentCell_22 = currentRow.GetCell(15);
if (currentCell_22 != null)
{
if (currentCell_22.CellType == CellType.Numeric)
{
workorder.Beat = (int)currentCell_22.NumericCellValue;
}
else if (currentCell_22.CellType == CellType.String && int.TryParse(currentCell_22.StringCellValue, out int beat))
{
workorder.Beat = beat;
}
else
{
workorder.Beat = 0;
}
}
else
{
workorder.Beat = 0;
}
//16 进料单号(外购)
ICell currentCell_010 = currentRow.GetCell(16);
workorder.FeedOrder = currentCell_010?.StringCellValue ?? string.Empty;
//17 客户单号(出货)
ICell currentCell_011 = currentRow.GetCell(17);
workorder.CustomerOrder = currentCell_011?.StringCellValue ?? string.Empty;
//18 备注
ICell currentCell_012 = currentRow.GetCell(18);
workorder.Remark01 = currentCell_012?.StringCellValue ?? string.Empty;
workorder.Id = SnowFlakeSingle.Instance.NextId().ToString();
workorder.CreatedBy = username;
workorder.CreatedTime = DateTime.Now;
workorder.WorkorderDate = dateValue;
workorder.DefectNum = 0;
workorder.ShipmentNum = 0;
workorder.Status = 1;
workorderList.Add(workorder);
}
}
}
return workorderList;
}
/// <summary>
/// 导入工单 必须整删除 整改
/// </summary>
/// <param name="formFile"></param>
/// <param name="username"></param>
/// <returns></returns>
public int ImportData(IFormFile formFile, string username)
{
int result = 0;
List<ProWorkorder> workorderList;
DateTime dateValue;
// XXX 改为从物料清单获取信息
List<MmMaterial> mmMaterials = Context
.Queryable<MmMaterial>()
.Where(it => it.Status == "启用")
.ToList();
try
{
Logger.Info($"开始导入工单数据,用户名: {username}");
workorderList = ReadWorkordersFromExcel(formFile, username, out dateValue);
Logger.Info($"读取到 {workorderList.Count} 条工单数据");
// 按productionCode分组并顺序编号
// 先按productionCode分组确保同一产品的工单连续排序
var productionCodeGroups = workorderList
.GroupBy(w => w.productionCode)
.ToList();
// 获取所有工单的最大sort值用于后续排序
var maxSortNullable = Context
.Queryable<ProWorkorder>()
.Where(it => it.WorkorderDate == dateValue.Date)
//.Select(it => it.Sort)
.Max(it => it.Sort);
// 如果没有找到记录设置默认值0
int maxSort = maxSortNullable.HasValue ? maxSortNullable.Value : 0;
// 从maxSort + 10开始确保sort值按10、20、30...递增
int currentSort = (maxSort / 10) * 10 + 10;
foreach (var group in productionCodeGroups)
{
Logger.Info($"处理产品代码: {group.Key},共 {group.Count()} 条工单");
// 获取当前productionCode当天已有的最大编号
int maxIndex = GetMaxWorkorderIndex(group.Key, dateValue);
// 从最大编号+1开始顺序编号
int currentIndex = maxIndex + 1;
Logger.Info($"产品代码: {group.Key},当前最大编号索引: {maxIndex},开始编号: {currentIndex}");
foreach (var workorder in group)
{
string nickCode = mmMaterials
.Where(it => it.MaterialCode == workorder.productionCode)
.Select(it => it.Type)
.FirstOrDefault();
// 生成唯一的工单编号
var generateResult = GenerateUniqueWorkorderNo(workorder, dateValue, currentIndex, nickCode);
workorder.Workorder = generateResult.Item1;
// 使用连续的sort值不受编号冲突影响
workorder.Sort = currentSort;
Logger.Info($"生成工单编号: {workorder.Workorder},产品: {workorder.productionName}sort: {currentSort}");
currentIndex = generateResult.Item2 + 1;
// 增加sort值确保下一个工单的sort值为当前值+10
currentSort += 10;
}
}
UseTran2(() =>
{
Logger.Info($"删除日期 {dateValue.ToShortDateString()} 的现有工单数据");
Context
.Deleteable<ProWorkorder>()
.Where(it => it.WorkorderDate == dateValue)
.ExecuteCommand();
Logger.Info($"插入新工单数据,共 {workorderList.Count} 条");
result = Context.Insertable(workorderList).ExecuteCommand();
});
Logger.Info($"工单导入完成,成功导入 {result} 条数据");
return result;
}
catch (Exception ex)
{
Logger.Error(ex, $"导入工单时出错: {ex.Message}");
throw new Exception($"导入工单时出错,错误: {ex.Message}");
}
}
/// <summary>
/// 分批导入,追加导入
/// </summary>
/// <param name="formFile"></param>
/// <param name="username"></param>
/// <returns></returns>
public int ImportDataAppend(IFormFile formFile, string username)
{
int result = 0;
List<ProWorkorder> workorderList;
DateTime dateValue;
// XXX 改为从物料清单获取信息
List<MmMaterial> mmMaterials = Context
.Queryable<MmMaterial>()
.Where(it => it.Status == "启用")
.ToList();
try
{
Logger.Info($"开始追加导入工单数据,用户名: {username}");
workorderList = ReadWorkordersFromExcel(formFile, username, out dateValue);
Logger.Info($"读取到 {workorderList.Count} 条工单数据");
// 按productionCode分组并顺序编号
var productionCodeGroups = workorderList.GroupBy(w => w.productionCode).ToList();
// 获取所有工单的最大sort值用于后续排序
var maxSortNullable = Context
.Queryable<ProWorkorder>()
.Where(it => it.WorkorderDate == dateValue.Date)
//.Select(it => it.Sort)
.Max(it => it.Sort);
// 如果没有找到记录设置默认值0
int maxSort = maxSortNullable.HasValue ? maxSortNullable.Value : 0;
// 从maxSort + 10开始确保sort值按10、20、30...递增
int currentSort = (maxSort / 10) * 10 + 10;
foreach (var group in productionCodeGroups)
{
Logger.Info($"处理产品代码: {group.Key},共 {group.Count()} 条工单");
// 获取当前productionCode当天已有的最大编号
int maxIndex = GetMaxWorkorderIndex(group.Key, dateValue);
// 从最大编号+1开始顺序编号
int currentIndex = maxIndex + 1;
Logger.Info($"产品代码: {group.Key},当前最大编号索引: {maxIndex},开始编号: {currentIndex}");
foreach (var workorder in group)
{
string nickCode = mmMaterials
.Where(it => it.MaterialCode == workorder.productionCode)
.Select(it => it.Type)
.FirstOrDefault();
// 生成唯一的工单编号
var generateResult = GenerateUniqueWorkorderNo(workorder, dateValue, currentIndex, nickCode);
workorder.Workorder = generateResult.Item1;
// 使用连续的sort值不受编号冲突影响
workorder.Sort = currentSort;
Logger.Info($"生成工单编号: {workorder.Workorder},产品: {workorder.productionName}sort: {currentSort}");
currentIndex = generateResult.Item2 + 1;
// 增加sort值确保下一个工单的sort值为当前值+10
currentSort += 10;
}
}
UseTran2(() =>
{
// 追加导入,不删除现有数据
Logger.Info($"追加插入工单数据,共 {workorderList.Count} 条");
result = Context.Insertable(workorderList).ExecuteCommand();
});
Logger.Info($"工单追加导入完成,成功导入 {result} 条数据");
return result;
}
catch (Exception ex)
{
Logger.Error(ex, $"追加导入工单时出错: {ex.Message}");
throw new Exception($"追加导入工单时出错,错误: {ex.Message}");
}
}
}
}