add money return / credit note receipt type with HTML test

This commit is contained in:
EugeneTes
2026-07-06 14:51:23 +02:00
parent 398a3e3db8
commit 91d2d18c80
8 changed files with 467 additions and 2 deletions

View File

@@ -0,0 +1,118 @@
namespace Inspectron.Epson.PrintServer.Printers.Utils.MoneyReturnReceipt;
/// <summary>
/// Data model for a refund / credit note ("money return") receipt.
/// Mirrors the sale receipt payload (see money_return_receipt_data.json) and adds
/// the credit-note specific fields that reference the original invoice being refunded.
/// </summary>
public class MoneyReturnReceipt
{
public string CompanyName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Phone { get; set; }
/// <summary>Credit note number (shown as "Credit note No. {ReceiptNumber}").</summary>
public string? ReceiptNumber { get; set; }
/// <summary>Date/time the credit note was issued.</summary>
public DateTime DateTime { get; set; }
public int Guests { get; set; }
public List<MoneyReturnReceiptItem> Items { get; set; } = new List<MoneyReturnReceiptItem>();
/// <summary>Refunded amount (positive). Rendered negative on the receipt.</summary>
public decimal Total { get; set; }
public string Currency { get; set; }
public decimal? TotalInAlternateCurrency { get; set; }
public string AlternateCurrency { get; set; }
public List<MoneyReturnSplitPaymentInfo> SplitPayments { get; set; } = new List<MoneyReturnSplitPaymentInfo>();
/// <summary>Method the original payment was made with (e.g. "Cash").</summary>
public string PaymentMethod { get; set; }
public decimal PaymentAmount { get; set; }
public List<MoneyReturnTaxInfo> TaxBreakdown { get; set; } = new List<MoneyReturnTaxInfo>();
public bool IsDebtor { get; set; } = false;
public string WaiterName { get; set; }
public string Terminal { get; set; }
public string TableNumber { get; set; }
public string VatNumber { get; set; }
public string ThankYouMessage { get; set; }
public string GoodbyeMessageLine1 { get; set; }
public string GoodbyeMessageLine2 { get; set; }
public List<MoneyReturnPaymentTerminalReceipt> TerminalReceipts { get; set; } = new List<MoneyReturnPaymentTerminalReceipt>();
public MoneyReturnDiscountInfo? DiscountInfo { get; set; }
// --- Credit-note specific fields ---
/// <summary>Number of the original invoice this credit note refunds ("Orig. invoice No.").</summary>
public string? OriginalInvoiceNumber { get; set; }
/// <summary>Date/time of the original invoice ("Orig. date").</summary>
public DateTime? OriginalDateTime { get; set; }
/// <summary>Refund type, e.g. "Full refund" or "Partial refund" ("Type").</summary>
public string? RefundType { get; set; }
/// <summary>Tip amount included in the refund. Shown as "incl. tip" when non-zero.</summary>
public decimal Tip { get; set; }
}
public class MoneyReturnReceiptItem
{
public int Quantity { get; set; }
public string Description { get; set; }
public decimal UnitPrice { get; set; }
public decimal TotalPrice { get; set; }
public string TaxCategory { get; set; }
public List<string> SubItems { get; set; }
}
public class MoneyReturnTaxInfo
{
public string Category { get; set; }
public decimal Rate { get; set; }
public decimal Gross { get; set; }
public decimal Net { get; set; }
public decimal TaxAmount { get; set; }
public string Currency { get; set; }
}
public class MoneyReturnSplitPaymentInfo
{
public string PaymentMethod { get; set; }
public decimal Amount { get; set; }
public string Currency { get; set; }
}
public class MoneyReturnDiscountInfo
{
public string Description { get; set; }
public decimal Amount { get; set; }
public string Currency { get; set; }
}
public class MoneyReturnPaymentTerminalReceipt
{
public string ReceiptType { get; set; }
public string BookingType { get; set; }
public string PaymentSystem { get; set; }
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

@@ -0,0 +1,22 @@
using System.Text.Json;
namespace Inspectron.Epson.PrintServer.Printers.Utils.MoneyReturnReceipt;
public class MoneyReturnReceiptConverter : IReceiptConverter
{
private readonly int _lineWidth;
private readonly int _bigFontLineWidth;
public MoneyReturnReceiptConverter(int lineWidth, int bigFontLineWidth)
{
_lineWidth = lineWidth;
_bigFontLineWidth = bigFontLineWidth;
}
public List<PrintCommand> Convert(string jsonContent)
{
var receipt = JsonSerializer.Deserialize<MoneyReturnReceipt>(jsonContent);
var converter = new ReceiptConverter(_lineWidth, _bigFontLineWidth);
return converter.ConvertToPrintCommands(receipt);
}
}

View File

@@ -0,0 +1,149 @@
namespace Inspectron.Epson.PrintServer.Printers.Utils.MoneyReturnReceipt;
/// <summary>
/// Builds the print command list for a refund / credit note receipt.
/// Layout follows money_return_receipt.jpg: company header, "Refund / Credit note"
/// title, credit note + original invoice reference block, the negative credit total,
/// the (negated) VAT breakdown and the goodbye footer.
/// </summary>
public class ReceiptConverter
{
private readonly int _lineWidth;
private readonly int _bigFontLineWidth;
public ReceiptConverter(int lineWidth = 48, int bigFontLineWidth = 24)
{
_lineWidth = lineWidth;
_bigFontLineWidth = bigFontLineWidth;
}
public List<PrintCommand> ConvertToPrintCommands(MoneyReturnReceipt receipt)
{
var commands = new List<PrintCommand>();
// Header - Company info
commands.Add(new PrintCommand(Center(receipt.CompanyName, false)));
commands.Add(new PrintCommand(Center(receipt.Address1, false)));
commands.Add(new PrintCommand(Center(receipt.Address2, false)));
commands.Add(new PrintCommand(Center(receipt.Phone, false)));
commands.Add(new PrintCommand(""));
commands.Add(new PrintCommand(""));
// Title between separators
commands.Add(new PrintCommand(new string('-', _lineWidth)));
commands.Add(new PrintCommand(Center("Refund / Credit note", false), isBold: true));
commands.Add(new PrintCommand(new string('-', _lineWidth)));
commands.Add(new PrintCommand(""));
// Credit note number + issue date
string creditNoteLabel = receipt.ReceiptNumber != null
? $"Credit note No. {receipt.ReceiptNumber}"
: "Credit note";
commands.Add(new PrintCommand(Justify(creditNoteLabel, $"{receipt.DateTime:HH:mm dd.MM.yyyy}")));
commands.Add(new PrintCommand(""));
// Original invoice reference block
commands.Add(new PrintCommand(Justify("Orig. invoice No.:", receipt.OriginalInvoiceNumber ?? "")));
if (receipt.OriginalDateTime.HasValue)
commands.Add(new PrintCommand(Justify("Orig. date:", $"{receipt.OriginalDateTime:HH:mm dd.MM.yyyy}")));
commands.Add(new PrintCommand(Justify("Orig. payment method:", receipt.PaymentMethod ?? "")));
if (!string.IsNullOrEmpty(receipt.RefundType))
commands.Add(new PrintCommand(Justify("Type:", receipt.RefundType)));
commands.Add(new PrintCommand(""));
commands.Add(new PrintCommand(new string('-', _lineWidth)));
commands.Add(new PrintCommand(""));
// Included tip
if (receipt.Tip != 0)
{
commands.Add(new PrintCommand($"incl. tip {receipt.Currency}: {receipt.Tip:F2}".PadLeft(_lineWidth)));
commands.Add(new PrintCommand(""));
}
// Credit total (refund is shown as a negative amount), big + bold, centered
string creditLine = $"Credit: {-receipt.Total:F2} {receipt.Currency}";
commands.Add(new PrintCommand(Center(creditLine, true), true, true));
commands.Add(new PrintCommand(""));
// Alternate currency
if (receipt.TotalInAlternateCurrency.HasValue)
{
string altCurrencyLine = $"{-receipt.TotalInAlternateCurrency:F2} {receipt.AlternateCurrency}";
commands.Add(new PrintCommand(altCurrencyLine.PadLeft(_lineWidth)));
commands.Add(new PrintCommand(""));
}
// Refund payment method line (e.g. "Cash: -20.00 CHF")
if (receipt.SplitPayments != null && receipt.SplitPayments.Count > 0)
{
foreach (var split in receipt.SplitPayments)
commands.Add(new PrintCommand($"{split.PaymentMethod}: {-split.Amount:F2} {split.Currency}".PadLeft(_lineWidth)));
}
else
{
commands.Add(new PrintCommand($"{receipt.PaymentMethod}: {-receipt.Total:F2} {receipt.Currency}".PadLeft(_lineWidth)));
}
commands.Add(new PrintCommand(""));
// Tax breakdown (amounts negated for the refund)
foreach (var tax in receipt.TaxBreakdown)
{
if (receipt.TaxBreakdown.IndexOf(tax) == 0)
{
string taxHeader = "VAT %".PadRight(_lineWidth / 4)
+ "Gross".PadLeft(_lineWidth / 4)
+ "Net".PadLeft(_lineWidth / 4)
+ "VAT".PadLeft(_lineWidth / 4);
commands.Add(new PrintCommand(taxHeader));
}
string taxDetail = ($"{tax.Category}:" + $"{tax.Rate}%".PadLeft(5)).PadRight(_lineWidth / 4)
+ $"{-tax.Gross:F2} {tax.Currency}".PadLeft(_lineWidth / 4)
+ $"{-tax.Net:F2} {tax.Currency}".PadLeft(_lineWidth / 4)
+ $"{-tax.TaxAmount:F2} {tax.Currency}".PadLeft(_lineWidth / 4);
commands.Add(new PrintCommand(taxDetail));
}
commands.Add(new PrintCommand(""));
if (receipt.TaxBreakdown.Count(x => x.Category != null && x.Category.ToLower() != "d") == 0)
{
commands.Add(new PrintCommand("Not subject to value added tax"));
}
commands.Add(new PrintCommand(""));
commands.Add(new PrintCommand(""));
// Footer / goodbye
commands.Add(new PrintCommand(Center(receipt.ThankYouMessage, false)));
commands.Add(new PrintCommand(Center(receipt.GoodbyeMessageLine1, false)));
commands.Add(new PrintCommand(Center(receipt.GoodbyeMessageLine2, false)));
return commands;
}
private string Justify(string left, string right)
{
left ??= "";
right ??= "";
int spaces = _lineWidth - left.Length - right.Length;
if (spaces < 1) spaces = 1;
return left + new string(' ', spaces) + right;
}
private string Center(string text, bool isBigFont)
{
int effectiveLineWidth = isBigFont ? _bigFontLineWidth : _lineWidth;
if (string.IsNullOrEmpty(text) || text.Length >= effectiveLineWidth)
return text;
int totalPadding = effectiveLineWidth - text.Length;
int leftPadding = totalPadding / 2;
return new string(' ', leftPadding) + text;
}
}

View File

@@ -20,6 +20,7 @@ public class ReceiptConverterFactory : IReceiptConverterFactory
EReceiptType.NextCourse => new KitchenReceipt.KitchenReceiptConverterAdapter(33, 20),
EReceiptType.OrdersOverview => new OrderItemsReceiptConverter(48, 24),
EReceiptType.Invoice => new InvoiceReceiptConverter(48, 24),
EReceiptType.MoneyReturn => new MoneyReturnReceipt.MoneyReturnReceiptConverter(48, 24),
_ => throw new ArgumentException($"Unknown receipt type: {receiptType}")
};
}
@@ -30,6 +31,7 @@ public class ReceiptConverterFactory : IReceiptConverterFactory
WorkareaTicket,
NextCourse,
OrdersOverview,
Invoice
Invoice,
MoneyReturn
}
}