print server with new concept

This commit is contained in:
EugeneTes
2026-01-16 12:00:24 +01:00
parent 147265358f
commit 5229cac987
30 changed files with 694 additions and 217 deletions

View File

@@ -4,6 +4,7 @@ using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System.Text;
using Inspectron.Epson.PrintServer.PrintServices;
namespace Inspectron.Epson;
@@ -14,6 +15,7 @@ namespace Inspectron.Epson;
public class HtmlPrinter : IEpsonPrinter
{
private readonly ILogger? _logger;
private readonly byte _id;
private readonly string _outputPath;
private readonly int _paperWidth;
private readonly string _imageDirectory;
@@ -60,11 +62,12 @@ public class HtmlPrinter : IEpsonPrinter
/// <param name="outputPath">Path to the output HTML file</param>
/// <param name="paperWidth">Paper width in pixels (default 384 for 80mm thermal printer)</param>
/// <param name="logger">Optional logger for diagnostic output</param>
public HtmlPrinter(string outputPath, int paperWidth = 384, ILogger? logger = null)
public HtmlPrinter(string outputPath, int paperWidth = 384, ILogger? logger = null, byte id=0x01)
{
_outputPath = outputPath;
_paperWidth = paperWidth;
_logger = logger;
_id = id;
// Create image directory next to HTML file
var directory = Path.GetDirectoryName(outputPath) ?? ".";
@@ -174,8 +177,7 @@ public class HtmlPrinter : IEpsonPrinter
/// <inheritdoc />
public Task<byte?> GetPrinterIdAsync()
{
_logger?.LogDebug("HtmlPrinter: Returning mock printer ID (0x01 = TM-T30III)");
return Task.FromResult<byte?>(0x01);
return Task.FromResult<byte?>(_id);
}
/// <inheritdoc />
@@ -320,9 +322,6 @@ public class HtmlPrinter : IEpsonPrinter
{
EnsureConnected();
_logger?.LogInformation("HtmlPrinter: Cutting paper");
_content.AppendLine("<div class=\"cut\"><span class=\"scissors\">✂</span></div>");
WriteHtmlFile();
return Task.CompletedTask;
}
@@ -493,7 +492,7 @@ public class HtmlPrinter : IEpsonPrinter
if (_isBiggerFontTM220)
{
effectiveHeightMultiplier *= 2;
effectiveWidthMultiplier *= 2;
}
int fontSize = baseHeight * effectiveHeightMultiplier;

View File

@@ -6,6 +6,7 @@ public class EpsonPrintServiceConfiguration: IPrinterConfigurationSource, IAssig
{
public string GroupId { get; set; }
public string RestaurantId { get; set; }
public string ApiKey { get; set; }
public Dictionary<string, PrinterConfiguration> PrinterConfigurations { get; set; }
public PrinterConfiguration GetConfiguration(string printerAddress)

View File

@@ -0,0 +1,26 @@
using Inspectron.Epson;
namespace EpsonPrintService;
/// <summary>
/// Simple console implementation that logs discovered printers to the console
/// </summary>
public class ConsoleDiscoveredPrintersReceiver : IDiscoveredPrintersReceiver
{
public Task OnPrintersDiscoveredAsync(IReadOnlyList<DiscoveredPrinter> printers)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Discovered printers changed. Found {printers.Count} printer(s):");
foreach (var printer in printers)
{
Console.WriteLine($" - {printer.IPAddress}");
}
if (printers.Count == 0)
{
Console.WriteLine(" (no printers found)");
}
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,15 @@
using Inspectron.Epson;
namespace EpsonPrintService;
/// <summary>
/// Interface for receiving discovered printer updates
/// </summary>
public interface IDiscoveredPrintersReceiver
{
/// <summary>
/// Called when the list of discovered printers has changed
/// </summary>
/// <param name="printers">The current list of discovered printers</param>
Task OnPrintersDiscoveredAsync(IReadOnlyList<DiscoveredPrinter> printers);
}

View File

@@ -0,0 +1,55 @@
using Inspectron.Epson;
using Inspectron.Epson.PrintServer.ConfigurationSources;
using System.Net.Http.Json;
namespace EpsonPrintService;
public class JamesDiscoveredPrintersReceiver: IDiscoveredPrintersReceiver
{
private readonly EpsonPrintServiceConfiguration _configuration;
private readonly HttpClient _httpClient;
public JamesDiscoveredPrintersReceiver(EpsonPrintServiceConfiguration configuration, HttpClient httpClient)
{
_configuration = configuration;
_httpClient = httpClient;
}
public async Task OnPrintersDiscoveredAsync(IReadOnlyList<DiscoveredPrinter> printers)
{
var apiKey = _configuration.ApiKey;
var request = new DiscoveredPrintersRequest
{
Printers = printers.Select(p => new DiscoveredPrinterDto
{
PrinterId = p.IPAddress,
Name = p.IPAddress,
Address = p.IPAddress,
Model = p.ModelName,
IsMain = false
}).ToList()
};
using var requestMessage = new HttpRequestMessage(HttpMethod.Post, "https://api.dev.gastrojames.ch/api/printerServer/discovered-printers");
requestMessage.Headers.Add("X-Printer-Server-Key", apiKey);
requestMessage.Content = JsonContent.Create(request);
var response = await _httpClient.SendAsync(requestMessage);
response.EnsureSuccessStatusCode();
}
private class DiscoveredPrintersRequest
{
public List<DiscoveredPrinterDto> Printers { get; set; } = new();
}
private class DiscoveredPrinterDto
{
public string PrinterId { get; set; } = "";
public string Name { get; set; } = "";
public string Address { get; set; } = "";
public string Model { get; set; } = "";
public bool IsMain { get; set; }
}
}

View File

@@ -20,7 +20,7 @@ public class SignalRPrintJobSource: IPrintJobSource
_logger = logger;
_connection = new HubConnectionBuilder()
.WithUrl("https://api.stage.gastrojames.ch/hubs/internal?access_token=https://api.stage.gastrojames.ch/hubs/internal")
.WithUrl("https://api.dev.gastrojames.ch/hubs/printer-servers")
.WithAutomaticReconnect(new InfiniteRetryPolicy())
.Build();
@@ -92,8 +92,8 @@ public class SignalRPrintJobSource: IPrintJobSource
_logger.LogInformation("Received print job for working area: {PrintJob}", JsonSerializer.Serialize(args));
_printJobChannel.Writer.TryWrite(new PrintJob
{
AreaId = args.WorkingAreaId,
Document = args.Content,
IP = args.PrinterIp,
Document = args,
});
}
@@ -104,8 +104,15 @@ public class SignalRPrintJobSource: IPrintJobSource
public class PrintJobFromSignalR
{
[JsonPropertyName("workingAreaId")]
public string WorkingAreaId { get; set; }
[JsonPropertyName("printerIp")]
public string PrinterIp { get; set; }
[JsonPropertyName("logoUrl")]
public string? LogoUrl { get; set; }
[JsonPropertyName("receiptType")]
public int ReceiptType { get; set; }
[JsonPropertyName("content")]
public string Content { get; set; }
}

View File

@@ -34,14 +34,8 @@ public class PrintLoop
while (!_cancellationSource!.Token.IsCancellationRequested)
{
var job = await _jobSource.GetNextJobAsync(_cancellationToken);
var assigned = _assignedPrinterRepository.GetAssignedPrinter(job.AreaId);
if (assigned == null)
{
_logger.LogWarning("Received print job for unassigned area {AreaId}, skipping", job.AreaId);
continue;
}
_printServer.SubmitJob(assigned, job);
_printServer.SubmitJob(job.IP, job);
}
}

View File

@@ -1,5 +1,6 @@
using Microsoft.Extensions.Logging;
using System.Reflection;
using Inspectron.Epson.PrintServer.Printers;
namespace Inspectron.Epson.PrintServer.PrintServices;
@@ -7,12 +8,14 @@ public class EpsonPrintService: IPrintService
{
private readonly ILogger _logger;
private readonly IPrinterFactory _printerFactory;
private readonly IWrapperPrinterFactory _wrapperPrinterFactory;
private readonly IPrinterConfigurationSource _printerConfigurationSource;
public EpsonPrintService(ILogger logger, IPrinterFactory printerFactory, IPrinterConfigurationSource printerConfigurationSource)
public EpsonPrintService(ILogger logger, IPrinterFactory printerFactory, IWrapperPrinterFactory wrapperPrinterFactory, IPrinterConfigurationSource printerConfigurationSource)
{
_logger = logger;
_printerFactory = printerFactory;
_wrapperPrinterFactory = wrapperPrinterFactory;
_printerConfigurationSource = printerConfigurationSource;
}
@@ -21,7 +24,7 @@ public class EpsonPrintService: IPrintService
try
{
var configuration = _printerConfigurationSource.GetConfiguration(printerIp);
await using var epsonPrinter = new EpsonPrinter(_logger);
await using var epsonPrinter = _printerFactory.CreatePrinter();
string address;
int port = 9100;
var printerAddress = configuration.Address;
@@ -45,19 +48,46 @@ public class EpsonPrintService: IPrintService
{
throw new InvalidOperationException("Printer is offline or out of paper.");
}
var printerAdapter = _printerFactory.CreatePrinterFromId(printerId.Value, epsonPrinter);
var printerAdapter = _wrapperPrinterFactory.CreatePrinterFromId(printerId.Value, epsonPrinter);
await printerAdapter.InitAsync();
if (configuration.LogoFilename != null)
if (!string.IsNullOrEmpty(job.Document.LogoUrl))
{
await printerAdapter.PrintImageAsync(configuration.LogoFilename);
// Download logo image
var logoFileName = Path.GetFileName(new Uri(job.Document.LogoUrl).LocalPath);
var logoCachePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logos");
// Ensure logos cache directory exists
if (!Directory.Exists(logoCachePath))
{
Directory.CreateDirectory(logoCachePath);
}
var cachedLogoPath = Path.Combine(logoCachePath, logoFileName);
// Download only if not already cached
if (!File.Exists(cachedLogoPath))
{
using var httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromSeconds(10);
var logoData = await httpClient.GetByteArrayAsync(job.Document.LogoUrl);
await File.WriteAllBytesAsync(cachedLogoPath, logoData);
_logger.LogInformation("Downloaded logo {LogoFileName} from {LogoUrl}", logoFileName, job.Document.LogoUrl);
}
else
{
_logger.LogDebug("Using cached logo {LogoFileName}", logoFileName);
}
await printerAdapter.PrintImageAsync(cachedLogoPath);
}
await printerAdapter.SetFontSizeAsync(configuration.FontSize);
await printerAdapter.PrintTextAsync(job.Document);
await printerAdapter.PrintTextAsync(job.Document.Content);
await Task.Delay(200);
status = await epsonPrinter.GetPrinterStatusAsync();
if (!status.IsOnline)

View File

@@ -1,6 +0,0 @@
namespace Inspectron.Epson.PrintServer.PrintServices;
public interface IPrinterFactory
{
IPrinter CreatePrinterFromId(byte printerId, EpsonPrinter epsonPrinter);
}

View File

@@ -0,0 +1,6 @@
namespace Inspectron.Epson.PrintServer.PrintServices;
public interface IWrapperPrinterFactory
{
IPrinter CreatePrinterFromId(byte printerId, IEpsonPrinter epsonPrinter);
}

View File

@@ -0,0 +1,18 @@
using Microsoft.Extensions.Logging;
namespace Inspectron.Epson.PrintServer.Printers;
public class EpsonPrinterFactory:IPrinterFactory
{
private readonly ILogger _logger;
public EpsonPrinterFactory(ILogger logger)
{
_logger = logger;
}
public IEpsonPrinter CreatePrinter()
{
return new EpsonPrinter(_logger);
}
}

View File

@@ -0,0 +1,12 @@
using Microsoft.Extensions.Logging;
namespace Inspectron.Epson.PrintServer.Printers;
public class HtmlPrinterFactory(string filename,int paperWidth=384, byte type=0x1, ILogger logger=null) : IPrinterFactory
{
public IEpsonPrinter CreatePrinter()
{
return new HtmlPrinter(filename,paperWidth: paperWidth, id:type);
}
}

View File

@@ -0,0 +1,6 @@
namespace Inspectron.Epson.PrintServer.Printers;
public interface IPrinterFactory
{
IEpsonPrinter CreatePrinter();
}

View File

@@ -2,9 +2,9 @@
namespace Inspectron.Epson.PrintServer.Printers;
public class PrinterFactory:IPrinterFactory
public class PrinterWrapperFactory:IWrapperPrinterFactory
{
public IPrinter CreatePrinterFromId(byte printerId, EpsonPrinter epsonPrinter)
public IPrinter CreatePrinterFromId(byte printerId, IEpsonPrinter epsonPrinter)
{
switch (printerId)
{

View File

@@ -8,9 +8,9 @@ namespace Inspectron.Epson.PrintServer.Printers;
public class TM_T30IIITranslated:IPrinter
{
private readonly EpsonPrinter _printer;
private readonly IEpsonPrinter _printer;
public TM_T30IIITranslated(EpsonPrinter printer)
public TM_T30IIITranslated(IEpsonPrinter printer)
{
_printer = printer;
}

View File

@@ -1,14 +1,16 @@
using Inspectron.Epson.PrintServer.Printers.Utils;
using Inspectron.Epson.PrintServer.Printers.Utils.KitchenReceipt;
using Inspectron.Epson.PrintServer.PrintServices;
using System.Reflection;
using System.Text.Json;
namespace Inspectron.Epson.PrintServer.Printers;
public class TM_U220IITranslated:IPrinter
{
private readonly EpsonPrinter _printer;
private readonly IEpsonPrinter _printer;
public TM_U220IITranslated(EpsonPrinter printer)
public TM_U220IITranslated(IEpsonPrinter printer)
{
_printer = printer;
}
@@ -34,11 +36,10 @@ public class TM_U220IITranslated:IPrinter
public async Task PrintTextAsync(string text)
{
ReceiptTranslator translator = new ReceiptTranslator();
var ast = translator.ParseReceipt(text);
KitchenReceiptPrinter printer = new KitchenReceiptPrinter(lineWidth:33,21);
var commands = printer.ConvertToCommands((TranslatedKitchenReceipt)ast);
foreach (KitchenPrintCommand command in commands)
var deserializedReceipt = JsonSerializer.Deserialize<KitchenReceipt>(text);
KitchenReceiptConverter translator = new KitchenReceiptConverter(bigLineWidth: 20, lineWidth: 33);
var commands = translator.ConvertToPrintCommands(deserializedReceipt);
foreach (var command in commands)
{
await _printer.SetBiggerFontTM220(command.IsBig);
await Task.Delay(200);
@@ -49,12 +50,7 @@ public class TM_U220IITranslated:IPrinter
await _printer.PrintTextAsync(command.Text + "\n");
await Task.Delay(200);
}
//await _printer.PrintTextAsync(text);
//var lines = text.Split('\n').Length;
//for (int i = 0; i < lines; i++)
//{
// await Task.Delay(200);
//}
await _printer.FeedLinesAsync(10);
await Task.Delay(1000);
}

View File

@@ -24,6 +24,7 @@ public class FinalReceipt
public string ThankYouMessage { get; set; }
public string GoodbyeMessageLine1 { get; set; }
public string GoodbyeMessageLine2 { get; set; }
public PaymentTerminalReceipt TerminalReceipt { get; set; }
}
public class ReceiptItem
@@ -43,4 +44,23 @@ public class TaxInfo
public decimal Net { get; set; }
public decimal TaxAmount { get; set; }
public string Currency { get; set; }
}
public class PaymentTerminalReceipt
{
public string ReceiptType { get; set; } // e.g., "*** Kundenbeleg ***"
public string BookingType { get; set; } // e.g., "Buchung"
public string PaymentSystem { get; set; } // e.g., "TWINT"
public string TransactionNumber { get; set; }
public DateTime TransactionDateTime { get; set; }
public string TerminalId { get; set; }
public string AID { get; set; }
public string TransactionSeqCount { get; set; }
public string TransactionRefNo { get; set; }
public string AuthCode { get; set; }
public string AcquirerId { get; set; }
public decimal EftAmount { get; set; }
public decimal TipAmount { get; set; }
public decimal TotalEftAmount { get; set; }
public string Currency { get; set; }
}

View File

@@ -83,9 +83,6 @@ public class ReceiptConverter
commands.Add(new PrintCommand(""));
// Footer info
//commands.Add(new PrintCommand(Center($"Bedient von: {finalReceipt.WaiterName}", false)));
//commands.Add(new PrintCommand(Center($"Terminal: {finalReceipt.Terminal}", false)));
//commands.Add(new PrintCommand(Center($"Tisch: {finalReceipt.TableNumber}", false)));
commands.Add(new PrintCommand($"Bedient von:".PadLeft(_lineWidth / 2) + $" {finalReceipt.WaiterName}"));
commands.Add(new PrintCommand($"Terminal:".PadLeft(_lineWidth / 2) + $" {finalReceipt.Terminal}"));
commands.Add(new PrintCommand($"Tisch:".PadLeft(_lineWidth / 2)+$" {finalReceipt.TableNumber}"));
@@ -94,6 +91,33 @@ public class ReceiptConverter
// VAT number
commands.Add(new PrintCommand(Center(finalReceipt.VatNumber, false)));
commands.Add(new PrintCommand(""));
// Payment Terminal Receipt
if (finalReceipt.TerminalReceipt != null)
{
var terminal = finalReceipt.TerminalReceipt;
commands.Add(new PrintCommand(Center(terminal.ReceiptType, false)));
commands.Add(new PrintCommand(Center(terminal.BookingType, false)));
commands.Add(new PrintCommand(Center(terminal.PaymentSystem, false)));
commands.Add(new PrintCommand(terminal.TransactionNumber));
commands.Add(new PrintCommand($"{terminal.TransactionDateTime:dd.MM.yyyy}".PadRight(_lineWidth/2) + $"{terminal.TransactionDateTime:HH:mm:ss}".PadLeft(_lineWidth/2)));
commands.Add(new PrintCommand($"Trm-Id:".PadRight(_lineWidth/2) + $"{terminal.TerminalId}".PadLeft(_lineWidth/2)));
commands.Add(new PrintCommand($"AID:".PadRight(_lineWidth/2) + $"{terminal.AID}".PadLeft(_lineWidth/2)));
commands.Add(new PrintCommand($"Trx. Seq-Cnt:".PadRight(_lineWidth/2) + $"{terminal.TransactionSeqCount}".PadLeft(_lineWidth/2)));
commands.Add(new PrintCommand($"Trx. Ref-No:".PadRight(_lineWidth/2) + $"{terminal.TransactionRefNo}".PadLeft(_lineWidth/2)));
commands.Add(new PrintCommand($"Auth. Code:".PadRight(_lineWidth/2) + $"{terminal.AuthCode}".PadLeft(_lineWidth/2)));
commands.Add(new PrintCommand($"Acq-Id:".PadRight(_lineWidth/2) + $"{terminal.AcquirerId}".PadLeft(_lineWidth/2)));
commands.Add(new PrintCommand($"EFT {terminal.Currency}:".PadRight(_lineWidth/2) + $"{terminal.EftAmount:F2}".PadLeft(_lineWidth/2)));
commands.Add(new PrintCommand($"Trinkgeld {terminal.Currency}:".PadRight(_lineWidth/2) + $"{terminal.TipAmount:F2}".PadLeft(_lineWidth/2)));
commands.Add(new PrintCommand($"Total-EFT {terminal.Currency}:".PadRight(_lineWidth/2) + $"{terminal.TotalEftAmount:F2}".PadLeft(_lineWidth/2)));
commands.Add(new PrintCommand(new string('-', _lineWidth)));
commands.Add(new PrintCommand(""));
}
// Thank you message
commands.Add(new PrintCommand(Center(finalReceipt.ThankYouMessage, false)));

View File

@@ -40,15 +40,11 @@
public List<Dish> Dishes { get; set; } = new List<Dish>();
/// <summary>
/// Additional items or notes (e.g., "Amuse Bouche")
/// </summary>
public List<string> AdditionalInfo { get; set; }
public KitchenReceipt()
{
Gangs = new List<Gang>();
AdditionalInfo = new List<string>();
}
}

View File

@@ -0,0 +1,27 @@
namespace Inspectron.Epson.PrintServer.Printers.Utils.OrderItems;
public class OrderItemsReceipt
{
public string OrderNumber { get; set; }
public string QRInfo { get; set; }
public DateTime DateTime { get; set; }
public string ClientNotes { get; set; }
public List<OrderItem> Items { get; set; } = new List<OrderItem>();
}
public class OrderItem
{
public int Number { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
public string Size { get; set; }
public List<string> SubItems { get; set; } = new List<string>();
public ItemModifications Modifications { get; set; }
public string Comment { get; set; }
}
public class ItemModifications
{
public List<string> Removed { get; set; } = new List<string>();
public List<string> Added { get; set; } = new List<string>();
}

View File

@@ -0,0 +1,183 @@
namespace Inspectron.Epson.PrintServer.Printers.Utils.OrderItems;
public class ReceiptConverter
{
private readonly int _lineWidth;
private readonly int _bigFontLineWidth;
public ReceiptConverter(int lineWidth = 42, int bigFontLineWidth = 21)
{
_lineWidth = lineWidth;
_bigFontLineWidth = bigFontLineWidth;
}
public List<PrintCommand> ConvertToPrintCommands(OrderItemsReceipt receipt)
{
var commands = new List<PrintCommand>();
// Header box
AddHeaderBox(commands, receipt);
// Client Notes
if (!string.IsNullOrEmpty(receipt.ClientNotes))
{
commands.Add(new PrintCommand($"|Client Notes: {receipt.ClientNotes}"));
}
// Separator line
commands.Add(new PrintCommand(new string('-', _lineWidth)));
// Items
foreach (var item in receipt.Items)
{
commands.Add(new PrintCommand(""));
AddOrderItem(commands, item);
}
return commands;
}
private void AddHeaderBox(List<PrintCommand> commands, OrderItemsReceipt receipt)
{
// Column widths (fixed proportions)
int col1Width = 8; // Order column
int col3Width = 12; // Date/Time column
int col2Width = _lineWidth - col1Width - col3Width - 4; // QR column (4 for borders |)
// Prepare column content with labels on first line, values on subsequent lines
var col1Lines = new List<string> { "Order:" };
col1Lines.AddRange(WrapText($"#{receipt.OrderNumber}", col1Width));
var col2Lines = new List<string> { "QR:" };
col2Lines.AddRange(WrapText(receipt.QRInfo, col2Width));
var col3Lines = new List<string>
{
$"{receipt.DateTime:dd.MM.yyyy}",
$"{receipt.DateTime:HH:mm:ss}"
};
// Determine max rows needed
int maxRows = Math.Max(Math.Max(col1Lines.Count, col2Lines.Count), col3Lines.Count);
// Pad columns to have equal rows
while (col1Lines.Count < maxRows) col1Lines.Add("");
while (col2Lines.Count < maxRows) col2Lines.Add("");
while (col3Lines.Count < maxRows) col3Lines.Add("");
// Top border
commands.Add(new PrintCommand("+" + new string('-', col1Width) + "+" + new string('-', col2Width) + "+" + new string('-', col3Width) + "+"));
// Content rows
for (int i = 0; i < maxRows; i++)
{
string row = "|" +
CenterInWidth(col1Lines[i], col1Width) + "|" +
CenterInWidth(col2Lines[i], col2Width) + "|" +
CenterInWidth(col3Lines[i], col3Width) + "|";
commands.Add(new PrintCommand(row, isBold: i == 0));
}
// Bottom border
commands.Add(new PrintCommand("+" + new string('-', col1Width) + "+" + new string('-', col2Width) + "+" + new string('-', col3Width) + "+"));
}
private List<string> WrapText(string text, int maxWidth)
{
var lines = new List<string>();
if (string.IsNullOrEmpty(text))
{
lines.Add("");
return lines;
}
// Trim to fit within column
while (text.Length > 0)
{
if (text.Length <= maxWidth)
{
lines.Add(text);
break;
}
// Find a good break point (prefer space)
int breakPoint = text.LastIndexOf(' ', Math.Min(maxWidth, text.Length - 1));
if (breakPoint <= 0 || breakPoint > maxWidth)
{
breakPoint = maxWidth;
}
lines.Add(text.Substring(0, breakPoint).TrimEnd());
text = text.Substring(breakPoint).TrimStart();
}
return lines;
}
private void AddOrderItem(List<PrintCommand> commands, OrderItem item)
{
// Main item line: "1 Item Name x1" or "3 Item Name Size: L x2"
string numberPart = $"{item.Number}";
string namePart = item.Name;
string quantityPart = $"x{item.Quantity}";
string sizePart = "";
if (!string.IsNullOrEmpty(item.Size))
{
sizePart = $"Size: {item.Size} ";
}
string rightPart = sizePart + quantityPart;
int spacesNeeded = _lineWidth - numberPart.Length - 1 - namePart.Length - rightPart.Length;
if (spacesNeeded < 1) spacesNeeded = 1;
string mainLine = numberPart + " " + namePart + new string(' ', spacesNeeded) + rightPart;
commands.Add(new PrintCommand(mainLine));
// Sub-items
foreach (var subItem in item.SubItems)
{
commands.Add(new PrintCommand($" - {subItem}"));
}
// Modifications
if (item.Modifications != null && (item.Modifications.Removed.Any() || item.Modifications.Added.Any()))
{
commands.Add(new PrintCommand(" Change of"));
commands.Add(new PrintCommand(" Ingredients:"));
foreach (var removed in item.Modifications.Removed)
{
commands.Add(new PrintCommand($" - {removed}"));
}
foreach (var added in item.Modifications.Added)
{
commands.Add(new PrintCommand($" + {added}"));
}
}
// Comment
if (!string.IsNullOrEmpty(item.Comment))
{
commands.Add(new PrintCommand($" Comment: {item.Comment}"));
}
}
private string CenterInWidth(string text, int width)
{
if (string.IsNullOrEmpty(text))
return new string(' ', width);
if (text.Length >= width)
return text.Substring(0, width);
int totalPadding = width - text.Length;
int leftPadding = totalPadding / 2;
int rightPadding = totalPadding - leftPadding;
return new string(' ', leftPadding) + text + new string(' ', rightPadding);
}
}

View File

@@ -1,13 +1,15 @@
public class PrintJob
using static Inspectron.Epson.PrintServer.JobSources.SignalRPrintJobSource;
public class PrintJob
{
public string AreaId { get; set; }
public string Document { get; set; }
// todo: change to ip address
public string IP { get; set; }
public PrintJobFromSignalR Document { get; set; }
public DateTime QueuedAt { get; set; }
public int RetryCount { get; set; }
public PrintJob()
{
AreaId = Guid.NewGuid().ToString();
QueuedAt = DateTime.UtcNow;
RetryCount = 0;
}

View File

@@ -34,7 +34,7 @@ public class PrinterQueue
{
_queue.Enqueue(job);
_signal.Release(); // Signal that there's work to do
_logger.LogInformation("Job {JobId} queued for printer {PrinterId}. Queue length: {QueueLength}", job.AreaId, PrinterIp, QueueLength);
_logger.LogInformation("Job queued for printer {PrinterId}. Queue length: {QueueLength}", PrinterIp, QueueLength);
}
public void Start()
@@ -50,7 +50,7 @@ public class PrinterQueue
{
_priorityQueue.Push(job);
_signal.Release();
_logger.LogInformation("Job {JobId} priority queued for printer {PrinterId}. Queue length: {QueueLength}", job.AreaId, PrinterIp, QueueLength);
_logger.LogInformation("Job priority queued for printer {PrinterId}. Queue length: {QueueLength}", PrinterIp, QueueLength);
}
private async Task ProcessQueueAsync(CancellationToken cancellationToken)
{
@@ -68,7 +68,7 @@ public class PrinterQueue
if (job!=null)
{
_logger.LogInformation("Processing job {JobId} on printer {PrinterId}", job.AreaId, PrinterIp);
_logger.LogInformation("Processing job on printer {PrinterId}", PrinterIp);
try
{
@@ -79,18 +79,18 @@ public class PrinterQueue
{
// Re-queue with retry logic
job.RetryCount++;
_logger.LogWarning("Job {JobId} failed, retrying ({RetryCount}/3)", job.AreaId, job.RetryCount);
_logger.LogWarning("Job failed, retrying ({RetryCount}/3)", job.RetryCount);
await Task.Delay(5000, cancellationToken); // Wait before retry
EnqueuePriority(job);
}
else
{
_logger.LogInformation("Job {JobId} completed successfully on printer {PrinterId}", job.AreaId, PrinterIp);
_logger.LogInformation("Job completed successfully on printer {PrinterId}", PrinterIp);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing job {JobId}", job.AreaId);
_logger.LogError(ex, "Error processing job");
// Handle exception (retry, log, etc.)
}
}