diff --git a/DOAN.Admin.WebApi/Controllers/MES/Product/ProWorkorderController.cs b/DOAN.Admin.WebApi/Controllers/MES/Product/ProWorkorderController.cs index 84d5006..3177f4a 100644 --- a/DOAN.Admin.WebApi/Controllers/MES/Product/ProWorkorderController.cs +++ b/DOAN.Admin.WebApi/Controllers/MES/Product/ProWorkorderController.cs @@ -27,7 +27,10 @@ namespace DOAN.Admin.WebApi.Controllers /// private readonly IProWorkorderMaterialService _ProWorkorderMaterialService; - public ProWorkorderController(IProWorkorderService ProWorkorderService, IProWorkorderMaterialService ProWorkorderMaterialService) + public ProWorkorderController( + IProWorkorderService ProWorkorderService, + IProWorkorderMaterialService ProWorkorderMaterialService + ) { _ProWorkorderService = ProWorkorderService; _ProWorkorderMaterialService = ProWorkorderMaterialService; @@ -231,7 +234,6 @@ namespace DOAN.Admin.WebApi.Controllers { return ToResponse(new ApiResult(500, ex.Message)); } - } //TODO 分批导入工单,追加工单 @@ -251,7 +253,10 @@ namespace DOAN.Admin.WebApi.Controllers { return SUCCESS(null); } - int response = _ProWorkorderService.ImportDataAppend(formFile, HttpContext.GetName()); + int response = _ProWorkorderService.ImportDataAppend( + formFile, + HttpContext.GetName() + ); return SUCCESS(response); } @@ -259,7 +264,6 @@ namespace DOAN.Admin.WebApi.Controllers { return ToResponse(new ApiResult(500, ex.Message)); } - } /// @@ -399,7 +403,7 @@ namespace DOAN.Admin.WebApi.Controllers [HttpPost("print")] [AllowAnonymous] - public async Task ExportWorkorderPDF([FromBody] ProWorkorderExportDto param) + public IActionResult ExportWorkorderPDF([FromBody] ProWorkorderExportDto param) { try { @@ -409,9 +413,15 @@ namespace DOAN.Admin.WebApi.Controllers } param.Path = "D:/mes/Label/Trace.btw"; //Task<(string, Stream)> conntext = _ProWorkorderService.ExportPDFByQuestPDFDemo(workorderArray); - var exception = await _ProWorkorderService.PrintTicketsByTemplate(param); - - return (IActionResult)exception; + var result = _ProWorkorderService.PrintTicketsByTemplate(param); + if (result == "ok") + { + return ToResponse(new ApiResult(200, result)); + } + else + { + return ToResponse(new ApiResult(500, result)); + } //return File(conntext.Result.Item2, "application/pdf", HttpUtility.UrlEncode(conntext.Result.Item1)); } catch (Exception ex) @@ -436,10 +446,8 @@ namespace DOAN.Admin.WebApi.Controllers } catch (Exception ex) { - return ToResponse(new ApiResult(500, ex.Message)); } - } /// @@ -640,7 +648,9 @@ namespace DOAN.Admin.WebApi.Controllers return ToResponse(ApiResult.Error($"工单号不能为空")); } - var response = _ProWorkorderMaterialService.GetPickableWorkordersByWorkorder(workorder); + var response = _ProWorkorderMaterialService.GetPickableWorkordersByWorkorder( + workorder + ); return SUCCESS(response); } catch (Exception ex) @@ -665,7 +675,9 @@ namespace DOAN.Admin.WebApi.Controllers return ToResponse(ApiResult.Error($"工单号不能为空")); } - var response = _ProWorkorderMaterialService.GetShippableOrdersByWorkorder(workorder); + var response = _ProWorkorderMaterialService.GetShippableOrdersByWorkorder( + workorder + ); return SUCCESS(response); } catch (Exception ex) diff --git a/DOAN.Service/MES/Material/MmInventoryService.cs b/DOAN.Service/MES/Material/MmInventoryService.cs index 4cf1b70..1178e9b 100644 --- a/DOAN.Service/MES/Material/MmInventoryService.cs +++ b/DOAN.Service/MES/Material/MmInventoryService.cs @@ -292,6 +292,7 @@ namespace DOAN.Service.BZFM } } + // 出库单 public string CreateOutboundReceipt(OutboundReceiptDto parm) { try @@ -377,8 +378,7 @@ namespace DOAN.Service.BZFM { OutboundNo = outboundNo, BatchNo = parm.BatchNo, - StoveCode = parm. - Operator = parm.Operator, + StoveCode = parm.Operator = parm.Operator, MaterialCode = mmMaterial.MaterialCode, MaterialName = mmMaterial.MaterialName, SupplierCode = mmMaterial.SupplierCode, @@ -994,8 +994,34 @@ namespace DOAN.Service.BZFM { return result; } - // 如果是出货则还要减少出货单库存和工单出货数量 + Context.Ado.BeginTran(); + // 如果是根据工单领料撤销,还需要调整领料记录 + if ( + recordOutbound.TransactionType == "领料出库" + && !string.IsNullOrEmpty(recordOutbound.WorkorderRaw) + ) + { + var workorderInfo = Context + .Queryable() + .Where(it => it.Workorder == recordOutbound.WorkorderRaw) + .First(); + if (workorderInfo == null) + { + Context.Ado.RollbackTran(); + return "工单不存在"; + } + workorderInfo.ShipmentNum -= Math.Abs((int)recordOutbound.Quantity); + if (workorderInfo.ShipmentNum < 0) + { + workorderInfo.ShipmentNum = 0; + } + Context + .Updateable(workorderInfo) + .UpdateColumns(it => new { it.ShipmentNum }) + .ExecuteCommand(); + } + // 如果是出货则还要减少出货单库存和工单出货数量 if (recordOutbound.TransactionType == "出货出库") { var workorderInfo = Context diff --git a/DOAN.Service/MES/Product/IService/IProWorkorderService.cs b/DOAN.Service/MES/Product/IService/IProWorkorderService.cs index a92a8b0..09eef6a 100644 --- a/DOAN.Service/MES/Product/IService/IProWorkorderService.cs +++ b/DOAN.Service/MES/Product/IService/IProWorkorderService.cs @@ -65,7 +65,7 @@ namespace DOAN.Service.MES.product.IService List WorkOrderLog(string workorder); Task<(string, Stream)> ExportPDFByQuestPDFDemo(string[] workorderArray); - Task PrintTicketsByTemplate(ProWorkorderExportDto param); + string PrintTicketsByTemplate(ProWorkorderExportDto param); /// /// 获取工单生产进度 diff --git a/DOAN.Service/MES/Product/IService/IProWorkorderUtilityService.cs b/DOAN.Service/MES/Product/IService/IProWorkorderUtilityService.cs index 4da9e56..1a3d37e 100644 --- a/DOAN.Service/MES/Product/IService/IProWorkorderUtilityService.cs +++ b/DOAN.Service/MES/Product/IService/IProWorkorderUtilityService.cs @@ -54,6 +54,6 @@ namespace DOAN.Service.MES.product.IService /// /// /// - Task PrintTicketsByTemplate(ProWorkorderExportDto param); + string PrintTicketsByTemplate(ProWorkorderExportDto param); } } \ No newline at end of file diff --git a/DOAN.Service/MES/Product/ProWorkorderMaterialService.cs b/DOAN.Service/MES/Product/ProWorkorderMaterialService.cs index f18fa8d..710e8f0 100644 --- a/DOAN.Service/MES/Product/ProWorkorderMaterialService.cs +++ b/DOAN.Service/MES/Product/ProWorkorderMaterialService.cs @@ -55,6 +55,22 @@ namespace DOAN.Service.MES.product WorkorderRaw = it.WorkorderRaw, }) .ToList(); + foreach(var item in result) + { + if (!string.IsNullOrEmpty(item.WorkorderRaw)) + { + var workorderRawInfo = Context + .Queryable() + .Where(x => x.Workorder == item.WorkorderRaw) + .First(); + if(workorderRawInfo != null) + { + item.StoveCode = workorderRawInfo.StoveCode; + item.BatchNo = workorderRawInfo.FeedOrder; + } + } + } + return result; } diff --git a/DOAN.Service/MES/Product/ProWorkorderQueryService.cs b/DOAN.Service/MES/Product/ProWorkorderQueryService.cs index d9a1b25..1cce63b 100644 --- a/DOAN.Service/MES/Product/ProWorkorderQueryService.cs +++ b/DOAN.Service/MES/Product/ProWorkorderQueryService.cs @@ -65,7 +65,7 @@ namespace DOAN.Service.MES.product it.WorkorderDate >= parm.WorkorderDate[0] && it.WorkorderDate <= parm.WorkorderDate[1] ) - .And(it => it.PlanNum > 0) + //.And(it => it.PlanNum > 0) .ToExpression(); var response = Context @@ -125,7 +125,7 @@ namespace DOAN.Service.MES.product && it.WorkorderDate <= parm.WorkorderDate[1] ) .AndIF(hasWorkorder, it => it.Workorder.Contains(parm.Workorder)) - .And(it => it.PlanNum > 0) + //.And(it => it.PlanNum > 0) .ToExpression(); var response = Context diff --git a/DOAN.Service/MES/Product/ProWorkorderService.cs b/DOAN.Service/MES/Product/ProWorkorderService.cs index d8f67db..137a7f7 100644 --- a/DOAN.Service/MES/Product/ProWorkorderService.cs +++ b/DOAN.Service/MES/Product/ProWorkorderService.cs @@ -242,7 +242,7 @@ namespace DOAN.Service.MES.product /// /// /// - public Task PrintTicketsByTemplate(ProWorkorderExportDto param) + public string PrintTicketsByTemplate(ProWorkorderExportDto param) { return _utilityService.PrintTicketsByTemplate(param); } diff --git a/DOAN.Service/MES/Product/ProWorkorderUtilityService.cs b/DOAN.Service/MES/Product/ProWorkorderUtilityService.cs index bcaee27..b633d58 100644 --- a/DOAN.Service/MES/Product/ProWorkorderUtilityService.cs +++ b/DOAN.Service/MES/Product/ProWorkorderUtilityService.cs @@ -3,6 +3,8 @@ using DOAN.Model.MES.product.Dto; using DOAN.Service.MES.product.IService; using Infrastructure; using Infrastructure.Attribute; +using linesider_screen_tool; +using Microsoft.AspNetCore.Routing.Template; namespace DOAN.Service.MES.product { @@ -15,6 +17,7 @@ namespace DOAN.Service.MES.product )] public class ProWorkorderUtilityService : BaseService, IProWorkorderUtilityService { + private readonly BartenderPrintHelper _printHelper = new(); /// /// 生成工单号 /// @@ -183,11 +186,94 @@ namespace DOAN.Service.MES.product /// /// /// - public Task PrintTicketsByTemplate(ProWorkorderExportDto param) + public string PrintTicketsByTemplate(ProWorkorderExportDto param) { - // 这里需要实现工单打印逻辑 - // 暂时返回成功 - return Task.FromResult((CustomException)null); + try + { + string templatePath = param.Path; + if (string.IsNullOrEmpty(templatePath)) + { + return "标签地址传入为空!"; + } + + // 检查文件是否存在 + if (!File.Exists(templatePath)) + { + return $"标签文件不存在: {templatePath}"; + } + + // 获取标签中的变量名 + var subString = _printHelper.GetNamedSubStrings(templatePath: templatePath); + + // 检查是否成功获取到标签变量 + if (subString == null || subString.Result == null || subString.Result.Count == 0) + { + return $"无法获取标签变量: {templatePath}"; + } + + // 获取工单信息 + List workorders = Context + .Queryable() + .Where(it => param.WorkorderArray.Contains(it.Workorder)) + .ToList(); + + if (workorders.Count == 0) + { + return "未找到指定的工单信息!"; + } + + // 对每个工单执行打印 + int successCount = 0; + foreach (ProWorkorder workorder in workorders) + { + try + { + // 为每个工单生成标签参数 + Dictionary parameters = new Dictionary + { + { "workorder", workorder.Workorder }, + { "stoveCode", workorder.StoveCode }, + { "qty", workorder.PlanNum.ToString() }, + //{ "productionName", workorder.productionName }, + //{ "productionCode", workorder.productionCode }, + //{ "materialCode", workorder.MaterialCode }, + //{ "drawingCode", workorder.DrawingCode } + }; + + // 过滤出标签中存在的参数 + var intersect = parameters.Where(x => subString.Result.Contains(x.Key)).ToDictionary( + x => x.Key, x => x.Value); + + // 执行打印 + bool printSuccess = _printHelper.PrintLabel( + templatePath: templatePath, + subStringValues: intersect); + + if (printSuccess) + { + successCount++; + } + } + catch (Exception ex) + { + // 记录单个工单打印失败的异常,但继续打印其他工单 + Console.WriteLine($"打印工单 {workorder.Workorder} 失败: {ex.Message}"); + } + } + + if (successCount > 0) + { + return $"ok"; + } + else + { + return $"工单标签打印失败: {templatePath}"; + } + } + catch (Exception ex) + { + return $"打印失败: {ex.Message}"; + } } } } diff --git a/DOAN.Service/MES/Quality/IPQC/QcScrapRecordsService.cs b/DOAN.Service/MES/Quality/IPQC/QcScrapRecordsService.cs index dfc36cc..e4a68df 100644 --- a/DOAN.Service/MES/Quality/IPQC/QcScrapRecordsService.cs +++ b/DOAN.Service/MES/Quality/IPQC/QcScrapRecordsService.cs @@ -311,103 +311,106 @@ namespace DOAN.Service.BZFM }) .ExecuteCommand(); + // 是否审批通过 if (isApproved) { + // 如果有工单则工单报废数增加 + if (!string.IsNullOrEmpty(record.Workorder)) + { + var workorderInfo = Context + .Queryable() + .Where(x => x.Workorder == record.Workorder) + .First(); + if (workorderInfo != null) + { + workorderInfo.DefectNum += (int)record.ScrapQuantity; + Context + .Updateable(workorderInfo) + .UpdateColumns(x => new { x.DefectNum }) + .ExecuteCommand(); + } + } + // 调用入库单服务添加到不良库 + InboundReceiptDto revokeRecepitDto = new() + { + ReceiptType = 1, + MaterialCode = record.ProductCode, + BatchNo = "000", + LocationCode = "BL001", + WarehouseCode = "WH007", + SupplierCode = record.SupplierCode, + StoveCode = record.StoveCode, + Workorder = record.Workorder, + WorkorderRaw = record.ScrapOrderNo, + Operator = approver, + Quantity = record.ScrapQuantity, + TransactionType = "不良入库", + Remarks = $"不良入库,不良单号:{record.ScrapOrderNo}", + }; + MmInventoryService mmInventoryService = new(); + string createReceiptresult = mmInventoryService.CreateInboundReceipt( + revokeRecepitDto + ); + if (createReceiptresult != "ok") + { + throw new Exception(createReceiptresult); + } + // 根据报废类型处理 - if (record.ScrapType == "转用") - { - // 转用单批准:生成新工单(这里需要根据实际业务调整工单生成逻辑) - // 注意:需要确保工单服务的命名空间和方法名正确 - // 示例:_proWorkorderService.GenerateWorkorder(record); - - // 如果有工单则工单报废数增加 - if (!string.IsNullOrEmpty(record.Workorder)) - { - var workorderInfo = Context - .Queryable() - .Where(x => x.Workorder == record.Workorder) - .First(); - if (workorderInfo != null) - { - workorderInfo.DefectNum += (int)record.ScrapQuantity; - Context - .Updateable(workorderInfo) - .UpdateColumns(x => new { x.DefectNum }) - .ExecuteCommand(); - } - } - - // 入报废库 - InboundReceiptDto revokeRecepitDto = new() - { - ReceiptType = 1, - MaterialCode = record.ProductCode, - BatchNo = "000", - LocationCode = "ZYK001", - WarehouseCode = "WH005", - SupplierCode = record.SupplierCode, - StoveCode = record.StoveCode, - Workorder = record.Workorder, - WorkorderRaw = record.ScrapOrderNo, - Operator = approver, - Quantity = record.ScrapQuantity, - TransactionType = "转用入库", - Remarks = $"转用入库,转用单号:{record.ScrapOrderNo}", - }; - MmInventoryService mmInventoryService = new(); - string createReceiptresult = mmInventoryService.CreateInboundReceipt( - revokeRecepitDto - ); - if (createReceiptresult != "ok") - { - throw new Exception(createReceiptresult); - } - } - else if (record.ScrapType == "报废") - { - // 报废单批准:生成入库单到报废库(这里需要根据实际业务调整入库逻辑) - // 如果有工单则工单报废数增加 - if (!string.IsNullOrEmpty(record.Workorder)) - { - var workorderInfo = Context - .Queryable() - .Where(x => x.Workorder == record.Workorder) - .First(); - if (workorderInfo != null) - { - workorderInfo.DefectNum += (int)record.ScrapQuantity; - Context - .Updateable(workorderInfo) - .UpdateColumns(x => new { x.DefectNum }) - .ExecuteCommand(); - } - } - // 调用入库单服务添加到报废库 - InboundReceiptDto revokeRecepitDto = new() - { - ReceiptType = 1, - MaterialCode = record.ProductCode, - BatchNo = "000", - LocationCode = "BFK001", - WarehouseCode = "WH006", - SupplierCode = record.SupplierCode, - StoveCode = record.StoveCode, - Workorder = record.Workorder, - WorkorderRaw = record.ScrapOrderNo, - Operator = approver, - Quantity = record.ScrapQuantity, - TransactionType = "报废入库", - Remarks = $"报废入库,报废单号:{record.ScrapOrderNo}", - }; - MmInventoryService mmInventoryService = new(); - string createReceiptresult = mmInventoryService.CreateInboundReceipt( - revokeRecepitDto - ); - if (createReceiptresult != "ok") - { - throw new Exception(createReceiptresult); - } - } + //if (record.ScrapType == "转用") + //{ + // InboundReceiptDto revokeRecepitDto = new() + // { + // ReceiptType = 1, + // MaterialCode = record.ProductCode, + // BatchNo = "000", + // LocationCode = "ZYK001", + // WarehouseCode = "WH005", + // SupplierCode = record.SupplierCode, + // StoveCode = record.StoveCode, + // Workorder = record.Workorder, + // WorkorderRaw = record.ScrapOrderNo, + // Operator = approver, + // Quantity = record.ScrapQuantity, + // TransactionType = "转用入库", + // Remarks = $"转用入库,转用单号:{record.ScrapOrderNo}", + // }; + // MmInventoryService mmInventoryService = new(); + // string createReceiptresult = mmInventoryService.CreateInboundReceipt( + // revokeRecepitDto + // ); + // if (createReceiptresult != "ok") + // { + // throw new Exception(createReceiptresult); + // } + //} + //else if (record.ScrapType == "报废") + //{ + // InboundReceiptDto revokeRecepitDto = new() + // { + // ReceiptType = 1, + // MaterialCode = record.ProductCode, + // BatchNo = "000", + // LocationCode = "BFK001", + // WarehouseCode = "WH006", + // SupplierCode = record.SupplierCode, + // StoveCode = record.StoveCode, + // Workorder = record.Workorder, + // WorkorderRaw = record.ScrapOrderNo, + // Operator = approver, + // Quantity = record.ScrapQuantity, + // TransactionType = "报废入库", + // Remarks = $"报废入库,报废单号:{record.ScrapOrderNo}", + // }; + // MmInventoryService mmInventoryService = new(); + // string createReceiptresult = mmInventoryService.CreateInboundReceipt( + // revokeRecepitDto + // ); + // if (createReceiptresult != "ok") + // { + // throw new Exception(createReceiptresult); + // } + //} } }); @@ -546,7 +549,7 @@ namespace DOAN.Service.BZFM .First(); // 单据不良数校验 - if(parm.ScrapQuantity + workorderInfo.DefectNum > workorderInfo.PlanNum) + if (parm.ScrapQuantity + workorderInfo.DefectNum > workorderInfo.PlanNum) { throw new Exception("不良数量总和大于计划数,请检查输入!"); } @@ -592,8 +595,6 @@ namespace DOAN.Service.BZFM return Context.Insertable(newQcScrapRecords).ExecuteReturnEntity(); } - - /// /// 根据不良品记录ID撤销不良品记录 /// diff --git a/DOAN.Service/Mobile/ReportFlowService.cs b/DOAN.Service/Mobile/ReportFlowService.cs index 61883d8..213d8ed 100644 --- a/DOAN.Service/Mobile/ReportFlowService.cs +++ b/DOAN.Service/Mobile/ReportFlowService.cs @@ -45,13 +45,13 @@ public class ReportFlowService : BaseService, IReportFlowServic /// /// 首工序报工(领料报工) /// - /// - /// - /// - /// - /// - /// - /// 原材料工单号 + /// 领料工单 + /// 工序 + /// 领料数 + /// 炉号 + /// 领料批次号 + /// 操作人 + /// 领料的原材料工单号 /// 领料库存id /// public bool FeedProcessReportwork( @@ -77,6 +77,10 @@ public class ReportFlowService : BaseService, IReportFlowServic { throw new Exception($"工单不存在,原材料无法出库:{workorder}"); } + if (proWorkorder.PlanNum == 0) + { + throw new Exception($"工单计划数为0,不可领料"); + } int routeId = Context .Queryable() @@ -97,27 +101,47 @@ public class ReportFlowService : BaseService, IReportFlowServic .First(); Worker = string.IsNullOrEmpty(NickName) ? Worker + "|异常人员|" : NickName; string stoveCode = stove_code; - // 检查采购记录 + // 检查采购记录获取炉号 var purchaseInfo = Context .Queryable() .Where(it => it.BatchNo == feed_order) .Where(it => it.TransactionType == "采购入库") .Where(it => it.Remarks == null || it.Remarks != "已撤销") .First(); + string newFeedOrder = feed_order; if (purchaseInfo != null) { stoveCode = purchaseInfo.StoveCode; } else { - throw new Exception($"未找到原材料采购批号:{feed_order}"); + // 根据原料工单领料逻辑获取炉号与原材料号 + if (!string.IsNullOrEmpty(workorderRaw)) + { + ProWorkorder proWorkorderRawInfo = Context + .Queryable() + .Where(it => it.Workorder == workorderRaw) + .First(); + if (proWorkorderRawInfo == null) + { + throw new Exception($"原材料工单不存在,无法领料:{workorderRaw}"); + } + stoveCode = proWorkorderRawInfo.StoveCode; + newFeedOrder = proWorkorderRawInfo.FeedOrder; + } + else + { + throw new Exception($"未找到原材料采购批号:{feed_order}"); + } + } Context.Ado.BeginTran(); + // 工单数据调整 Context .Updateable() .Where(it => it.Workorder == workorder) - .SetColumns(it => it.StoveCode == stoveCode) - .SetColumns(it => it.FeedOrder == feed_order) + .SetColumnsIF(!string.IsNullOrEmpty(stoveCode),it => it.StoveCode == stoveCode) + .SetColumnsIF(!string.IsNullOrEmpty(newFeedOrder),it => it.FeedOrder == newFeedOrder) .ExecuteCommand(); if (Exist) { @@ -153,6 +177,7 @@ public class ReportFlowService : BaseService, IReportFlowServic .Where(it => it.TransactionType == "领料出库") .Where(it => it.Remarks != "已撤销") .First(); + //1.检查领料是否超出限额 if (outRecordbound == null || proWorkorder.RouteCode == "10") { @@ -193,7 +218,7 @@ public class ReportFlowService : BaseService, IReportFlowServic .Where(it => it.Workorder == workorderRaw) .SetColumns(it => it.ShipmentNum == newShipmentNum) .ExecuteCommand(); - inboundRecord.Remarks += $"[已领料{finish_num}]"; + inboundRecord.Remarks += $"[{workorder}已领料{finish_num}]"; Context.Updateable(inboundRecord).ExecuteCommand(); } string supplierCode = string.Empty; @@ -248,11 +273,11 @@ public class ReportFlowService : BaseService, IReportFlowServic Context.Ado.RollbackTran(); throw new Exception("修改领料数量前请先撤销已有领料出库记录!"); } - if (outRecordbound.BatchNo != feed_order) - { - Context.Ado.RollbackTran(); - throw new Exception("修改领料号前请先撤销已有领料出库记录!"); - } + //if (outRecordbound.BatchNo != feed_order) + //{ + // Context.Ado.RollbackTran(); + // throw new Exception("修改领料号前请先撤销已有领料出库记录!"); + //} } Context.Ado.CommitTran(); return result > 0; diff --git a/Infrastructure/Helper/BartenderPrintHelper.cs b/Infrastructure/Helper/BartenderPrintHelper.cs new file mode 100644 index 0000000..50e46d5 --- /dev/null +++ b/Infrastructure/Helper/BartenderPrintHelper.cs @@ -0,0 +1,331 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SqlServer.Server; + +namespace linesider_screen_tool +{ + public sealed class BartenderPrintHelper : IDisposable + { + private const string BarTenderProgId = "BarTender.Application"; + private object? _btApp; // 声明为可空类型 + private int _disposedValue; + + public BartenderPrintHelper() + { + // 延迟初始化,在首次使用时创建 Bartender 应用实例 + } + + /// + /// 打印单个标签(同步) + /// + public bool PrintLabel( + string templatePath, + Dictionary subStringValues, + int copies = 1, + int serializedLabels = 1 + ) + { + ValidateParameters(templatePath, subStringValues); + + return ExecuteBartenderAction( + format => + { + SetPrintSettings(format, copies, serializedLabels); + SetSubStringValues(format, subStringValues); + InvokeMethod(format, "PrintOut", false, false); + return true; + }, + templatePath + ); + } + + public Task> GetNamedSubStrings(string templatePath) + { + dynamic format = null; + dynamic btApp = null; + try + { + btApp = Activator.CreateInstance(Type.GetTypeFromProgID(BarTenderProgId)); + format = btApp.Formats.Open(templatePath, false, null); + format.PrintSetup.IdenticalCopiesOfLabel = 1; + format.PrintSetup.NumberSerializedLabels = 1; + List subStrings = new List(); + dynamic namedSubStrings = format.NamedSubStrings; + + foreach (var iteam in namedSubStrings) + { + subStrings.Add(iteam.Name); + } + + return Task.FromResult(subStrings); + } + catch (Exception ex) + { + Console.WriteLine($"发生错误: {ex.Message}"); + return null; + } + finally + { + // 清理资源 + if (format != null) + { + Marshal.ReleaseComObject(format); + } + if (btApp != null) + { + btApp.Quit(0); + Marshal.ReleaseComObject(btApp); + } + } + } + + /// + /// 批量打印标签(高性能实现) + /// + public bool PrintBatchLabels( + string templatePath, + IEnumerable> labelsData, + int copiesPerLabel = 1, + int serializedLabels = 1 + ) + { + ValidateParameters(templatePath, labelsData); + + return ExecuteBartenderAction( + format => + { + SetPrintSettings(format, copiesPerLabel, serializedLabels); + bool allSuccess = true; + + foreach (var data in labelsData) + { + try + { + SetSubStringValues(format, data); + InvokeMethod(format, "PrintOut", false, false); + } + catch (Exception ex) + { + allSuccess = false; + LogError($"打印标签时出错: {ex.Message}"); + } + } + + return allSuccess; + }, + templatePath + ); + } + + private T ExecuteBartenderAction(Func action, string templatePath) + { + if (IsDisposed) + throw new ObjectDisposedException(nameof(BartenderPrintHelper)); + + EnsureBartenderInitialized(); + object? format = null; + + try + { + format = OpenFormat(templatePath); + return action(format!); + } + catch (Exception ex) + { + LogError($"执行 Bartender 操作失败: {ex.Message}"); + throw new InvalidOperationException("Bartender 操作失败", ex); + } + finally + { + ReleaseFormat(format); + } + } + + private void EnsureBartenderInitialized() + { + if (_btApp == null) + { + try + { + _btApp = Activator.CreateInstance(Type.GetTypeFromProgID(BarTenderProgId)); + SetProperty(_btApp, "Visible", false); // 确保应用程序不可见 + } + catch (Exception ex) + { + LogError($"初始化 Bartender 失败: {ex.Message}"); + throw new InvalidOperationException("无法初始化 Bartender 应用程序", ex); + } + } + } + + private object OpenFormat(string path) + { + EnsureBartenderInitialized(); + + try + { + var formats = GetProperty(_btApp!, "Formats"); + return InvokeMethod(formats, "Open", path, false, null); + } + catch (Exception ex) + { + LogError($"打开标签模板失败: {path}, 错误: {ex.Message}"); + throw new FileNotFoundException($"无法打开标签模板: {path}", ex); + } + } + + private void SetPrintSettings(object format, int copies, int serializedLabels) + { + var printSetup = GetProperty(format, "PrintSetup"); + SetProperty(printSetup, "IdenticalCopiesOfLabel", copies); + SetProperty(printSetup, "NumberSerializedLabels", serializedLabels); + } + + private void SetSubStringValues(object format, Dictionary values) + { + if (values == null) + return; + + foreach (var kv in values) + { + try + { + InvokeMethod(format, "SetNamedSubStringValue", kv.Key, kv.Value); + } + catch (Exception ex) + { + LogError($"设置标签变量失败: {kv.Key}={kv.Value}, 错误: {ex.Message}"); + throw new ArgumentException($"无法设置标签变量: {kv.Key}", ex); + } + } + } + + private void ReleaseFormat(object? format) + { + if (format == null) + return; + + try + { + InvokeMethod(format, "Close", 0); // 0 = BarTender.BtSaveOptions.btDoNotSaveChanges + } + catch (Exception ex) + { + LogError($"关闭标签格式失败: {ex.Message}"); + } + finally + { + ReleaseComObject(format); + } + } + + private void ReleaseComObject(object obj) + { + if (obj == null || !Marshal.IsComObject(obj)) + return; + + try + { + // 循环调用直到引用计数为 0 + int refCount; + while ((refCount = Marshal.ReleaseComObject(obj)) > 0) + { + LogError($"COM 对象引用计数: {refCount}"); + } + } + catch (Exception ex) + { + LogError($"释放 COM 对象失败: {ex.Message}"); + } + } + + private void ValidateParameters(string templatePath, object data = null) + { + if (string.IsNullOrEmpty(templatePath)) + throw new ArgumentNullException(nameof(templatePath), "标签模板路径不能为空"); + + if (!File.Exists(templatePath)) + throw new FileNotFoundException("指定的标签模板文件不存在", templatePath); + + if (data is IEnumerable> labelsData && labelsData == null) + throw new ArgumentNullException(nameof(labelsData), "标签数据不能为空"); + + if (data is Dictionary singleLabelData && singleLabelData == null) + throw new ArgumentNullException(nameof(singleLabelData), "标签数据不能为空"); + } + + private void LogError(string message) + { + // 这里可以添加实际的日志记录逻辑 + Console.WriteLine($"[ERROR] {message}"); + } + + private bool IsDisposed => Interlocked.CompareExchange(ref _disposedValue, 0, 0) != 0; + + public void Dispose() + { + // 使用 Interlocked.Exchange 确保线程安全 + if (Interlocked.Exchange(ref _disposedValue, 1) != 0) + return; + + try + { + // 使用临时变量确保线程安全 + var btApp = Interlocked.Exchange(ref _btApp, null); + + if (btApp != null && Marshal.IsComObject(btApp)) + { + try + { + // 使用反射调用 Quit 方法 + InvokeMethod(btApp, "Quit", 0); + } + catch (Exception ex) + { + LogError($"退出 Bartender 应用程序失败: {ex.Message}"); + } + finally + { + ReleaseComObject(btApp); + } + } + } + finally + { + GC.SuppressFinalize(this); + } + } + + ~BartenderPrintHelper() => Dispose(); + + // 反射辅助方法 + private object GetProperty(object obj, string propertyName) + { + return obj.GetType() + .InvokeMember(propertyName, BindingFlags.GetProperty, null, obj, null)!; + } + + private void SetProperty(object obj, string propertyName, object value) + { + obj.GetType() + .InvokeMember(propertyName, BindingFlags.SetProperty, null, obj, new[] { value }); + } + + private object InvokeMethod(object obj, string methodName, params object?[]? parameters) + { + return obj.GetType() + .InvokeMember( + methodName, + BindingFlags.InvokeMethod, + null, + obj, + parameters ?? Array.Empty() + )!; + } + } +}