final receipt update
This commit is contained in:
@@ -16,6 +16,39 @@
|
|||||||
<None Update="kitchen-u220.template">
|
<None Update="kitchen-u220.template">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="Samples\bar-default.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Samples\fallback.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Samples\final-receipt.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Samples\kitchen-default.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Samples\kitchen-u220.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Templates\assignments.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Templates\bar-default.template">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Templates\fallback.template">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Templates\final-receipt.template">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Templates\kitchen-default.template">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Templates\kitchen-u220.template">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -6,62 +6,22 @@ using Inspectron.Epson.Templates.Interpreter;
|
|||||||
using Inspectron.Epson.Templates.Language;
|
using Inspectron.Epson.Templates.Language;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
var receipt = new KitchenReceipt
|
|
||||||
{
|
|
||||||
Title = "Warme Küche",
|
|
||||||
TransactionDateTime = new DateTime(2025, 10, 23, 12, 18, 0),
|
|
||||||
ReceiptNumber = "132018166",
|
|
||||||
WaiterName = "Mariano Amato",
|
|
||||||
WaiterId = "32 (VK Restaurant)",
|
|
||||||
TableNumber = "22",
|
|
||||||
SpecialInstruction = "Next dish"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add dishes (this section can be empty if no dishes were ordered)
|
|
||||||
|
|
||||||
receipt.Gangs.Add(new Gang(2, "Gang"));
|
|
||||||
|
|
||||||
receipt.Gangs[0].Dishes.Add(new Dish(1, "Avocado Sashimi")
|
|
||||||
{
|
|
||||||
Modifications = new DishModifications
|
|
||||||
{
|
|
||||||
Removed = new List<string> { "Wasabi" },
|
|
||||||
Added = new List<string> { "Extra Ginger" }
|
|
||||||
},
|
|
||||||
Comment = "No soy sauce"
|
|
||||||
});
|
|
||||||
receipt.Gangs[0].Dishes.Add(new Dish(1, "Baby Spinach Salad with Truffl"));
|
|
||||||
receipt.Gangs[0].Dishes.Add(new Dish(1, "Beef Tataki")
|
|
||||||
{
|
|
||||||
Modifications = new DishModifications
|
|
||||||
{
|
|
||||||
Removed = new List<string> { "Onion" },
|
|
||||||
Added = new List<string> { "Extra Sauce" }
|
|
||||||
},
|
|
||||||
Comment = "Medium rare"
|
|
||||||
});
|
|
||||||
receipt.Gangs[0].Dishes.Add(new Dish(1, "Salmon Taco")
|
|
||||||
{
|
|
||||||
Modifications = new DishModifications
|
|
||||||
{
|
|
||||||
Added = new List<string> { "Extra Lime" }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add additional info
|
// Add additional info
|
||||||
|
|
||||||
var serializedReceipt = JsonSerializer.Serialize(receipt, new JsonSerializerOptions { WriteIndented = true });
|
|
||||||
|
|
||||||
var lexer = new Lexer(File.ReadAllText("kitchen-u220.template"));
|
|
||||||
var parser = new Parser(lexer.Tokenize().Tokens);
|
|
||||||
var template = parser.Parse().Template;
|
|
||||||
PrinterProfile _profileT30 = new("tm-t30iii", "TM-T30III", 48, 24, false);
|
PrinterProfile _profileT30 = new("tm-t30iii", "TM-T30III", 48, 24, false);
|
||||||
PrinterProfile _profile220 = new("tm-220", "TM-220", 33, 18, true);
|
PrinterProfile _profile220 = new("tm-220", "TM-220", 33, 18, true);
|
||||||
|
|
||||||
var interpreter = new TemplateInterpreter(_profile220);
|
|
||||||
var printCommands = interpreter.Interpret(template, serializedReceipt);
|
var printCommands = CreateCommands(_profileT30, "Templates\\final-receipt.template", "Samples\\final-receipt.json");
|
||||||
|
|
||||||
|
|
||||||
Console.WriteLine("=== PRINT COMMANDS ===\n");
|
Console.WriteLine("=== PRINT COMMANDS ===\n");
|
||||||
var printer = new HtmlPrinter(@".\test.html", paperWidth: 258);
|
var printer = new HtmlPrinter(@".\test.html", paperWidth: 384);
|
||||||
await printer.ConnectAsync("");
|
await printer.ConnectAsync("");
|
||||||
bool useDelay = false;
|
bool useDelay = false;
|
||||||
//var printer = new EpsonPrinter();
|
//var printer = new EpsonPrinter();
|
||||||
@@ -97,4 +57,17 @@ foreach (var command in printCommands)
|
|||||||
|
|
||||||
await printer.FeedLinesAsync(5);
|
await printer.FeedLinesAsync(5);
|
||||||
if (useDelay) await Task.Delay(200 * 5);
|
if (useDelay) await Task.Delay(200 * 5);
|
||||||
await printer.CutAsync();
|
await printer.CutAsync();
|
||||||
|
|
||||||
|
|
||||||
|
List<PrintCommand> CreateCommands(PrinterProfile printerProfile, string templateFile,string jsonFile)
|
||||||
|
{
|
||||||
|
var lexer = new Lexer(File.ReadAllText(templateFile));
|
||||||
|
var parser = new Parser(lexer.Tokenize().Tokens);
|
||||||
|
var template = parser.Parse().Template;
|
||||||
|
|
||||||
|
var interpreter = new TemplateInterpreter(printerProfile);
|
||||||
|
var printCommands = interpreter.Interpret(template, File.ReadAllText(jsonFile));
|
||||||
|
return printCommands;
|
||||||
|
|
||||||
|
}
|
||||||
64
EpsonTemplatesTest/Samples/bar-default.json
Normal file
64
EpsonTemplatesTest/Samples/bar-default.json
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"Title": "Bar",
|
||||||
|
"TransactionDateTime": "2025-10-23T20:30:00",
|
||||||
|
"ReceiptNumber": "132018300",
|
||||||
|
"WaiterName": "Thomas Weber",
|
||||||
|
"WaiterId": "28 (Bar Staff)",
|
||||||
|
"TableNumber": "B5",
|
||||||
|
"SpecialInstruction": "VIP Table",
|
||||||
|
"Gangs": [
|
||||||
|
{
|
||||||
|
"Id": 1,
|
||||||
|
"Name": "Round",
|
||||||
|
"Dishes": [
|
||||||
|
{
|
||||||
|
"Number": 2,
|
||||||
|
"Name": "Aperol Spritz",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": ["Extra Ice"]
|
||||||
|
},
|
||||||
|
"Comment": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Number": 1,
|
||||||
|
"Name": "Negroni",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": ["Orange Peel"],
|
||||||
|
"Added": []
|
||||||
|
},
|
||||||
|
"Comment": "Stirred, not shaken"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Number": 3,
|
||||||
|
"Name": "San Pellegrino 50cl",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": []
|
||||||
|
},
|
||||||
|
"Comment": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Dishes": [
|
||||||
|
{
|
||||||
|
"Number": 2,
|
||||||
|
"Name": "Espresso",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": []
|
||||||
|
},
|
||||||
|
"Comment": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Number": 1,
|
||||||
|
"Name": "Cappuccino",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": ["Oat Milk"]
|
||||||
|
},
|
||||||
|
"Comment": "Extra hot"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
4
EpsonTemplatesTest/Samples/fallback.json
Normal file
4
EpsonTemplatesTest/Samples/fallback.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"Title": "General Receipt",
|
||||||
|
"TransactionDateTime": "2025-12-01T14:00:00"
|
||||||
|
}
|
||||||
108
EpsonTemplatesTest/Samples/final-receipt.json
Normal file
108
EpsonTemplatesTest/Samples/final-receipt.json
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
"CompanyName": "Klingler Gastro AG",
|
||||||
|
"Address1": "Munzplatz 3",
|
||||||
|
"Address2": "CH-8001 Zurich",
|
||||||
|
"Phone": "043 321 22 22",
|
||||||
|
"ReceiptNumber": "1-81-202",
|
||||||
|
"DateTime": "2025-12-16T14:58:00",
|
||||||
|
"Guests": 2,
|
||||||
|
"Total": 160.00,
|
||||||
|
"Currency": "CHF",
|
||||||
|
"TotalInAlternateCurrency": 172.80,
|
||||||
|
"AlternateCurrency": "EUR",
|
||||||
|
"PaymentMethod": "MASTER",
|
||||||
|
"PaymentAmount": 160.00,
|
||||||
|
"WaiterName": "Yves",
|
||||||
|
"Terminal": "Hauptkasse ZH",
|
||||||
|
"TableNumber": "12",
|
||||||
|
"VatNumber": "CHE-449.635.880 MWST",
|
||||||
|
"ThankYouMessage": "Das Team bedankt sich herzlich fur Ihren",
|
||||||
|
"GoodbyeMessageLine1": "Besuch.",
|
||||||
|
"GoodbyeMessageLine2": "Auf Wiedersehen.",
|
||||||
|
"Items": [
|
||||||
|
{
|
||||||
|
"Quantity": 1,
|
||||||
|
"Description": "San Pellegrino 50cl",
|
||||||
|
"UnitPrice": 6.50,
|
||||||
|
"TotalPrice": 6.50,
|
||||||
|
"TaxCategory": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Quantity": 1,
|
||||||
|
"Description": "Panna 50cl",
|
||||||
|
"UnitPrice": 6.50,
|
||||||
|
"TotalPrice": 6.50,
|
||||||
|
"TaxCategory": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Quantity": 1,
|
||||||
|
"Description": "Granini Tomatensaft",
|
||||||
|
"UnitPrice": 5.50,
|
||||||
|
"TotalPrice": 5.50,
|
||||||
|
"TaxCategory": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Quantity": 2,
|
||||||
|
"Description": "Business Lunch Menu Seco",
|
||||||
|
"UnitPrice": 44.00,
|
||||||
|
"TotalPrice": 88.00,
|
||||||
|
"TaxCategory": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Quantity": 2,
|
||||||
|
"Description": "Brunello di Montalcino 1",
|
||||||
|
"UnitPrice": 16.00,
|
||||||
|
"TotalPrice": 32.00,
|
||||||
|
"TaxCategory": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Quantity": 2,
|
||||||
|
"Description": "Espresso",
|
||||||
|
"UnitPrice": 5.50,
|
||||||
|
"TotalPrice": 11.00,
|
||||||
|
"TaxCategory": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Quantity": 1,
|
||||||
|
"Description": "Tip",
|
||||||
|
"UnitPrice": 10.50,
|
||||||
|
"TotalPrice": 10.50,
|
||||||
|
"TaxCategory": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TaxBreakdown": [
|
||||||
|
{
|
||||||
|
"Category": "A",
|
||||||
|
"Rate": 8.1,
|
||||||
|
"Gross": 149.50,
|
||||||
|
"Net": 138.30,
|
||||||
|
"TaxAmount": 11.20,
|
||||||
|
"Currency": "CHF"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Category": "B",
|
||||||
|
"Rate": 0,
|
||||||
|
"Gross": 10.50,
|
||||||
|
"Net": 10.50,
|
||||||
|
"TaxAmount": 0.00,
|
||||||
|
"Currency": "CHF"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TerminalReceipt": {
|
||||||
|
"ReceiptType": "*** Kundenbeleg ***",
|
||||||
|
"BookingType": "Buchung",
|
||||||
|
"PaymentSystem": "TWINT",
|
||||||
|
"TransactionNumber": "XXXXXXXXXXXXXXX1494",
|
||||||
|
"TransactionDateTime": "2025-11-11T12:34:49",
|
||||||
|
"TerminalId": "31108834",
|
||||||
|
"AID": "A0000015749E",
|
||||||
|
"TransactionSeqCount": "6127",
|
||||||
|
"TransactionRefNo": "99036644599",
|
||||||
|
"AuthCode": "0d3396",
|
||||||
|
"AcquirerId": "2",
|
||||||
|
"EftAmount": 67.00,
|
||||||
|
"TipAmount": 6.70,
|
||||||
|
"TotalEftAmount": 73.70,
|
||||||
|
"Currency": "CHF"
|
||||||
|
}
|
||||||
|
}
|
||||||
60
EpsonTemplatesTest/Samples/kitchen-default.json
Normal file
60
EpsonTemplatesTest/Samples/kitchen-default.json
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"Title": "Warme Kuche",
|
||||||
|
"TransactionDateTime": "2025-10-23T12:18:00",
|
||||||
|
"ReceiptNumber": "132018166",
|
||||||
|
"WaiterName": "Mariano Amato",
|
||||||
|
"WaiterId": "32 (VK Restaurant)",
|
||||||
|
"TableNumber": "22",
|
||||||
|
"SpecialInstruction": "Next dish",
|
||||||
|
"Gangs": [
|
||||||
|
{
|
||||||
|
"Id": 1,
|
||||||
|
"Name": "Gang",
|
||||||
|
"Dishes": [
|
||||||
|
{
|
||||||
|
"Number": 1,
|
||||||
|
"Name": "Avocado Sashimi",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": ["Wasabi"],
|
||||||
|
"Added": ["Extra Ginger"]
|
||||||
|
},
|
||||||
|
"Comment": "No soy sauce"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Number": 1,
|
||||||
|
"Name": "Baby Spinach Salad with Truffl",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": []
|
||||||
|
},
|
||||||
|
"Comment": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": 2,
|
||||||
|
"Name": "Gang",
|
||||||
|
"Dishes": [
|
||||||
|
{
|
||||||
|
"Number": 1,
|
||||||
|
"Name": "Beef Tataki",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": ["Onion"],
|
||||||
|
"Added": ["Extra Sauce"]
|
||||||
|
},
|
||||||
|
"Comment": "Medium rare"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Number": 1,
|
||||||
|
"Name": "Salmon Taco",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": ["Extra Lime"]
|
||||||
|
},
|
||||||
|
"Comment": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Dishes": []
|
||||||
|
}
|
||||||
39
EpsonTemplatesTest/Samples/kitchen-u220.json
Normal file
39
EpsonTemplatesTest/Samples/kitchen-u220.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"Title": "Kalte Kuche",
|
||||||
|
"TransactionDateTime": "2025-11-15T18:45:00",
|
||||||
|
"ReceiptNumber": "132018200",
|
||||||
|
"WaiterName": "Sofia Mueller",
|
||||||
|
"WaiterId": "45 (VK Restaurant)",
|
||||||
|
"TableNumber": "8",
|
||||||
|
"SpecialInstruction": null,
|
||||||
|
"Gangs": [],
|
||||||
|
"Dishes": [
|
||||||
|
{
|
||||||
|
"Number": 2,
|
||||||
|
"Name": "Caesar Salad",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": ["Croutons"],
|
||||||
|
"Added": ["Extra Parmesan"]
|
||||||
|
},
|
||||||
|
"Comment": "Dressing on the side"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Number": 1,
|
||||||
|
"Name": "Carpaccio",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": []
|
||||||
|
},
|
||||||
|
"Comment": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Number": 3,
|
||||||
|
"Name": "Bruschetta",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": ["Extra Basil"]
|
||||||
|
},
|
||||||
|
"Comment": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
25
EpsonTemplatesTest/Templates/assignments.json
Normal file
25
EpsonTemplatesTest/Templates/assignments.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"assignments": [
|
||||||
|
{
|
||||||
|
"receiptType": 1,
|
||||||
|
"profileId": "tm-t30iii",
|
||||||
|
"template": "kitchen-default.template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"receiptType": 1,
|
||||||
|
"profileId": "tm-u220ii",
|
||||||
|
"template": "kitchen-u220.template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"receiptType": 1,
|
||||||
|
"profileId": null,
|
||||||
|
"template": "kitchen-default.template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"receiptType": 2,
|
||||||
|
"profileId": null,
|
||||||
|
"template": "bar-default.template"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fallbackTemplate": "fallback.template"
|
||||||
|
}
|
||||||
59
EpsonTemplatesTest/Templates/bar-default.template
Normal file
59
EpsonTemplatesTest/Templates/bar-default.template
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#red,big,center# {Title} #
|
||||||
|
---
|
||||||
|
|
||||||
|
#center# {TransactionDateTime:dd-MMM-yy HH:mm} Nr.:{ReceiptNumber} #
|
||||||
|
#center# {WaiterName} #
|
||||||
|
#center# {WaiterId} #
|
||||||
|
#big,bold,center# Tisch: {TableNumber} #
|
||||||
|
|
||||||
|
@if SpecialInstruction
|
||||||
|
|
||||||
|
#big,bold,center# {SpecialInstruction} #
|
||||||
|
|
||||||
|
@end
|
||||||
|
---
|
||||||
|
@foreach gang in Gangs
|
||||||
|
#red,center# {gang.Id}. {gang.Name} #
|
||||||
|
@foreach drink in gang.Dishes
|
||||||
|
{drink.Number}x {drink.Name}
|
||||||
|
@if drink.Modifications.Removed.count > 0
|
||||||
|
@foreach removed in drink.Modifications.Removed
|
||||||
|
#bold# - {removed} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if drink.Modifications.Added.count > 0
|
||||||
|
@foreach added in drink.Modifications.Added
|
||||||
|
#bold# + {added} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if drink.Comment
|
||||||
|
#bold# Comment: {drink.Comment} #
|
||||||
|
@end
|
||||||
|
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if Gangs.count > 0
|
||||||
|
---
|
||||||
|
@end
|
||||||
|
@foreach drink in Dishes
|
||||||
|
{drink.Number}x {drink.Name}
|
||||||
|
@if drink.Modifications.Removed.count > 0
|
||||||
|
@foreach removed in drink.Modifications.Removed
|
||||||
|
#bold# - {removed} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if drink.Modifications.Added.count > 0
|
||||||
|
@foreach added in drink.Modifications.Added
|
||||||
|
#bold# + {added} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if drink.Comment
|
||||||
|
#bold# Comment: {drink.Comment} #
|
||||||
|
@end
|
||||||
|
|
||||||
|
@end
|
||||||
|
@if Dishes.count > 0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
@end
|
||||||
9
EpsonTemplatesTest/Templates/fallback.template
Normal file
9
EpsonTemplatesTest/Templates/fallback.template
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#center# Receipt #
|
||||||
|
---
|
||||||
|
@if Title
|
||||||
|
{Title}
|
||||||
|
@end
|
||||||
|
@if TransactionDateTime
|
||||||
|
{TransactionDateTime:dd-MMM-yy HH:mm}
|
||||||
|
@end
|
||||||
|
---
|
||||||
60
EpsonTemplatesTest/Templates/final-receipt.template
Normal file
60
EpsonTemplatesTest/Templates/final-receipt.template
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#center# {CompanyName} #
|
||||||
|
#center# {Address1} #
|
||||||
|
#center# {Address2} #
|
||||||
|
#center# {Phone} #
|
||||||
|
|
||||||
|
@row bold
|
||||||
|
|24|Rechnung Nr. {ReceiptNumber}|24,right|{DateTime:HH:mm dd.MM.yyyy}
|
||||||
|
Guests: {Guests}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@foreach item in Items
|
||||||
|
{item.Quantity}x {item.Description} {right:{item.UnitPrice:F2} {item.TotalPrice:F2} {item.TaxCategory}}
|
||||||
|
@end
|
||||||
|
|
||||||
|
{right:---------}
|
||||||
|
|
||||||
|
#big,bold,center# Summe: {Total:F2} {Currency} #
|
||||||
|
|
||||||
|
@if TotalInAlternateCurrency
|
||||||
|
{right:{TotalInAlternateCurrency:F2} {AlternateCurrency}}
|
||||||
|
|
||||||
|
@end
|
||||||
|
#bold# {PaymentMethod} {right:{PaymentAmount:F2} {Currency}} #
|
||||||
|
|
||||||
|
@if TaxBreakdown.count > 0
|
||||||
|
MwSt % Brutto Netto MwSt
|
||||||
|
@foreach tax in TaxBreakdown
|
||||||
|
{tax.Category}: {tax.Rate}% {tax.Gross:F2} {tax.Currency} {tax.Net:F2} {tax.Currency} {tax.TaxAmount:F2} {tax.Currency}
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
|
||||||
|
{right:Bedient von:} {WaiterName}
|
||||||
|
{right:Terminal:} {Terminal}
|
||||||
|
{right:Tisch:} {TableNumber}
|
||||||
|
|
||||||
|
|
||||||
|
#center# {VatNumber} #
|
||||||
|
|
||||||
|
@if TerminalReceipt
|
||||||
|
#center# {TerminalReceipt.ReceiptType} #
|
||||||
|
#center# {TerminalReceipt.BookingType} #
|
||||||
|
#center# {TerminalReceipt.PaymentSystem} #
|
||||||
|
{TerminalReceipt.TransactionNumber}
|
||||||
|
{TerminalReceipt.TransactionDateTime:dd.MM.yyyy} {right:{TerminalReceipt.TransactionDateTime:HH:mm:ss}}
|
||||||
|
Trm-Id: {right:{TerminalReceipt.TerminalId}}
|
||||||
|
AID: {right:{TerminalReceipt.AID}}
|
||||||
|
Trx. Seq-Cnt: {right:{TerminalReceipt.TransactionSeqCount}}
|
||||||
|
Trx. Ref-No: {right:{TerminalReceipt.TransactionRefNo}}
|
||||||
|
Auth. Code: {right:{TerminalReceipt.AuthCode}}
|
||||||
|
Acq-Id: {right:{TerminalReceipt.AcquirerId}}
|
||||||
|
EFT {TerminalReceipt.Currency}: {right:{TerminalReceipt.EftAmount:F2}}
|
||||||
|
Trinkgeld {TerminalReceipt.Currency}: {right:{TerminalReceipt.TipAmount:F2}}
|
||||||
|
Total-EFT {TerminalReceipt.Currency}: {right:{TerminalReceipt.TotalEftAmount:F2}}
|
||||||
|
---
|
||||||
|
@end
|
||||||
|
|
||||||
|
#center# {ThankYouMessage} #
|
||||||
|
#center# {GoodbyeMessageLine1} #
|
||||||
|
#center# {GoodbyeMessageLine2} #
|
||||||
57
EpsonTemplatesTest/Templates/kitchen-default.template
Normal file
57
EpsonTemplatesTest/Templates/kitchen-default.template
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#red,big,tall,center# {Title} #
|
||||||
|
---
|
||||||
|
|
||||||
|
#center# {TransactionDateTime:dd-MMM-yy HH:mm} Nr.:{ReceiptNumber} #
|
||||||
|
#center# {WaiterName} #
|
||||||
|
#center# {WaiterId} #
|
||||||
|
#big,bold,center# Tisch: {TableNumber} #
|
||||||
|
|
||||||
|
@if SpecialInstruction
|
||||||
|
|
||||||
|
#big,bold,center# {SpecialInstruction} #
|
||||||
|
|
||||||
|
@end
|
||||||
|
---
|
||||||
|
@foreach gang in Gangs
|
||||||
|
#red,big,tall,center# {gang.Id}. {gang.Name} #
|
||||||
|
@foreach dish in gang.Dishes
|
||||||
|
#tall# {dish.Number}x {dish.Name} #
|
||||||
|
@if dish.Modifications.Removed.count > 0
|
||||||
|
@foreach removed in dish.Modifications.Removed
|
||||||
|
#bold,tall# - {removed} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if dish.Modifications.Added.count > 0
|
||||||
|
@foreach added in dish.Modifications.Added
|
||||||
|
#bold,tall# + {added} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if dish.Comment
|
||||||
|
#bold,tall# Comment: {dish.Comment} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if Gangs.count > 0
|
||||||
|
---
|
||||||
|
@end
|
||||||
|
@foreach dish in Dishes
|
||||||
|
#tall# {dish.Number}x {dish.Name} #
|
||||||
|
@if dish.Modifications.Removed.count > 0
|
||||||
|
@foreach removed in dish.Modifications.Removed
|
||||||
|
#bold,tall# - {removed} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if dish.Modifications.Added.count > 0
|
||||||
|
@foreach added in dish.Modifications.Added
|
||||||
|
#bold,tall# + {added} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if dish.Comment
|
||||||
|
#bold,tall# Comment: {dish.Comment} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if Dishes.count > 0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
@end
|
||||||
57
EpsonTemplatesTest/Templates/kitchen-u220.template
Normal file
57
EpsonTemplatesTest/Templates/kitchen-u220.template
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#red,big,tall,center# {Title} #
|
||||||
|
---
|
||||||
|
|
||||||
|
#center# {TransactionDateTime:dd-MMM-yy HH:mm} Nr.:{ReceiptNumber} #
|
||||||
|
#center# {WaiterName} #
|
||||||
|
#center# {WaiterId} #
|
||||||
|
#big,bold,center# Tisch: {TableNumber} #
|
||||||
|
|
||||||
|
@if SpecialInstruction
|
||||||
|
|
||||||
|
#big,bold,center# {SpecialInstruction} #
|
||||||
|
|
||||||
|
@end
|
||||||
|
---
|
||||||
|
@foreach gang in Gangs
|
||||||
|
#red,big,tall,center# {gang.Id}. {gang.Name} #
|
||||||
|
@foreach dish in gang.Dishes
|
||||||
|
#tall# {dish.Number}x {dish.Name} #
|
||||||
|
@if dish.Modifications.Removed.count > 0
|
||||||
|
@foreach removed in dish.Modifications.Removed
|
||||||
|
#bold,tall# - {removed} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if dish.Modifications.Added.count > 0
|
||||||
|
@foreach added in dish.Modifications.Added
|
||||||
|
#bold,tall# + {added} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if dish.Comment
|
||||||
|
#bold,tall# Comment: {dish.Comment} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if Gangs.count > 0
|
||||||
|
---
|
||||||
|
@end
|
||||||
|
@foreach dish in Dishes
|
||||||
|
#tall# {dish.Number}x {dish.Name} #
|
||||||
|
@if dish.Modifications.Removed.count > 0
|
||||||
|
@foreach removed in dish.Modifications.Removed
|
||||||
|
#bold,tall# - {removed} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if dish.Modifications.Added.count > 0
|
||||||
|
@foreach added in dish.Modifications.Added
|
||||||
|
#bold,tall# + {added} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if dish.Comment
|
||||||
|
#bold,tall# Comment: {dish.Comment} #
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
@if Dishes.count > 0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -60,178 +60,204 @@ using FinalReceipt = Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe.Fin
|
|||||||
//Console.ReadLine();
|
//Console.ReadLine();
|
||||||
|
|
||||||
|
|
||||||
//var receipt = new FinalReceipt
|
var receipt = new FinalReceipt
|
||||||
//{
|
{
|
||||||
// CompanyName = "Klingler Gastro AG",
|
CompanyName = "Klingler Gastro AG",
|
||||||
// Address1 = "Münzplatz 3",
|
Address1 = "Münzplatz 3",
|
||||||
// Address2 = "CH-8001 Zürich",
|
Address2 = "CH-8001 Zürich",
|
||||||
// Phone = "043 321 22 22",
|
Phone = "043 321 22 22",
|
||||||
// ReceiptNumber = "1-81-202",
|
ReceiptNumber = "1-81-202",
|
||||||
// DateTime = new DateTime(2025, 12, 16, 14, 58, 0),
|
DateTime = new DateTime(2025, 12, 16, 14, 58, 0),
|
||||||
// Guests = 2,
|
Guests = 2,
|
||||||
// Total = 160.00m,
|
Total = 160.00m,
|
||||||
// Currency = "CHF",
|
Currency = "CHF",
|
||||||
// TotalInAlternateCurrency = 172.80m,
|
TotalInAlternateCurrency = 172.80m,
|
||||||
// AlternateCurrency = "EUR",
|
AlternateCurrency = "EUR",
|
||||||
// PaymentMethod = "MASTER",
|
PaymentMethod = "MASTER",
|
||||||
// PaymentAmount = 160.00m,
|
PaymentAmount = 160.00m,
|
||||||
// WaiterName = "Yves",
|
WaiterName = "Yves",
|
||||||
// Terminal = "Hauptkasse ZH",
|
Terminal = "Hauptkasse ZH",
|
||||||
// TableNumber = "12",
|
TableNumber = "12",
|
||||||
// VatNumber = "CHE-449.635.880 MWST",
|
VatNumber = "CHE-449.635.880 MWST",
|
||||||
// ThankYouMessage = "Das Team bedankt sich herzlich für Ihren",
|
ThankYouMessage = "Das Team bedankt sich herzlich für Ihren",
|
||||||
// GoodbyeMessageLine1 = "Besuch.",
|
GoodbyeMessageLine1 = "Besuch.",
|
||||||
// GoodbyeMessageLine2 = "Auf Wiedersehen.",
|
GoodbyeMessageLine2 = "Auf Wiedersehen.",
|
||||||
// TerminalReceipt = new PaymentTerminalReceipt
|
SplitPayments = new List<SplitPaymentInfo>
|
||||||
// {
|
{
|
||||||
// ReceiptType = "*** Kundenbeleg ***",
|
new SplitPaymentInfo { PaymentMethod = "Cash", Amount = 50.00m, Currency = "CHF"},
|
||||||
// BookingType = "Buchung",
|
new SplitPaymentInfo { PaymentMethod = "Card", Amount = 110.00m, Currency = "CHF" }
|
||||||
// PaymentSystem = "TWINT",
|
},
|
||||||
// TransactionNumber = "XXXXXXXXXXXXXXX1494",
|
TerminalReceipts = new List<PaymentTerminalReceipt>
|
||||||
// TransactionDateTime = new DateTime(2025, 11, 11, 12, 34, 49),
|
{
|
||||||
// TerminalId = "31108834",
|
new PaymentTerminalReceipt
|
||||||
// AID = "A0000015749E",
|
{
|
||||||
// TransactionSeqCount = "6127",
|
ReceiptType = "*** Kundenbeleg ***",
|
||||||
// TransactionRefNo = "99036644599",
|
BookingType = "Buchung",
|
||||||
// AuthCode = "0d3396",
|
PaymentSystem = "TWINT",
|
||||||
// AcquirerId = "2",
|
TransactionNumber = "XXXXXXXXXXXXXXX1494",
|
||||||
// EftAmount = 67.00m,
|
TransactionDateTime = new DateTime(2025, 11, 11, 12, 34, 49),
|
||||||
// TipAmount = 6.70m,
|
TerminalId = "31108834",
|
||||||
// TotalEftAmount = 73.70m,
|
AID = "A0000015749E",
|
||||||
// Currency = "CHF"
|
TransactionSeqCount = "6127",
|
||||||
// }
|
TransactionRefNo = "99036644599",
|
||||||
//};
|
AuthCode = "0d3396",
|
||||||
|
AcquirerId = "2",
|
||||||
|
EftAmount = 67.00m,
|
||||||
|
TipAmount = 6.70m,
|
||||||
|
TotalEftAmount = 73.70m,
|
||||||
|
Currency = "CHF"
|
||||||
|
},
|
||||||
|
new PaymentTerminalReceipt
|
||||||
|
{
|
||||||
|
ReceiptType = "*** Kundenbeleg ***",
|
||||||
|
BookingType = "Buchung",
|
||||||
|
PaymentSystem = "MASTER",
|
||||||
|
TransactionNumber = "XXXXXXXXXXXXXXX2587",
|
||||||
|
TransactionDateTime = new DateTime(2025, 11, 11, 12, 36, 12),
|
||||||
|
TerminalId = "31108834",
|
||||||
|
AID = "A0000000041010",
|
||||||
|
TransactionSeqCount = "6128",
|
||||||
|
TransactionRefNo = "99036644600",
|
||||||
|
AuthCode = "1a4b2c",
|
||||||
|
AcquirerId = "1",
|
||||||
|
EftAmount = 93.00m,
|
||||||
|
TipAmount = 0.00m,
|
||||||
|
TotalEftAmount = 93.00m,
|
||||||
|
Currency = "CHF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//// Add items
|
// Add items
|
||||||
//receipt.Items.Add(new ReceiptItem
|
receipt.Items.Add(new ReceiptItem
|
||||||
//{
|
{
|
||||||
// Quantity = 1,
|
Quantity = 1,
|
||||||
// Description = "San Pellegrino 50cl",
|
Description = "San Pellegrino 50cl",
|
||||||
// UnitPrice = 6.50m,
|
UnitPrice = 6.50m,
|
||||||
// TotalPrice = 6.50m,
|
TotalPrice = 6.50m,
|
||||||
// TaxCategory = "A"
|
TaxCategory = "A"
|
||||||
//});
|
});
|
||||||
|
|
||||||
//receipt.Items.Add(new ReceiptItem
|
receipt.Items.Add(new ReceiptItem
|
||||||
//{
|
{
|
||||||
// Quantity = 1,
|
Quantity = 1,
|
||||||
// Description = "Panna 50cl",
|
Description = "Panna 50cl",
|
||||||
// UnitPrice = 6.50m,
|
UnitPrice = 6.50m,
|
||||||
// TotalPrice = 6.50m,
|
TotalPrice = 6.50m,
|
||||||
// TaxCategory = "A"
|
TaxCategory = "A"
|
||||||
//});
|
});
|
||||||
|
|
||||||
//receipt.Items.Add(new ReceiptItem
|
receipt.Items.Add(new ReceiptItem
|
||||||
//{
|
{
|
||||||
// Quantity = 1,
|
Quantity = 1,
|
||||||
// Description = "Granini Tomatensaft",
|
Description = "Granini Tomatensaft",
|
||||||
// UnitPrice = 5.50m,
|
UnitPrice = 5.50m,
|
||||||
// TotalPrice = 5.50m,
|
TotalPrice = 5.50m,
|
||||||
// TaxCategory = "A"
|
TaxCategory = "A"
|
||||||
//});
|
});
|
||||||
|
|
||||||
//receipt.Items.Add(new ReceiptItem
|
receipt.Items.Add(new ReceiptItem
|
||||||
//{
|
{
|
||||||
// Quantity = 2,
|
Quantity = 2,
|
||||||
// Description = "Business Lunch Menu Seco",
|
Description = "Business Lunch Menu Seco",
|
||||||
// UnitPrice = 44.00m,
|
UnitPrice = 44.00m,
|
||||||
// TotalPrice = 88.00m,
|
TotalPrice = 88.00m,
|
||||||
// TaxCategory = "A"
|
TaxCategory = "A"
|
||||||
//});
|
});
|
||||||
|
|
||||||
//receipt.Items.Add(new ReceiptItem
|
receipt.Items.Add(new ReceiptItem
|
||||||
//{
|
{
|
||||||
// Quantity = 2,
|
Quantity = 2,
|
||||||
// Description = "Brunello di Montalcino 1",
|
Description = "Brunello di Montalcino 1",
|
||||||
// UnitPrice = 16.00m,
|
UnitPrice = 16.00m,
|
||||||
// TotalPrice = 32.00m,
|
TotalPrice = 32.00m,
|
||||||
// TaxCategory = "A"
|
TaxCategory = "A"
|
||||||
//});
|
});
|
||||||
|
|
||||||
//receipt.Items.Add(new ReceiptItem
|
receipt.Items.Add(new ReceiptItem
|
||||||
//{
|
{
|
||||||
// Quantity = 2,
|
Quantity = 2,
|
||||||
// Description = "Espresso",
|
Description = "Espresso",
|
||||||
// UnitPrice = 5.50m,
|
UnitPrice = 5.50m,
|
||||||
// TotalPrice = 11.00m,
|
TotalPrice = 11.00m,
|
||||||
// TaxCategory = "A"
|
TaxCategory = "A"
|
||||||
//});
|
});
|
||||||
|
|
||||||
//receipt.Items.Add(new ReceiptItem
|
receipt.Items.Add(new ReceiptItem
|
||||||
//{
|
{
|
||||||
// Quantity = 1,
|
Quantity = 1,
|
||||||
// Description = "Tip",
|
Description = "Tip",
|
||||||
// UnitPrice = 10.50m,
|
UnitPrice = 10.50m,
|
||||||
// TotalPrice = 10.50m,
|
TotalPrice = 10.50m,
|
||||||
// TaxCategory = "B"
|
TaxCategory = "B"
|
||||||
//});
|
});
|
||||||
|
|
||||||
//// Add tax breakdown
|
// Add tax breakdown
|
||||||
//receipt.TaxBreakdown.Add(new TaxInfo
|
receipt.TaxBreakdown.Add(new TaxInfo
|
||||||
//{
|
{
|
||||||
// Category = "A",
|
Category = "A",
|
||||||
// Rate = 8.1m,
|
Rate = 8.1m,
|
||||||
// Gross = 149.50m,
|
Gross = 149.50m,
|
||||||
// Net = 138.30m,
|
Net = 138.30m,
|
||||||
// TaxAmount = 11.20m,
|
TaxAmount = 11.20m,
|
||||||
// Currency = "CHF"
|
Currency = "CHF"
|
||||||
//});
|
});
|
||||||
|
|
||||||
//receipt.TaxBreakdown.Add(new TaxInfo
|
receipt.TaxBreakdown.Add(new TaxInfo
|
||||||
//{
|
{
|
||||||
// Category = "B",
|
Category = "B",
|
||||||
// Rate = 0m,
|
Rate = 0m,
|
||||||
// Gross = 10.50m,
|
Gross = 10.50m,
|
||||||
// Net = 10.50m,
|
Net = 10.50m,
|
||||||
// TaxAmount = 0.00m,
|
TaxAmount = 0.00m,
|
||||||
// Currency = "CHF"
|
Currency = "CHF"
|
||||||
//});
|
});
|
||||||
|
|
||||||
//var serializedReceipt = JsonSerializer.Serialize(receipt, new JsonSerializerOptions { WriteIndented = true });
|
var serializedReceipt = JsonSerializer.Serialize(receipt, new JsonSerializerOptions { WriteIndented = true });
|
||||||
//var deserializedReceipt = JsonSerializer.Deserialize<FinalReceipt>(serializedReceipt);
|
var deserializedReceipt = JsonSerializer.Deserialize<FinalReceipt>(serializedReceipt);
|
||||||
//// Convert to print commands
|
// Convert to print commands
|
||||||
//var converter = new FinalReceiptConverter(lineWidth: 48, bigFontLineWidth: 24);
|
var converter = new FinalReceiptConverter(lineWidth: 48, bigFontLineWidth: 24);
|
||||||
//var printCommands = converter.Convert(serializedReceipt);
|
var printCommands = converter.Convert(serializedReceipt);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//// Print the commands
|
// Print the commands
|
||||||
//Console.WriteLine("=== PRINT COMMANDS ===\n");
|
Console.WriteLine("=== PRINT COMMANDS ===\n");
|
||||||
//var printer = new HtmlPrinter(@".\test.html", paperWidth: 384);
|
var printer = new HtmlPrinter(@".\test.html", paperWidth: 384);
|
||||||
//await printer.ConnectAsync("");
|
await printer.ConnectAsync("");
|
||||||
//await printer.SetAbsolutePrintPosition(42);
|
await printer.SetAbsolutePrintPosition(42);
|
||||||
//await printer.LoadImageAsync("ristorante-klinglers.ch-logo_white_bg.png", 300);
|
await printer.LoadImageAsync("ristorante-klinglers.ch-logo_white_bg.png", 300);
|
||||||
//await printer.PrintLoadedImage();
|
await printer.PrintLoadedImage();
|
||||||
//await printer.SetAbsolutePrintPosition(0);
|
await printer.SetAbsolutePrintPosition(0);
|
||||||
//await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
////await printer.FeedLinesAsync(1);
|
//await printer.FeedLinesAsync(1);
|
||||||
//await printer.SetCustomLineSpacing(22);
|
await printer.SetCustomLineSpacing(22);
|
||||||
//foreach (var command in printCommands)
|
foreach (var command in printCommands)
|
||||||
//{
|
{
|
||||||
// string attributes = "";
|
string attributes = "";
|
||||||
// if (command.IsBig) attributes += "[BIG] ";
|
if (command.IsBig) attributes += "[BIG] ";
|
||||||
// if (command.IsBold) attributes += "[BOLD] ";
|
if (command.IsBold) attributes += "[BOLD] ";
|
||||||
|
|
||||||
// Console.WriteLine($"{attributes}{command.Text}");
|
Console.WriteLine($"{attributes}{command.Text}");
|
||||||
|
|
||||||
// if (command.IsBig)
|
if (command.IsBig)
|
||||||
// {
|
{
|
||||||
// await printer.SetFontSizeAsync(2, 1);
|
await printer.SetFontSizeAsync(2, 1);
|
||||||
// }
|
}
|
||||||
// else
|
else
|
||||||
// {
|
{
|
||||||
// await printer.SetFontSizeAsync(1, 1);
|
await printer.SetFontSizeAsync(1, 1);
|
||||||
// }
|
}
|
||||||
// //await Task.Delay(200);
|
//await Task.Delay(200);
|
||||||
|
|
||||||
// await printer.SetEmphasized(command.IsBold);
|
await printer.SetEmphasized(command.IsBold);
|
||||||
// //await Task.Delay(200);
|
//await Task.Delay(200);
|
||||||
|
|
||||||
// await printer.PrintTextAsync(command.Text + "\n");
|
await printer.PrintTextAsync(command.Text + "\n");
|
||||||
// //await Task.Delay(200);
|
//await Task.Delay(200);
|
||||||
|
|
||||||
//}
|
}
|
||||||
|
|
||||||
|
|
||||||
//var receipt = new KitchenReceipt
|
//var receipt = new KitchenReceipt
|
||||||
@@ -332,93 +358,93 @@ using FinalReceipt = Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe.Fin
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
|
|
||||||
var receipt = new KitchenReceipt
|
//var receipt = new KitchenReceipt
|
||||||
{
|
//{
|
||||||
Title = "Warme Küche",
|
// Title = "Warme Küche",
|
||||||
TransactionDateTime = new DateTime(2025, 10, 23, 12, 18, 0),
|
// TransactionDateTime = new DateTime(2025, 10, 23, 12, 18, 0),
|
||||||
ReceiptNumber = "132018166",
|
// ReceiptNumber = "132018166",
|
||||||
WaiterName = "Mariano Amato",
|
// WaiterName = "Mariano Amato",
|
||||||
WaiterId = "32 (VK Restaurant)",
|
// WaiterId = "32 (VK Restaurant)",
|
||||||
TableNumber = "22",
|
// TableNumber = "22",
|
||||||
SpecialInstruction = "Next dish"
|
// SpecialInstruction = "Next dish"
|
||||||
};
|
//};
|
||||||
|
|
||||||
// Add dishes (this section can be empty if no dishes were ordered)
|
//// Add dishes (this section can be empty if no dishes were ordered)
|
||||||
|
|
||||||
receipt.Gangs.Add(new Gang(2, "Gang"));
|
//receipt.Gangs.Add(new Gang(2, "Gang"));
|
||||||
|
|
||||||
receipt.Gangs[0].Dishes.Add(new Dish(1, "Avocado Sashimi")
|
//receipt.Gangs[0].Dishes.Add(new Dish(1, "Avocado Sashimi")
|
||||||
{
|
//{
|
||||||
Modifications = new DishModifications
|
// Modifications = new DishModifications
|
||||||
{
|
// {
|
||||||
Removed = new List<string> { "Wasabi" },
|
// Removed = new List<string> { "Wasabi" },
|
||||||
Added = new List<string> { "Extra Ginger" }
|
// Added = new List<string> { "Extra Ginger" }
|
||||||
},
|
// },
|
||||||
Comment = "No soy sauce"
|
// Comment = "No soy sauce"
|
||||||
});
|
//});
|
||||||
receipt.Gangs[0].Dishes.Add(new Dish(1, "Baby Spinach Salad with Truffl"));
|
//receipt.Gangs[0].Dishes.Add(new Dish(1, "Baby Spinach Salad with Truffl"));
|
||||||
receipt.Gangs[0].Dishes.Add(new Dish(1, "Beef Tataki")
|
//receipt.Gangs[0].Dishes.Add(new Dish(1, "Beef Tataki")
|
||||||
{
|
//{
|
||||||
Modifications = new DishModifications
|
// Modifications = new DishModifications
|
||||||
{
|
// {
|
||||||
Removed = new List<string> { "Onion" },
|
// Removed = new List<string> { "Onion" },
|
||||||
Added = new List<string> { "Extra Sauce" }
|
// Added = new List<string> { "Extra Sauce" }
|
||||||
},
|
// },
|
||||||
Comment = "Medium rare"
|
// Comment = "Medium rare"
|
||||||
});
|
//});
|
||||||
receipt.Gangs[0].Dishes.Add(new Dish(1, "Salmon Taco")
|
//receipt.Gangs[0].Dishes.Add(new Dish(1, "Salmon Taco")
|
||||||
{
|
//{
|
||||||
Modifications = new DishModifications
|
// Modifications = new DishModifications
|
||||||
{
|
// {
|
||||||
Added = new List<string> { "Extra Lime" }
|
// Added = new List<string> { "Extra Lime" }
|
||||||
}
|
// }
|
||||||
});
|
//});
|
||||||
|
|
||||||
// Add additional info
|
//// Add additional info
|
||||||
|
|
||||||
var serializedReceipt = JsonSerializer.Serialize(receipt, new JsonSerializerOptions { WriteIndented = true });
|
//var serializedReceipt = JsonSerializer.Serialize(receipt, new JsonSerializerOptions { WriteIndented = true });
|
||||||
var deserializedReceipt = JsonSerializer.Deserialize<KitchenReceipt>(serializedReceipt);
|
//var deserializedReceipt = JsonSerializer.Deserialize<KitchenReceipt>(serializedReceipt);
|
||||||
KitchenReceiptConverter converter = new KitchenReceiptConverter(bigLineWidth: 18, lineWidth: 33);
|
//KitchenReceiptConverter converter = new KitchenReceiptConverter(bigLineWidth: 18, lineWidth: 33);
|
||||||
var printCommands = converter.ConvertToPrintCommands(deserializedReceipt);
|
//var printCommands = converter.ConvertToPrintCommands(deserializedReceipt);
|
||||||
|
|
||||||
Console.WriteLine("=== PRINT COMMANDS ===\n");
|
//Console.WriteLine("=== PRINT COMMANDS ===\n");
|
||||||
//var printer = new HtmlPrinter(@".\test.html", paperWidth: 258);
|
////var printer = new HtmlPrinter(@".\test.html", paperWidth: 258);
|
||||||
//await printer.ConnectAsync("");
|
////await printer.ConnectAsync("");
|
||||||
bool useDelay=true;
|
//bool useDelay=true;
|
||||||
var printer = new EpsonPrinter();
|
//var printer = new EpsonPrinter();
|
||||||
await printer.ConnectAsync("127.0.0.1", 8888);
|
//await printer.ConnectAsync("127.0.0.1", 8888);
|
||||||
if (useDelay)await Task.Delay(200);
|
//if (useDelay)await Task.Delay(200);
|
||||||
await printer.FeedLinesAsync(1);
|
//await printer.FeedLinesAsync(1);
|
||||||
await printer.SetCustomLineSpacing(22);
|
//await printer.SetCustomLineSpacing(22);
|
||||||
foreach (var command in printCommands)
|
//foreach (var command in printCommands)
|
||||||
{
|
//{
|
||||||
string attributes = "";
|
// string attributes = "";
|
||||||
if (command.IsBig) attributes += "[BIG] ";
|
// if (command.IsBig) attributes += "[BIG] ";
|
||||||
if (command.IsBold) attributes += "[BOLD] ";
|
// if (command.IsBold) attributes += "[BOLD] ";
|
||||||
if (command.IsRed) attributes += "[RED] ";
|
// if (command.IsRed) attributes += "[RED] ";
|
||||||
|
|
||||||
Console.WriteLine($"{attributes}{command.Text}");
|
// Console.WriteLine($"{attributes}{command.Text}");
|
||||||
await printer.SetBiggerFontTM220(command.IsBig, command.IsTall| command.IsBig, secondaryFont: false);
|
// await printer.SetBiggerFontTM220(command.IsBig, command.IsTall| command.IsBig, secondaryFont: false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (useDelay) await Task.Delay(200);
|
// if (useDelay) await Task.Delay(200);
|
||||||
|
|
||||||
await printer.SetRedColor(command.IsRed);
|
// await printer.SetRedColor(command.IsRed);
|
||||||
|
|
||||||
if (useDelay) await Task.Delay(200);
|
// if (useDelay) await Task.Delay(200);
|
||||||
|
|
||||||
await printer.SetEmphasized(command.IsBold);
|
// await printer.SetEmphasized(command.IsBold);
|
||||||
if (useDelay) await Task.Delay(200);
|
// if (useDelay) await Task.Delay(200);
|
||||||
|
|
||||||
await printer.PrintTextAsync(command.Text + "\n");
|
// await printer.PrintTextAsync(command.Text + "\n");
|
||||||
if (useDelay) await Task.Delay(200);
|
// if (useDelay) await Task.Delay(200);
|
||||||
|
|
||||||
}
|
//}
|
||||||
|
|
||||||
await printer.FeedLinesAsync(5);
|
//await printer.FeedLinesAsync(5);
|
||||||
if (useDelay) await Task.Delay(200*5);
|
//if (useDelay) await Task.Delay(200*5);
|
||||||
await printer.CutAsync();
|
//await printer.CutAsync();
|
||||||
|
|
||||||
//await printer.SetDefaultLineSpacing();
|
//await printer.SetDefaultLineSpacing();
|
||||||
//await printer.PrintTextAsync(
|
//await printer.PrintTextAsync(
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ public class InterpreterLoopTests
|
|||||||
var interpreter = new TemplateInterpreter(_profile);
|
var interpreter = new TemplateInterpreter(_profile);
|
||||||
var commands = interpreter.Interpret(template, """{"Items": ["A", "B"]}""");
|
var commands = interpreter.Interpret(template, """{"Items": ["A", "B"]}""");
|
||||||
|
|
||||||
Assert.Contains(commands, c => c.Text == "0");
|
Assert.Contains(commands, c => c.Text == "0:A");
|
||||||
Assert.Contains(commands, c => c.Text == "1");
|
Assert.Contains(commands, c => c.Text == "1:B");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -67,8 +67,8 @@ public class InterpreterLoopTests
|
|||||||
var interpreter = new TemplateInterpreter(_profile);
|
var interpreter = new TemplateInterpreter(_profile);
|
||||||
var commands = interpreter.Interpret(template, """{"Items": ["A", "B"]}""");
|
var commands = interpreter.Interpret(template, """{"Items": ["A", "B"]}""");
|
||||||
|
|
||||||
Assert.Contains(commands, c => c.Text == "1");
|
Assert.Contains(commands, c => c.Text == "1. A");
|
||||||
Assert.Contains(commands, c => c.Text == "2");
|
Assert.Contains(commands, c => c.Text == "2. B");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -78,9 +78,7 @@ public class InterpreterLoopTests
|
|||||||
var interpreter = new TemplateInterpreter(_profile);
|
var interpreter = new TemplateInterpreter(_profile);
|
||||||
var commands = interpreter.Interpret(template, """{"Items": ["A", "B", "C"]}""");
|
var commands = interpreter.Interpret(template, """{"Items": ["A", "B", "C"]}""");
|
||||||
|
|
||||||
var firstCmds = commands.Where(c => c.Text.Contains("First")).ToList();
|
Assert.Contains(commands, c => c.Text == "First: A");
|
||||||
Assert.Single(firstCmds);
|
|
||||||
Assert.Contains(commands, c => c.Text == "A");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -90,8 +88,7 @@ public class InterpreterLoopTests
|
|||||||
var interpreter = new TemplateInterpreter(_profile);
|
var interpreter = new TemplateInterpreter(_profile);
|
||||||
var commands = interpreter.Interpret(template, """{"Items": ["A", "B", "C"]}""");
|
var commands = interpreter.Interpret(template, """{"Items": ["A", "B", "C"]}""");
|
||||||
|
|
||||||
Assert.Contains(commands, c => c.Text.Contains("Last"));
|
Assert.Contains(commands, c => c.Text == "Last: C");
|
||||||
Assert.Contains(commands, c => c.Text == "C");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -115,9 +112,8 @@ public class InterpreterLoopTests
|
|||||||
var interpreter = new TemplateInterpreter(_profile);
|
var interpreter = new TemplateInterpreter(_profile);
|
||||||
var commands = interpreter.Interpret(template, """{"Title": "List", "Items": ["A", "B"]}""");
|
var commands = interpreter.Interpret(template, """{"Title": "List", "Items": ["A", "B"]}""");
|
||||||
|
|
||||||
Assert.Contains(commands, c => c.Text == "List");
|
Assert.Contains(commands, c => c.Text == "List: A");
|
||||||
Assert.Contains(commands, c => c.Text == "A");
|
Assert.Contains(commands, c => c.Text == "List: B");
|
||||||
Assert.Contains(commands, c => c.Text == "B");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -102,4 +102,105 @@ public class InterpreterRowTests
|
|||||||
Assert.NotNull(rowCmd);
|
Assert.NotNull(rowCmd);
|
||||||
Assert.Equal(30, rowCmd.Text.Length);
|
Assert.Equal(30, rowCmd.Text.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Interpret_Row_WithBoldStyle_SetsBold()
|
||||||
|
{
|
||||||
|
var template = Parse("@row bold\n|20|Name|10,right|Price\n@endrow");
|
||||||
|
var interpreter = new TemplateInterpreter(_profile);
|
||||||
|
var commands = interpreter.Interpret(template, "{}");
|
||||||
|
|
||||||
|
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("Name") && c.Text.Contains("Price"));
|
||||||
|
Assert.NotNull(rowCmd);
|
||||||
|
Assert.True(rowCmd.IsBold);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Interpret_Row_WithBigStyle_SetsBig()
|
||||||
|
{
|
||||||
|
var template = Parse("@row big\n|12|Name|6,right|Price\n@endrow");
|
||||||
|
var interpreter = new TemplateInterpreter(_profile);
|
||||||
|
var commands = interpreter.Interpret(template, "{}");
|
||||||
|
|
||||||
|
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("Name") && c.Text.Contains("Price"));
|
||||||
|
Assert.NotNull(rowCmd);
|
||||||
|
Assert.True(rowCmd.IsBig);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Interpret_Row_WithTallStyle_SetsTall()
|
||||||
|
{
|
||||||
|
var template = Parse("@row tall\n|20|Name|10,right|Price\n@endrow");
|
||||||
|
var interpreter = new TemplateInterpreter(_profile);
|
||||||
|
var commands = interpreter.Interpret(template, "{}");
|
||||||
|
|
||||||
|
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("Name") && c.Text.Contains("Price"));
|
||||||
|
Assert.NotNull(rowCmd);
|
||||||
|
Assert.True(rowCmd.IsTall);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Interpret_Row_WithMultipleStyles_AppliesAll()
|
||||||
|
{
|
||||||
|
var template = Parse("@row bold, big\n|12|Name|6,right|Price\n@endrow");
|
||||||
|
var interpreter = new TemplateInterpreter(_profile);
|
||||||
|
var commands = interpreter.Interpret(template, "{}");
|
||||||
|
|
||||||
|
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("Name") && c.Text.Contains("Price"));
|
||||||
|
Assert.NotNull(rowCmd);
|
||||||
|
Assert.True(rowCmd.IsBold);
|
||||||
|
Assert.True(rowCmd.IsBig);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Interpret_Row_WithRedStyle_OnSupportedPrinter_SetsRed()
|
||||||
|
{
|
||||||
|
var redProfile = new PrinterProfile("test", "Test Printer", 48, 24, SupportsRed: true);
|
||||||
|
var template = Parse("@row red\n|20|Warning|10,right|!\n@endrow");
|
||||||
|
var interpreter = new TemplateInterpreter(redProfile);
|
||||||
|
var commands = interpreter.Interpret(template, "{}");
|
||||||
|
|
||||||
|
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("Warning"));
|
||||||
|
Assert.NotNull(rowCmd);
|
||||||
|
Assert.True(rowCmd.IsRed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Interpret_Row_WithRedStyle_OnUnsupportedPrinter_DoesNotSetRed()
|
||||||
|
{
|
||||||
|
var template = Parse("@row red\n|20|Warning|10,right|!\n@endrow");
|
||||||
|
var interpreter = new TemplateInterpreter(_profile); // _profile has supportsRed: false
|
||||||
|
var commands = interpreter.Interpret(template, "{}");
|
||||||
|
|
||||||
|
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("Warning"));
|
||||||
|
Assert.NotNull(rowCmd);
|
||||||
|
Assert.False(rowCmd.IsRed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Interpret_Row_WithoutStyles_NoStylesApplied()
|
||||||
|
{
|
||||||
|
var template = Parse("@row\n|20|Name|10,right|Price\n@endrow");
|
||||||
|
var interpreter = new TemplateInterpreter(_profile);
|
||||||
|
var commands = interpreter.Interpret(template, "{}");
|
||||||
|
|
||||||
|
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("Name") && c.Text.Contains("Price"));
|
||||||
|
Assert.NotNull(rowCmd);
|
||||||
|
Assert.False(rowCmd.IsBold);
|
||||||
|
Assert.False(rowCmd.IsBig);
|
||||||
|
Assert.False(rowCmd.IsTall);
|
||||||
|
Assert.False(rowCmd.IsRed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Interpret_Row_WithSpacingStyle_SetsLineSpacing()
|
||||||
|
{
|
||||||
|
var template = Parse("@row spacing:50\n|20|Name|10,right|Price\n@endrow");
|
||||||
|
var interpreter = new TemplateInterpreter(_profile);
|
||||||
|
var commands = interpreter.Interpret(template, "{}");
|
||||||
|
|
||||||
|
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("Name") && c.Text.Contains("Price"));
|
||||||
|
Assert.NotNull(rowCmd);
|
||||||
|
Assert.Equal(50, rowCmd.SetLineSpacing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,4 +216,70 @@ Line 3";
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Comment lines should not produce empty lines
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Tokenize_ConsecutiveCommentLines_ProduceNoNewLineTokens()
|
||||||
|
{
|
||||||
|
var lexer = new Lexer("@**@\n@**@\n@**@");
|
||||||
|
var result = lexer.Tokenize();
|
||||||
|
|
||||||
|
Assert.False(result.HasErrors);
|
||||||
|
// Should only have EOF token, no NewLine tokens for comment-only lines
|
||||||
|
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.NewLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Tokenize_CommentLineBetweenContent_NoExtraNewLines()
|
||||||
|
{
|
||||||
|
var lexer = new Lexer("Line1\n@**@\nLine2");
|
||||||
|
var result = lexer.Tokenize();
|
||||||
|
|
||||||
|
Assert.False(result.HasErrors);
|
||||||
|
// Should have exactly 2 NewLine tokens (after Line1 and after Line2)
|
||||||
|
var newLineCount = result.Tokens.Count(t => t.Type == TokenType.NewLine);
|
||||||
|
Assert.Equal(2, newLineCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Tokenize_MultipleConsecutiveCommentLines_NoNewLines()
|
||||||
|
{
|
||||||
|
var lexer = new Lexer("Header\n@* comment 1 *@\n@* comment 2 *@\n@* comment 3 *@\nFooter");
|
||||||
|
var result = lexer.Tokenize();
|
||||||
|
|
||||||
|
Assert.False(result.HasErrors);
|
||||||
|
Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Header");
|
||||||
|
Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Footer");
|
||||||
|
// Should have exactly 2 NewLine tokens (after Header and after Footer)
|
||||||
|
var newLineCount = result.Tokens.Count(t => t.Type == TokenType.NewLine);
|
||||||
|
Assert.Equal(2, newLineCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Tokenize_EmptyLinesBetweenContent_ProducesNewLines()
|
||||||
|
{
|
||||||
|
var lexer = new Lexer("Line1\n\n\nLine2");
|
||||||
|
var result = lexer.Tokenize();
|
||||||
|
|
||||||
|
Assert.False(result.HasErrors);
|
||||||
|
// Should have 4 NewLine tokens (after Line1, after each empty line, after Line2)
|
||||||
|
var newLineCount = result.Tokens.Count(t => t.Type == TokenType.NewLine);
|
||||||
|
Assert.Equal(4, newLineCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Tokenize_EmptyLinesPreserved_CommentsRemoved()
|
||||||
|
{
|
||||||
|
var lexer = new Lexer("Header\n\n@* comment *@\n\nFooter");
|
||||||
|
var result = lexer.Tokenize();
|
||||||
|
|
||||||
|
Assert.False(result.HasErrors);
|
||||||
|
// Should have 4 NewLine tokens: after Header, after first empty line, after second empty line, after Footer
|
||||||
|
// The comment line should NOT produce a NewLine
|
||||||
|
var newLineCount = result.Tokens.Count(t => t.Type == TokenType.NewLine);
|
||||||
|
Assert.Equal(4, newLineCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,78 +41,77 @@ public class TemplateInterpreter
|
|||||||
List<PrintCommand> commands,
|
List<PrintCommand> commands,
|
||||||
StyleContext style)
|
StyleContext style)
|
||||||
{
|
{
|
||||||
|
var lineBuffer = new StringBuilder();
|
||||||
|
var hasInlineContent = false;
|
||||||
|
|
||||||
|
void FlushLineBuffer()
|
||||||
|
{
|
||||||
|
if (hasInlineContent)
|
||||||
|
{
|
||||||
|
var text = ApplyAlignment(lineBuffer.ToString(), style);
|
||||||
|
commands.Add(CreateCommand(text, style));
|
||||||
|
lineBuffer.Clear();
|
||||||
|
hasInlineContent = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var node in nodes)
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
InterpretNode(node, context, commands, style);
|
switch (node)
|
||||||
|
{
|
||||||
|
// Inline nodes - accumulate into line buffer
|
||||||
|
case TextNode textNode:
|
||||||
|
lineBuffer.Append(textNode.Text);
|
||||||
|
hasInlineContent = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BindingNode bindingNode:
|
||||||
|
var element = context.Resolve(bindingNode.Path);
|
||||||
|
lineBuffer.Append(_formatter.Format(element, bindingNode.Format));
|
||||||
|
hasInlineContent = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Styled text - flush buffer, emit styled content, continue accumulating
|
||||||
|
case StyledTextNode styledNode:
|
||||||
|
FlushLineBuffer();
|
||||||
|
InterpretStyledText(styledNode, context, commands, style);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Block nodes - flush buffer first, then process
|
||||||
|
case SeparatorNode separatorNode:
|
||||||
|
FlushLineBuffer();
|
||||||
|
InterpretSeparator(separatorNode, commands, style);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EmptyLineNode:
|
||||||
|
FlushLineBuffer();
|
||||||
|
commands.Add(CreateCommand(string.Empty, style));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IfNode ifNode:
|
||||||
|
FlushLineBuffer();
|
||||||
|
InterpretIf(ifNode, context, commands, style);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ForeachNode foreachNode:
|
||||||
|
FlushLineBuffer();
|
||||||
|
InterpretForeach(foreachNode, context, commands, style);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RowNode rowNode:
|
||||||
|
FlushLineBuffer();
|
||||||
|
InterpretRow(rowNode, context, commands, style);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ColumnNode columnNode:
|
||||||
|
FlushLineBuffer();
|
||||||
|
InterpretColumn(columnNode, context, commands, style);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void InterpretNode(
|
// Flush any remaining content
|
||||||
ITemplateNode node,
|
FlushLineBuffer();
|
||||||
DataContext context,
|
|
||||||
List<PrintCommand> commands,
|
|
||||||
StyleContext style)
|
|
||||||
{
|
|
||||||
switch (node)
|
|
||||||
{
|
|
||||||
case TextNode textNode:
|
|
||||||
InterpretText(textNode, context, commands, style);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BindingNode bindingNode:
|
|
||||||
InterpretBinding(bindingNode, context, commands, style);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case StyledTextNode styledNode:
|
|
||||||
InterpretStyledText(styledNode, context, commands, style);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SeparatorNode separatorNode:
|
|
||||||
InterpretSeparator(separatorNode, commands, style);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EmptyLineNode:
|
|
||||||
commands.Add(CreateCommand(string.Empty, style));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IfNode ifNode:
|
|
||||||
InterpretIf(ifNode, context, commands, style);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ForeachNode foreachNode:
|
|
||||||
InterpretForeach(foreachNode, context, commands, style);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RowNode rowNode:
|
|
||||||
InterpretRow(rowNode, context, commands, style);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ColumnNode columnNode:
|
|
||||||
InterpretColumn(columnNode, context, commands, style);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InterpretText(
|
|
||||||
TextNode node,
|
|
||||||
DataContext context,
|
|
||||||
List<PrintCommand> commands,
|
|
||||||
StyleContext style)
|
|
||||||
{
|
|
||||||
var text = ApplyAlignment(node.Text, style);
|
|
||||||
commands.Add(CreateCommand(text, style));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InterpretBinding(
|
|
||||||
BindingNode node,
|
|
||||||
DataContext context,
|
|
||||||
List<PrintCommand> commands,
|
|
||||||
StyleContext style)
|
|
||||||
{
|
|
||||||
var element = context.Resolve(node.Path);
|
|
||||||
var text = _formatter.Format(element, node.Format);
|
|
||||||
text = ApplyAlignment(text, style);
|
|
||||||
commands.Add(CreateCommand(text, style));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InterpretStyledText(
|
private void InterpretStyledText(
|
||||||
@@ -217,7 +216,11 @@ public class TemplateInterpreter
|
|||||||
List<PrintCommand> commands,
|
List<PrintCommand> commands,
|
||||||
StyleContext style)
|
StyleContext style)
|
||||||
{
|
{
|
||||||
var lineWidth = style.IsBig ? _profile.BigLineWidth : _profile.LineWidth;
|
// Apply row-level styles
|
||||||
|
var rowStyle = style.Clone();
|
||||||
|
ApplyStyles(node.Styles, rowStyle);
|
||||||
|
|
||||||
|
var lineWidth = rowStyle.IsBig ? _profile.BigLineWidth : _profile.LineWidth;
|
||||||
var rowBuilder = new StringBuilder();
|
var rowBuilder = new StringBuilder();
|
||||||
|
|
||||||
foreach (var column in node.Columns)
|
foreach (var column in node.Columns)
|
||||||
@@ -270,7 +273,7 @@ public class TemplateInterpreter
|
|||||||
rowBuilder.Append(aligned);
|
rowBuilder.Append(aligned);
|
||||||
}
|
}
|
||||||
|
|
||||||
commands.Add(CreateCommand(rowBuilder.ToString(), style));
|
commands.Add(CreateCommand(rowBuilder.ToString(), rowStyle));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InterpretColumn(
|
private void InterpretColumn(
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public class Lexer
|
|||||||
private readonly List<LexerError> _errors = new();
|
private readonly List<LexerError> _errors = new();
|
||||||
private bool _inMultiLineComment;
|
private bool _inMultiLineComment;
|
||||||
private SourcePosition _multiLineCommentStart;
|
private SourcePosition _multiLineCommentStart;
|
||||||
|
private bool _isCommentOnlyLine;
|
||||||
|
|
||||||
private static readonly Regex StyleStartRegex = new(@"^#([a-zA-Z0-9,:]+)#", RegexOptions.Compiled);
|
private static readonly Regex StyleStartRegex = new(@"^#([a-zA-Z0-9,:]+)#", RegexOptions.Compiled);
|
||||||
private static readonly Regex BindingRegex = new(@"^\{([^}]+)\}", RegexOptions.Compiled);
|
private static readonly Regex BindingRegex = new(@"^\{([^}]+)\}", RegexOptions.Compiled);
|
||||||
@@ -40,8 +41,14 @@ public class Lexer
|
|||||||
|
|
||||||
while (_lineIndex < _lines.Count)
|
while (_lineIndex < _lines.Count)
|
||||||
{
|
{
|
||||||
|
_isCommentOnlyLine = false;
|
||||||
TokenizeLine(_lines[_lineIndex]);
|
TokenizeLine(_lines[_lineIndex]);
|
||||||
_tokens.Add(new Token(TokenType.NewLine, "\n", new SourcePosition(_lineIndex + 1, _columnIndex + 1)));
|
|
||||||
|
// Skip NewLine for comment-only lines and lines inside multi-line comments
|
||||||
|
if (!_isCommentOnlyLine && !_inMultiLineComment)
|
||||||
|
{
|
||||||
|
_tokens.Add(new Token(TokenType.NewLine, "\n", new SourcePosition(_lineIndex + 1, _columnIndex + 1)));
|
||||||
|
}
|
||||||
_lineIndex++;
|
_lineIndex++;
|
||||||
_columnIndex = 0;
|
_columnIndex = 0;
|
||||||
}
|
}
|
||||||
@@ -76,12 +83,14 @@ public class Lexer
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Comment close line with no content after
|
||||||
|
_isCommentOnlyLine = true;
|
||||||
_columnIndex = line.Length;
|
_columnIndex = line.Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Still in comment - skip entire line
|
// Still in comment - skip entire line (handled by _inMultiLineComment check in Tokenize)
|
||||||
_columnIndex = line.Length;
|
_columnIndex = line.Length;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -107,6 +116,8 @@ public class Lexer
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Comment-only line (no content after *@)
|
||||||
|
_isCommentOnlyLine = true;
|
||||||
_columnIndex = line.Length;
|
_columnIndex = line.Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,12 +125,14 @@ public class Lexer
|
|||||||
{
|
{
|
||||||
// Start of multi-line comment block
|
// Start of multi-line comment block
|
||||||
_inMultiLineComment = true;
|
_inMultiLineComment = true;
|
||||||
|
_isCommentOnlyLine = true;
|
||||||
_multiLineCommentStart = new SourcePosition(lineNumber, leadingWhitespace + 1);
|
_multiLineCommentStart = new SourcePosition(lineNumber, leadingWhitespace + 1);
|
||||||
_columnIndex = line.Length;
|
_columnIndex = line.Length;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Single-line comment to end of line
|
// Single-line comment to end of line
|
||||||
|
_isCommentOnlyLine = true;
|
||||||
_columnIndex = line.Length;
|
_columnIndex = line.Length;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -215,7 +228,7 @@ public class Lexer
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case "@row":
|
case "@row":
|
||||||
_tokens.Add(new Token(TokenType.Row, string.Empty, position));
|
_tokens.Add(new Token(TokenType.Row, value, position));
|
||||||
_columnIndex = trimmed.Length + leadingWhitespace;
|
_columnIndex = trimmed.Length + leadingWhitespace;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Inspectron.Epson.Templates.Language.Nodes;
|
namespace Inspectron.Epson.Templates.Language.Nodes;
|
||||||
|
|
||||||
public record RowNode(List<ColumnNode> Columns, SourcePosition Position) : ITemplateNode
|
public record RowNode(List<ColumnNode> Columns, IReadOnlyList<string> Styles, SourcePosition Position) : ITemplateNode
|
||||||
{
|
{
|
||||||
public IReadOnlyList<ITemplateNode> Children => Columns;
|
public IReadOnlyList<ITemplateNode> Children => Columns;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,6 +254,9 @@ public class Parser
|
|||||||
private RowNode ParseRow()
|
private RowNode ParseRow()
|
||||||
{
|
{
|
||||||
var rowToken = Current;
|
var rowToken = Current;
|
||||||
|
var styles = string.IsNullOrWhiteSpace(rowToken.Value)
|
||||||
|
? Array.Empty<string>()
|
||||||
|
: rowToken.Value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
Advance(); // consume @row
|
Advance(); // consume @row
|
||||||
SkipNewlines();
|
SkipNewlines();
|
||||||
|
|
||||||
@@ -300,7 +303,7 @@ public class Parser
|
|||||||
AddError("Expected @endrow to close @row block", Current.Position);
|
AddError("Expected @endrow to close @row block", Current.Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RowNode(columns, rowToken.Position);
|
return new RowNode(columns, styles, rowToken.Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ColumnNode ParseColumnDef()
|
private ColumnNode ParseColumnDef()
|
||||||
|
|||||||
64
Inspectron.Epson.Templates/Samples/bar-default.json
Normal file
64
Inspectron.Epson.Templates/Samples/bar-default.json
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"Title": "Bar",
|
||||||
|
"TransactionDateTime": "2025-10-23T20:30:00",
|
||||||
|
"ReceiptNumber": "132018300",
|
||||||
|
"WaiterName": "Thomas Weber",
|
||||||
|
"WaiterId": "28 (Bar Staff)",
|
||||||
|
"TableNumber": "B5",
|
||||||
|
"SpecialInstruction": "VIP Table",
|
||||||
|
"Gangs": [
|
||||||
|
{
|
||||||
|
"Id": 1,
|
||||||
|
"Name": "Round",
|
||||||
|
"Dishes": [
|
||||||
|
{
|
||||||
|
"Number": 2,
|
||||||
|
"Name": "Aperol Spritz",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": ["Extra Ice"]
|
||||||
|
},
|
||||||
|
"Comment": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Number": 1,
|
||||||
|
"Name": "Negroni",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": ["Orange Peel"],
|
||||||
|
"Added": []
|
||||||
|
},
|
||||||
|
"Comment": "Stirred, not shaken"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Number": 3,
|
||||||
|
"Name": "San Pellegrino 50cl",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": []
|
||||||
|
},
|
||||||
|
"Comment": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Dishes": [
|
||||||
|
{
|
||||||
|
"Number": 2,
|
||||||
|
"Name": "Espresso",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": []
|
||||||
|
},
|
||||||
|
"Comment": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Number": 1,
|
||||||
|
"Name": "Cappuccino",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": ["Oat Milk"]
|
||||||
|
},
|
||||||
|
"Comment": "Extra hot"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
4
Inspectron.Epson.Templates/Samples/fallback.json
Normal file
4
Inspectron.Epson.Templates/Samples/fallback.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"Title": "General Receipt",
|
||||||
|
"TransactionDateTime": "2025-12-01T14:00:00"
|
||||||
|
}
|
||||||
108
Inspectron.Epson.Templates/Samples/final-receipt.json
Normal file
108
Inspectron.Epson.Templates/Samples/final-receipt.json
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
"CompanyName": "Klingler Gastro AG",
|
||||||
|
"Address1": "Munzplatz 3",
|
||||||
|
"Address2": "CH-8001 Zurich",
|
||||||
|
"Phone": "043 321 22 22",
|
||||||
|
"ReceiptNumber": "1-81-202",
|
||||||
|
"DateTime": "2025-12-16T14:58:00",
|
||||||
|
"Guests": 2,
|
||||||
|
"Total": 160.00,
|
||||||
|
"Currency": "CHF",
|
||||||
|
"TotalInAlternateCurrency": 172.80,
|
||||||
|
"AlternateCurrency": "EUR",
|
||||||
|
"PaymentMethod": "MASTER",
|
||||||
|
"PaymentAmount": 160.00,
|
||||||
|
"WaiterName": "Yves",
|
||||||
|
"Terminal": "Hauptkasse ZH",
|
||||||
|
"TableNumber": "12",
|
||||||
|
"VatNumber": "CHE-449.635.880 MWST",
|
||||||
|
"ThankYouMessage": "Das Team bedankt sich herzlich fur Ihren",
|
||||||
|
"GoodbyeMessageLine1": "Besuch.",
|
||||||
|
"GoodbyeMessageLine2": "Auf Wiedersehen.",
|
||||||
|
"Items": [
|
||||||
|
{
|
||||||
|
"Quantity": 1,
|
||||||
|
"Description": "San Pellegrino 50cl",
|
||||||
|
"UnitPrice": 6.50,
|
||||||
|
"TotalPrice": 6.50,
|
||||||
|
"TaxCategory": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Quantity": 1,
|
||||||
|
"Description": "Panna 50cl",
|
||||||
|
"UnitPrice": 6.50,
|
||||||
|
"TotalPrice": 6.50,
|
||||||
|
"TaxCategory": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Quantity": 1,
|
||||||
|
"Description": "Granini Tomatensaft",
|
||||||
|
"UnitPrice": 5.50,
|
||||||
|
"TotalPrice": 5.50,
|
||||||
|
"TaxCategory": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Quantity": 2,
|
||||||
|
"Description": "Business Lunch Menu Seco",
|
||||||
|
"UnitPrice": 44.00,
|
||||||
|
"TotalPrice": 88.00,
|
||||||
|
"TaxCategory": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Quantity": 2,
|
||||||
|
"Description": "Brunello di Montalcino 1",
|
||||||
|
"UnitPrice": 16.00,
|
||||||
|
"TotalPrice": 32.00,
|
||||||
|
"TaxCategory": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Quantity": 2,
|
||||||
|
"Description": "Espresso",
|
||||||
|
"UnitPrice": 5.50,
|
||||||
|
"TotalPrice": 11.00,
|
||||||
|
"TaxCategory": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Quantity": 1,
|
||||||
|
"Description": "Tip",
|
||||||
|
"UnitPrice": 10.50,
|
||||||
|
"TotalPrice": 10.50,
|
||||||
|
"TaxCategory": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TaxBreakdown": [
|
||||||
|
{
|
||||||
|
"Category": "A",
|
||||||
|
"Rate": 8.1,
|
||||||
|
"Gross": 149.50,
|
||||||
|
"Net": 138.30,
|
||||||
|
"TaxAmount": 11.20,
|
||||||
|
"Currency": "CHF"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Category": "B",
|
||||||
|
"Rate": 0,
|
||||||
|
"Gross": 10.50,
|
||||||
|
"Net": 10.50,
|
||||||
|
"TaxAmount": 0.00,
|
||||||
|
"Currency": "CHF"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TerminalReceipt": {
|
||||||
|
"ReceiptType": "*** Kundenbeleg ***",
|
||||||
|
"BookingType": "Buchung",
|
||||||
|
"PaymentSystem": "TWINT",
|
||||||
|
"TransactionNumber": "XXXXXXXXXXXXXXX1494",
|
||||||
|
"TransactionDateTime": "2025-11-11T12:34:49",
|
||||||
|
"TerminalId": "31108834",
|
||||||
|
"AID": "A0000015749E",
|
||||||
|
"TransactionSeqCount": "6127",
|
||||||
|
"TransactionRefNo": "99036644599",
|
||||||
|
"AuthCode": "0d3396",
|
||||||
|
"AcquirerId": "2",
|
||||||
|
"EftAmount": 67.00,
|
||||||
|
"TipAmount": 6.70,
|
||||||
|
"TotalEftAmount": 73.70,
|
||||||
|
"Currency": "CHF"
|
||||||
|
}
|
||||||
|
}
|
||||||
60
Inspectron.Epson.Templates/Samples/kitchen-default.json
Normal file
60
Inspectron.Epson.Templates/Samples/kitchen-default.json
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"Title": "Warme Kuche",
|
||||||
|
"TransactionDateTime": "2025-10-23T12:18:00",
|
||||||
|
"ReceiptNumber": "132018166",
|
||||||
|
"WaiterName": "Mariano Amato",
|
||||||
|
"WaiterId": "32 (VK Restaurant)",
|
||||||
|
"TableNumber": "22",
|
||||||
|
"SpecialInstruction": "Next dish",
|
||||||
|
"Gangs": [
|
||||||
|
{
|
||||||
|
"Id": 1,
|
||||||
|
"Name": "Gang",
|
||||||
|
"Dishes": [
|
||||||
|
{
|
||||||
|
"Number": 1,
|
||||||
|
"Name": "Avocado Sashimi",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": ["Wasabi"],
|
||||||
|
"Added": ["Extra Ginger"]
|
||||||
|
},
|
||||||
|
"Comment": "No soy sauce"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Number": 1,
|
||||||
|
"Name": "Baby Spinach Salad with Truffl",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": []
|
||||||
|
},
|
||||||
|
"Comment": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": 2,
|
||||||
|
"Name": "Gang",
|
||||||
|
"Dishes": [
|
||||||
|
{
|
||||||
|
"Number": 1,
|
||||||
|
"Name": "Beef Tataki",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": ["Onion"],
|
||||||
|
"Added": ["Extra Sauce"]
|
||||||
|
},
|
||||||
|
"Comment": "Medium rare"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Number": 1,
|
||||||
|
"Name": "Salmon Taco",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": ["Extra Lime"]
|
||||||
|
},
|
||||||
|
"Comment": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Dishes": []
|
||||||
|
}
|
||||||
39
Inspectron.Epson.Templates/Samples/kitchen-u220.json
Normal file
39
Inspectron.Epson.Templates/Samples/kitchen-u220.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"Title": "Kalte Kuche",
|
||||||
|
"TransactionDateTime": "2025-11-15T18:45:00",
|
||||||
|
"ReceiptNumber": "132018200",
|
||||||
|
"WaiterName": "Sofia Mueller",
|
||||||
|
"WaiterId": "45 (VK Restaurant)",
|
||||||
|
"TableNumber": "8",
|
||||||
|
"SpecialInstruction": null,
|
||||||
|
"Gangs": [],
|
||||||
|
"Dishes": [
|
||||||
|
{
|
||||||
|
"Number": 2,
|
||||||
|
"Name": "Caesar Salad",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": ["Croutons"],
|
||||||
|
"Added": ["Extra Parmesan"]
|
||||||
|
},
|
||||||
|
"Comment": "Dressing on the side"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Number": 1,
|
||||||
|
"Name": "Carpaccio",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": []
|
||||||
|
},
|
||||||
|
"Comment": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Number": 3,
|
||||||
|
"Name": "Bruschetta",
|
||||||
|
"Modifications": {
|
||||||
|
"Removed": [],
|
||||||
|
"Added": ["Extra Basil"]
|
||||||
|
},
|
||||||
|
"Comment": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
58
Inspectron.Epson.Templates/Templates/final-receipt.template
Normal file
58
Inspectron.Epson.Templates/Templates/final-receipt.template
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#center# {CompanyName} #
|
||||||
|
#center# {Address1} #
|
||||||
|
#center# {Address2} #
|
||||||
|
#center# {Phone} #
|
||||||
|
|
||||||
|
|
||||||
|
#bold# {left:Rechnung Nr. {ReceiptNumber}} {right:{DateTime:HH:mm dd.MM.yyyy}} #
|
||||||
|
Guests: {Guests}
|
||||||
|
|
||||||
|
@foreach item in Items
|
||||||
|
{item.Quantity}x {item.Description} {right:{item.UnitPrice:F2} {item.TotalPrice:F2} {item.TaxCategory}}
|
||||||
|
@end
|
||||||
|
|
||||||
|
{right:---------}
|
||||||
|
|
||||||
|
#big,bold,center# Summe: {Total:F2} {Currency} #
|
||||||
|
|
||||||
|
@if TotalInAlternateCurrency
|
||||||
|
{right:{TotalInAlternateCurrency:F2} {AlternateCurrency}}
|
||||||
|
|
||||||
|
@end
|
||||||
|
#bold# {PaymentMethod} {right:{PaymentAmount:F2} {Currency}} #
|
||||||
|
|
||||||
|
@if TaxBreakdown.count > 0
|
||||||
|
MwSt % Brutto Netto MwSt
|
||||||
|
@foreach tax in TaxBreakdown
|
||||||
|
{tax.Category}: {tax.Rate}% {tax.Gross:F2} {tax.Currency} {tax.Net:F2} {tax.Currency} {tax.TaxAmount:F2} {tax.Currency}
|
||||||
|
@end
|
||||||
|
@end
|
||||||
|
|
||||||
|
{right:Bedient von:} {WaiterName}
|
||||||
|
{right:Terminal:} {Terminal}
|
||||||
|
{right:Tisch:} {TableNumber}
|
||||||
|
|
||||||
|
|
||||||
|
#center# {VatNumber} #
|
||||||
|
|
||||||
|
@if TerminalReceipt
|
||||||
|
#center# {TerminalReceipt.ReceiptType} #
|
||||||
|
#center# {TerminalReceipt.BookingType} #
|
||||||
|
#center# {TerminalReceipt.PaymentSystem} #
|
||||||
|
{TerminalReceipt.TransactionNumber}
|
||||||
|
{TerminalReceipt.TransactionDateTime:dd.MM.yyyy} {right:{TerminalReceipt.TransactionDateTime:HH:mm:ss}}
|
||||||
|
Trm-Id: {right:{TerminalReceipt.TerminalId}}
|
||||||
|
AID: {right:{TerminalReceipt.AID}}
|
||||||
|
Trx. Seq-Cnt: {right:{TerminalReceipt.TransactionSeqCount}}
|
||||||
|
Trx. Ref-No: {right:{TerminalReceipt.TransactionRefNo}}
|
||||||
|
Auth. Code: {right:{TerminalReceipt.AuthCode}}
|
||||||
|
Acq-Id: {right:{TerminalReceipt.AcquirerId}}
|
||||||
|
EFT {TerminalReceipt.Currency}: {right:{TerminalReceipt.EftAmount:F2}}
|
||||||
|
Trinkgeld {TerminalReceipt.Currency}: {right:{TerminalReceipt.TipAmount:F2}}
|
||||||
|
Total-EFT {TerminalReceipt.Currency}: {right:{TerminalReceipt.TotalEftAmount:F2}}
|
||||||
|
---
|
||||||
|
@end
|
||||||
|
|
||||||
|
#center# {ThankYouMessage} #
|
||||||
|
#center# {GoodbyeMessageLine1} #
|
||||||
|
#center# {GoodbyeMessageLine2} #
|
||||||
@@ -14,6 +14,7 @@ public class FinalReceipt
|
|||||||
public string Currency { get; set; }
|
public string Currency { get; set; }
|
||||||
public decimal? TotalInAlternateCurrency { get; set; }
|
public decimal? TotalInAlternateCurrency { get; set; }
|
||||||
public string AlternateCurrency { get; set; }
|
public string AlternateCurrency { get; set; }
|
||||||
|
public List<SplitPaymentInfo> SplitPayments { get; set; } = new List<SplitPaymentInfo>();
|
||||||
public string PaymentMethod { get; set; }
|
public string PaymentMethod { get; set; }
|
||||||
public decimal PaymentAmount { get; set; }
|
public decimal PaymentAmount { get; set; }
|
||||||
public List<TaxInfo> TaxBreakdown { get; set; } = new List<TaxInfo>();
|
public List<TaxInfo> TaxBreakdown { get; set; } = new List<TaxInfo>();
|
||||||
@@ -24,7 +25,7 @@ public class FinalReceipt
|
|||||||
public string ThankYouMessage { get; set; }
|
public string ThankYouMessage { get; set; }
|
||||||
public string GoodbyeMessageLine1 { get; set; }
|
public string GoodbyeMessageLine1 { get; set; }
|
||||||
public string GoodbyeMessageLine2 { get; set; }
|
public string GoodbyeMessageLine2 { get; set; }
|
||||||
public PaymentTerminalReceipt TerminalReceipt { get; set; }
|
public List<PaymentTerminalReceipt> TerminalReceipts { get; set; } = new List<PaymentTerminalReceipt>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ReceiptItem
|
public class ReceiptItem
|
||||||
@@ -46,6 +47,13 @@ public class TaxInfo
|
|||||||
public string Currency { get; set; }
|
public string Currency { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class SplitPaymentInfo
|
||||||
|
{
|
||||||
|
public string PaymentMethod { get; set; }
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
public string Currency { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class PaymentTerminalReceipt
|
public class PaymentTerminalReceipt
|
||||||
{
|
{
|
||||||
public string ReceiptType { get; set; } // e.g., "*** Kundenbeleg ***"
|
public string ReceiptType { get; set; } // e.g., "*** Kundenbeleg ***"
|
||||||
|
|||||||
@@ -60,6 +60,17 @@ public class ReceiptConverter
|
|||||||
commands.Add(new PrintCommand(""));
|
commands.Add(new PrintCommand(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split payments
|
||||||
|
if (finalReceipt.SplitPayments != null && finalReceipt.SplitPayments.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var splitPayment in finalReceipt.SplitPayments)
|
||||||
|
{
|
||||||
|
string splitLine = $"{splitPayment.PaymentMethod}: {splitPayment.Amount:F2} {splitPayment.Currency}";
|
||||||
|
commands.Add(new PrintCommand(splitLine.PadLeft(_lineWidth)));
|
||||||
|
}
|
||||||
|
commands.Add(new PrintCommand(""));
|
||||||
|
}
|
||||||
|
|
||||||
// Payment method
|
// Payment method
|
||||||
string paymentLine = $"{finalReceipt.PaymentMethod}";
|
string paymentLine = $"{finalReceipt.PaymentMethod}";
|
||||||
string paymentAmount = $"{finalReceipt.PaymentAmount:F2} {finalReceipt.Currency}";
|
string paymentAmount = $"{finalReceipt.PaymentAmount:F2} {finalReceipt.Currency}";
|
||||||
@@ -93,30 +104,31 @@ public class ReceiptConverter
|
|||||||
commands.Add(new PrintCommand(Center(finalReceipt.VatNumber, false)));
|
commands.Add(new PrintCommand(Center(finalReceipt.VatNumber, false)));
|
||||||
commands.Add(new PrintCommand(""));
|
commands.Add(new PrintCommand(""));
|
||||||
|
|
||||||
// Payment Terminal Receipt
|
// Payment Terminal Receipts
|
||||||
if (finalReceipt.TerminalReceipt != null)
|
if (finalReceipt.TerminalReceipts != null && finalReceipt.TerminalReceipts.Count > 0)
|
||||||
{
|
{
|
||||||
var terminal = finalReceipt.TerminalReceipt;
|
foreach (var terminal in finalReceipt.TerminalReceipts)
|
||||||
|
{
|
||||||
|
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(Center(terminal.ReceiptType, false)));
|
commands.Add(new PrintCommand($"Trm-Id:".PadRight(_lineWidth/2) + $"{terminal.TerminalId}".PadLeft(_lineWidth/2)));
|
||||||
commands.Add(new PrintCommand(Center(terminal.BookingType, false)));
|
commands.Add(new PrintCommand($"AID:".PadRight(_lineWidth/2) + $"{terminal.AID}".PadLeft(_lineWidth/2)));
|
||||||
commands.Add(new PrintCommand(Center(terminal.PaymentSystem, false)));
|
commands.Add(new PrintCommand($"Trx. Seq-Cnt:".PadRight(_lineWidth/2) + $"{terminal.TransactionSeqCount}".PadLeft(_lineWidth/2)));
|
||||||
commands.Add(new PrintCommand(terminal.TransactionNumber));
|
commands.Add(new PrintCommand($"Trx. Ref-No:".PadRight(_lineWidth/2) + $"{terminal.TransactionRefNo}".PadLeft(_lineWidth/2)));
|
||||||
commands.Add(new PrintCommand($"{terminal.TransactionDateTime:dd.MM.yyyy}".PadRight(_lineWidth/2) + $"{terminal.TransactionDateTime:HH:mm:ss}".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($"Trm-Id:".PadRight(_lineWidth/2) + $"{terminal.TerminalId}".PadLeft(_lineWidth/2)));
|
commands.Add(new PrintCommand($"EFT {terminal.Currency}:".PadRight(_lineWidth/2) + $"{terminal.EftAmount:F2}".PadLeft(_lineWidth/2)));
|
||||||
commands.Add(new PrintCommand($"AID:".PadRight(_lineWidth/2) + $"{terminal.AID}".PadLeft(_lineWidth/2)));
|
commands.Add(new PrintCommand($"Trinkgeld {terminal.Currency}:".PadRight(_lineWidth/2) + $"{terminal.TipAmount:F2}".PadLeft(_lineWidth/2)));
|
||||||
commands.Add(new PrintCommand($"Trx. Seq-Cnt:".PadRight(_lineWidth/2) + $"{terminal.TransactionSeqCount}".PadLeft(_lineWidth/2)));
|
commands.Add(new PrintCommand($"Total-EFT {terminal.Currency}:".PadRight(_lineWidth/2) + $"{terminal.TotalEftAmount:F2}".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(new string('-', _lineWidth)));
|
||||||
commands.Add(new PrintCommand($"Trinkgeld {terminal.Currency}:".PadRight(_lineWidth/2) + $"{terminal.TipAmount:F2}".PadLeft(_lineWidth/2)));
|
commands.Add(new PrintCommand(""));
|
||||||
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
|
// Thank you message
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -624,6 +624,54 @@ Create tabular layouts with fixed-width columns.
|
|||||||
@endrow
|
@endrow
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Row Styles
|
||||||
|
|
||||||
|
Apply styles to an entire row by adding style names after `@row`:
|
||||||
|
|
||||||
|
```
|
||||||
|
@row bold
|
||||||
|
|width|content|width|content
|
||||||
|
@endrow
|
||||||
|
```
|
||||||
|
|
||||||
|
Multiple styles can be combined with commas:
|
||||||
|
|
||||||
|
```
|
||||||
|
@row bold, big
|
||||||
|
|width|content|width|content
|
||||||
|
@endrow
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Available Row Styles
|
||||||
|
|
||||||
|
| Style | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `bold` | Bold text for entire row |
|
||||||
|
| `big` | Double-width and double-height |
|
||||||
|
| `tall` | Double-height only |
|
||||||
|
| `red` | Red color (if printer supports) |
|
||||||
|
| `spacing:N` | Set line spacing to N |
|
||||||
|
|
||||||
|
#### Row Style Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
@row bold
|
||||||
|
|30|{Name}|10,right|{Price:F2}
|
||||||
|
@endrow
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
@row bold, big
|
||||||
|
|15|TOTAL|9,right|{Total:F2}
|
||||||
|
@endrow
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
@row red, bold
|
||||||
|
|40|*** WARNING ***
|
||||||
|
@endrow
|
||||||
|
```
|
||||||
|
|
||||||
### Column Syntax
|
### Column Syntax
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -870,6 +918,7 @@ Templates are assigned to receipt types and printer profiles in `assignments.jso
|
|||||||
| `@end` | End block |
|
| `@end` | End block |
|
||||||
| `@foreach var in collection` | Start loop |
|
| `@foreach var in collection` | Start loop |
|
||||||
| `@row` | Start row |
|
| `@row` | Start row |
|
||||||
|
| `@row style1, style2` | Start row with styles |
|
||||||
| `@endrow` | End row |
|
| `@endrow` | End row |
|
||||||
| `\|width\|content` | Column definition |
|
| `\|width\|content` | Column definition |
|
||||||
| `\|width,align\|content` | Column with alignment |
|
| `\|width,align\|content` | Column with alignment |
|
||||||
|
|||||||
Reference in New Issue
Block a user