DataReceived;
+ public TCPConnection(string destination)
+ {
+ _client = new SimpleTcpClient(destination);
+ _client.Events.Connected += ConnectedEventHandler;
+ _client.Events.Disconnected += DisconnectedEventHandler;
+ _client.Events.DataReceived += DataReceivedEventHandler;
+ _client.Keepalive = new SimpleTcpKeepaliveSettings() { EnableTcpKeepAlives = true, TcpKeepAliveInterval = 1, TcpKeepAliveTime = 1, TcpKeepAliveRetryCount = 3 };
+ ReadStream.ReadTimeout = 1500;
+ WriteStream = new InterceptableWriteMemoryStream(bytes => _client.Send(bytes));
+ }
+ private void ConnectedEventHandler(object sender, ClientConnectedEventArgs e)
+ {
+ Connected?.Invoke(sender, e);
+ }
+ private void DisconnectedEventHandler(object sender, ClientDisconnectedEventArgs e)
+ {
+ Disconnected?.Invoke(sender, e);
+ }
+ private void DataReceivedEventHandler(object sender, DataReceivedEventArgs e)
+ {
+ ReadStream.Write(e.Data, 0, e.Data.Length);
+ }
+ public void ConnectWithRetries(int timeoutMs)
+ {
+ _client.ConnectWithRetries(timeoutMs);
+ }
+
+ ~TCPConnection()
+ {
+ try
+ {
+ _client.Events.DataReceived -= DataReceivedEventHandler;
+ _client.Events.Connected -= ConnectedEventHandler;
+ _client.Events.Disconnected -= DisconnectedEventHandler;
+ _client?.Dispose();
+ }
+ catch { }
+ }
+
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/LICENSE b/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/LICENSE
new file mode 100644
index 0000000..5631092
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Luke Paireepinart
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/README.md b/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/README.md
new file mode 100644
index 0000000..196762a
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/README.md
@@ -0,0 +1,212 @@
+
+ESCPOS.NET - Easy to use, Cross-Platform, Fast and Efficient.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ESCPOS.NET is a super easy to use library that supports the most common functionality of the ESC/POS standard by Epson. It is highly compatible, and runs on full framework .NET as well as .NET Core.
+
+It works with Serial, USB, Ethernet, and WiFi printers, and works great on Windows, Linux and OSX.
+
+This library is used for thermal receipt printers, line displays, cash drawers, and more!
+
+ESC/POS is a binary protocol that's a type of "raw" text, which means you do not need drivers to use it.
+
+This library encompasses helper functions that assist in creating the binary command stream that is needed to control this hardware, as well as the underlying communications that are needed to interface with the hardware.
+
+This means that Bluetooth, WiFi, Ethernet, USB, and Serial printers are all usable with just this software library and nothing else.
+
+# Get Started
+
+## Step 1: Create a Printer object
+```csharp
+// Ethernet or WiFi
+var printer = new NetworkPrinter(ipAddress: "192.168.1.50", port: 9000, reconnectOnTimeout: true);
+
+// USB, Bluetooth, or Serial
+var printer = new SerialPrinter(portName: "COM5", baudRate: 115200);
+
+// Linux output to USB / Serial file
+var printer = new FilePrinter(filePath: "/dev/usb/lp0");
+```
+## Step 1a (optional): Monitor for Events - out of paper, cover open...
+```csharp
+// Define a callback method.
+static void StatusChanged(object sender, EventArgs ps)
+{
+ var status = (PrinterStatusEventArgs)ps;
+ Console.WriteLine($"Status: {status.IsPrinterOnline}");
+ Console.WriteLine($"Has Paper? {status.IsPaperOut}");
+ Console.WriteLine($"Paper Running Low? {status.IsPaperLow}");
+ Console.WriteLine($"Cash Drawer Open? {status.IsCashDrawerOpen}");
+ Console.WriteLine($"Cover Open? {status.IsCoverOpen}");
+}
+
+...
+
+// In your program, register event handler to call the method when printer status changes:
+printer.StatusChanged += StatusChanged;
+
+// and start monitoring for changes.
+printer.StartMonitoring();
+
+```
+
+## Step 2: Write a receipt to the printer
+```csharp
+var e = new EPSON();
+printer.Write(
+ ByteSplicer.Combine(
+ e.CenterAlign(),
+ e.PrintImage(File.ReadAllBytes("images/pd-logo-300.png"), true),
+ e.PrintLine(),
+ e.SetBarcodeHeightInDots(360),
+ e.SetBarWidth(BarWidth.Default),
+ e.SetBarLabelPosition(BarLabelPrintPosition.None),
+ e.PrintBarcode(BarcodeType.ITF, "0123456789"),
+ e.PrintLine(),
+ e.PrintLine("B&H PHOTO & VIDEO"),
+ e.PrintLine("420 NINTH AVE."),
+ e.PrintLine("NEW YORK, NY 10001"),
+ e.PrintLine("(212) 502-6380 - (800)947-9975"),
+ e.SetStyles(PrintStyle.Underline),
+ e.PrintLine("www.bhphotovideo.com"),
+ e.SetStyles(PrintStyle.None),
+ e.PrintLine(),
+ e.LeftAlign(),
+ e.PrintLine("Order: 123456789 Date: 02/01/19"),
+ e.PrintLine(),
+ e.PrintLine(),
+ e.SetStyles(PrintStyle.FontB),
+ e.PrintLine("1 TRITON LOW-NOISE IN-LINE MICROPHONE PREAMP"),
+ e.PrintLine(" TRFETHEAD/FETHEAD 89.95 89.95"),
+ e.PrintLine("----------------------------------------------------------------"),
+ e.RightAlign(),
+ e.PrintLine("SUBTOTAL 89.95"),
+ e.PrintLine("Total Order: 89.95"),
+ e.PrintLine("Total Payment: 89.95"),
+ e.PrintLine(),
+ e.LeftAlign(),
+ e.SetStyles(PrintStyle.Bold | PrintStyle.FontB),
+ e.PrintLine("SOLD TO: SHIP TO:"),
+ e.SetStyles(PrintStyle.FontB),
+ e.PrintLine(" FIRSTN LASTNAME FIRSTN LASTNAME"),
+ e.PrintLine(" 123 FAKE ST. 123 FAKE ST."),
+ e.PrintLine(" DECATUR, IL 12345 DECATUR, IL 12345"),
+ e.PrintLine(" (123)456-7890 (123)456-7890"),
+ e.PrintLine(" CUST: 87654321"),
+ e.PrintLine(),
+ e.PrintLine()
+ )
+);
+```
+
+
+# Supported Platforms
+Desktop support (WiFI, Ethernet, Bluetooth, USB, Serial):
+* Windows
+ - Windows 7+ can support .NET Core or the .NET 471 runtime, and can use this library.
+* Linux
+ - ARM platforms such as Raspberry Pi
+ - x86/64 platform
+* Mac OSX
+ - Tested on high sierra
+
+Mobile support (WiFi/Ethernet only):
+* iOS
+ - Xamarin.iOS
+* Android
+ - Xamarin.Android
+* Windows
+ - UWP
+
+# Supported Hardware
+Epson thermal receipt printers are supported, and most common functions such as test printing, styling, alignment, image printing, and barcode printing.
+
+Generic thermal printers that implement ESC/POS typically work, for example the Royal PT-300, and BemaTech printers are also tested by some members of the community, @juliogamasso and @ivanmontilla.
+
+Cash Drawers are supported, as are Line Displays.
+
+
+## Further Documentation and Usage
+Check out the ESCPOS_NET.ConsoleTest for a comprehensive test suite that covers all implemented functions.
+
+This package is available on NuGet @ https://www.nuget.org/packages/ESCPOS_NET/
+
+Please comment / DM / open issues and let me know how the library is working for you!
+
+## Contributors
+Thanks to all of our contributors working to make this the best .NET thermal printer library out there!!
+
+* [@lukevp](https://github.com/lukevp)
+* [@juliogamasso](https://github.com/juliogamasso)
+* [@naaeef](https://github.com/naaeef)
+* [@netgg93](https://github.com/netgg93)
+* [@igorocampos](https://github.com/igorocampos)
+* [@kodejack](https://github.com/kodejack)
+
+# USB Usage Guide
+
+For cross-platform support and ease of maintenance, all USB printers are supported over Serial-USB interfaces. These are full-speed and work just as well as native USB as long as you have your port settings optimized.
+
+On Linux and Mac, USB for Epson printers is exposed as a serial port directly by the os under /dev/ttyusb or something similar based on your platform, and doesn't require drivers.
+
+On Windows, you must install some type of virtual COM port driver for native USB support, and then map your printer to a specific port, or use a USB-Serial cable and use a serial printer.
+
+If you have an official Epson printer, the link to install it from Epson is here: https://download.epson-biz.com/modules/pos/index.php?page=single_soft&cid=6481&scat=36&pcat=5
+
+If you do not have an official Epson printer, you will have to find a compatible way to expose the USB interface as a virtual Serial port.
+
+NOTE: The cross platform .NET library we use from Microsoft only supports COM ports 8 and below on windows, so be sure not to use a very high # COM port.
+
+
+# Implemented Commands
+
+## Bit Image Commands
+* `ESC ✻` Select bit-image mode
+* `GS ( L` OR `GS 8 L` Set graphics data
+ * Set the reference dot density for graphics.
+ * Print the graphics data in the print buffer.
+ * Store the graphics data in the print buffer (raster format).
+
+## Character Commands
+* `ESC !` Select print mode(s)
+* `GS B` Turn white/black reverse print mode on/off - Thanks @juliogamasso!
+
+
+## Print Commands
+* `LF` Print and line feed
+* `CR` Print and carriage return
+* `ESC J` Print and feed paper
+* `ESC K` Print and reverse feed
+* `ESC d` Print and feed n lines
+* `ESC e` Print and reverse feed n lines
+
+## Bar Code Commands
+* `GS H` Select print position of HRI characters
+* `GS f` Select font for HRI characters
+* `GS h` Set bar code height
+* `GS k` Print bar code
+* `GS w` Set bar code width
+
+## Status Commands
+* `GS a` Enable/disable Automatic Status Back (ASB)
+
+## Open Cash Drawer Commands
+* `ESC p 0` Open cash drawer pin 2
+* `ESC p 1` Open cash drawer pin 5
+
+## Miscellaneous Commands
+* `ESC @` Initialize printer
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/banner2.jpg b/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/banner2.jpg
new file mode 100644
index 0000000..ef31efa
Binary files /dev/null and b/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/banner2.jpg differ
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/command_list.md b/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/command_list.md
new file mode 100644
index 0000000..eb17eda
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/command_list.md
@@ -0,0 +1,494 @@
+## Line Spacing Commands
+* ESC 2 Select default line spacing
+* ESC 3 Set line spacing
+
+## Character Commands
+* `ESC SP` Set right-side character spacing
+* `ESC –` Turn underline mode on/off
+* `ESC E` Turn emphasized mode on/off
+* `ESC G` Turn double-strike mode on/off
+* `ESC M` Select character font
+* `ESC V` Turn 90° clockwise rotation mode on/off
+* `ESC r` Select print color
+* `ESC t` Select character code table
+* `ESC {` Turn upside-down print mode on/off
+* `GS !` Select character size
+* `GS b` Turn smoothing mode on/off
+
+## Panel Button Commands
+* `ESC c 5` Enable/disable panel buttons
+
+## Paper Sensor Commands
+@@this is for parallel only ESC c 3 Select paper sensor(s) to output paper-end signals
+@@this should stop by default ESC c 4 Select paper sensor(s) to stop printing
+
+## Print Position Commands
+* `HT` Horizontal tab
+* `ESC $` Set absolute print position
+* `ESC D` Set horizontal tab positions
+* `ESC a` Select justification
+* `GS L` Set left margin
+* `GS W` Set print area width
+
+## Bit Image Commands
+* `ESC *` Select bit-image mode
+
+
+
+## Mechanism Control Commands
+* `ESC U` Turn unidirectional print mode on/off
+* `GS V` Select cut mode and cut paper
+
+## Miscellaneous Commands
+* `ESC @` Initialize printer
+* `DLE ENQ` Send real-time request to printer
+* `DLE DC4 (fn = 1)` Generate pulse in real-time
+* `DLE DC4 (fn = 2)` Execute power-off sequence
+
+TODO: check for DLE DC4 other functions and any other real time commands to make sure they're not part of graphics data because they will be processed immediately. also can use GS ( D to disable realtime commands before processing graphics data
+
+* `ESC p` Generate pulse
+* `GS ( A` Execute test print
+* `GS ( D` Enable/disable real-time command
+* `GS ( H` Request transmission of response or status
+* `GS ( K` Select print control method(s)
+ * Select the print control mode
+ * Select the print density
+ * Select the print speed
+ * Select the number of parts for the thermal head energizing
+* `GS ( P` Page mode control
+ * Printable area setting when page mode is selected
+* `GS ( Q` Commands for drawing graphics
+ * Draw line
+ * Draw rectangle
+
+## Two Dimension Code Commands (QR Codes)
+* `GS ( k` Set up and print the symbol
+
+## Customize Commands
+* `GS ( C` Edit NV user memory
+ * Delete the specified record
+ * Store the data in the specified record
+ * Transmit the data in the specified record
+ * Transmit capacity of the NV user memory currently being used
+ * Transmit the remaining capacity of the NV user memory
+ * Transmit the key code list
+ * Delete all data in the NV user memory
+* `GS ( E` Set user setup commands
+ * Change into the user setting mode
+ * End the user setting mode session
+ * Change the memory switch
+ * Transmit the settings of the memory switch
+ * Set the customized setting values
+ * Transmit the customized setting values
+ * Copy the user-defined page
+ * Define the data (column format) for the character code page
+ * Define the data (raster format) for the character code page
+ * Delete the data for the character code page
+ * Set the configuration item for the serial interface
+ * Transmit the configuration item for the serial interface
+ * Set the configuration item for the Bluetooth interface
+ * Transmit the configuration item for the Bluetooth interface
+ * Delete the paper layout
+ * Set the paper layout
+
+## Counter Printing Commands
+
+## Printing Paper Commands
+
+
+
+
+
+
+
+
+
+
+
+## All Commands
+
+## Print Commands
+* LF Print and line feed
+* FF (in page mode) Print and return to standard mode (in page mode)
+* CR Print and carriage return
+* ESC FF Print data in page mode
+* ESC J Print and feed paper
+* ESC K Print and reverse feed
+* ESC d Print and feed n lines
+* ESC e Print and reverse feed n lines
+
+## Line Spacing Commands
+* ESC 2 Select default line spacing
+* ESC 3 Set line spacing
+
+## Character Commands
+* CAN Cancel print data in page mode
+* ESC SP Set right-side character spacing
+* ESC ! Select print mode(s)
+* ESC % Select/cancel user-defined character set
+* ESC & Define user-defined characters
+* ESC – Turn underline mode on/off
+* ESC ? Cancel user-defined characters
+* ESC E Turn emphasized mode on/off
+* ESC G Turn double-strike mode on/off
+* ESC M Select character font
+* ESC R Select an international character set
+* ESC V Turn 90° clockwise rotation mode on/off
+* ESC r Select print color
+* ESC t Select character code table
+* ESC { Turn upside-down print mode on/off
+* GS ( N Select character effects
+ * Select character color
+ * Select background color
+ * Turn shading mode on/off
+* GS ! Select character size
+* GS B Turn white/black reverse print mode on/off
+* GS b Turn smoothing mode on/off
+
+
+## Panel Button Commands
+* ESC c 5 Enable/disable panel buttons
+
+
+## Paper Sensor Commands
+* ESC c 3 Select paper sensor(s) to output paper-end signals
+* ESC c 4 Select paper sensor(s) to stop printing
+
+## Print Position Commands
+* HT Horizontal tab
+* ESC $ Set absolute print position
+* ESC D Set horizontal tab positions
+* ESC T Select print direction in page mode
+* ESC W Set print area in page mode
+* ESC \ Set relative print position
+* ESC a Select justification
+* GS $ Set absolute vertical print position in page mode
+* GS L Set left margin
+* GS T Set print position to the beginning of print line
+* GS W Set print area width
+* GS \ Set relative vertical print position in page mode
+
+## Bit Image Commands
+* ESC ✻ Select bit-image mode
+* FS p Print NV bit image
+* GS ( L GS 8 L Set graphics data
+ * Transmit the NV graphics memory capacity.
+ * Set the reference dot density for graphics.
+ * Print the graphics data in the print buffer.
+ * Transmit the remaining capacity of the NV graphics memory.
+ * Transmit the remaining capacity of the download graphics memory.
+ * Transmit the key code list for defined NV graphics.
+ * Delete all NV graphics data.
+ * Delete the specified NV graphics data.
+ * Define the NV graphics data (raster format).
+ * Define the NV graphics data (column format).
+ * Print the specified NV graphics data.
+ * Transmit the key code list for defined download graphics.
+ * Delete all NV graphics data.
+ * Delete the specified download graphics data.
+ * Define the downloaded graphics data (raster format).
+ * Define the downloaded graphics data (column format).
+ * Print the specified download graphics data.
+ * Store the graphics data in the print buffer (raster format).
+ * Store the graphics data in the print buffer (column format).
+* FS q Define NV bit image
+* GS v 0 Print raster bit image
+* GS ✻ Define downloaded bit image
+* GS / Print downloaded bit image
+* GS Q 0 Print variable vertical size bit image
+
+## Status Commands
+* DLE EOT Transmit real-time status
+* ESC u Transmit peripheral device status
+* ESC v Transmit paper sensor status
+* GS a Enable/disable Automatic Status Back (ASB)
+* GS j Enable/disable Automatic Status Back (ASB) for ink
+* GS r Transmit status
+
+## Bar Code Commands
+* GS H Select print position of HRI characters
+* GS f Select font for HRI characters
+* GS h Set bar code height
+* GS k Print bar code
+* GS w Set bar code width
+
+## Macro Function Commands
+* GS : Start/end macro definition
+* GS ^ Execute macro
+
+## Mechanism Control Commands
+* ESC < Return home
+* ESC U Turn unidirectional print mode on/off
+* ESC i Partial cut (one point left uncut)
+* ESC m Partial cut (three points left uncut)
+* GS V Select cut mode and cut paper
+
+## Miscellaneous Commands
+* DLE ENQ Send real-time request to printer
+* DLE DC4 (fn = 1) Generate pulse in real-time
+* DLE DC4 (fn = 2) Execute power-off sequence
+* DLE DC4 (fn = 7) Transmit specified status in real time
+* DLE DC4 (fn = 8) Clear buffer (s)
+* ESC ( A Control beeper tones
+ * Beep integrated beeper in TM-U230 models
+ * Set integrated beeper when offline factors occur in TM-U230 models
+ * Set integrated beeper except when offline factors occur in TM-U230 models
+* ESC = Select peripheral device
+* ESC @ Initialize printer
+* ESC L Select page mode
+* ESC S Select standard mode
+* ESC p Generate pulse
+* GS ( A Execute test print
+* GS ( D Enable/disable real-time command
+* GS ( H Request transmission of response or status
+* GS ( K Select print control method(s)
+ * Select the print control mode
+ * Select the print density
+ * Select the print speed
+ * Select the number of parts for the thermal head energizing
+* GS ( P Page mode control
+ * Printable area setting when page mode is selected
+* GS ( Q Commands for drawing graphics
+ * Draw line
+ * Draw rectangle
+
+## Kanji Commands
+* FS ! Select print mode(s) for Kanji characters
+* FS & Select Kanji character mode
+* FS ( A Select Kanji character style(s)
+* Select Kanji character font
+* FS – Turn underline mode on/off for Kanji characters
+* FS . Cancel Kanji character mode
+* FS 2 Define user-defined Kanji characters
+* FS C Select Kanji character code system
+* FS S Set Kanji character spacing
+* FS W Turn quadruple-size mode on/off for Kanji characters
+* FS ? Cancel user-defined Kanji characters
+
+## Two Dimension Code Commands (QR Codes)
+* GS ( k Set up and print the symbol
+
+## Customize Commands
+* FS g 1 Write to NV user memory
+* FS g 2 Read from NV user memory
+* GS ( C Edit NV user memory
+ * Delete the specified record
+ * Store the data in the specified record
+ * Transmit the data in the specified record
+ * Transmit capacity of the NV user memory currently being used
+ * Transmit the remaining capacity of the NV user memory
+ * Transmit the key code list
+ * Delete all data in the NV user memory
+* GS ( E Set user setup commands
+ * Change into the user setting mode
+ * End the user setting mode session
+ * Change the memory switch
+ * Transmit the settings of the memory switch
+ * Set the customized setting values
+ * Transmit the customized setting values
+ * Copy the user-defined page
+ * Define the data (column format) for the character code page
+ * Define the data (raster format) for the character code page
+ * Delete the data for the character code page
+ * Set the configuration item for the serial interface
+ * Transmit the configuration item for the serial interface
+ * Set the configuration item for the Bluetooth interface
+ * Transmit the configuration item for the Bluetooth interface
+ * Delete the paper layout
+ * Set the paper layout
+
+## Counter Printing Commands
+* GS C 0 Select counter print mode
+* GS C 1 Select count mode (A)
+* GS C 2 Set counter
+* GS C ; Select counter mode (B)
+* GS c Print counter
+
+## Printing Paper Commands
+* FS ( L Select label and black mark control function(s)
+ * Paper layout setting
+ * Paper layout information transmission
+ * Transmit the positioning information
+ * Feed paper to the label peeling position
+ * Feed paper to the cutting position
+ * Feed paper to the print starting position
+ * Paper layout error special margin setting
+
+
+
+
+## Mechanism Control Commands
+
+
+
+
+
+
+## Graphics Commands
+
+## NV Graphics Commands
+
+
+## Download Graphics Commands
+
+## Logo Print Commands
+
+## Bar Code Commands
+
+## Two-Dimensional Code Commands
+
+## Status Commands
+
+## Macro Function Commands
+
+## Miscellaneous Commands
+
+
+### User Setup Commands
+
+TODO:
+* Line Spacing
+* SetLineSpacing()
+* SetLineSpacing(n dots) 0 <= n <= 255
+
+Not In Scope:
+* Drawer Kick Out Commands
+* Page Mode Commands
+
+
+
+
+# COMMANDS IN ALPHANUMERIC ORDER
+Command|Name|Function type
+---|---|---
+HT |Horizontal tab |PRINT POSITION COMMANDS
+LF |Print and line feed |PRINT COMMANDS
+FF (in page mode) |Print and return to standard mode| PRINT COMMANDS
+CR |Print and carriage return |PRINT COMMANDS
+CAN |Cancel print data in page mode |CHARACTER COMMANDS
+DLE EOT |Real-time status transmission |STATUS COMMANDS
+DLE ENQ |Real-time request to printer| MISCELLANEOUS COMMANDS
+DLE DC4 (fn = 1) |Generate pulse at real-time |MISCELLANEOUS COMMANDS
+DLE DC4 (fn = 2) |Turn off the power| MISCELLANEOUS COMMANDS
+DLE DC4 (fn = 7) |Transmit specified status in real time |MISCELLANEOUS COMMANDS
+DLE DC4 (fn = 8) |Clear buffer| MISCELLANEOUS COMMANDS
+ESC FF |Print data in page mode |PRINT COMMANDS
+ESC SP |Set right-side character spacing |CHARACTER COMMANDS
+ESC ! |Select print mode(s) |CHARACTER COMMANDS
+ESC $ |Set absolute print position |PRINT POSITION COMMANDS
+ESC % |Select/cancel user-defined character set| CHARACTER COMMANDS
+ESC & |Define user-defined characters |CHARACTER COMMANDS
+ESC ( A |Control of the beeper |MISCELLANEOUS COMMANDS
+ESC ✻ |Select bit-image mode| BIT-IMAGE COMMANDS
+ESC – |Turn underline mode on/off| CHARACTER COMMANDS
+ESC 2 |Select default line spacing| LINE SPACING COMMANDS
+ESC 3 |Set line spacing |LINE SPACING COMMANDS
+ESC < |Return home| MECHANISM CONTROL COMMANDS
+ESC = |Select peripheral device |MISCELLANEOUS COMMANDS
+ESC ? |Cancel user-defined characters| CHARACTER COMMANDS
+ESC @ |Initialize printer| MISCELLANEOUS COMMANDS
+ESC D |Set horizontal tab positions |PRINT POSITION COMMANDS
+ESC E |Turn emphasized mode on/off |CHARACTER COMMANDS
+ESC G |Turn double-strike mode on/off| CHARACTER COMMANDS
+ESC J |Print and feed paper |PRINT COMMANDS
+ESC K |Print and reverse feed |PRINT COMMANDS
+ESC L |Select page mode |MISCELLANEOUS COMMANDS
+ESC M |Select character font| CHARACTER COMMANDS
+ESC R |Select an international character set| CHARACTER COMMANDS
+ESC S |Select standard mode |MISCELLANEOUS COMMANDS
+ESC T |Select print direction in page mode |PRINT POSITION COMMANDS
+ESC U |Turn unidirectional printing mode on/off |MECHANISM CONTROL COMMANDS
+ESC V |Turn 90° clockwise rotation mode on/off |CHARACTER COMMANDS
+ESC W |Set printing area in page mode |PRINT POSITION COMMANDS
+ESC \ |Set relative print position| PRINT POSITION COMMANDS
+ESC a |Select justification |PRINT POSITION COMMANDS
+ESC c 3 |Select paper sensor(s) to output paper-end signals |PAPER SENSOR COMMANDS
+ESC c 4 |Select paper sensor(s) to stop printing |PAPER SENSOR COMMANDS
+ESC c 5 |Enable/disable panel buttons| PANEL BUTTON COMMAND
+ESC d |Print and feed n lines |PRINT COMMANDS
+ESC e |Print and reverse feed n lines |PRINT COMMANDS
+ESC i |Partial cut (one point left uncut) |MECHANISM CONTROL COMMANDS
+ESC m |Partial cut (three points left uncut) |MECHANISM CONTROL COMMANDS
+ESC p |Generate pulse| MISCELLANEOUS COMMANDS
+ESC r |Select print color| CHARACTER COMMANDS
+ESC t |Select character code table |CHARACTER COMMANDS
+ESC u |Transmit peripheral device status| STATUS COMMANDS
+ESC { |Turn upside-down printing mode on/off| CHARACTER COMMANDS
+FS ! |Set print mode(s) for Kanji characters |KANJI COMMANDS
+FS & |Select Kanji character mode |KANJI COMMANDS
+FS ( A |Define character effects of Kanji characters. |KANJI COMMANDS
+FS ( L |Control of the label paper / black mark paper |PRINTING PAPER COMMANDS
+FS – |Turn underline mode on/off for Kanji characters |KANJI COMMANDS
+FS . |Cancel Kanji character mode |KANJI COMMANDS
+FS 2 |Define user-defined Kanji characters| KANJI COMMANDS
+FS C |Select Kanji character code system |KANJI COMMANDS
+FS S |Set Kanji character spacing |KANJI COMMANDS
+FS W |Turn quadruple-size mode on/off for Kanji characters| KANJI COMMANDS
+FS ? |Cancel user-defined Kanji characters |KANJI COMMANDS
+FS g 1 |Write to NV user memory |CUSTOMIZE COMMANDS
+FS g 2 |Read from NV user memory |CUSTOMIZE COMMANDS
+FS p |Print NV bit image |BIT-IMAGE COMMANDS
+FS q |Define NV bit image |BIT-IMAGE COMMANDS
+GS ! |Select character size |CHARACTER COMMANDS
+GS $ |Set absolute vertical print position in page mode| PRINT POSITION COMMANDS
+GS ( A |Execute test print| MISCELLANEOUS COMMANDS
+GS ( C |Edit NV user memory |CUSTOMIZE COMMANDS
+GS ( D |Enable/disable real-time command| MISCELLANEOUS COMMANDS
+GS ( E |User setup commands |CUSTOMIZE COMMANDS
+GS ( H |Require response transmission |MISCELLANEOUS COMMANDS
+GS ( K |Select printing control| MISCELLANEOUS COMMANDS
+GS ( L GS 8 L |Specify graphics data| BIT-IMAGE COMMANDS
+GS ( M |Customize printer control value(s)| CUSTOMIZE COMMANDS
+GS ( N |Select character effects| CHARACTER COMMANDS
+GS ( k |Specify and print the symbol |TWO DIMENSION CODE COMMANDS
+GS ✻ |Define downloaded bit image |BIT-IMAGE COMMANDS
+GS / |Print downloaded bit image |BIT-IMAGE COMMANDS
+GS : |Start/end macro definition |MACRO FUNCTION COMMANDS
+GS B |Turn white/black reverse printing mode on/off| CHARACTER COMMANDS
+GS C 0 |Select counter print mode| COUNTER PRINTING COMMANDS
+GS C 1 |Select count mode (A) |COUNTER PRINTING COMMANDS
+GS C 2 |Select counter print mode| COUNTER PRINTING COMMANDS
+GS C ; |Select counter mode (B) |COUNTER PRINTING COMMANDS
+GS H |Select printing position of HRI characters |BAR CODE COMMANDS
+GS I |Transmit printer |ID MISCELLANEOUS COMMANDS
+GS L |Set left margin |PRINT POSITION COMMANDS
+GS P |Set horizontal and vertical motion units| MISCELLANEOUS COMMANDS
+GS Q 0 |Print variable vertical size bit image |BIT-IMAGE COMMANDS
+GS T |Set print position to the beginning of print line| PRINT POSITION COMMANDS
+GS V |Select cut mode and cut paper |MECHANISM CONTROL COMMANDS
+GS W |Set printing area width PRINT |POSITION COMMANDS
+GS \ |Set relative vertical print position in page mode| PRINT POSITION COMMANDS
+GS ^ |Execute macro |MACRO FUNCTION COMMANDS
+GS a |Enable/disable Automatic Status Back (ASB) |STATUS COMMANDS
+GS b |Turn smoothing mode on/off |CHARACTER COMMANDS
+GS c |Print counter| COUNTER PRINTING COMMANDS
+GS f |Select font for HRI characters |BAR CODE COMMANDS
+GS g 0 |Initialize maintenance counter| MISCELLANEOUS COMMANDS
+GS g 2 |Transmit maintenance counter |MISCELLANEOUS COMMANDS
+GS h |Set bar code height |BAR CODE COMMANDS
+GS j |Enable/disable Ink Automatic Status Back (ASB) |STATUS COMMANDS
+GS k |Print bar code |BAR CODE COMMANDS
+GS r |Transmit status |STATUS COMMANDS
+GS v 0 |Print raster bit image |BIT-IMAGE COMMANDS
+GS w |Set bar code width |BAR CODE COMMANDS
+GS z 0 |Set online recovery wait time| MISCELLANEOUS COMMANDS
+GS z 0 |(TM-L90 w/ Peeler) Set online recovery wait time| MISCELLANEOUS COMMANDS
+
+## The following commands are supported only by Simplified Chinese/Traditional Chinese/Japanese models.
+
+Command|Name|Function type
+---|---|---
+FS ! |Set print mode(s) for Kanji characters |KANJI COMMANDS
+FS & |Select Kanji character mode |KANJI COMMANDS
+FS ( A |Define character effects of Kanji characters |KANJI COMMANDS
+FS – |Turn underline mode on/off for Kanji characters |KANJI COMMANDS
+FS . |Cancel Kanji character mode |KANJI COMMANDS
+FS 2 |Define user-defined Kanji characters| KANJI COMMANDS
+FS C |Select Kanji character code system |KANJI COMMANDS
+FS S |Set Kanji character spacing| KANJI COMMANDS
+FS W |Turn quadruple-size mode on/off for Kanji characters |KANJI COMMANDS
+FS ? |Cancel user-defined Kanji characters| KANJI COMMANDS
+
+
+
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/nuget.exe b/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/nuget.exe
new file mode 100644
index 0000000..e00ef51
Binary files /dev/null and b/PointOfSale/Pos.Ui/EpsonPrinter/ESC-POS/ESC-POS-.NET-master/nuget.exe differ
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Enums/AlignmentEnum.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Enums/AlignmentEnum.cs
new file mode 100644
index 0000000..4efe3a3
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Enums/AlignmentEnum.cs
@@ -0,0 +1,9 @@
+namespace EpsonPrinter.Enums
+{
+ public enum AlignmentEnum
+ {
+ Left,
+ Center,
+ Right
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Enums/PrintStylesEnum.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Enums/PrintStylesEnum.cs
new file mode 100644
index 0000000..597669b
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Enums/PrintStylesEnum.cs
@@ -0,0 +1,12 @@
+namespace EpsonPrinter.Enums
+{
+ public enum PrintStylesEnum
+ {
+ None,
+ FontB,
+ Bold,
+ DoubleHeight,
+ DoubleWidth,
+ UnderLine
+ }
+}
\ No newline at end of file
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/EpsonPrinter.csproj b/PointOfSale/Pos.Ui/EpsonPrinter/EpsonPrinter.csproj
new file mode 100644
index 0000000..aeb893c
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/EpsonPrinter.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net6.0
+ Linux
+ ..\Pos
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Model/BodyModel.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Model/BodyModel.cs
new file mode 100644
index 0000000..7e89768
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Model/BodyModel.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EpsonPrinter.Model
+{
+ public class BodyModel
+ {
+ public List Products { get; set; }
+ public decimal TotalPrice { get; set; }
+ public decimal TotalVat { get; set; }
+ public int ReceiptNumber { get; set; }
+ public string ReceiptTime { get; set; }
+ public string Staff { get; set; }
+
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Model/BodyProductModel.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Model/BodyProductModel.cs
new file mode 100644
index 0000000..00d72aa
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Model/BodyProductModel.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EpsonPrinter.Model
+{
+ public class BodyProductModel
+ {
+ public string Product { get; set; }
+ public int NoOfProduct { get; set; }
+ public decimal Price { get; set; }
+ public decimal TotalPrice { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Model/FooterModel.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Model/FooterModel.cs
new file mode 100644
index 0000000..d943e1c
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Model/FooterModel.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using EpsonPrinter.Enums;
+
+namespace EpsonPrinter.Model
+{
+ public class FooterModel
+ {
+ public string Value { get; set; }
+ public PrintStyleModel PrintStyles { get; set; }
+ public AlignmentEnum TextAlignment { get; set; }
+ public int FeedingLines { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Model/HeaderModel.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Model/HeaderModel.cs
new file mode 100644
index 0000000..37e9550
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Model/HeaderModel.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using EpsonPrinter.Enums;
+using ESCPOS_NET.Emitters;
+
+namespace EpsonPrinter.Model
+{
+ public class HeaderModel
+ {
+ public string Value { get; set; }
+ public PrintStyleModel PrintStyles { get; set; }
+ public AlignmentEnum TextAlignment { get; set; }
+ public int FeedingLines { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Model/PrintStyleModel.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Model/PrintStyleModel.cs
new file mode 100644
index 0000000..853dc5c
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Model/PrintStyleModel.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EpsonPrinter.Model
+{
+ public class PrintStyleModel
+ {
+ public bool FontB { get; set; }
+ public bool Bold { get; set; }
+ public bool DoubleHeight { get; set; }
+ public bool DoubleWidth { get; set; }
+ public bool Underline { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Model/ReceiptModel.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Model/ReceiptModel.cs
new file mode 100644
index 0000000..04f486b
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Model/ReceiptModel.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+
+namespace EpsonPrinter.Model
+{
+ public class ReceiptModel
+ {
+ public string LogoBase64 { get; set; }
+ public List Header { get; set; }
+ public BodyModel BodyModel { get; set; }
+ public List Footer { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Model/SaleOfDayDetail.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Model/SaleOfDayDetail.cs
new file mode 100644
index 0000000..f0b67c1
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Model/SaleOfDayDetail.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace EpsonPrinter.Model
+{
+ public class SaleOfDayDetail
+ {
+ public string Category { get; set; }
+ public decimal TotalSale { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Model/SaleOfDayModel.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Model/SaleOfDayModel.cs
new file mode 100644
index 0000000..c50f634
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Model/SaleOfDayModel.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+
+namespace EpsonPrinter.Model
+{
+ public class SaleOfDayModel
+ {
+ public DateTime Date { get; set; }
+ public int TotalCustomers { get; set; }
+ public Decimal TotalSale { get; set; }
+ public List SaleOfDayDetail { get; set; } = new();
+
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Program.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Program.cs
new file mode 100644
index 0000000..85c4e64
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Program.cs
@@ -0,0 +1,26 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EpsonPrinter
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ CreateHostBuilder(args).Build().Run();
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.UseStartup();
+ });
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Properties/launchSettings.json b/PointOfSale/Pos.Ui/EpsonPrinter/Properties/launchSettings.json
new file mode 100644
index 0000000..896e040
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Properties/launchSettings.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:23653",
+ "sslPort": 44334
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "EpsonPrinter": {
+ "commandName": "Project",
+ "dotnetRunMessages": "true",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Services/EpsonPrintService.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Services/EpsonPrintService.cs
new file mode 100644
index 0000000..1dba4e0
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Services/EpsonPrintService.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Threading.Tasks;
+using EpsonPrinter.Model;
+using ESCPOS_NET;
+using ESCPOS_NET.Emitters;
+using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json.Linq;
+
+namespace EpsonPrinter.Services
+{
+ public class EpsonPrintService
+ {
+ private readonly SerialPrinter _serialPrinter;
+ private readonly IConfiguration _config;
+
+ public EpsonPrintService(IConfiguration config)
+ {
+ _config = config;
+ string comPort = _config["PrintSettings:ComPort"];
+ int baudRate = Convert.ToInt32(_config["PrintSettings:BaudRate"]);
+ _serialPrinter = new SerialPrinter(comPort,baudRate);
+
+ }
+
+ private void PrintReceipt(ReceiptModel model)
+ {
+
+ }
+
+
+ public void PrintSaleOfDay(SaleOfDayModel model)
+ {
+ ICommandEmitter e = new EPSON();
+ Init(e);
+ PrintSaleOfDay(model,e);
+ }
+
+ private void Init(ICommandEmitter e)
+ {
+ _serialPrinter.Write(e.Initialize());
+ _serialPrinter.Write(e.Enable());
+ _serialPrinter.Write(e.CodePage(CodePage.ISO8859_15_LATIN9));
+ _serialPrinter.Write(e.EnableAutomaticStatusBack());
+ }
+
+ //private void PrintReceipt(ReceiptModel model, ICommandEmitter e)
+ //{
+ // List printBytes = new List();
+ // PrintHeader printHeader = new PrintHeader(e);
+ // printBytes.AddRange(printHeader.Print(model.Header));
+
+ // PrintBody printBody = new PrintBody(e,_config);
+ // printBytes.AddRange(printBody.Print(model.BodyModel));
+
+ // PrintFooter printFooter = new PrintFooter(e);
+ // printBytes.AddRange(printFooter.Print(model.Footer));
+
+ // //And at last, print
+ // byte[][] array = printBytes.SelectMany(c => c).ToArray();
+
+ // _serialPrinter.Write(array);
+ // _serialPrinter.Write(e.FeedLines(5));
+ // _serialPrinter.Write(e.FullCut());
+
+ //}
+ private void PrintSaleOfDay(SaleOfDayModel model, ICommandEmitter e)
+ {
+ List data = new List();
+
+ int printWidth = Convert.ToInt32(_config["PrintSettings:PrintWidth"])-8;
+ PrintString printString = new PrintString();
+
+ byte[][] dateBytes =
+ {
+ e.SetStyles(PrintStyle.DoubleWidth),
+ e.LeftAlign(),
+ e.PrintLine($"Dato: {model.Date:dd-MM-yyyy}")
+ };
+ data.Add(dateBytes);
+
+ byte[][] seperater =
+ {
+ e.SetStyles(PrintStyle.None),
+ e.LeftAlign(),
+ e.PrintLine($"-----------------------------------")
+ };
+ data.Add(seperater);
+
+
+ foreach (SaleOfDayDetail saleOfDayDetail in model.SaleOfDayDetail)
+ {
+ //string pS = printString.MakePrintString(printWidth, saleOfDayDetail.Category, saleOfDayDetail.TotalSale.ToString(CultureInfo.InvariantCulture));
+ string pS = $"{saleOfDayDetail.Category} : {saleOfDayDetail.TotalSale.ToString("#.#0")}";
+ byte[][] totalPrice =
+ {
+ e.SetStyles(PrintStyle.DoubleWidth),
+ e.LeftAlign(),
+ e.PrintLine(pS)
+ };
+ data.Add(totalPrice);
+ }
+
+ data.Add(seperater);
+
+ //string totalSale = printString.MakePrintString(printWidth, "Salg", model.TotalSale.ToString(CultureInfo.InvariantCulture));
+ string totalSale = $"Total salg: {model.TotalSale.ToString("#.#0")}";
+ byte[][] tSBytes =
+ {
+ e.SetStyles(PrintStyle.DoubleWidth),
+ e.LeftAlign(),
+ e.PrintLine(totalSale)
+ };
+ data.Add(tSBytes);
+
+ //string totalCustomer = printString.MakePrintString(printWidth, "Kunder", model.TotalCustomers.ToString());
+ string totalCustomer = $"Antal kunder: {model.TotalCustomers}";
+ byte[][] tCBytes =
+ {
+ e.SetStyles(PrintStyle.DoubleWidth),
+ e.LeftAlign(),
+ e.PrintLine(totalCustomer)
+ };
+ data.Add(tCBytes);
+
+ byte[][] array = data.SelectMany(c => c).ToArray();
+ _serialPrinter.Write(array);
+ _serialPrinter.Write(e.FeedLines(5));
+ _serialPrinter.Write(e.FullCut());
+ }
+
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintAlignment.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintAlignment.cs
new file mode 100644
index 0000000..4a7ad9f
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintAlignment.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using EpsonPrinter.Enums;
+using ESCPOS_NET.Emitters;
+
+namespace EpsonPrinter.Services
+{
+ public class PrintAlignment
+ {
+ public byte[] GetTextAlignment(ICommandEmitter e, AlignmentEnum alignment)
+ {
+ if (alignment == AlignmentEnum.Left)
+ return e.LeftAlign();
+
+ if (alignment == AlignmentEnum.Right)
+ return e.RightAlign();
+
+ return e.CenterAlign();
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintBody.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintBody.cs
new file mode 100644
index 0000000..afb1d26
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintBody.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using EpsonPrinter.Model;
+using ESCPOS_NET.Emitters;
+using Microsoft.Extensions.Configuration;
+
+namespace EpsonPrinter.Services
+{
+ public class PrintBody
+ {
+ private ICommandEmitter _e;
+ private IConfiguration _config;
+
+ private int productMaxWidth;
+
+ public PrintBody(ICommandEmitter e, IConfiguration config)
+ {
+ _e = e;
+ _config = config;
+ }
+
+ public List Print(BodyModel model)
+ {
+ List data = new List();
+
+ byte[][] headerModel =
+ {
+ _e.SetStyles(PrintStyle.None),
+ _e.LeftAlign(),
+ _e.PrintLine($"Dato.........: {model.ReceiptTime}"),
+ _e.PrintLine($"Ekspedient...: {model.Staff}"),
+ _e.PrintLine($"Kvittering...: {model.ReceiptNumber}"),
+ _e.FeedLines(1)
+ };
+ data.Add(headerModel);
+
+ int printWidth = Convert.ToInt32(_config["PrintSettings:PrintWidth"]);
+ foreach (BodyProductModel product in model.Products)
+ {
+ //Size of product is max half of recipt
+ string totalProductLine = MakeProductNameString(product.Product, printWidth);
+ totalProductLine += MakeProductNumber(product.NoOfProduct, product.Price);
+ totalProductLine = MakeTotalProductPrice(totalProductLine, product.TotalPrice,printWidth);
+
+ byte[][] p =
+ {
+ _e.PrintLine(totalProductLine)
+ };
+ data.Add(p);
+ }
+
+ PrintString printString = new PrintString();
+ string totalPricePrint = printString.MakePrintString(printWidth, $"Total: ", $"{model.TotalPrice:F} DKK");
+ byte[][] totalPrice =
+ {
+ _e.FeedLines(1),
+ _e.SetStyles(PrintStyle.Bold | PrintStyle.DoubleHeight),
+ _e.RightAlign(),
+ _e.PrintLine(totalPricePrint)
+ };
+ data.Add(totalPrice);
+
+ byte[][] vat =
+ {
+ _e.FeedLines(1),
+ _e.SetStyles(PrintStyle.None),
+ _e.LeftAlign(),
+ _e.PrintLine(MakeVat(printWidth,model.TotalVat))
+ };
+ data.Add(vat);
+
+ return data;
+ }
+
+ private string MakeProductNameString(string product, int totalWidth)
+ {
+ string productString = product;
+ productMaxWidth = (totalWidth / 2) - 5;
+ int productLength = product.Length;
+ if (productLength < productMaxWidth)
+ {
+
+ for (int i = productLength; i < productMaxWidth; i++)
+ {
+ productString += ".";
+ }
+ }
+
+ return productString;
+ }
+
+ private string MakeProductNumber(int noOfProduct, decimal price)
+ {
+ return $"{noOfProduct} á {price:F} DKK :";
+ }
+
+ private string MakeTotalProductPrice(string buffer, decimal totalPrice, int printWidth)
+ {
+ PrintString printString = new PrintString();
+ string p = $"{totalPrice:F} DKK";
+ string makePrintString = printString.MakePrintString(printWidth, buffer, p);
+ return makePrintString;
+ }
+
+ private string MakeVat(int printWidth, decimal vat)
+ {
+ PrintString printString = new PrintString();
+ string vatDesc = $"Moms udgør 25% :";
+ string v = $"{vat:F} DKK";
+ string vatString = printString.MakePrintString(printWidth, vatDesc, v);
+ return vatString;
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintFooter.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintFooter.cs
new file mode 100644
index 0000000..d23e8f4
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintFooter.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using EpsonPrinter.Model;
+using ESCPOS_NET;
+using ESCPOS_NET.Emitters;
+
+namespace EpsonPrinter.Services
+{
+ public class PrintFooter
+ {
+ private ICommandEmitter _e;
+
+ public PrintFooter(ICommandEmitter e)
+ {
+ _e = e;
+ }
+
+ public List Print(List model)
+ {
+ List data = new List();
+ PrintStyleCombination printStyleCombination = new PrintStyleCombination();
+ PrintAlignment printAlignment = new PrintAlignment();
+ byte[][] feedLines =
+ {
+ _e.FeedLines(1)
+ };
+ data.Add(feedLines);
+ foreach (FooterModel footerModel in model)
+ {
+
+ PrintStyle printStyle = printStyleCombination.Combine(footerModel.PrintStyles);
+ byte[][] bytes = {
+ _e.SetStyles(PrintStyle.None),
+ printAlignment.GetTextAlignment(_e,footerModel.TextAlignment),
+ _e.SetStyles(printStyle),
+ _e.PrintLine(footerModel.Value),
+ _e.FeedLines(footerModel.FeedingLines)
+ };
+ data.Add(bytes);
+ }
+
+ return data;
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintHeader.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintHeader.cs
new file mode 100644
index 0000000..eec3fd3
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintHeader.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using EpsonPrinter.Model;
+using ESCPOS_NET;
+using ESCPOS_NET.Emitters;
+using ESCPOS_NET.Utilities;
+
+namespace EpsonPrinter.Services
+{
+ public class PrintHeader
+ {
+ private ICommandEmitter _e;
+
+ public PrintHeader(ICommandEmitter e)
+ {
+ _e = e;
+ }
+
+ public List Print(List model)
+ {
+ List data = new List();
+ PrintStyleCombination printStyleCombination = new PrintStyleCombination();
+ PrintAlignment printAlignment = new PrintAlignment();
+ foreach (HeaderModel headerModel in model)
+ {
+
+ PrintStyle printStyle = printStyleCombination.Combine(headerModel.PrintStyles);
+ byte[][] bytes = {
+ _e.SetStyles(PrintStyle.None),
+ printAlignment.GetTextAlignment(_e,headerModel.TextAlignment),
+ _e.SetStyles(printStyle),
+ _e.PrintLine(headerModel.Value),
+ _e.FeedLines(headerModel.FeedingLines)
+ };
+ data.Add(bytes);
+ }
+ byte[][] feedLines =
+ {
+ _e.FeedLines(1)
+ };
+ data.Add(feedLines);
+ return data;
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintString.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintString.cs
new file mode 100644
index 0000000..fb664a1
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintString.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EpsonPrinter.Services
+{
+ public class PrintString
+ {
+ ///
+ /// An appropriate interval is converted into the length of
+ /// the tab about two texts. And make a printing data.
+ ///
+ ///
+ /// The width of the territory which it prints on is converted into the number of
+ /// characters, and that value is specified.
+ ///
+ ///
+ /// It is necessary as an information for deciding the interval of the text.
+ ///
+ ///
+ /// It is necessary as an information for deciding the interval of the text, too.
+ ///
+ /// printing data.
+ ///
+ public String MakePrintString(int iLineChars, String strBuf, String strPrice)
+ {
+ int iSpaces = 0;
+ String tab = "";
+ try
+ {
+ iSpaces = iLineChars - (strBuf.Length + strPrice.Length);
+ for (int j = 0; j < iSpaces; j++)
+ {
+ tab += " ";
+ }
+ }
+ catch (Exception)
+ {
+ }
+ return strBuf + tab + strPrice;
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintStyleCombination.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintStyleCombination.cs
new file mode 100644
index 0000000..bd6adfd
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Services/PrintStyleCombination.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using EpsonPrinter.Enums;
+using EpsonPrinter.Model;
+using ESCPOS_NET.Emitters;
+
+namespace EpsonPrinter.Services
+{
+ public class PrintStyleCombination
+ {
+ public PrintStyle Combine(PrintStyleModel printStyleModel)
+ {
+
+ //Bold
+ if (printStyleModel.Bold && !printStyleModel.DoubleHeight && !printStyleModel.DoubleWidth && !printStyleModel.FontB && !printStyleModel.Underline)
+ return PrintStyle.Bold;
+
+ if (printStyleModel.Bold && printStyleModel.DoubleHeight && !printStyleModel.DoubleWidth && !printStyleModel.FontB && !printStyleModel.Underline)
+ return PrintStyle.Bold | PrintStyle.DoubleHeight;
+
+ if(printStyleModel.Bold && printStyleModel.DoubleHeight && printStyleModel.DoubleWidth && !printStyleModel.FontB && !printStyleModel.Underline)
+ return PrintStyle.Bold | PrintStyle.DoubleHeight | PrintStyle.DoubleWidth;
+
+ if (printStyleModel.Bold && printStyleModel.DoubleHeight && printStyleModel.DoubleWidth && printStyleModel.FontB && !printStyleModel.Underline)
+ return PrintStyle.Bold | PrintStyle.DoubleHeight | PrintStyle.DoubleWidth | PrintStyle.FontB;
+
+ if (printStyleModel.Bold && printStyleModel.DoubleHeight && printStyleModel.DoubleWidth && printStyleModel.FontB && printStyleModel.Underline)
+ return PrintStyle.Bold | PrintStyle.DoubleHeight | PrintStyle.DoubleWidth | PrintStyle.FontB | PrintStyle.Underline;
+
+ //DoubleHeight
+ if (printStyleModel.DoubleHeight && !printStyleModel.DoubleWidth && !printStyleModel.FontB && !printStyleModel.Underline)
+ return PrintStyle.DoubleHeight;
+
+ if (printStyleModel.DoubleHeight && printStyleModel.DoubleWidth && !printStyleModel.FontB && !printStyleModel.Underline)
+ return PrintStyle.DoubleHeight | PrintStyle.DoubleWidth;
+
+ if (printStyleModel.DoubleHeight && printStyleModel.DoubleWidth && printStyleModel.FontB && !printStyleModel.Underline)
+ return PrintStyle.DoubleHeight | PrintStyle.DoubleWidth | PrintStyle.FontB;
+
+ if (printStyleModel.DoubleHeight && printStyleModel.DoubleWidth && printStyleModel.FontB && printStyleModel.Underline)
+ return PrintStyle.DoubleHeight | PrintStyle.DoubleWidth | PrintStyle.FontB | PrintStyle.Underline;
+
+ //DoubleWidth
+ if (printStyleModel.DoubleWidth && !printStyleModel.FontB && !printStyleModel.Underline)
+ return PrintStyle.DoubleWidth;
+
+ if (printStyleModel.DoubleWidth && printStyleModel.FontB && !printStyleModel.Underline)
+ return PrintStyle.DoubleWidth | PrintStyle.FontB;
+
+ if (printStyleModel.DoubleWidth && printStyleModel.FontB && printStyleModel.Underline)
+ return PrintStyle.DoubleWidth | PrintStyle.FontB | PrintStyle.Underline;
+
+ //FontB
+ if (printStyleModel.FontB && !printStyleModel.Underline)
+ return PrintStyle.FontB;
+
+ if(printStyleModel.FontB && printStyleModel.Underline)
+ return PrintStyle.FontB | PrintStyle.Underline;
+
+ //Underline
+ if (printStyleModel.Underline)
+ return PrintStyle.Underline;
+
+ return PrintStyle.None;
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/Startup.cs b/PointOfSale/Pos.Ui/EpsonPrinter/Startup.cs
new file mode 100644
index 0000000..8278a63
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/Startup.cs
@@ -0,0 +1,65 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.HttpsPolicy;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.OpenApi.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using EpsonPrinter.Services;
+using ESCPOS_NET;
+
+namespace EpsonPrinter
+{
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+
+ services.AddControllers();
+ services.AddSwaggerGen(c =>
+ {
+ c.SwaggerDoc("v1", new OpenApiInfo { Title = "EpsonPrinter", Version = "v1" });
+ });
+
+ services.AddSingleton();
+
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ app.UseSwagger();
+ app.UseSwaggerUI(
+ c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "EpsonPrinter v1"));
+ }
+
+ app.UseHttpsRedirection();
+
+ app.UseRouting();
+
+ app.UseAuthorization();
+
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllers();
+ });
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/appsettings.Development.json b/PointOfSale/Pos.Ui/EpsonPrinter/appsettings.Development.json
new file mode 100644
index 0000000..8983e0f
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/appsettings.json b/PointOfSale/Pos.Ui/EpsonPrinter/appsettings.json
new file mode 100644
index 0000000..1f90b74
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/appsettings.json
@@ -0,0 +1,15 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*",
+ "PrintSettings": {
+ "ComPort": "COM5",
+ "BaudRate": 115200,
+ "PrintWidth": 48
+ }
+}
diff --git a/PointOfSale/Pos.Ui/EpsonPrinter/testdata.json b/PointOfSale/Pos.Ui/EpsonPrinter/testdata.json
new file mode 100644
index 0000000..2d10382
--- /dev/null
+++ b/PointOfSale/Pos.Ui/EpsonPrinter/testdata.json
@@ -0,0 +1,73 @@
+{
+ "logoBase64": "string",
+ "header": [
+ {
+ "value": "Frste linie med ",
+ "printStyles": {
+ "fontB": false,
+ "bold": true,
+ "doubleHeight": false,
+ "doubleWidth": false,
+ "underline": false
+ },
+ "textAlignment": 0
+ },
+ {
+ "value": "Anden linie",
+ "printStyles": {
+ "fontB": false,
+ "bold": true,
+ "doubleHeight": false,
+ "doubleWidth": false,
+ "underline": false
+ },
+ "textAlignment": 0
+ },
+ {
+ "value": "Tredje linie",
+ "printStyles": {
+ "fontB": false,
+ "bold": true,
+ "doubleHeight": false,
+ "doubleWidth": false,
+ "underline": false
+ },
+ "textAlignment": 0
+ },
+ {
+ "value": "Fjerde linie",
+ "printStyles": {
+ "fontB": false,
+ "bold": true,
+ "doubleHeight": false,
+ "doubleWidth": false,
+ "underline": false
+ },
+ "textAlignment": 0
+ },
+ {
+ "value": "Femte linie",
+ "printStyles": {
+ "fontB": false,
+ "bold": true,
+ "doubleHeight": false,
+ "doubleWidth": false,
+ "underline": false
+ },
+ "textAlignment": 0
+ }
+ ],
+ "footer": [
+ {
+ "value": "string",
+ "printStyles": {
+ "fontB": true,
+ "bold": true,
+ "doubleHeight": true,
+ "doubleWidth": true,
+ "underline": true
+ },
+ "textAlignment": 0
+ }
+ ]
+}
\ No newline at end of file
diff --git a/PointOfSale/Pos.Ui/Pos/.config/dotnet-tools.json b/PointOfSale/Pos.Ui/Pos/.config/dotnet-tools.json
new file mode 100644
index 0000000..06b8682
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "dotnet-ef": {
+ "version": "5.0.9",
+ "commands": [
+ "dotnet-ef"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/PointOfSale/Pos.Ui/Pos/App.xaml b/PointOfSale/Pos.Ui/Pos/App.xaml
new file mode 100644
index 0000000..9efbbcd
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/App.xaml.cs b/PointOfSale/Pos.Ui/Pos/App.xaml.cs
new file mode 100644
index 0000000..46101cf
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/App.xaml.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace Pos
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ public App()
+ {
+ //Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("NDk1NTE0QDMxMzkyZTMyMmUzMFI1bUkyeWZBZngydzlUaHJpUEdiNmtnWnhFUytPYUVvYnQvZFErWXdkN3c9");
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/AssemblyInfo.cs b/PointOfSale/Pos.Ui/Pos/AssemblyInfo.cs
new file mode 100644
index 0000000..8b5504e
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/PointOfSale/Pos.Ui/Pos/Icons/cashier.png b/PointOfSale/Pos.Ui/Pos/Icons/cashier.png
new file mode 100644
index 0000000..e6b672a
Binary files /dev/null and b/PointOfSale/Pos.Ui/Pos/Icons/cashier.png differ
diff --git a/PointOfSale/Pos.Ui/Pos/Icons/category.png b/PointOfSale/Pos.Ui/Pos/Icons/category.png
new file mode 100644
index 0000000..85eca0e
Binary files /dev/null and b/PointOfSale/Pos.Ui/Pos/Icons/category.png differ
diff --git a/PointOfSale/Pos.Ui/Pos/Icons/employee.png b/PointOfSale/Pos.Ui/Pos/Icons/employee.png
new file mode 100644
index 0000000..63d317c
Binary files /dev/null and b/PointOfSale/Pos.Ui/Pos/Icons/employee.png differ
diff --git a/PointOfSale/Pos.Ui/Pos/Icons/employees.png b/PointOfSale/Pos.Ui/Pos/Icons/employees.png
new file mode 100644
index 0000000..631de69
Binary files /dev/null and b/PointOfSale/Pos.Ui/Pos/Icons/employees.png differ
diff --git a/PointOfSale/Pos.Ui/Pos/Icons/exchange.png b/PointOfSale/Pos.Ui/Pos/Icons/exchange.png
new file mode 100644
index 0000000..64973d5
Binary files /dev/null and b/PointOfSale/Pos.Ui/Pos/Icons/exchange.png differ
diff --git a/PointOfSale/Pos.Ui/Pos/Icons/pos.png b/PointOfSale/Pos.Ui/Pos/Icons/pos.png
new file mode 100644
index 0000000..4868792
Binary files /dev/null and b/PointOfSale/Pos.Ui/Pos/Icons/pos.png differ
diff --git a/PointOfSale/Pos.Ui/Pos/Icons/product.png b/PointOfSale/Pos.Ui/Pos/Icons/product.png
new file mode 100644
index 0000000..0cee4d6
Binary files /dev/null and b/PointOfSale/Pos.Ui/Pos/Icons/product.png differ
diff --git a/PointOfSale/Pos.Ui/Pos/Icons/receipt.png b/PointOfSale/Pos.Ui/Pos/Icons/receipt.png
new file mode 100644
index 0000000..1403a8a
Binary files /dev/null and b/PointOfSale/Pos.Ui/Pos/Icons/receipt.png differ
diff --git a/PointOfSale/Pos.Ui/Pos/Icons/settings.png b/PointOfSale/Pos.Ui/Pos/Icons/settings.png
new file mode 100644
index 0000000..f31d364
Binary files /dev/null and b/PointOfSale/Pos.Ui/Pos/Icons/settings.png differ
diff --git a/PointOfSale/Pos.Ui/Pos/Json/BodyModel.cs b/PointOfSale/Pos.Ui/Pos/Json/BodyModel.cs
new file mode 100644
index 0000000..759e8fe
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Json/BodyModel.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Pos.Json
+{
+ public class BodyModel
+ {
+ public ProductModel[] products { get; set; }
+ public float totalPrice { get; set; }
+ public float totalVat { get; set; }
+ public int receiptNumber { get; set; }
+ public string receiptTime { get; set; }
+ public string staff { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Json/FooterModel.cs b/PointOfSale/Pos.Ui/Pos/Json/FooterModel.cs
new file mode 100644
index 0000000..f17fc20
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Json/FooterModel.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Pos.Json
+{
+ public class FooterModel
+ {
+ public string value { get; set; }
+ public PrintStylesModel printStyles { get; set; }
+ public int textAlignment { get; set; }
+ public int feedingLines { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Json/HeaderModel.cs b/PointOfSale/Pos.Ui/Pos/Json/HeaderModel.cs
new file mode 100644
index 0000000..7b63d15
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Json/HeaderModel.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Pos.Json
+{
+ public class HeaderModel
+ {
+ public string value { get; set; }
+ public PrintStylesModel printStyles { get; set; }
+ public int textAlignment { get; set; }
+ public int feedingLines { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Json/PosReceipt.cs b/PointOfSale/Pos.Ui/Pos/Json/PosReceipt.cs
new file mode 100644
index 0000000..2b400c3
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Json/PosReceipt.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using EpsonPrinter.Model;
+
+namespace Pos.Json
+{
+ public class PosReceipt
+ {
+ public string logoBase64 { get; set; }
+ public HeaderModel[] header { get; set; }
+ public BodyModel bodyModel { get; set; } = new();
+ public FooterModel[] footer { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Json/PrintStylesModel.cs b/PointOfSale/Pos.Ui/Pos/Json/PrintStylesModel.cs
new file mode 100644
index 0000000..f21910f
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Json/PrintStylesModel.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Pos.Json
+{
+ public class PrintStylesModel
+ {
+ public bool fontB { get; set; }
+ public bool bold { get; set; }
+ public bool doubleHeight { get; set; }
+ public bool doubleWidth { get; set; }
+ public bool underline { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Json/ProductModel.cs b/PointOfSale/Pos.Ui/Pos/Json/ProductModel.cs
new file mode 100644
index 0000000..32a4ae8
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Json/ProductModel.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Pos.Json
+{
+ public class ProductModel
+ {
+ public string product { get; set; }
+ public string noOfProduct { get; set; }
+ public float price { get; set; }
+ public float totalPrice { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/MainWindow.xaml b/PointOfSale/Pos.Ui/Pos/MainWindow.xaml
new file mode 100644
index 0000000..7db951b
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/MainWindow.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/MainWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/MainWindow.xaml.cs
new file mode 100644
index 0000000..b24dc57
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/MainWindow.xaml.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using Pos.Setting;
+
+namespace Pos
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+
+ private void btnSettings_Click(object sender, RoutedEventArgs e)
+ {
+ SettingWindow settingWindow = new SettingWindow();
+ settingWindow.Show();
+ }
+
+ private void btnSale_Click(object sender, RoutedEventArgs e)
+ {
+ SalesWindow salesWindow = new SalesWindow();
+ salesWindow.Show();
+ }
+
+ private void btnPrintReceipt_Click(object sender, RoutedEventArgs e)
+ {
+ PrintReceiptWindow printReceiptWindow = new PrintReceiptWindow();
+ printReceiptWindow.Show();
+ }
+
+ private void BtnTodaySale_OnClick(object sender, RoutedEventArgs e)
+ {
+ SaleOfDay saleOfDay = new SaleOfDay();
+ saleOfDay.Show();
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Models/AmountGridModel.cs b/PointOfSale/Pos.Ui/Pos/Models/AmountGridModel.cs
new file mode 100644
index 0000000..c573212
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Models/AmountGridModel.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Pos.Models
+{
+ public class AmountGridModel
+ {
+ public string PaymentMethodText { get; set; }
+ public decimal Amount { get; set; }
+ public string AmountText { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Models/Enums.cs b/PointOfSale/Pos.Ui/Pos/Models/Enums.cs
new file mode 100644
index 0000000..c303042
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Models/Enums.cs
@@ -0,0 +1,9 @@
+namespace Pos.Models;
+
+public enum PaymentMethod
+{
+ Cash,
+ Card,
+ GiftCard,
+ MobilePay
+}
\ No newline at end of file
diff --git a/PointOfSale/Pos.Ui/Pos/Models/PriceLineModel.cs b/PointOfSale/Pos.Ui/Pos/Models/PriceLineModel.cs
new file mode 100644
index 0000000..ad61717
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Models/PriceLineModel.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Pos.Models
+{
+ public class PriceLineModel
+ {
+ public string Name { get; set; }
+ public int NumberProducts { get; set; }
+ public decimal Price { get; set; }
+ public int Id { get; set; }
+ public bool IsProductGroup { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Models/SaleGridInternalModel.cs b/PointOfSale/Pos.Ui/Pos/Models/SaleGridInternalModel.cs
new file mode 100644
index 0000000..155bc6c
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Models/SaleGridInternalModel.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Pos.Models
+{
+ public class SaleGridInternalModel
+ {
+ public decimal InternPrice { get; set; }
+ public int InternPieces { get; set; }
+
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Models/SaleGridModel.cs b/PointOfSale/Pos.Ui/Pos/Models/SaleGridModel.cs
new file mode 100644
index 0000000..1a11c83
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Models/SaleGridModel.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Pos.Models
+{
+ public class SaleGridModel
+ {
+ public string Navn { get; set; }
+ public string Stk { get; set; }
+ public string Pris { get; set; }
+ public string Total { get; set; }
+ public SaleGridInternalModel SaleGridInternalModel { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Models/TotalSaleCategory.cs b/PointOfSale/Pos.Ui/Pos/Models/TotalSaleCategory.cs
new file mode 100644
index 0000000..9c5e8ac
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Models/TotalSaleCategory.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Pos.Models
+{
+ public class TotalSaleCategory
+ {
+ public string Category { get; set; }
+ public decimal Sale { get; set; }
+ public string SaleString { get; set; }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Models/TotalSaleDetail.cs b/PointOfSale/Pos.Ui/Pos/Models/TotalSaleDetail.cs
new file mode 100644
index 0000000..1ec27fa
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Models/TotalSaleDetail.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Pos.Models
+{
+ public class TotalSaleDetail
+ {
+ public decimal TotalSale { get; set; }
+ public int TotalCustomer { get; set; }
+ public List TotalSaleCategories { get; set; } = new();
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Pos - Backup.csproj b/PointOfSale/Pos.Ui/Pos/Pos - Backup.csproj
new file mode 100644
index 0000000..1a541fc
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Pos - Backup.csproj
@@ -0,0 +1,33 @@
+
+
+
+ WinExe
+ net5.0-windows
+ true
+
+
+
+
+ C:\Program Files (x86)\Syncfusion\Essential Studio\WPF\19.2.0.44\precompiledassemblies\net5.0\Syncfusion.SfSkinManager.WPF.dll
+
+
+ C:\Program Files (x86)\Syncfusion\Essential Studio\WPF\19.2.0.44\precompiledassemblies\net5.0\Syncfusion.Shared.WPF.dll
+
+
+ C:\Program Files (x86)\Syncfusion\Essential Studio\WPF\19.2.0.44\precompiledassemblies\net5.0\Syncfusion.Themes.FluentLight.WPF.dll
+
+
+ C:\Program Files (x86)\Syncfusion\Essential Studio\WPF\19.2.0.44\precompiledassemblies\net5.0\Syncfusion.Tools.WPF.dll
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Pos.csproj b/PointOfSale/Pos.Ui/Pos/Pos.csproj
new file mode 100644
index 0000000..c40b5bd
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Pos.csproj
@@ -0,0 +1,82 @@
+
+
+
+ WinExe
+ net6.0-windows
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ Always
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/PrintReceiptWindow.xaml b/PointOfSale/Pos.Ui/Pos/PrintReceiptWindow.xaml
new file mode 100644
index 0000000..4149595
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/PrintReceiptWindow.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/PrintReceiptWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/PrintReceiptWindow.xaml.cs
new file mode 100644
index 0000000..4f7acd5
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/PrintReceiptWindow.xaml.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Models;
+using Database.Repository;
+using Pos.Service;
+
+namespace Pos
+{
+ ///
+ /// Interaction logic for PrintReceiptWindow.xaml
+ ///
+ public partial class PrintReceiptWindow : Window
+ {
+ private List _receiptList = new List();
+ private bool _init = false;
+ private int _saleId = 0;
+
+ public PrintReceiptWindow()
+ {
+ InitializeComponent();
+
+ StartDatePicker.DisplayDate = DateTime.Now.AddDays(-7);
+ StartDatePicker.SelectedDate = DateTime.Now.AddDays(-7);
+ EndDatePicker.DisplayDate = DateTime.Now;
+ EndDatePicker.SelectedDate = DateTime.Now;
+ }
+
+ private void Search_OnClick(object sender, RoutedEventArgs e)
+ {
+ DateTime startDate = (DateTime)StartDatePicker.SelectedDate;
+ DateTime endDate = (DateTime)EndDatePicker.SelectedDate;
+ SaleRepository saleRepository = new SaleRepository();
+ List saleEntities = saleRepository.GetByDateRange(startDate, endDate);
+ foreach (SaleEntity entity in saleEntities)
+ {
+ List saleLineBySaleId = saleRepository.GetSaleLineBySaleId(entity.Id);
+ decimal total = 0;
+ foreach (SaleLineEntity lineEntity in saleLineBySaleId)
+ {
+ total += lineEntity.Total;
+ }
+
+ ListReceiptModel receiptModel = new ListReceiptModel
+ {
+ Id = entity.Id,
+ Content = $"{entity.Time:dd-MM-yyyy HH:mm} - Pris: {total:0.00}"
+ };
+ _receiptList.Add(receiptModel);
+ }
+
+ _init = true;
+ ListReciept.ItemsSource = _receiptList;
+ _init = false;
+ }
+
+
+ public class ListReceiptModel
+ {
+ public int Id { get; set; }
+ public string Content { get; set; }
+
+ public override string ToString()
+ {
+ return Content;
+ }
+ }
+
+ private void ListReciept_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (_init)
+ return;
+
+ ListReceiptModel data = (ListReceiptModel) e.AddedItems[0];
+ _saleId = data.Id;
+ SaleRepository saleRepository = new SaleRepository();
+ List saleLineBySaleId = saleRepository.GetSaleLineBySaleId(data.Id);
+ StringBuilder sb = new StringBuilder();
+ decimal total = 0;
+ foreach (SaleLineEntity saleLine in saleLineBySaleId)
+ {
+ total += saleLine.Total;
+ sb.AppendLine(
+ $"{saleLine.Product} - {saleLine.Pieces} stk. a' {saleLine.Price.ToString("0.00")} = {saleLine.Total.ToString("0.00")}");
+ }
+
+ sb.AppendLine($"Total: {total.ToString("0.00")} kr.");
+ PrintReceipt.Visibility = Visibility.Visible;
+ SaleLines.Clear();
+ SaleLines.Text = sb.ToString();
+ }
+
+ private void PrintReceipt_OnClick(object sender, RoutedEventArgs e)
+ {
+ SaleService saleService = new SaleService();
+ saleService.PrintReceipt(_saleId);
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/ResourceDictionaries/Resource.xaml b/PointOfSale/Pos.Ui/Pos/ResourceDictionaries/Resource.xaml
new file mode 100644
index 0000000..ba0f04a
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/ResourceDictionaries/Resource.xaml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/SaleOfDay.xaml b/PointOfSale/Pos.Ui/Pos/SaleOfDay.xaml
new file mode 100644
index 0000000..2a15329
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/SaleOfDay.xaml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/SaleOfDay.xaml.cs b/PointOfSale/Pos.Ui/Pos/SaleOfDay.xaml.cs
new file mode 100644
index 0000000..b40f5c5
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/SaleOfDay.xaml.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using EpsonPrinter.Model;
+using Pos.Models;
+using Pos.Service;
+
+namespace Pos
+{
+ ///
+ /// Interaction logic for SaleOfDay.xaml
+ ///
+ public partial class SaleOfDay : Window
+ {
+ private bool _initialLoad = true;
+ private TotalSaleDetail totalSaleDetail;
+ public SaleOfDay()
+ {
+ InitializeComponent();
+ SaleService saleService = new SaleService();
+ totalSaleDetail = saleService.TotalSale(DateTime.Now);
+ TotalSale.Content = $"Dagens salg: kr. {totalSaleDetail.TotalSale:0.00}";
+ StartDatePicker.SelectedDate = DateTime.Now;
+ GridSaleDetail.ItemsSource = totalSaleDetail.TotalSaleCategories;
+ _initialLoad = false;
+ }
+
+ private void StartDatePicker_SelectedDateChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (_initialLoad)
+ return;
+
+ _initialLoad = false;
+ DateTime selectedDate = DateTime.Parse(sender.ToString());
+ SaleService saleService = new SaleService();
+ totalSaleDetail = saleService.TotalSale(selectedDate);
+ TotalSale.Content = $"Dagens salg: kr. {totalSaleDetail.TotalSale:0.00}";
+ GridSaleDetail.ItemsSource = totalSaleDetail.TotalSaleCategories;
+ }
+
+ private void PrintSale_OnClick(object sender, RoutedEventArgs e)
+ {
+ SaleService saleService = new SaleService();
+ saleService.PrintSaleOfDay(StartDatePicker.SelectedDate.Value,totalSaleDetail);
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/SalesWindow.xaml b/PointOfSale/Pos.Ui/Pos/SalesWindow.xaml
new file mode 100644
index 0000000..7a7757e
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/SalesWindow.xaml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PointOfSale/Pos.Ui/Pos/SalesWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/SalesWindow.xaml.cs
new file mode 100644
index 0000000..62a5a4c
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/SalesWindow.xaml.cs
@@ -0,0 +1,513 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Markup.Localizer;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Models;
+using Pos.Models;
+using Pos.Service;
+
+namespace Pos
+{
+ ///
+ /// Interaction logic for SalesWindow.xaml
+ ///
+ public partial class SalesWindow : Window
+ {
+ private ObservableCollection _employees;
+ private ObservableCollection _productGroups;
+ private ObservableCollection _saleGridModels;
+ private ObservableCollection _paymentGridModels;
+ private List _priceLineModels;
+
+ private ProductEntity _selectedProduct;
+ private ProductGroupEntity _selectedProductGroup;
+
+ private int _employeeId = -1;
+ private int _productGroupId = -1;
+ private int _productId = -1;
+ private int _numberOfProducts = 1;
+ private int _selectedSaleGridIndex = -1;
+
+ private int _maxCharOfName = 0;
+
+ private decimal _price = 0;
+ private decimal _totalPrice = 0;
+
+ private Button _prevEmpButton = new Button();
+ private Button _prevProductGroupButton = new Button();
+ private Button _prevProduct = new Button();
+
+ public SalesWindow()
+ {
+ InitializeComponent();
+ InitialLoad();
+ }
+
+ private void InitialLoad()
+ {
+ CacheService.Invalidate();
+ _employees = CacheService.GetEmployee() as ObservableCollection;
+ _productGroups = CacheService.GetProductGroupsIncludeProducts() as ObservableCollection;
+ _saleGridModels = new ObservableCollection();
+ _priceLineModels = new List();
+ _paymentGridModels = new ObservableCollection();
+ SaleGrid.ItemsSource = _saleGridModels;
+ PaymentGrid.ItemsSource = _paymentGridModels;
+ BuildEmployee();
+ BuildProductGroup();
+ ManipulateBuySection();
+ UpdateWindowTitle();
+
+ foreach (var productGroup in _productGroups)
+ {
+ if (_maxCharOfName < productGroup.Name.Length)
+ _maxCharOfName = productGroup.Name.Length;
+
+ foreach (ProductEntity product in productGroup.Products)
+ {
+ if (_maxCharOfName < product.Name.Length)
+ _maxCharOfName = product.Name.Length;
+ }
+ }
+ }
+
+
+
+ private void BtnPrGr_Click(object sender, RoutedEventArgs e)
+ {
+ _productId = -1;
+ _prevProductGroupButton.Style = (Style)FindResource("SaleButtons");
+ Button button = sender as Button;
+ _prevProductGroupButton = button;
+ button.Style = (Style)FindResource("SaleButtonsActive");
+ _productGroupId = (int)button.Tag;
+ _selectedProductGroup = _productGroups.Single(c => c.Id == _productGroupId);
+ BuildProducts(_selectedProductGroup.Products);
+ ManipulateBuySection();
+ }
+
+ private void BtnProduct_Click(object sender, RoutedEventArgs e)
+ {
+ _prevProduct.Style = (Style)FindResource("SaleButtons");
+ Button button = sender as Button;
+ _prevProduct = button;
+ button.Style = (Style)FindResource("SaleButtonsActive");
+ _productId = (int)button.Tag;
+ _selectedProduct = _selectedProductGroup.Products.Single(c => c.Id == _productId);
+ ManipulateBuySection();
+ }
+
+ private void BtnEmp_Click(object sender, RoutedEventArgs e)
+ {
+ _prevEmpButton.Style = (Style)FindResource("SaleButtons");
+ _prevProductGroupButton.Style = (Style)FindResource("SaleButtons");
+ //DockProduct.Children.Clear();
+ Button button = sender as Button;
+ _prevEmpButton = button;
+ button.Style = (Style)FindResource("SaleButtonsActive");
+ _employeeId = (int) button.Tag;
+ UIElementCollection employees = DockProductGroup.Children;
+ foreach (UIElement emp in employees)
+ {
+ button = (Button)emp;
+ button.IsEnabled = true;
+ }
+
+ }
+
+ private void SaleGrid_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ DataGrid dg = (DataGrid)sender;
+ _selectedSaleGridIndex = dg.SelectedIndex;
+
+ }
+
+ private void NumberOfProducts_GotFocus(object sender, RoutedEventArgs e)
+ {
+ NumberOfProducts.Text = string.Empty;
+ }
+
+ private void Buy_Click(object sender, RoutedEventArgs e)
+ {
+ bool isValid = ValidatePriceInput();
+ if (!isValid)
+ return;
+
+ isValid = ValidateNumberOfProductsInput();
+ if (!isValid)
+ return;
+
+ decimal price = Convert.ToDecimal(Price.Text);
+ int number = Convert.ToInt32(NumberOfProducts.Text);
+ NumberOfProducts.Text = "1";
+ Price.Text = string.Empty;
+ TotalPrice.Content = "0";
+ BuildPrice(false);
+ _productId = -1;
+ _productGroupId = -1;
+ ManipulateBuySection();
+ }
+
+ private void Payment_Click(object sender, RoutedEventArgs e)
+ {
+ //bool isValid = ValidateAmountInput();
+ //if (!isValid)
+ // return;
+ decimal amount = -1;
+ if (!string.IsNullOrEmpty(Amount.Text))
+ amount = Convert.ToDecimal(Amount.Text);
+
+ Amount.Text = string.Empty;
+ Button button = sender as Button;
+ BuildAmuntDataGrid(amount,(string)button.Tag);
+ }
+
+ private void NumberOfProducts_LostFocus(object sender, RoutedEventArgs e)
+ {
+ bool isValid = ValidateNumberOfProductsInput();
+ if (!isValid)
+ return;
+
+ CalculateSubPrice();
+ }
+
+ private void Price_KeyUp(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Enter)
+ {
+ Buy_Click(null,null);
+ }
+ else
+ {
+ CalculateSubPrice();
+ }
+
+ }
+
+ private void BuildEmployee()
+ {
+ DockProductGroup.Children.Clear();
+ //DockProduct.Children.Clear();
+ DockEmployee.Children.Clear();
+ bool firstEmployee = true;
+ foreach (EmployeeEntity employee in _employees)
+ {
+ Button btnEmp = new Button();
+ btnEmp.Style = (Style)FindResource("SaleButtons");
+ btnEmp.Content = employee.Name;
+ btnEmp.Tag = employee.Id;
+ btnEmp.Click += BtnEmp_Click;
+ DockEmployee.Children.Add(btnEmp);
+
+ if (firstEmployee)
+ {
+ firstEmployee = false;
+ btnEmp.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));
+ }
+ }
+ }
+
+ private void BuildProductGroup()
+ {
+ DockProductGroup.Children.Clear();
+ foreach (ProductGroupEntity productGroup in _productGroups)
+ {
+ Button btnPrGr = new Button();
+ btnPrGr.Style = (Style)FindResource("SaleButtons");
+ btnPrGr.Content = productGroup.Name;
+ btnPrGr.Tag = productGroup.Id;
+ btnPrGr.Click += BtnPrGr_Click;
+ if (_employeeId == -1)
+ btnPrGr.IsEnabled = false;
+ DockProductGroup.Children.Add(btnPrGr);
+ }
+ }
+
+ private void BuildProducts(ICollection products)
+ {
+ // DockProduct.Children.Clear();
+ foreach (ProductEntity product in products)
+ {
+ Button btnProduct = new Button();
+ btnProduct.Style = (Style)FindResource("SaleButtons");
+ btnProduct.Content = product.Name;
+ btnProduct.Tag = product.Id;
+ btnProduct.Click += BtnProduct_Click;
+ // DockProduct.Children.Add(btnProduct);
+ }
+
+ }
+ private void BuildPrice(bool returnProduct)
+ {
+ PriceLineModel pModel = new PriceLineModel();
+ pModel.NumberProducts = _numberOfProducts;
+ pModel.Price = _price;
+ if (_selectedProduct != null)
+ {
+ pModel.Id = _selectedProduct.Id;
+ pModel.Name = _selectedProduct.Name;
+ pModel.IsProductGroup = false;
+ }
+ else
+ {
+ pModel.Id = _selectedProductGroup.Id;
+ pModel.Name = _selectedProductGroup.Name;
+ pModel.IsProductGroup = true;
+ }
+
+ BuildPriceDataGrid(pModel);
+ //BuildEmployee();
+ BuildProductGroup();
+ }
+
+ private void CalculateSubPrice()
+ {
+ _price = 0;
+ bool isValid = decimal.TryParse(Price.Text, out _price);
+ if (!isValid)
+ return;
+
+ isValid = Int32.TryParse(NumberOfProducts.Text, out _numberOfProducts);
+ if (!isValid)
+ return;
+
+ TotalPrice.Content = _price * _numberOfProducts;
+ }
+
+ private void CalculateTotalLine()
+ {
+ _totalPrice = 0;
+ foreach (PriceLineModel model in _priceLineModels)
+ {
+ _totalPrice += model.Price * model.NumberProducts;
+ }
+
+ Total.Content = $"At betale: Kr. {_totalPrice}";
+ }
+
+ private void BuildPriceDataGrid(PriceLineModel priceLineModel)
+ {
+ _selectedProduct = null;
+ _selectedProductGroup = null;
+ _numberOfProducts = 1;
+
+ _priceLineModels.Add(priceLineModel);
+ CalculateTotalLine();
+
+ SaleGridModel model = new SaleGridModel();
+ model.Navn = priceLineModel.Name;
+ model.Pris = $"kr. {priceLineModel.Price}";
+ model.Stk = $"- {priceLineModel.NumberProducts} stk. à";
+ model.Total = $"= Kr. {priceLineModel.NumberProducts * priceLineModel.Price}";
+ SaleGridInternalModel saleGridInternalModel = new SaleGridInternalModel();
+ saleGridInternalModel.InternPrice = priceLineModel.Price;
+ saleGridInternalModel.InternPieces = priceLineModel.NumberProducts;
+ model.SaleGridInternalModel = saleGridInternalModel;
+ //model.InternPrice = priceLineModel.Price;
+ //model.InternPieces = priceLineModel.NumberProducts;
+ _saleGridModels.Add(model);
+ }
+
+ private bool ValidateNumberOfProductsInput()
+ {
+ int notUsed;
+ bool isValid = Int32.TryParse(NumberOfProducts.Text, out notUsed);
+ if (!isValid)
+ {
+ MessageBox.Show($"{NumberOfProducts.Text} er ikke en gyldig værdi! \nSætter 1 som standard værdi ", "Ikke gyldig værdi",
+ MessageBoxButton.OK, MessageBoxImage.Error);
+ NumberOfProducts.Text = "1";
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool ValidatePriceInput()
+ {
+ decimal p = 0;
+ bool isValid = Decimal.TryParse(Price.Text, out p);
+ if (!isValid)
+ {
+ string msg = "";
+ if (string.IsNullOrEmpty(Price.Text))
+ {
+ msg = "En tom pris er ikke en gyldig værdi!";
+ }
+ else
+ {
+ msg = $"{Price.Text} er ikke en gyldig pris!";
+ }
+
+ MessageBox.Show(msg, "Ugyldig pris", MessageBoxButton.OK,
+ MessageBoxImage.Error);
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool ValidateAmountInput()
+ {
+ decimal p = 0;
+ bool isValid = Decimal.TryParse(Amount.Text, out p);
+ if (!isValid)
+ {
+ string msg = "";
+ if (string.IsNullOrEmpty(Amount.Text))
+ {
+ msg = "Et tomt beløb er ikke en gyldig værdi!";
+ }
+ else
+ {
+ msg = $"{Amount.Text} er ikke et gyldigt beløb!";
+ }
+
+ MessageBox.Show(msg, "Ugyldigt beløb", MessageBoxButton.OK,
+ MessageBoxImage.Error);
+ return false;
+ }
+
+ return true;
+
+ }
+
+ private void ManipulateBuySection()
+ {
+ bool enabled = _productGroupId > 0 || _productId > 0;
+
+ PricePanel.IsEnabled = enabled;
+ NumberOfProducts.IsEnabled = enabled;
+ Price.IsEnabled = enabled;
+ Price.Focus();
+ BtnBuy.IsEnabled = enabled;
+ LblStk.IsEnabled = enabled;
+ LblPrice.IsEnabled = enabled;
+ LblTotal.IsEnabled = enabled;
+ TotalPrice.IsEnabled = enabled;
+ }
+
+ private void BuildAmuntDataGrid(decimal amount, string paymentMethod)
+ {
+ if (amount == -1)
+ {
+ amount = 0;
+ foreach (PriceLineModel lineModel in _priceLineModels)
+ {
+ amount += (lineModel.Price * lineModel.NumberProducts);
+ }
+ }
+
+ AmountGridModel amountGridModel = new AmountGridModel();
+ amountGridModel.Amount = amount;
+ amountGridModel.AmountText = $"Kr. {amount}";
+ switch (paymentMethod)
+ {
+ case "PaymentMethod.Card":
+ amountGridModel.PaymentMethodText = "Kort";
+ break;
+
+ case "PaymentMethod.Cash":
+ amountGridModel.PaymentMethodText = "Kontant";
+ break;
+
+ case "PaymentMethod.GiftCard":
+ amountGridModel.PaymentMethodText = "Gavekort";
+ break;
+
+ case "PaymentMethod.MobilePay":
+ amountGridModel.PaymentMethodText = "MobilePay";
+ break;
+ }
+ _paymentGridModels.Add(amountGridModel);
+
+ AmountPayed();
+ }
+
+ private void AmountPayed()
+ {
+ decimal amountPayed = 0;
+ foreach (AmountGridModel paymentGridModel in _paymentGridModels)
+ {
+ amountPayed += paymentGridModel.Amount;
+ }
+
+ if (amountPayed == _totalPrice)
+ {
+ btnFinish_Click(null,null);
+ return;
+ }
+
+ if (amountPayed > _totalPrice)
+ {
+ lblRecived.Content = $"Byttepenge: Kr. {amountPayed - _totalPrice}";
+ Style style = this.FindResource("BoldLabelWarningNoMargin") as Style;
+ lblRecived.Style = style;
+ btnFinish.IsEnabled = true;
+ }
+ else
+ {
+ lblRecived.Content = $"At betale: Kr. {_totalPrice - amountPayed}";
+ Style style = this.FindResource("BoldLabelNoMargin") as Style;
+ lblRecived.Style = style;
+ }
+ }
+
+
+ private void SaleGrid_KeyUp(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Delete)
+ {
+ if (_selectedSaleGridIndex == -1)
+ return;
+ int index = _selectedSaleGridIndex;
+ //_saleGridModels.RemoveAt(_selectedSaleGridIndex);
+ _priceLineModels.RemoveAt(index);
+ _selectedSaleGridIndex = -1;
+ CalculateTotalLine();
+ }
+ }
+
+ private void btnFinish_Click(object sender, RoutedEventArgs e)
+ {
+ SaleService saleService = new SaleService();
+ saleService.Save(_saleGridModels.ToList(),_paymentGridModels.ToList(),_employeeId);
+ _paymentGridModels.Clear();
+ _priceLineModels.Clear();
+ UpdateWindowTitle();
+ _totalPrice = 0;
+ _saleGridModels.Clear();
+ lblRecived.Content = string.Empty;
+ _selectedSaleGridIndex = -1;
+ btnFinish.IsEnabled = false;
+ CalculateTotalLine();
+ BuildEmployee();
+ BuildProductGroup();
+ ManipulateBuySection();
+ }
+
+ private void BtnPrint_Click(object sender, RoutedEventArgs e)
+ {
+ SaleService saleService = new SaleService();
+ saleService.PrintReceipt();
+ }
+
+ private void UpdateWindowTitle()
+ {
+ SaleService saleService = new SaleService();
+ Title = saleService.LastAndTotalSale();
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Service/CacheService.cs b/PointOfSale/Pos.Ui/Pos/Service/CacheService.cs
new file mode 100644
index 0000000..55f0173
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Service/CacheService.cs
@@ -0,0 +1,57 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Database.Models;
+using Database.Repository;
+
+namespace Pos.Service
+{
+ public static class CacheService
+ {
+
+ private static bool _invalidEmployee = false;
+ private static bool _invalidProductGroup = false;
+ private static List _employees;
+ private static List _productGroups;
+
+
+ public static void Invalidate()
+ {
+ _invalidEmployee = true;
+ _invalidProductGroup = true;
+ }
+
+ public static ObservableCollection GetEmployee()
+ {
+ ObservableCollection obsEmployees = new ObservableCollection();
+ if (_invalidEmployee)
+ {
+ EmployeeRepository employeeRepository = new EmployeeRepository();
+ _employees = employeeRepository.GetAll();
+ foreach (EmployeeEntity employee in _employees)
+ {
+ obsEmployees.Add(employee);
+ }
+ _invalidEmployee = false;
+ }
+
+ return obsEmployees;
+ }
+
+ public static ObservableCollection GetProductGroupsIncludeProducts()
+ {
+ ObservableCollection obsProductGroups = new ObservableCollection();
+ if(_invalidProductGroup)
+ {
+ ProductGroupRepository productGroupRepository = new ProductGroupRepository();
+ _productGroups = productGroupRepository.GetAll();
+ _invalidProductGroup = false;
+ foreach (ProductGroupEntity productGroup in _productGroups)
+ {
+ obsProductGroups.Add(productGroup);
+ }
+ }
+
+ return obsProductGroups;
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Service/SaleService.cs b/PointOfSale/Pos.Ui/Pos/Service/SaleService.cs
new file mode 100644
index 0000000..9aaebd7
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Service/SaleService.cs
@@ -0,0 +1,293 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading.Tasks;
+using Database.Models;
+using Database.Repository;
+using EpsonPrinter.Model;
+using EpsonPrinter.Services;
+using Microsoft.Extensions.Configuration;
+using Pos.Json;
+using Pos.Models;
+using RestSharp;
+using SixLabors.ImageSharp;
+using BodyModel = Pos.Json.BodyModel;
+using FooterModel = Pos.Json.FooterModel;
+using HeaderModel = Pos.Json.HeaderModel;
+
+namespace Pos.Service
+{
+ public class SaleService
+ {
+ private float _totalPrice = 0;
+ private SaleRepository _saleRepository = new SaleRepository();
+
+ public void Save(List saleGridModels, List amountGridModels, int employeeNo)
+ {
+ SaleRepository saleRepository = new SaleRepository();
+ SaleEntity saleEntity = saleRepository.New(employeeNo);
+
+ foreach (SaleGridModel model in saleGridModels)
+ {
+ if(String.IsNullOrEmpty(model.Navn))
+ continue;
+
+ SaleLineEntity saleLineEntity = new SaleLineEntity();
+ saleLineEntity.SaleId = saleEntity.Id;
+ saleLineEntity.Price = model.SaleGridInternalModel.InternPrice;
+ saleLineEntity.Product = model.Navn;
+ saleLineEntity.Pieces = model.SaleGridInternalModel.InternPieces;
+ saleLineEntity.Total = saleLineEntity.Pieces * saleLineEntity.Price;
+ saleRepository.SaveSaleLine(saleLineEntity);
+ }
+
+ foreach (AmountGridModel model in amountGridModels)
+ {
+ PaymentEntity payment = new PaymentEntity();
+ payment.Amount = model.Amount;
+ payment.Type = model.PaymentMethodText;
+ payment.SaleId = saleEntity.Id;
+ saleRepository.SavePayment(payment);
+ }
+ }
+
+ public void PrintReceipt()
+ {
+
+ SaleEntity sale = _saleRepository.GetLatest();
+ if (sale == null)
+ return;
+
+ BuildSaleReceipt(sale);
+ }
+
+ public void PrintReceipt(int saleId)
+ {
+ SaleEntity sale = _saleRepository.Get(saleId);
+ BuildSaleReceipt(sale);
+ }
+
+ public void PrintSaleOfDay(DateTime selectedDate, TotalSaleDetail totalSaleDetail)
+ {
+ SaleOfDayModel saleOfDayModel = new SaleOfDayModel();
+ saleOfDayModel.Date = selectedDate;
+ saleOfDayModel.TotalSale = totalSaleDetail.TotalSale;
+ saleOfDayModel.TotalCustomers = totalSaleDetail.TotalCustomer;
+ foreach (TotalSaleCategory category in totalSaleDetail.TotalSaleCategories)
+ {
+ SaleOfDayDetail saleOfDayDetail = new SaleOfDayDetail();
+ saleOfDayDetail.TotalSale = category.Sale;
+ saleOfDayDetail.Category = category.Category;
+ saleOfDayModel.SaleOfDayDetail.Add(saleOfDayDetail);
+ }
+
+ LoadConfig loadConfig = new LoadConfig();
+ IConfiguration config = loadConfig.ByEnvironment();
+ string url = config["API:URL"];
+
+ RestClient restClient = new RestClient($"{url}/api/PosPrinter/SaleOfDay");
+ RestRequest request = new RestRequest();
+ request.AddJsonBody(saleOfDayModel);
+ request.Method = Method.Post;
+ restClient.Post(request);
+ }
+
+ public TotalSaleDetail TotalSale(DateTime selectedDate)
+ {
+ List sales = _saleRepository.GetByDateRange(selectedDate);
+ TotalSaleDetail totalSaleDetail = new TotalSaleDetail();
+ totalSaleDetail.TotalCustomer = sales.Count;
+ foreach (SaleEntity sale in sales)
+ {
+ List saleLineBySaleId = _saleRepository.GetSaleLineBySaleId(sale.Id);
+ foreach (SaleLineEntity saleLine in saleLineBySaleId)
+ {
+
+ if (totalSaleDetail.TotalSaleCategories.Any(c => c.Category.Contains(saleLine.Product)))
+ {
+ TotalSaleCategory totalSaleCategory = totalSaleDetail.TotalSaleCategories.Single(c => c.Category.Contains(saleLine.Product));
+ totalSaleCategory.Sale += saleLine.Total;
+ totalSaleCategory.SaleString = "Kr. " + totalSaleCategory.Sale.ToString("0.00");
+ }
+ else
+ {
+ TotalSaleCategory totalSaleCategory = new TotalSaleCategory();
+ totalSaleCategory.Sale = saleLine.Total;
+ totalSaleCategory.SaleString ="Kr. " + totalSaleCategory.Sale.ToString("0.00");
+ totalSaleCategory.Category = saleLine.Product;
+ totalSaleDetail.TotalSaleCategories.Add(totalSaleCategory);
+ }
+
+ totalSaleDetail.TotalSale += saleLine.Total;
+ }
+ }
+
+ return totalSaleDetail;
+ }
+
+ public string LastAndTotalSale()
+ {
+ SaleEntity saleEntity = _saleRepository.GetLatest();
+ List saleLineBySaleId = _saleRepository.GetSaleLineBySaleId(saleEntity.Id);
+ decimal lastSale = 0;
+ foreach (SaleLineEntity saleLine in saleLineBySaleId)
+ {
+ lastSale += saleLine.Total;
+ }
+ TotalSaleDetail totalSale = TotalSale(DateTime.Now);
+ string todaySale = $"Sidste salg: kr. {lastSale.ToString("N")} - Dagens salg: kr. {totalSale.TotalSale.ToString("N")}";
+ return todaySale;
+ }
+
+ private void BuildSaleReceipt(SaleEntity sale)
+ {
+ EmployeeRepository employeeRepository = new EmployeeRepository();
+ EmployeeEntity employee = employeeRepository.Get(sale.EmployeeId);
+
+
+ List salesLines = _saleRepository.GetSaleLineBySaleId(sale.Id);
+ List payments = _saleRepository.GetPaymentBySaleId(sale.Id);
+
+ PosReceipt posReceipt = new PosReceipt();
+ posReceipt.header = BuildReceiptHeader().ToArray();
+ ProductModel[] productModels = BuildSaleLines(salesLines).ToArray();
+ posReceipt.bodyModel = BuildBody(sale, employee.Name);
+ posReceipt.bodyModel.products = productModels;
+ posReceipt.footer = BuildFooter().ToArray();
+ posReceipt.logoBase64 = String.Empty;
+
+ LoadConfig loadConfig = new LoadConfig();
+ IConfiguration config = loadConfig.ByEnvironment();
+ string url = $"{config["API:URL"]}/api/PosPrinter/Receipt";
+
+ RestClient restClient = new RestClient(url);
+ RestRequest request = new RestRequest();
+ request.AddJsonBody(posReceipt);
+ request.Method = Method.Post;
+ restClient.Post(request);
+ }
+
+ private List BuildFooter()
+ {
+ List footerModels = new List();
+ FooterModel model = new FooterModel();
+ model.value = "Tak for handlen";
+ model.printStyles = new PrintStylesModel
+ {
+ bold = false,
+ fontB = false,
+ doubleHeight = false,
+ doubleWidth = false,
+ underline = false
+ };
+ model.feedingLines = 0;
+ model.textAlignment = 0;
+ footerModels.Add(model);
+ return footerModels;
+ }
+
+ private List BuildSaleLines(List salesLines)
+ {
+ List products = new List();
+ foreach (SaleLineEntity salesLine in salesLines)
+ {
+ ProductModel productModel = new ProductModel();
+ productModel.noOfProduct = salesLine.Pieces.ToString();
+ productModel.price = (float)salesLine.Price;
+ productModel.totalPrice = salesLine.Pieces * productModel.price;
+ _totalPrice += productModel.totalPrice;
+ productModel.product = salesLine.Product;
+ products.Add(productModel);
+ }
+
+ return products;
+ }
+
+ private BodyModel BuildBody(SaleEntity sale, string employeeName)
+ {
+ BodyModel body = new BodyModel
+ {
+ receiptNumber = sale.Id,
+ receiptTime = sale.Time.ToString("HH:mm dd-MM-yyyy"),
+ staff = employeeName,
+ totalPrice = _totalPrice,
+ totalVat = (_totalPrice / 100) * 25
+ };
+
+ return body;
+ }
+
+ private List BuildReceiptHeader()
+ {
+ List headerModels = new List();
+
+ HeaderModel headerModel = new HeaderModel
+ {
+ feedingLines = 1,
+ textAlignment = 1,
+ value = "Blomster Til Alt"
+ };
+ PrintStylesModel printStylesModel = new PrintStylesModel
+ {
+ bold = true,
+ fontB = false,
+ doubleHeight = true,
+ doubleWidth = true,
+ underline = false
+ };
+ headerModel.printStyles = printStylesModel;
+ headerModels.Add(headerModel);
+
+ printStylesModel = new PrintStylesModel
+ {
+ bold = false,
+ fontB = false,
+ doubleHeight = false,
+ doubleWidth = false,
+ underline = false
+ };
+
+ headerModel = new HeaderModel
+ {
+ feedingLines = 0,
+ textAlignment = 1,
+ value = "Adelgade 91",
+ printStyles = printStylesModel
+ };
+ headerModels.Add(headerModel);
+
+ headerModel = new HeaderModel
+ {
+ feedingLines = 0,
+ textAlignment = 1,
+ value = "5400 Bogense",
+ printStyles = printStylesModel
+ };
+ headerModels.Add(headerModel);
+
+ headerModel = new HeaderModel
+ {
+ feedingLines = 0,
+ textAlignment = 1,
+ value = "Tlf: 41 82 71 66",
+ printStyles = printStylesModel
+ };
+ headerModels.Add(headerModel);
+
+ headerModel = new HeaderModel
+ {
+ feedingLines = 0,
+ textAlignment = 1,
+ value = "CVR: 37 14 44 36",
+ printStyles = printStylesModel
+ };
+ headerModels.Add(headerModel);
+
+ return headerModels;
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Employee/AddEmployeeWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/Employee/AddEmployeeWindow.xaml
new file mode 100644
index 0000000..e7c0b0f
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Employee/AddEmployeeWindow.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Employee/AddEmployeeWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/Employee/AddEmployeeWindow.xaml.cs
new file mode 100644
index 0000000..43844f4
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Employee/AddEmployeeWindow.xaml.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Repository;
+
+namespace Pos.Setting
+{
+ ///
+ /// Interaction logic for AddEmployeeWindow.xaml
+ ///
+ public partial class AddEmployeeWindow : Window
+ {
+ public AddEmployeeWindow()
+ {
+ InitializeComponent();
+ }
+
+ private void btnAdd_Click(object sender, RoutedEventArgs e)
+ {
+ EmployeeRepository employeeRepository = new EmployeeRepository();
+ employeeRepository.Add(txtStaff.Text);
+ this.Close();
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Employee/DeleteEmployeeWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/Employee/DeleteEmployeeWindow.xaml
new file mode 100644
index 0000000..ff27339
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Employee/DeleteEmployeeWindow.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Employee/DeleteEmployeeWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/Employee/DeleteEmployeeWindow.xaml.cs
new file mode 100644
index 0000000..2609278
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Employee/DeleteEmployeeWindow.xaml.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Repository;
+
+namespace Pos.Setting
+{
+ ///
+ /// Interaction logic for DeleteEmployeeWindow.xaml
+ ///
+ public partial class DeleteEmployeeWindow : Window
+ {
+ private int _employeeId;
+
+ public DeleteEmployeeWindow(int employeeId, string staffName)
+ {
+ InitializeComponent();
+ _employeeId = employeeId;
+ txtDelEmployee.Text = staffName;
+ }
+
+ private void btnYes_Click(object sender, RoutedEventArgs e)
+ {
+ EmployeeRepository employeeRepository = new EmployeeRepository();
+ employeeRepository.Delete(_employeeId);
+ this.Close();
+ }
+
+ private void btnNo_Click(object sender, RoutedEventArgs e)
+ {
+ this.Close();
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Employee/EditEmployeeWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/Employee/EditEmployeeWindow.xaml
new file mode 100644
index 0000000..c4325bd
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Employee/EditEmployeeWindow.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Employee/EditEmployeeWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/Employee/EditEmployeeWindow.xaml.cs
new file mode 100644
index 0000000..ecfbfa0
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Employee/EditEmployeeWindow.xaml.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Repository;
+
+namespace Pos.Setting
+{
+ ///
+ /// Interaction logic for EditEmployeeWindow.xaml
+ ///
+ public partial class EditEmployeeWindow : Window
+ {
+ private int _employeeId;
+
+ public EditEmployeeWindow(int employeeId, string staffName)
+ {
+ _employeeId = employeeId;
+ InitializeComponent();
+ txtEmployee.Text = staffName;
+ }
+
+ private void btnEdit_Click(object sender, RoutedEventArgs e)
+ {
+ EmployeeRepository employeeRepository = new EmployeeRepository();
+ employeeRepository.Edit(_employeeId,txtEmployee.Text);
+ this.Close();
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Employee/EmployeeWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/Employee/EmployeeWindow.xaml
new file mode 100644
index 0000000..b53b762
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Employee/EmployeeWindow.xaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Employee/EmployeeWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/Employee/EmployeeWindow.xaml.cs
new file mode 100644
index 0000000..151051f
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Employee/EmployeeWindow.xaml.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Models;
+using Database.Repository;
+
+namespace Pos.Setting
+{
+ ///
+ /// Interaction logic for EmployeeWindow.xaml
+ ///
+ public partial class EmployeeWindow : Window
+ {
+ private int _selectedEmployee = -1;
+ private string _employeeName = string.Empty;
+ public EmployeeWindow()
+ {
+ InitializeComponent();
+ LoadData();
+ }
+
+
+ private void Item_Selected(object sender, RoutedEventArgs e)
+ {
+ ListBoxItem item = (ListBoxItem)sender;
+ _selectedEmployee = Convert.ToInt32(item.Tag);
+ _employeeName = item.Content.ToString();
+ btnDelete.IsEnabled = true;
+ btnEdit.IsEnabled = true;
+ }
+
+ private void btnEdit_Click(object sender, RoutedEventArgs e)
+ {
+ EditEmployeeWindow editEmployeeWindow = new EditEmployeeWindow(_selectedEmployee, _employeeName);
+ editEmployeeWindow.Closed += WindowClosed;
+ editEmployeeWindow.Show();
+ }
+
+ private void btnCreate_Click(object sender, RoutedEventArgs e)
+ {
+ AddEmployeeWindow addEmployeeWindow = new AddEmployeeWindow();
+ addEmployeeWindow.Closed += WindowClosed;
+ addEmployeeWindow.Show();
+ }
+
+
+ private void btnDelete_Click(object sender, RoutedEventArgs e)
+ {
+ DeleteEmployeeWindow deleteEmployeeWindow = new DeleteEmployeeWindow(_selectedEmployee,_employeeName);
+ deleteEmployeeWindow.Closed += WindowClosed;
+ deleteEmployeeWindow.Show();
+ }
+
+ private void WindowClosed(object? sender, EventArgs e)
+ {
+ LoadData();
+ }
+
+ private void LoadData()
+ {
+ using EmployeeRepository employeeRepository = new EmployeeRepository();
+ List allStaff = employeeRepository.GetAll();
+ LstStaff.Items.Clear();
+ foreach (EmployeeEntity staff in allStaff)
+ {
+ ListBoxItem item = new ListBoxItem();
+ item.Content = staff.Name;
+ item.Tag = staff.Id;
+ item.Selected += Item_Selected;
+ LstStaff.Items.Add(item);
+ }
+ }
+
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Product/AddProductWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/Product/AddProductWindow.xaml
new file mode 100644
index 0000000..520926f
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Product/AddProductWindow.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Product/AddProductWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/Product/AddProductWindow.xaml.cs
new file mode 100644
index 0000000..b75fbe9
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Product/AddProductWindow.xaml.cs
@@ -0,0 +1,45 @@
+using Database.Repository;
+using System;
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Pos.Setting.ProductGroup
+{
+ ///
+ /// Interaction logic for AddProductWindow.xaml
+ ///
+ public partial class AddProductWindow : Window
+ {
+ public AddProductWindow()
+ {
+ InitializeComponent();
+ LoadData(0);
+ }
+
+ private void LoadData(int selectedIndex)
+ {
+ ProductGroupRepository productGroupRepository = new ProductGroupRepository();
+ List products = productGroupRepository.GetAll();
+ foreach (Database.Models.ProductGroupEntity productGroup in products)
+ {
+ ComboBoxItem comboBoxItem = new ComboBoxItem();
+ comboBoxItem.Tag = productGroup.Id;
+ comboBoxItem.Content = productGroup.Name;
+ cmbProductGroup.Items.Add(comboBoxItem);
+ }
+
+ cmbProductGroup.SelectedIndex = selectedIndex;
+ }
+
+ private void btnAdd_Click(object sender, RoutedEventArgs e)
+ {
+ ComboBoxItem item = (ComboBoxItem) cmbProductGroup.SelectedItem;
+ string name = txtProduct.Text;
+ int id = Convert.ToInt32(item.Tag);
+ ProductRepository productRepository = new ProductRepository();
+ productRepository.Add(name,id);
+ this.Close();
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Product/ArchiveProductWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/Product/ArchiveProductWindow.xaml
new file mode 100644
index 0000000..35b6524
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Product/ArchiveProductWindow.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Product/ArchiveProductWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/Product/ArchiveProductWindow.xaml.cs
new file mode 100644
index 0000000..b6126de
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Product/ArchiveProductWindow.xaml.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Repository;
+
+namespace Pos.Setting.Product
+{
+ ///
+ /// Interaction logic for ArchiveProductWindow.xaml
+ ///
+ public partial class ArchiveProductWindow : Window
+ {
+ private int _productId;
+ public ArchiveProductWindow(int productId)
+ {
+ InitializeComponent();
+ _productId = productId;
+ ProductRepository productRepository = new ProductRepository();
+ Database.Models.ProductEntity product = productRepository.GetById(_productId);
+ txtDelProduct.Text = product.Name;
+ }
+
+ private void btnYes_Click(object sender, RoutedEventArgs e)
+ {
+ ProductRepository productRepository = new ProductRepository();
+ productRepository.Archive(_productId);
+ this.Close();
+ }
+
+ private void btnNo_Click(object sender, RoutedEventArgs e)
+ {
+ this.Close();
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Product/ChangeProductOrderWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/Product/ChangeProductOrderWindow.xaml
new file mode 100644
index 0000000..ddcb601
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Product/ChangeProductOrderWindow.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Product/ChangeProductOrderWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/Product/ChangeProductOrderWindow.xaml.cs
new file mode 100644
index 0000000..4b378f7
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Product/ChangeProductOrderWindow.xaml.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Repository;
+
+namespace Pos.Setting.Product
+{
+ ///
+ /// Interaction logic for ChangeProductOrderWindow.xaml
+ ///
+ public partial class ChangeProductOrderWindow : Window
+ {
+ ObservableCollection _productList = new ObservableCollection();
+ private int _productGroupId;
+ public ChangeProductOrderWindow(int productGroupId)
+ {
+ InitializeComponent();
+ _productGroupId = productGroupId;
+ LoadData();
+ }
+
+ private void LoadData()
+ {
+ ProductRepository productRepository = new ProductRepository();
+ List products = productRepository.GetByProductGroup(_productGroupId);
+ foreach (Database.Models.ProductEntity product in products)
+ {
+ _productList.Add(product);
+ }
+ lstProduct.Items.Clear();
+ lstProduct.DisplayMemberPath = "Name";
+ lstProduct.ItemsSource = _productList;
+
+ Style itemContainerStyle = new Style(typeof(ListBoxItem));
+ itemContainerStyle.Setters.Add(new Setter(ListBoxItem.AllowDropProperty, true));
+ itemContainerStyle.Setters.Add(new EventSetter(ListBoxItem.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(s_PreviewMouseLeftButtonDown)));
+ itemContainerStyle.Setters.Add(new EventSetter(ListBoxItem.DropEvent, new DragEventHandler(lstCategory_Drop)));
+ lstProduct.ItemContainerStyle = itemContainerStyle;
+ }
+
+ void s_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+
+ if (sender is ListBoxItem)
+ {
+ ListBoxItem draggedItem = sender as ListBoxItem;
+ DragDrop.DoDragDrop(draggedItem, draggedItem.DataContext, DragDropEffects.Move);
+ draggedItem.IsSelected = true;
+ }
+ }
+
+ void lstCategory_Drop(object sender, DragEventArgs e)
+ {
+ Database.Models.ProductEntity droppedData = e.Data.GetData(typeof(Database.Models.ProductEntity)) as Database.Models.ProductEntity;
+ Database.Models.ProductEntity target = ((ListBoxItem)(sender)).DataContext as Database.Models.ProductEntity;
+
+ int removedIdx = lstProduct.Items.IndexOf(droppedData);
+ int targetIdx = lstProduct.Items.IndexOf(target);
+
+ if (removedIdx < targetIdx)
+ {
+ _productList.Insert(targetIdx + 1, droppedData);
+ _productList.RemoveAt(removedIdx);
+ }
+ else
+ {
+ int remIdx = removedIdx + 1;
+ if (_productList.Count + 1 > remIdx)
+ {
+ _productList.Insert(targetIdx, droppedData);
+ _productList.RemoveAt(remIdx);
+ }
+ }
+ }
+
+ private void btnSave_Click(object sender, RoutedEventArgs e)
+ {
+ ProductRepository productRepository = new ProductRepository();
+ for (int i = 0; i < _productList.Count; i++)
+ {
+ Database.Models.ProductEntity product = _productList[i];
+ productRepository.SetIndex(product.Id, i);
+ }
+ this.Close();
+ }
+
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Product/EditProductWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/Product/EditProductWindow.xaml
new file mode 100644
index 0000000..4292595
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Product/EditProductWindow.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Product/EditProductWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/Product/EditProductWindow.xaml.cs
new file mode 100644
index 0000000..d375fe4
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Product/EditProductWindow.xaml.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Repository;
+
+namespace Pos.Setting.Product
+{
+ ///
+ /// Interaction logic for EditProductWindow.xaml
+ ///
+ public partial class EditProductWindow : Window
+ {
+ private int _productId;
+ private int _selectedProductGroupId;
+ private Database.Models.ProductEntity _product;
+
+ public EditProductWindow(int productId, int selectedProductGroupId)
+ {
+ InitializeComponent();
+ _productId = productId;
+ _selectedProductGroupId = selectedProductGroupId;
+ Init();
+ }
+
+ private void Init()
+ {
+ ProductGroupRepository categoryRepository = new ProductGroupRepository();
+ List productGroups = categoryRepository.GetAll();
+ foreach (Database.Models.ProductGroupEntity productGroup in productGroups)
+ {
+
+ ComboBoxItem comboBoxItem = new ComboBoxItem();
+ comboBoxItem.Tag = productGroup.Id;
+ comboBoxItem.Content = productGroup.Name;
+ cmbProductGroup.Items.Add(comboBoxItem);
+ }
+ cmbProductGroup.SelectedIndex = _selectedProductGroupId;
+
+ ProductRepository productRepository = new ProductRepository();
+ _product = productRepository.GetById(_productId);
+ txtProduct.Text = _product.Name;
+ }
+
+ private void btnEdit_Click(object sender, RoutedEventArgs e)
+ {
+ ComboBoxItem item = (ComboBoxItem)cmbProductGroup.SelectedItem;
+ ProductRepository productRepository = new ProductRepository();
+ productRepository.Update(_product.Id,(int)item.Tag,txtProduct.Text);
+ this.Close();
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Product/ProductWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/Product/ProductWindow.xaml
new file mode 100644
index 0000000..aa50b69
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Product/ProductWindow.xaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/Product/ProductWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/Product/ProductWindow.xaml.cs
new file mode 100644
index 0000000..60e2332
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/Product/ProductWindow.xaml.cs
@@ -0,0 +1,126 @@
+
+using System;
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Controls;
+using Database.Repository;
+using Pos.Setting.Product;
+
+namespace Pos.Setting.ProductGroup
+{
+ ///
+ /// Interaction logic for ProductWindow.xaml
+ ///
+ public partial class ProductWindow : Window
+ {
+ private List _productGroups;
+ private int _productId;
+
+ public ProductWindow()
+ {
+ InitializeComponent();
+ Init();
+ LoadData();
+ }
+
+
+ private void Init()
+ {
+ ProductGroupRepository categoryRepository = new ProductGroupRepository();
+ _productGroups = categoryRepository.GetAll();
+ cmbCat.Items.Clear();
+ foreach (Database.Models.ProductGroupEntity productGroup in _productGroups)
+ {
+
+ ComboBoxItem comboBoxItem = new ComboBoxItem();
+ comboBoxItem.Tag = productGroup.Id;
+ comboBoxItem.Content = productGroup.Name;
+ cmbCat.Items.Add(comboBoxItem);
+ }
+
+ cmbCat.SelectedIndex = 0;
+ }
+
+ private void LoadData()
+ {
+
+ ProductRepository productRepository = new ProductRepository();
+ List products = productRepository.GetByProductGroup(_productGroups[cmbCat.SelectedIndex].Id);
+ lstProductGroup.Items.Clear();
+ foreach (Database.Models.ProductEntity product in products)
+ {
+ ListBoxItem item = new ListBoxItem();
+ item.Content = product.Name;
+ item.Tag = product.Id;
+ lstProductGroup.Items.Add(item);
+ }
+
+ btnEdit.IsEnabled = false;
+ btnDelete.IsEnabled = false;
+ }
+
+ private void btnCreate_Click(object sender, RoutedEventArgs e)
+ {
+ AddProductWindow addProductWindow = new AddProductWindow();
+ addProductWindow.Closed += AddProductWindow_Closed;
+ addProductWindow.Show();
+ }
+
+ private void AddProductWindow_Closed(object sender, EventArgs e)
+ {
+ LoadData();
+ }
+
+ private void cmbCat_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ LoadData();
+ }
+
+ private void LstProductGroup_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ btnEdit.IsEnabled = true;
+ btnDelete.IsEnabled = true;
+ //ListBoxItem item = (ListBoxItem)sender;
+ //_productId = (int)item.Tag;
+ }
+
+ private void btnEdit_Click(object sender, RoutedEventArgs e)
+ {
+ ListBoxItem selectedItem = lstProductGroup.SelectedItem as ListBoxItem;
+ EditProductWindow editProductWindow = new EditProductWindow((int)selectedItem.Tag, cmbCat.SelectedIndex);
+ editProductWindow.Closed += EditProductWindow_Closed;
+ editProductWindow.Show();
+ }
+
+ private void EditProductWindow_Closed(object sender, EventArgs e)
+ {
+ LoadData();
+ }
+
+ private void btnDelete_Click(object sender, RoutedEventArgs e)
+ {
+ ListBoxItem selectedItem = lstProductGroup.SelectedItem as ListBoxItem;
+ ArchiveProductWindow archiveProductWindow = new ArchiveProductWindow((int)selectedItem.Tag);
+ archiveProductWindow.Closed += ArchiveProductWindow_Closed;
+ archiveProductWindow.Show();
+
+ }
+
+ private void ArchiveProductWindow_Closed(object sender, EventArgs e)
+ {
+ LoadData();
+ }
+
+ private void btnOrder_Click(object sender, RoutedEventArgs e)
+ {
+ ChangeProductOrderWindow changeProductOrderWindow = new ChangeProductOrderWindow(_productGroups[cmbCat.SelectedIndex].Id);
+ changeProductOrderWindow.Closed += ChangeProductOrderWindow_Closed;
+ changeProductOrderWindow.Show();
+ }
+
+ private void ChangeProductOrderWindow_Closed(object sender, EventArgs e)
+ {
+ LoadData();
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/AddProductGroupWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/AddProductGroupWindow.xaml
new file mode 100644
index 0000000..f343cca
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/AddProductGroupWindow.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/AddProductGroupWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/AddProductGroupWindow.xaml.cs
new file mode 100644
index 0000000..4ddb1ab
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/AddProductGroupWindow.xaml.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Repository;
+
+namespace Pos.Setting.Category
+{
+ ///
+ /// Interaction logic for AddProductGroupWindow.xaml
+ ///
+ public partial class AddProductGroupWindow : Window
+ {
+ public AddProductGroupWindow()
+ {
+ InitializeComponent();
+ }
+
+ private void btnAdd_Click(object sender, RoutedEventArgs e)
+ {
+ ProductGroupRepository categoryRepository = new ProductGroupRepository();
+ categoryRepository.Add(txtProductGroup.Text);
+ this.Close();
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ArchiveProductGroupWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ArchiveProductGroupWindow.xaml
new file mode 100644
index 0000000..827a11e
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ArchiveProductGroupWindow.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ArchiveProductGroupWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ArchiveProductGroupWindow.xaml.cs
new file mode 100644
index 0000000..3b79849
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ArchiveProductGroupWindow.xaml.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Repository;
+
+namespace Pos.Setting.Category
+{
+ ///
+ /// Interaction logic for ArchiveProductGroupWindow.xaml
+ ///
+ public partial class ArchiveProductGroupWindow : Window
+ {
+ private readonly int _id;
+ public ArchiveProductGroupWindow(string name, int id)
+ {
+ InitializeComponent();
+ _id = id;
+ txtDelProductGroup.Text = name;
+
+ }
+
+ private void btnNo_Click(object sender, RoutedEventArgs e)
+ {
+ this.Close();
+ }
+
+ private void btnYes_Click(object sender, RoutedEventArgs e)
+ {
+ ProductGroupRepository categoryRepository = new ProductGroupRepository();
+ categoryRepository.Archive(_id);
+ this.Close();
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ChangeProductGroupOrderWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ChangeProductGroupOrderWindow.xaml
new file mode 100644
index 0000000..d8365ab
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ChangeProductGroupOrderWindow.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ChangeProductGroupOrderWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ChangeProductGroupOrderWindow.xaml.cs
new file mode 100644
index 0000000..8c1d84c
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ChangeProductGroupOrderWindow.xaml.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Repository;
+
+namespace Pos.Setting.Category
+{
+ ///
+ /// Interaction logic for ChangeProductGroupOrderWindow.xaml
+ ///
+ public partial class ChangeProductGroupOrderWindow : Window
+ {
+ ObservableCollection _prodGroupList = new ObservableCollection();
+ public ChangeProductGroupOrderWindow()
+ {
+ InitializeComponent();
+ LoadData();
+ }
+
+ private void LoadData()
+ {
+ ProductGroupRepository categoryRepository = new ProductGroupRepository();
+ List categories = categoryRepository.GetAll();
+ foreach (Database.Models.ProductGroupEntity category in categories)
+ {
+ _prodGroupList.Add(category);
+ }
+ lstCategory.Items.Clear();
+ lstCategory.DisplayMemberPath = "Name";
+ lstCategory.ItemsSource = _prodGroupList;
+
+ Style itemContainerStyle = new Style(typeof(ListBoxItem));
+ itemContainerStyle.Setters.Add(new Setter(ListBoxItem.AllowDropProperty, true));
+ itemContainerStyle.Setters.Add(new EventSetter(ListBoxItem.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(s_PreviewMouseLeftButtonDown)));
+ itemContainerStyle.Setters.Add(new EventSetter(ListBoxItem.DropEvent, new DragEventHandler(lstCategory_Drop)));
+ lstCategory.ItemContainerStyle = itemContainerStyle;
+ }
+
+ void s_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+
+ if (sender is ListBoxItem)
+ {
+ ListBoxItem draggedItem = sender as ListBoxItem;
+ DragDrop.DoDragDrop(draggedItem, draggedItem.DataContext, DragDropEffects.Move);
+ draggedItem.IsSelected = true;
+ }
+ }
+
+ void lstCategory_Drop(object sender, DragEventArgs e)
+ {
+ Database.Models.ProductGroupEntity droppedData = e.Data.GetData(typeof(Database.Models.ProductGroupEntity)) as Database.Models.ProductGroupEntity;
+ Database.Models.ProductGroupEntity target = ((ListBoxItem)(sender)).DataContext as Database.Models.ProductGroupEntity;
+
+ int removedIdx = lstCategory.Items.IndexOf(droppedData);
+ int targetIdx = lstCategory.Items.IndexOf(target);
+
+ if (removedIdx < targetIdx)
+ {
+ _prodGroupList.Insert(targetIdx + 1, droppedData);
+ _prodGroupList.RemoveAt(removedIdx);
+ }
+ else
+ {
+ int remIdx = removedIdx + 1;
+ if (_prodGroupList.Count + 1 > remIdx)
+ {
+ _prodGroupList.Insert(targetIdx, droppedData);
+ _prodGroupList.RemoveAt(remIdx);
+ }
+ }
+ }
+
+ private void btnSave_Click(object sender, RoutedEventArgs e)
+ {
+ ProductGroupRepository categoryRepository = new ProductGroupRepository();
+ for (int i = 0; i < _prodGroupList.Count; i++)
+ {
+ Database.Models.ProductGroupEntity productGroup = _prodGroupList[i];
+ categoryRepository.SetIndex(productGroup.Id,i);
+ }
+ this.Close();
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/EditProductGroupWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/EditProductGroupWindow.xaml
new file mode 100644
index 0000000..2554633
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/EditProductGroupWindow.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/EditProductGroupWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/EditProductGroupWindow.xaml.cs
new file mode 100644
index 0000000..4b02f8c
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/EditProductGroupWindow.xaml.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Repository;
+
+namespace Pos.Setting.Category
+{
+ ///
+ /// Interaction logic for EditProductGroupWindow.xaml
+ ///
+ public partial class EditProductGroupWindow : Window
+ {
+ private readonly int _id;
+
+ public EditProductGroupWindow(string name, int id)
+ {
+ InitializeComponent();
+ _id = id;
+ txtProductGroup.Text = name;
+ }
+
+ private void btnEdit_Click(object sender, RoutedEventArgs e)
+ {
+ ProductGroupRepository categoryRepository = new ProductGroupRepository();
+ categoryRepository.Edit(txtProductGroup.Text,_id);
+ this.Close();
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ProductGroupWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ProductGroupWindow.xaml
new file mode 100644
index 0000000..3ef5c0e
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ProductGroupWindow.xaml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ProductGroupWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ProductGroupWindow.xaml.cs
new file mode 100644
index 0000000..10cde55
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/ProductGroup/ProductGroupWindow.xaml.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Repository;
+using Pos.Setting.Category;
+
+namespace Pos.Category
+{
+ ///
+ /// Interaction logic for ProductGroupWindow.xaml
+ ///
+ public partial class ProductGroupWindow : Window
+ {
+ private int _id = -1;
+ private string _name = string.Empty;
+
+ public ProductGroupWindow()
+ {
+ InitializeComponent();
+ LoadData();
+ }
+
+ private void btnCreate_Click(object sender, RoutedEventArgs e)
+ {
+ AddProductGroupWindow addProductGroupWindow = new AddProductGroupWindow();
+ addProductGroupWindow.Closed += WindowClosed;
+ addProductGroupWindow.Show();
+ }
+
+ private void WindowClosed(object sender, EventArgs e)
+ {
+ LoadData();
+ }
+
+ private void ItemOnSelected(object sender, RoutedEventArgs e)
+ {
+ ListBoxItem item = (ListBoxItem)sender;
+ _id = Convert.ToInt32(item.Tag);
+ _name = item.Content.ToString();
+ btnDelete.IsEnabled = true;
+ btnEdit.IsEnabled = true;
+ }
+
+ private void LoadData()
+ {
+ ProductGroupRepository categoryRepository = new ProductGroupRepository();
+ List categories = categoryRepository.GetAll();
+ LstCategory.Items.Clear();
+ foreach (Database.Models.ProductGroupEntity category in categories)
+ {
+ ListBoxItem item = new ListBoxItem();
+ item.Tag = category.Id;
+ item.Content = category.Name;
+ item.Selected += ItemOnSelected;
+ LstCategory.Items.Add(item);
+ }
+ }
+
+ private void btnEdit_Click(object sender, RoutedEventArgs e)
+ {
+ EditProductGroupWindow editProductGroupWindow = new EditProductGroupWindow(_name,_id);
+ editProductGroupWindow.Closed += WindowClosed;
+ editProductGroupWindow.Show();
+ }
+
+ private void btnDelete_Click(object sender, RoutedEventArgs e)
+ {
+ ArchiveProductGroupWindow archiveProductGroupWindow = new ArchiveProductGroupWindow(_name, _id);
+ archiveProductGroupWindow.Closed += WindowClosed;
+ archiveProductGroupWindow.Show();
+ }
+
+
+ private void btnOrder_Click(object sender, RoutedEventArgs e)
+ {
+ ChangeProductGroupOrderWindow changeProductGroupOrderWindow = new ChangeProductGroupOrderWindow();
+ changeProductGroupOrderWindow.Closed += WindowClosed;
+ changeProductGroupOrderWindow.Show();
+ }
+
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/SettingWindow.xaml b/PointOfSale/Pos.Ui/Pos/Setting/SettingWindow.xaml
new file mode 100644
index 0000000..f393565
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/SettingWindow.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/PointOfSale/Pos.Ui/Pos/Setting/SettingWindow.xaml.cs b/PointOfSale/Pos.Ui/Pos/Setting/SettingWindow.xaml.cs
new file mode 100644
index 0000000..bfaab83
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/Setting/SettingWindow.xaml.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Database.Repository;
+using Pos.Category;
+using Pos.Setting.ProductGroup;
+
+namespace Pos.Setting
+{
+ ///
+ /// Interaction logic for SettingWindow.xaml
+ ///
+ public partial class SettingWindow : Window
+ {
+ public SettingWindow()
+ {
+ InitializeComponent();
+ }
+
+ private void btnClerks_Click(object sender, RoutedEventArgs e)
+ {
+ EmployeeWindow employeeWindow = new EmployeeWindow();
+ employeeWindow.Show();
+
+ }
+
+ private void btnCategories_Click(object sender, RoutedEventArgs e)
+ {
+ ProductGroupWindow productGroupWindow = new ProductGroupWindow();
+ productGroupWindow.Show();
+ }
+
+ private void btnProductGroups_Click(object sender, RoutedEventArgs e)
+ {
+ ProductWindow productWindow = new ProductWindow();
+ //Check if there is any categories, if not close this window with a message.
+ ProductGroupRepository categoryRepository = new ProductGroupRepository();
+ bool anyExist = categoryRepository.Any();
+ if (!anyExist)
+ {
+ MessageBox.Show("Der er ingen kategorier, opret dem først", "Kategorier", MessageBoxButton.OK,
+ MessageBoxImage.Warning);
+ }
+ else
+ {
+ productWindow.Show();
+ }
+ }
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/appsettings.development.json b/PointOfSale/Pos.Ui/Pos/appsettings.development.json
new file mode 100644
index 0000000..cdae5c4
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/appsettings.development.json
@@ -0,0 +1,11 @@
+{
+ "MariaSqlServer": "Data Source=localhost;Initial Catalog=PointOfSale;Persist Security Info=False;User ID=root;Password=Maxbp6703",
+ "PrintSettings": {
+ "ComPort": "COM5",
+ "BaudRate": 115200,
+ "PrintWidth": 48
+ },
+ "API": {
+ "Url": "https://localhost:44334"
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/appsettings.json b/PointOfSale/Pos.Ui/Pos/appsettings.json
new file mode 100644
index 0000000..cdae5c4
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/appsettings.json
@@ -0,0 +1,11 @@
+{
+ "MariaSqlServer": "Data Source=localhost;Initial Catalog=PointOfSale;Persist Security Info=False;User ID=root;Password=Maxbp6703",
+ "PrintSettings": {
+ "ComPort": "COM5",
+ "BaudRate": 115200,
+ "PrintWidth": 48
+ },
+ "API": {
+ "Url": "https://localhost:44334"
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/appsettings.production.json b/PointOfSale/Pos.Ui/Pos/appsettings.production.json
new file mode 100644
index 0000000..cdae5c4
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/appsettings.production.json
@@ -0,0 +1,11 @@
+{
+ "MariaSqlServer": "Data Source=localhost;Initial Catalog=PointOfSale;Persist Security Info=False;User ID=root;Password=Maxbp6703",
+ "PrintSettings": {
+ "ComPort": "COM5",
+ "BaudRate": 115200,
+ "PrintWidth": 48
+ },
+ "API": {
+ "Url": "https://localhost:44334"
+ }
+}
diff --git a/PointOfSale/Pos.Ui/Pos/json.cs b/PointOfSale/Pos.Ui/Pos/json.cs
new file mode 100644
index 0000000..2b8b7c1
--- /dev/null
+++ b/PointOfSale/Pos.Ui/Pos/json.cs
@@ -0,0 +1,60 @@
+
+//public class Rootobject
+//{
+// public string logoBase64 { get; set; }
+// public Header[] header { get; set; }
+// public Bodymodel bodyModel { get; set; }
+// public Footer[] footer { get; set; }
+//}
+
+//public class Bodymodel
+//{
+// public Product[] products { get; set; }
+// public int totalPrice { get; set; }
+// public int totalVat { get; set; }
+// public int receiptNumber { get; set; }
+// public string receiptTime { get; set; }
+// public string staff { get; set; }
+//}
+
+//public class Product
+//{
+// public string product { get; set; }
+// public string noOfProduct { get; set; }
+// public float price { get; set; }
+// public float totalPrice { get; set; }
+//}
+
+//public class Header
+//{
+// public string value { get; set; }
+// public Printstyles printStyles { get; set; }
+// public int textAlignment { get; set; }
+// public int feedingLines { get; set; }
+//}
+
+//public class Printstyles
+//{
+// public bool fontB { get; set; }
+// public bool bold { get; set; }
+// public bool doubleHeight { get; set; }
+// public bool doubleWidth { get; set; }
+// public bool underline { get; set; }
+//}
+
+//public class Footer
+//{
+// public string value { get; set; }
+// public Printstyles1 printStyles { get; set; }
+// public int textAlignment { get; set; }
+// public int feedingLines { get; set; }
+//}
+
+//public class Printstyles1
+//{
+// public bool fontB { get; set; }
+// public bool bold { get; set; }
+// public bool doubleHeight { get; set; }
+// public bool doubleWidth { get; set; }
+// public bool underline { get; set; }
+//}
diff --git a/PointOfSale/Pos.sln b/PointOfSale/Pos.sln
new file mode 100644
index 0000000..b4b83e2
--- /dev/null
+++ b/PointOfSale/Pos.sln
@@ -0,0 +1,75 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 18
+VisualStudioVersion = 18.4.11620.152
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pos.UI", "Pos.UI", "{5EA5190D-D086-4568-8EDD-359BEBF41CEE}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pos.Apis", "Pos.Apis", "{D2F60CBE-F5B2-451D-BBDC-06EA499E6503}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Database", "Pos.Ui\Database\Database.csproj", "{6DD387E5-F403-40D5-A816-EAF6AE5809B2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pos", "Pos.Ui\Pos\Pos.csproj", "{7865371E-75E4-45A6-99A1-0A32F1D29906}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pos.Api", "Pos.Api\Pos.Api\Pos.Api.csproj", "{266F649E-5DDF-4665-B84E-02067767C62C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pos.Api.Database", "Pos.Api\Pos.Api.Database\Pos.Api.Database.csproj", "{2C0AFEA8-E48E-4DB2-9EAB-71325D5A1CEC}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pos.Dispatcher", "Pos.Dispatcher", "{BEBDA585-6A9B-4FDD-822A-B9F70664E78B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pos.Dispatcher", "Pos.Dispatcher\Pos.Dispatcher\Pos.Dispatcher.csproj", "{01655A40-CD98-4BCA-9D9C-A9499F5012AD}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pos.Dispatcher.Database", "Pos.Dispatcher\Pos.Dispatcher.Database\Pos.Dispatcher.Database.csproj", "{2326DCAA-4332-49DB-A948-BB7C9A1459F5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EpsonReceiptPrinter", "Pos.EpsonPrinter\EpsonReceiptPrinter.csproj", "{865C07B5-E581-05E1-19F1-1536067DF479}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6DD387E5-F403-40D5-A816-EAF6AE5809B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6DD387E5-F403-40D5-A816-EAF6AE5809B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6DD387E5-F403-40D5-A816-EAF6AE5809B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6DD387E5-F403-40D5-A816-EAF6AE5809B2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7865371E-75E4-45A6-99A1-0A32F1D29906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7865371E-75E4-45A6-99A1-0A32F1D29906}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7865371E-75E4-45A6-99A1-0A32F1D29906}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7865371E-75E4-45A6-99A1-0A32F1D29906}.Release|Any CPU.Build.0 = Release|Any CPU
+ {266F649E-5DDF-4665-B84E-02067767C62C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {266F649E-5DDF-4665-B84E-02067767C62C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {266F649E-5DDF-4665-B84E-02067767C62C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {266F649E-5DDF-4665-B84E-02067767C62C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2C0AFEA8-E48E-4DB2-9EAB-71325D5A1CEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2C0AFEA8-E48E-4DB2-9EAB-71325D5A1CEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2C0AFEA8-E48E-4DB2-9EAB-71325D5A1CEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2C0AFEA8-E48E-4DB2-9EAB-71325D5A1CEC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {01655A40-CD98-4BCA-9D9C-A9499F5012AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {01655A40-CD98-4BCA-9D9C-A9499F5012AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {01655A40-CD98-4BCA-9D9C-A9499F5012AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {01655A40-CD98-4BCA-9D9C-A9499F5012AD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2326DCAA-4332-49DB-A948-BB7C9A1459F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2326DCAA-4332-49DB-A948-BB7C9A1459F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2326DCAA-4332-49DB-A948-BB7C9A1459F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2326DCAA-4332-49DB-A948-BB7C9A1459F5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {865C07B5-E581-05E1-19F1-1536067DF479}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {865C07B5-E581-05E1-19F1-1536067DF479}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {865C07B5-E581-05E1-19F1-1536067DF479}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {865C07B5-E581-05E1-19F1-1536067DF479}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {6DD387E5-F403-40D5-A816-EAF6AE5809B2} = {5EA5190D-D086-4568-8EDD-359BEBF41CEE}
+ {7865371E-75E4-45A6-99A1-0A32F1D29906} = {5EA5190D-D086-4568-8EDD-359BEBF41CEE}
+ {266F649E-5DDF-4665-B84E-02067767C62C} = {D2F60CBE-F5B2-451D-BBDC-06EA499E6503}
+ {2C0AFEA8-E48E-4DB2-9EAB-71325D5A1CEC} = {D2F60CBE-F5B2-451D-BBDC-06EA499E6503}
+ {01655A40-CD98-4BCA-9D9C-A9499F5012AD} = {BEBDA585-6A9B-4FDD-822A-B9F70664E78B}
+ {2326DCAA-4332-49DB-A948-BB7C9A1459F5} = {BEBDA585-6A9B-4FDD-822A-B9F70664E78B}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {47E8B417-57AB-486D-8755-950B4AC84ACF}
+ EndGlobalSection
+EndGlobal
diff --git a/PointOfSale/Utilities/ESCPOS_NET/ByteArrayBuilder.cs b/PointOfSale/Utilities/ESCPOS_NET/ByteArrayBuilder.cs
new file mode 100644
index 0000000..6246f2d
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/ByteArrayBuilder.cs
@@ -0,0 +1,182 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+/* FROM: https://www.codeproject.com/Tips/674256/ByteArrayBuilder-a-StringBuilder-for-Bytes 2/9/2019 */
+namespace ESCPOS_NET.Utilities
+{
+ ///
+ /// Provides similar functionality to a StringBuilder, but for bytes.
+ ///
+ ///
+ /// To fill the builder, construct a new, empty builder, and call the
+ /// appropriate Append method overloads.
+ /// To read data from the builder, either use Rewind on an existing
+ /// builder, or construct a new builder by passing it the byte array
+ /// from a previous builder - which you can get with the ToArray
+ /// method.
+ ///
+ ///
+ ///
+ /// ByteArrayBuilder bab = new ByteArrayBuilder();
+ /// string[] lines = File.ReadAllLines(@"D:\Temp\myText.txt");
+ /// bab.Append(lines.Length);
+ /// foreach (string s in lines)
+ /// {
+ /// bab.Append(s);
+ /// }
+ /// byte[] data = bab.ToArray();
+ /// ...
+ /// ByteArrayBuilder babOut = new ByteArrayBuilder(data);
+ /// int count = bab.GetInt();
+ /// string[] linesOut = new string[count];
+ /// for (int lineNo = 0; lineNo < count; lineNo++)
+ /// {
+ /// linesOut[lineNo](babOut.GetString());
+ /// }
+ /// .
+ ///
+ public class ByteArrayBuilder : IDisposable
+ {
+ ///
+ /// True in a byte form of the Line.
+ ///
+ private const byte StreamTrue = (byte)1;
+
+ ///
+ /// False in the byte form of a line.
+ ///
+ private const byte StreamFalse = (byte)0;
+
+ ///
+ /// Holds the actual bytes.
+ ///
+ private MemoryStream store = new MemoryStream();
+
+ ///
+ /// Gets bytes in the store.
+ ///
+ public int Length => (int)store.Length;
+
+ ///
+ /// Create a new, empty builder ready to be filled.
+ ///
+ public ByteArrayBuilder()
+ {
+ }
+
+ public ByteArrayBuilder Append(byte b)
+ {
+ AddBytes(new byte[] { b });
+ return this;
+ }
+
+ ///
+ /// Adds an IEnumerable of bytes to an array.
+ ///
+ /// Value to append to existing builder data.
+ ///
+ public ByteArrayBuilder Append(IEnumerable b)
+ {
+ if (b is byte[])
+ {
+ AddBytes((byte[])b);
+ return this;
+ }
+
+ AddBytes(b.ToArray());
+ return this;
+ }
+
+ ///
+ /// Clear all content from the builder.
+ ///
+ public void Clear()
+ {
+ store.Close();
+ store.Dispose();
+ store = new MemoryStream();
+ }
+
+ ///
+ /// Rewind the builder ready to read data.
+ ///
+ public void Rewind()
+ {
+ store.Seek(0, SeekOrigin.Begin);
+ }
+
+ ///
+ /// Set an absolute position in the builder.
+ /// **WARNING**
+ /// If you add any variable size objects to the builder, the results of
+ /// reading after a Seek to a non-zero value are unpredictable.
+ /// A builder does not store just objects - for some it stores additional
+ /// information as well.
+ ///
+ /// the position to seek.
+ public void Seek(int position)
+ {
+ store.Seek((long)position, SeekOrigin.Begin);
+ }
+
+ ///
+ /// Returns the builder as an array of bytes.
+ ///
+ /// An array.
+ public byte[] ToArray()
+ {
+ byte[] data = new byte[Length];
+ Array.Copy(store.GetBuffer(), data, Length);
+ return data;
+ }
+
+ ///
+ /// Returns a text based (Base64) string version of the current content.
+ ///
+ /// The converted string.
+ public override string ToString()
+ {
+ return Convert.ToBase64String(ToArray());
+ }
+
+ ///
+ /// Add a string of raw bytes to the store.
+ ///
+ /// the byte array.
+ private void AddBytes(byte[] byteArray)
+ {
+ store.Write(byteArray, 0, byteArray.Length);
+ }
+
+ ///
+ /// Reads a specific number of bytes from the store.
+ ///
+ /// The length.
+ /// The byte array.
+ private byte[] GetBytes(int length)
+ {
+ byte[] data = new byte[length];
+ if (length > 0)
+ {
+ int read = store.Read(data, 0, length);
+ if (read != length)
+ {
+ throw new ApplicationException("Buffer did not contain " + length + " bytes");
+ }
+ }
+
+ return data;
+ }
+
+ ///
+ /// Dispose of this builder and its resources.
+ ///
+ public void Dispose()
+ {
+ store.Close();
+ store.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/PointOfSale/Utilities/ESCPOS_NET/ByteExtensions.cs b/PointOfSale/Utilities/ESCPOS_NET/ByteExtensions.cs
new file mode 100644
index 0000000..3f73a5e
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/ByteExtensions.cs
@@ -0,0 +1,15 @@
+namespace ESCPOS_NET
+{
+ public static class ByteExtensions
+ {
+ public static bool IsBitSet(this byte b, int offset)
+ {
+ return (b & (1 << offset)) != 0;
+ }
+
+ public static bool IsBitNotSet(this byte b, int offset)
+ {
+ return (b & (1 << offset)) == 0;
+ }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/ByteSplicer.cs b/PointOfSale/Utilities/ESCPOS_NET/ByteSplicer.cs
new file mode 100644
index 0000000..fa2f694
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/ByteSplicer.cs
@@ -0,0 +1,15 @@
+namespace ESCPOS_NET.Utilities
+{
+ public static class ByteSplicer
+ {
+ public static byte[] Combine(params byte[][] byteArrays)
+ {
+ ByteArrayBuilder builder = new ByteArrayBuilder();
+ foreach (var byteArray in byteArrays)
+ {
+ builder.Append(byteArray);
+ }
+ return builder.ToArray();
+ }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/DataValidation/DataConstraint.cs b/PointOfSale/Utilities/ESCPOS_NET/DataValidation/DataConstraint.cs
new file mode 100644
index 0000000..745ca70
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/DataValidation/DataConstraint.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+
+namespace ESCPOS_NET.DataValidation
+{
+ public class DataConstraint
+ {
+ public int MinLength { get; set; }
+
+ public int MaxLength { get; set; }
+
+ public List ValidLengths { get; set; }
+
+ public string ValidChars { get; set; }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/DataValidation/DataValidator.cs b/PointOfSale/Utilities/ESCPOS_NET/DataValidation/DataValidator.cs
new file mode 100644
index 0000000..e8d021d
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/DataValidation/DataValidator.cs
@@ -0,0 +1,193 @@
+using ESCPOS_NET.Emitters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ESCPOS_NET.DataValidation
+{
+ public abstract class BaseDataValidator where T : Enum
+ {
+ protected Dictionary _constraints;
+
+ public void Validate(T type, string data, BarcodeCode? code = null)
+ {
+ if (data is null)
+ {
+ throw new ArgumentNullException(nameof(data));
+ }
+
+ // Validate constraints on barcode.
+ _constraints.TryGetValue(type, out var constraints);
+ if (constraints is null)
+ {
+ return;
+ }
+
+ // Check lengths
+ if (constraints.ValidLengths != null)
+ {
+ if (!constraints.ValidLengths.Contains(data.Length))
+ {
+ throw new ArgumentException($"Code '{data}' is not a valid length from: [{string.Join(", ", constraints.ValidLengths)}].");
+ }
+ }
+ else if (data.Length < constraints.MinLength)
+ {
+ throw new ArgumentException($"Code '{data}' is shorter than minimum length {constraints.MinLength}.");
+ }
+ else if (data.Length > constraints.MaxLength)
+ {
+ throw new ArgumentException($"Code '{data}' is longer than maximum length {constraints.MaxLength}.");
+ }
+
+ // Check if barcode contains invalid characters.
+ if (constraints.ValidChars == "7BIT-ASCII")
+ {
+ if (!data.All(x => x <= 127 && x >= 0))
+ {
+ throw new ArgumentException($"Code '{data}' contained invalid characters not in: {constraints.ValidChars}.");
+ }
+ }
+ else if (constraints.ValidChars != null && !data.All(x => constraints.ValidChars.Contains(x)))
+ {
+ throw new ArgumentException($"Code '{data}' contained invalid characters not in: {constraints.ValidChars}.");
+ }
+
+ RunSpecificValidations(type, data, code);
+ }
+
+ protected abstract void RunSpecificValidations(T type, string data, BarcodeCode? code);
+ }
+
+ public static class DataValidator
+ {
+ private static BarcodeDataValidator singletonBarcode = null;
+ private static TwoDimensionCodeDataValidator singleton2DCode = null;
+
+ public static void ValidateBarcode(BarcodeType type, BarcodeCode code, string data)
+ {
+ if (singletonBarcode is null)
+ {
+ singletonBarcode = new BarcodeDataValidator();
+ }
+
+ singletonBarcode.Validate(type, data, code);
+ }
+
+ public static void Validate2DCode(TwoDimensionCodeType type, string data)
+ {
+ if (singleton2DCode is null)
+ {
+ singleton2DCode = new TwoDimensionCodeDataValidator();
+ }
+
+ singleton2DCode.Validate(type, data);
+ }
+
+ private class BarcodeDataValidator : BaseDataValidator
+ {
+ public BarcodeDataValidator()
+ {
+ _constraints = new Dictionary()
+ {
+ { BarcodeType.UPC_A, new DataConstraint() { MinLength = 11, MaxLength = 12, ValidChars = "0123456789" } },
+ { BarcodeType.UPC_E, new DataConstraint() { ValidLengths = new List() { 6, 7, 8, 11, 12 }, ValidChars = "0123456789" } },
+ { BarcodeType.JAN13_EAN13, new DataConstraint() { MinLength = 12, MaxLength = 13, ValidChars = "0123456789" } },
+ { BarcodeType.JAN8_EAN8, new DataConstraint() { MinLength = 7, MaxLength = 8, ValidChars = "0123456789" } },
+ { BarcodeType.CODE39, new DataConstraint() { MinLength = 1, MaxLength = 255, ValidChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./" } },
+ { BarcodeType.ITF, new DataConstraint() { MinLength = 2, MaxLength = 255, ValidChars = "0123456789" } },
+ { BarcodeType.CODABAR_NW_7, new DataConstraint() { MinLength = 2, MaxLength = 255, ValidChars = "0123456789ABCDabcd$+-./:" } },
+ { BarcodeType.CODE93, new DataConstraint() { MinLength = 1, MaxLength = 255, ValidChars = "7BIT-ASCII" } },
+ { BarcodeType.CODE128, new DataConstraint() { MinLength = 1, MaxLength = 253, ValidChars = "7BIT-ASCII" } },
+ { BarcodeType.GS1_128, new DataConstraint() { MinLength = 1, MaxLength = 253, ValidChars = "7BIT-ASCII" } },
+ { BarcodeType.GS1_DATABAR_OMNIDIRECTIONAL, new DataConstraint() { MinLength = 13, MaxLength = 13, ValidChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./" } },
+ { BarcodeType.GS1_DATABAR_TRUNCATED, new DataConstraint() { MinLength = 13, MaxLength = 13, ValidChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./" } },
+ { BarcodeType.GS1_DATABAR_LIMITED, new DataConstraint() { MinLength = 13, MaxLength = 13, ValidChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./" } },
+ { BarcodeType.GS1_DATABAR_EXPANDED, new DataConstraint() { MinLength = 2, MaxLength = 255, ValidChars = "0123456789ABCDabcd !\"%$'()*+,-./:;<=>?_{" } },
+ };
+ }
+
+ protected override void RunSpecificValidations(BarcodeType type, string barcode, BarcodeCode? code)
+ {
+ switch (type)
+ {
+ case BarcodeType.UPC_E:
+ if (barcode.Length != 6 && !barcode.StartsWith("0", StringComparison.InvariantCulture))
+ {
+ throw new ArgumentException($"UPC_E Barcode {barcode} with length of 7, 8, 11, or 12 must start with 0.");
+ }
+
+ break;
+
+ case BarcodeType.ITF:
+ if (barcode.Length % 2 != 0)
+ {
+ throw new ArgumentException($"ITF Barcode {barcode} has length {barcode.Length}, which is not an even number.");
+ }
+
+ break;
+
+ case BarcodeType.CODABAR_NW_7:
+ if (!"ABCD".Contains(barcode[0]) || !"ABCD".Contains(barcode[barcode.Length - 1]))
+ {
+ throw new ArgumentException($"CODABAR_NW_7 Barcode {barcode} must start and end with an ABCD character.");
+ }
+
+ if (barcode.Skip(1).Take(barcode.Length - 2).Any(x => "ABCD".Contains(x)))
+ {
+ throw new ArgumentException($"CODABAR_NW_7 Barcode {barcode} must not include ABCD characters in the body of the barcode.");
+ }
+
+ break;
+
+ case BarcodeType.CODE93:
+ if (!barcode.StartsWith("*", StringComparison.InvariantCulture) || !barcode.EndsWith("*", StringComparison.InvariantCulture))
+ {
+ throw new ArgumentException($"CODE93 Barcode {barcode} must start and end with * characters.");
+ }
+
+ break;
+
+ case BarcodeType.CODE128:
+ if (code == BarcodeCode.CODE_C)
+ {
+ if (barcode.Length % 2 != 0)
+ {
+ throw new ArgumentException($"{nameof(barcode)} length must be divisible by 2");
+ }
+
+ if (!barcode.All(x => x <= '9' && x >= '0'))
+ {
+ throw new ArgumentException($"Barcode {barcode} is invalid. CODE128 CODE_C barcodes only support numeric characters.");
+ }
+ }
+
+ break;
+ }
+ }
+ }
+
+ private class TwoDimensionCodeDataValidator : BaseDataValidator
+ {
+ public TwoDimensionCodeDataValidator()
+ {
+ _constraints = new Dictionary()
+ {
+ { TwoDimensionCodeType.PDF417, new DataConstraint() { MinLength = 0, MaxLength = 255 } },
+ { TwoDimensionCodeType.QRCODE_MODEL1, new DataConstraint() { MinLength = 0, MaxLength = 707 } },
+ { TwoDimensionCodeType.QRCODE_MODEL2, new DataConstraint() { MinLength = 0, MaxLength = 4296 } },
+ { TwoDimensionCodeType.QRCODE_MICRO, new DataConstraint() { MinLength = 0, MaxLength = 21 } },
+ };
+ }
+
+ // TODO: Research specific validations for QRCode & PDF417
+ protected override void RunSpecificValidations(TwoDimensionCodeType type, string barcode, BarcodeCode? code)
+ {
+ if (code != null)
+ {
+ throw new ArgumentException($"Barcode code should be always null for 2D Codes.", nameof(code));
+ }
+ }
+ }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/ESCPOS_NET.csproj b/PointOfSale/Utilities/ESCPOS_NET/ESCPOS_NET.csproj
new file mode 100644
index 0000000..7f90e86
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/ESCPOS_NET.csproj
@@ -0,0 +1,33 @@
+
+
+ netstandard2.0
+ false
+ true
+ animalistic.io
+ ESCPOS_NET
+ Luke Paireepinart
+ .NET Standard 2.0 Implementation of ESC/POS command generation and integration with thermal printers.
+Allows creating receipts with all common functionality supported.
+ Copyright 2021 CandL Development, LLC.
+
+ https://github.com/lukevp/ESC-POS-.NET
+ git
+ epson thermal receipt printing printer usb wifi bluetooth serial esc pos escpos escp esc/pos esc/pos.net esc_pos esc_pos_net esc_pos_usb esc_pos_usb_net
+ 2.0.0
+ 2.0.0.0
+ 2.0.0.0
+ MIT
+ https://github.com/lukevp/ESC-POS-.NET
+ - Completely rebuilt the network printer code
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/ActionCommands.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/ActionCommands.cs
new file mode 100644
index 0000000..e713659
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/ActionCommands.cs
@@ -0,0 +1,16 @@
+using ESCPOS_NET.Emitters.BaseCommandValues;
+
+namespace ESCPOS_NET.Emitters
+{
+ public abstract partial class BaseCommandEmitter : ICommandEmitter
+ {
+ /* Action Commands */
+ public virtual byte[] FullCut() => new byte[] { Cmd.GS, Ops.PaperCut, Functions.PaperCutFullCut };
+
+ public virtual byte[] PartialCut() => new byte[] { Cmd.GS, Ops.PaperCut, Functions.PaperCutPartialCut };
+
+ public virtual byte[] FullCutAfterFeed(int lineCount) => new byte[] { Cmd.GS, Ops.PaperCut, Functions.PaperCutFullCutWithFeed, (byte)lineCount };
+
+ public virtual byte[] PartialCutAfterFeed(int lineCount) => new byte[] { Cmd.GS, Ops.PaperCut, Functions.PaperCutPartialCutWithFeed, (byte)lineCount };
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/BarcodeCommands.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/BarcodeCommands.cs
new file mode 100644
index 0000000..63f27ae
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/BarcodeCommands.cs
@@ -0,0 +1,115 @@
+using ESCPOS_NET.DataValidation;
+using ESCPOS_NET.Emitters.BaseCommandValues;
+using ESCPOS_NET.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ESCPOS_NET.Emitters
+{
+ public abstract partial class BaseCommandEmitter : ICommandEmitter
+ {
+ /* Barcode Commands */
+ public virtual byte[] PrintBarcode(BarcodeType type, string barcode, BarcodeCode code = BarcodeCode.CODE_B)
+ {
+ DataValidator.ValidateBarcode(type, code, barcode);
+ return BarcodeBytes(type, barcode, code);
+ }
+
+ protected virtual byte[] BarcodeBytes(BarcodeType type, string barcode, BarcodeCode code)
+ {
+ // For CODE128, prepend the first 2 characters as 0x7B and the CODE type, and escape 0x7B characters.
+ if (type == BarcodeType.CODE128)
+ {
+ if (code == BarcodeCode.CODE_C)
+ {
+ byte[] b = Encoding.ASCII.GetBytes(barcode);
+ byte[] ob = new byte[b.Length / 2];
+ for (int i = 0, obc = 0; i < b.Length; i += 2)
+ {
+ ob[obc++] = (byte)(((b[i] - '0') * 10) + (b[i + 1] - '0'));
+ }
+
+ barcode = Encoding.ASCII.GetString(ob);
+ }
+
+ barcode = barcode.Replace("{", "{{");
+ barcode = $"{(char)0x7B}{(char)code}" + barcode;
+ }
+
+ var command = new List { Cmd.GS, Barcodes.PrintBarcode, (byte)type, (byte)barcode.Length };
+ command.AddRange(barcode.ToCharArray().Select(x => (byte)x));
+ return command.ToArray();
+ }
+
+ public virtual byte[] PrintQRCode(string data, TwoDimensionCodeType type = TwoDimensionCodeType.QRCODE_MODEL2, Size2DCode size = Size2DCode.NORMAL, CorrectionLevel2DCode correction = CorrectionLevel2DCode.PERCENT_7)
+ {
+ if (type == TwoDimensionCodeType.PDF417)
+ {
+ throw new ArgumentException($"{nameof(TwoDimensionCodeType.PDF417)} is not a valid QRCode type. Please use {nameof(Print2DCode)} method", nameof(type));
+ }
+
+ return Print2DCode(type, data, size, correction);
+ }
+
+ public virtual byte[] Print2DCode(TwoDimensionCodeType type, string data, Size2DCode size = Size2DCode.NORMAL, CorrectionLevel2DCode correction = CorrectionLevel2DCode.PERCENT_7)
+ {
+ DataValidator.Validate2DCode(type, data);
+ return TwoDimensionCodeBytes(type, data, size, correction);
+ }
+
+ protected virtual byte[] TwoDimensionCodeBytes(TwoDimensionCodeType type, string data, Size2DCode size, CorrectionLevel2DCode correction)
+ {
+ List command = new List();
+ byte[] initial = { Cmd.GS, Barcodes.Set2DCode, Barcodes.PrintBarcode };
+ switch (type)
+ {
+ case TwoDimensionCodeType.PDF417:
+ command.AddRange(initial, Barcodes.SetPDF417NumberOfColumns, Barcodes.AutoEnding);
+ command.AddRange(initial, Barcodes.SetPDF417NumberOfRows, Barcodes.AutoEnding);
+ command.AddRange(initial, Barcodes.SetPDF417DotSize, (byte)size);
+ command.AddRange(initial, Barcodes.SetPDF417CorrectionLevel, (byte)correction);
+
+ // k = (pL + pH * 256) - 3 --> But pH is always 0.
+ int k = data.Length;
+ int l = k + 3;
+ command.AddRange(initial, (byte)l, Barcodes.StorePDF417Data);
+ command.AddRange(data.ToCharArray().Select(x => (byte)x));
+
+ // Prints stored PDF417
+ command.AddRange(initial, Barcodes.PrintPDF417);
+ break;
+
+ case TwoDimensionCodeType.QRCODE_MODEL1:
+ case TwoDimensionCodeType.QRCODE_MODEL2:
+ case TwoDimensionCodeType.QRCODE_MICRO:
+ command.AddRange(initial, Barcodes.SelectQRCodeModel, (byte)type, Barcodes.AutoEnding);
+ command.AddRange(initial, Barcodes.SetQRCodeDotSize, (byte)size);
+ command.AddRange(initial, Barcodes.SetQRCodeCorrectionLevel, (byte)correction);
+ int num = data.Length + 3;
+ int pL = num % 256;
+ int pH = num / 256;
+ command.AddRange(initial, (byte)pL, (byte)pH, Barcodes.StoreQRCodeData);
+ command.AddRange(data.ToCharArray().Select(x => (byte)x));
+
+ // Prints stored QRCode
+ command.AddRange(initial, Barcodes.PrintQRCode);
+ break;
+
+ default:
+ throw new NotImplementedException($"2D Code '{type}' was not implemented yet.");
+ }
+
+ return command.ToArray();
+ }
+
+ public virtual byte[] SetBarcodeHeightInDots(int height) => new byte[] { Cmd.GS, Barcodes.SetBarcodeHeightInDots, (byte)height };
+
+ public virtual byte[] SetBarWidth(BarWidth width) => new byte[] { Cmd.GS, Barcodes.SetBarWidth, (byte)width };
+
+ public virtual byte[] SetBarLabelPosition(BarLabelPrintPosition position) => new byte[] { Cmd.GS, Barcodes.SetBarLabelPosition, (byte)position };
+
+ public virtual byte[] SetBarLabelFontB(bool fontB) => new byte[] { Cmd.GS, Barcodes.SetBarLabelFont, (byte)(fontB ? 1 : 0) };
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/CashDrawerCommands.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/CashDrawerCommands.cs
new file mode 100644
index 0000000..78891b3
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/CashDrawerCommands.cs
@@ -0,0 +1,16 @@
+using ESCPOS_NET.Emitters.BaseCommandValues;
+
+namespace ESCPOS_NET.Emitters
+{
+ public abstract partial class BaseCommandEmitter : ICommandEmitter
+ {
+ /* Cash Drawer Commands */
+ public virtual byte[] CashDrawerOpenPin2(int pulseOnTimeMs = 120, int pulseOffTimeMs = 240) {
+ return new byte[] { Cmd.ESC, Ops.CashDrawerPulse, 0x00, (byte) (pulseOnTimeMs / 2), (byte) (pulseOffTimeMs / 2) };
+ }
+
+ public virtual byte[] CashDrawerOpenPin5(int pulseOnTimeMs = 120, int pulseOffTimeMs = 240) {
+ return new byte[] { Cmd.ESC, Ops.CashDrawerPulse, 0x01, (byte) (pulseOnTimeMs / 2), (byte) (pulseOffTimeMs / 2) };
+ }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/CharacterCommands.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/CharacterCommands.cs
new file mode 100644
index 0000000..5dac730
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/CharacterCommands.cs
@@ -0,0 +1,22 @@
+using ESCPOS_NET.Emitters.BaseCommandValues;
+
+namespace ESCPOS_NET.Emitters
+{
+ public abstract partial class BaseCommandEmitter : ICommandEmitter
+ {
+ /* Character Commands */
+ public virtual byte[] SetStyles(PrintStyle style) => new byte[] { Cmd.ESC, Chars.StyleMode, (byte)style };
+
+ public virtual byte[] LeftAlign() => new byte[] { Cmd.ESC, Chars.Alignment, (byte)Align.Left };
+
+ public virtual byte[] CenterAlign() => new byte[] { Cmd.ESC, Chars.Alignment, (byte)Align.Center };
+
+ public virtual byte[] RightAlign() => new byte[] { Cmd.ESC, Chars.Alignment, (byte)Align.Right };
+
+ public virtual byte[] RightCharacterSpacing(int spaceCount) => new byte[] { Cmd.ESC, Chars.RightCharacterSpacing, (byte)spaceCount };
+
+ public virtual byte[] CodePage(CodePage codePage) => new byte[] { Cmd.ESC, Chars.CodePage, (byte)codePage };
+
+ public virtual byte[] Color(Color color) => new byte[] { Cmd.ESC, Chars.Color, (byte)color };
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/DisplayCommands.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/DisplayCommands.cs
new file mode 100644
index 0000000..ba6c170
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/DisplayCommands.cs
@@ -0,0 +1,10 @@
+using ESCPOS_NET.Emitters.BaseCommandValues;
+
+namespace ESCPOS_NET.Emitters
+{
+ public abstract partial class BaseCommandEmitter : ICommandEmitter
+ {
+ /* Display Commands */
+ public virtual byte[] Clear() => new byte[] { Display.CLR };
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/ImageCommands.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/ImageCommands.cs
new file mode 100644
index 0000000..6b247d2
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/ImageCommands.cs
@@ -0,0 +1,122 @@
+using ESCPOS_NET.Emitters.BaseCommandValues;
+using ESCPOS_NET.Utilities;
+using SixLabors.ImageSharp;
+
+namespace ESCPOS_NET.Emitters
+{
+ public abstract partial class BaseCommandEmitter : ICommandEmitter
+ {
+ private byte[] GetImageHeader(int commandLength)
+ {
+ byte[] lengths = new byte[4];
+ int i = 0;
+ while (commandLength > 0)
+ {
+ lengths[i] = (byte)(commandLength & 0xFF);
+ commandLength >>= 8;
+ i++;
+ }
+
+ if (i >= 3)
+ {
+ return new byte[] { Cmd.GS, Images.ImageCmd8, Images.ImageCmdL, lengths[0], lengths[1], lengths[2], lengths[3] };
+ }
+ else
+ {
+ return new byte[] { Cmd.GS, Images.ImageCmdParen, Images.ImageCmdL, lengths[0], lengths[1] };
+ }
+ }
+
+ /* Image Commands */
+ public virtual byte[] SetImageDensity(bool isHiDPI)
+ {
+ ByteArrayBuilder builder = new ByteArrayBuilder();
+ byte dpiSetting = isHiDPI ? (byte)0x33 : (byte)0x32; // TODO: is this right??
+ byte[] baseCommand = new byte[] { 0x30, 0x31, dpiSetting, dpiSetting };
+ builder.Append(GetImageHeader(baseCommand.Length));
+ builder.Append(baseCommand);
+ return builder.ToArray();
+ }
+
+ public virtual byte[] BufferImage(byte[] image, int maxWidth = -1, bool isLegacy = false, int color = 1)
+ {
+ ByteArrayBuilder imageCommand = new ByteArrayBuilder();
+
+ byte colorByte;
+ switch (color)
+ {
+ case 2:
+ colorByte = 0x32;
+ break;
+ case 3:
+ colorByte = 0x33;
+ break;
+ default:
+ colorByte = 0x31;
+ break;
+ }
+
+ int width;
+ int height;
+ byte[] imageData;
+ using (var img = Image.Load(image))
+ {
+ imageData = img.ToSingleBitPixelByteArray(maxWidth: maxWidth == -1 ? (int?)null : maxWidth);
+ height = img.Height;
+ width = img.Width;
+ }
+
+ byte heightL = (byte)height;
+ byte heightH = (byte)(height >> 8);
+
+ if (isLegacy)
+ {
+ var byteWidth = (width + 7 & -8) / 8;
+ byte widthL = (byte)byteWidth;
+ byte widthH = (byte)(byteWidth >> 8);
+ imageCommand.Append(new byte[] { Cmd.GS, Images.ImageCmdLegacy, 0x30, 0x00, widthL, widthH, heightL, heightH });
+ }
+ else
+ {
+ byte widthL = (byte)width;
+ byte widthH = (byte)(width >> 8);
+ imageCommand.Append(new byte[] { 0x30, 0x70, 0x30, 0x01, 0x01, colorByte, widthL, widthH, heightL, heightH });
+ }
+
+ imageCommand.Append(imageData);
+
+ // Load image to print buffer
+ ByteArrayBuilder response = new ByteArrayBuilder();
+ byte[] imageCommandBytes = imageCommand.ToArray();
+ if (!isLegacy)
+ {
+ response.Append(GetImageHeader(imageCommandBytes.Length));
+ }
+
+ response.Append(imageCommandBytes);
+ return response.ToArray();
+ }
+
+ public virtual byte[] WriteImageFromBuffer()
+ {
+ // Print image that's already buffered.
+ ByteArrayBuilder response = new ByteArrayBuilder();
+ byte[] printCommandBytes = new byte[] { 0x30, 0x32 };
+ response.Append(GetImageHeader(printCommandBytes.Length));
+ response.Append(printCommandBytes);
+ return response.ToArray();
+ }
+
+ public virtual byte[] PrintImage(byte[] image, bool isHiDPI, bool isLegacy = false, int maxWidth = -1, int color = 1)
+ {
+ if (isLegacy)
+ {
+ return ByteSplicer.Combine(BufferImage(image, maxWidth, isLegacy));
+ }
+ else
+ {
+ return ByteSplicer.Combine(SetImageDensity(isHiDPI), BufferImage(image, maxWidth, isLegacy, color), WriteImageFromBuffer());
+ }
+ }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/LineSpacingCommands.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/LineSpacingCommands.cs
new file mode 100644
index 0000000..69e4f70
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/LineSpacingCommands.cs
@@ -0,0 +1,11 @@
+using ESCPOS_NET.Emitters.BaseCommandValues;
+
+namespace ESCPOS_NET.Emitters
+{
+ public abstract partial class BaseCommandEmitter : ICommandEmitter
+ {
+ public virtual byte[] ResetLineSpacing() => new byte[] { Cmd.ESC, Whitespace.ResetLineSpacing };
+
+ public virtual byte[] SetLineSpacingInDots(int dots) => new byte[] { Cmd.ESC, Whitespace.LineSpacingInDots, (byte)dots };
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/OperationalCommands.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/OperationalCommands.cs
new file mode 100644
index 0000000..8f40497
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/OperationalCommands.cs
@@ -0,0 +1,14 @@
+using ESCPOS_NET.Emitters.BaseCommandValues;
+
+namespace ESCPOS_NET.Emitters
+{
+ public abstract partial class BaseCommandEmitter : ICommandEmitter
+ {
+ /* Operational Commands */
+ public virtual byte[] Initialize() => new byte[] { Cmd.ESC, Ops.Initialize };
+
+ public virtual byte[] Enable() => new byte[] { Cmd.ESC, Ops.EnableDisable, 1 };
+
+ public virtual byte[] Disable() => new byte[] { Cmd.ESC, Ops.EnableDisable, 0 };
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/PrintCommands.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/PrintCommands.cs
new file mode 100644
index 0000000..725a300
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/PrintCommands.cs
@@ -0,0 +1,39 @@
+using System.Linq;
+using ESCPOS_NET.Emitters.BaseCommandValues;
+
+namespace ESCPOS_NET.Emitters
+{
+ public abstract partial class BaseCommandEmitter : ICommandEmitter
+ {
+ /* Printing Commands */
+ public virtual byte[] Print(string data)
+ {
+ // Fix OSX or Windows-style newlines
+ data = data.Replace("\r\n", "\n");
+ data = data.Replace("\r", "\n");
+
+ // TODO: Sanitize...
+ return data.ToCharArray().Select(x => (byte)x).ToArray();
+ }
+
+ public virtual byte[] PrintLine(string line)
+ {
+ if (line == null)
+ {
+ return Print("\n");
+ }
+
+ return Print(line.Replace("\r", string.Empty).Replace("\n", string.Empty) + "\n");
+ }
+
+ public virtual byte[] FeedLines(int lineCount) => new byte[] { Cmd.ESC, Whitespace.FeedLines, (byte)lineCount };
+
+ public virtual byte[] FeedLinesReverse(int lineCount) => new byte[] { Cmd.ESC, Whitespace.FeedLinesReverse, (byte)lineCount };
+
+ public virtual byte[] FeedDots(int dotCount) => new byte[] { Cmd.ESC, Whitespace.FeedDots, (byte)dotCount };
+
+ public virtual byte[] ReverseMode(bool enable) => new byte[] { Cmd.GS, Chars.ReversePrintMode, enable ? (byte)0x01 : (byte)0x00 };
+
+ public virtual byte[] UpsideDownMode(bool enable) => new byte[] { Cmd.ESC, Chars.UpsideDownMode, enable ? (byte)0x01 : (byte)0x00 };
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/StatusCommands.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/StatusCommands.cs
new file mode 100644
index 0000000..ebe801f
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandEmitter/StatusCommands.cs
@@ -0,0 +1,18 @@
+using ESCPOS_NET.Emitters.BaseCommandValues;
+
+namespace ESCPOS_NET.Emitters
+{
+ public abstract partial class BaseCommandEmitter : ICommandEmitter
+ {
+ /* Status Commands */
+ public virtual byte[] EnableAutomaticStatusBack() => new byte[] { Cmd.GS, Status.AutomaticStatusBack, 0xFF };
+
+ public virtual byte[] EnableAutomaticInkStatusBack() => new byte[] { Cmd.GS, Status.AutomaticInkStatusBack, 0xFF };
+ public virtual byte[] DisableAutomaticStatusBack() => new byte[] { Cmd.GS, Status.AutomaticStatusBack, 0x00 };
+
+ public virtual byte[] DisableAutomaticInkStatusBack() => new byte[] { Cmd.GS, Status.AutomaticInkStatusBack, 0x00 };
+ public virtual byte[] RequestPaperStatus() => new byte[] { Cmd.GS, Status.RequestStatus, 0x31 };
+ public virtual byte[] RequestDrawerStatus() => new byte[] { Cmd.GS, Status.RequestStatus, 0x32 };
+ public virtual byte[] RequestInkStatus() => new byte[] { Cmd.GS, Status.RequestStatus, 0x34 };
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Barcodes.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Barcodes.cs
new file mode 100644
index 0000000..3b1fd80
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Barcodes.cs
@@ -0,0 +1,26 @@
+namespace ESCPOS_NET.Emitters.BaseCommandValues
+{
+ public static class Barcodes
+ {
+ public static readonly byte PrintBarcode = 0x6B;
+ public static readonly byte SetBarcodeHeightInDots = 0x68;
+ public static readonly byte SetBarWidth = 0x77;
+ public static readonly byte SetBarLabelPosition = 0x48;
+ public static readonly byte SetBarLabelFont = 0x66;
+
+ public static readonly byte Set2DCode = 0x28;
+ public static readonly byte AutoEnding = 0x00;
+ public static readonly byte[] SetPDF417NumberOfColumns = { 0x03, 0x00, 0x30, 0x41 };
+ public static readonly byte[] SetPDF417NumberOfRows = { 0x03, 0x00, 0x30, 0x42 };
+ public static readonly byte[] SetPDF417DotSize = { 0x03, 0x00, 0x30, 0x43 };
+ public static readonly byte[] SetPDF417CorrectionLevel = { 0x04, 0x00, 0x30, 0x45, 0x30 };
+ public static readonly byte[] StorePDF417Data = { 0x00, 0x30, 0x50, 0x30 };
+ public static readonly byte[] PrintPDF417 = { 0x03, 0x00, 0x30, 0x51, 0x30 };
+
+ public static readonly byte[] SelectQRCodeModel = { 0x04, 0x00, 0x31, 0x41 };
+ public static readonly byte[] SetQRCodeDotSize = { 0x03, 0x00, 0x31, 0x43 };
+ public static readonly byte[] SetQRCodeCorrectionLevel = { 0x03, 0x00, 0x31, 0x45 };
+ public static readonly byte[] StoreQRCodeData = { 0x31, 0x50, 0x30 };
+ public static readonly byte[] PrintQRCode = { 0x03, 0x00, 0x31, 0x51, 0x30 };
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Chars.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Chars.cs
new file mode 100644
index 0000000..c629b3a
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Chars.cs
@@ -0,0 +1,13 @@
+namespace ESCPOS_NET.Emitters.BaseCommandValues
+{
+ public static class Chars
+ {
+ public static readonly byte StyleMode = 0x21;
+ public static readonly byte Alignment = 0x61;
+ public static readonly byte ReversePrintMode = 0x42;
+ public static readonly byte RightCharacterSpacing = 0x20;
+ public static readonly byte UpsideDownMode = 0x7B;
+ public static readonly byte CodePage = 0x74;
+ public static readonly byte Color = 0x72;
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Cmd.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Cmd.cs
new file mode 100644
index 0000000..40b4662
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Cmd.cs
@@ -0,0 +1,8 @@
+namespace ESCPOS_NET.Emitters.BaseCommandValues
+{
+ public static class Cmd
+ {
+ public static readonly byte ESC = 0x1B;
+ public static readonly byte GS = 0x1D;
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Display.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Display.cs
new file mode 100644
index 0000000..e14d0f0
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Display.cs
@@ -0,0 +1,7 @@
+namespace ESCPOS_NET.Emitters.BaseCommandValues
+{
+ public static class Display
+ {
+ public static readonly byte CLR = 0x0C;
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Functions.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Functions.cs
new file mode 100644
index 0000000..fbd9856
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Functions.cs
@@ -0,0 +1,10 @@
+namespace ESCPOS_NET.Emitters.BaseCommandValues
+{
+ public static class Functions
+ {
+ public static readonly byte PaperCutFullCut = 0x00;
+ public static readonly byte PaperCutFullCutWithFeed = 0x41;
+ public static readonly byte PaperCutPartialCut = 0x01;
+ public static readonly byte PaperCutPartialCutWithFeed = 0x42;
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Images.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Images.cs
new file mode 100644
index 0000000..7f4cb99
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Images.cs
@@ -0,0 +1,10 @@
+namespace ESCPOS_NET.Emitters.BaseCommandValues
+{
+ public static class Images
+ {
+ public static readonly byte ImageCmdParen = 0x28;
+ public static readonly byte ImageCmdLegacy = 0x76;
+ public static readonly byte ImageCmd8 = 0x38;
+ public static readonly byte ImageCmdL = 0x4C;
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Ops.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Ops.cs
new file mode 100644
index 0000000..9cf4263
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Ops.cs
@@ -0,0 +1,10 @@
+namespace ESCPOS_NET.Emitters.BaseCommandValues
+{
+ public static class Ops
+ {
+ public static readonly byte Initialize = 0x40;
+ public static readonly byte EnableDisable = 0x3D;
+ public static readonly byte PaperCut = 0x56;
+ public static readonly byte CashDrawerPulse = 0x70;
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Status.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Status.cs
new file mode 100644
index 0000000..cc819e6
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Status.cs
@@ -0,0 +1,9 @@
+namespace ESCPOS_NET.Emitters.BaseCommandValues
+{
+ public static class Status
+ {
+ public static readonly byte AutomaticStatusBack = 0x61;
+ public static readonly byte AutomaticInkStatusBack = 0x6A;
+ public static readonly byte RequestStatus = 0x72;
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Whitespace.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Whitespace.cs
new file mode 100644
index 0000000..f679a76
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/BaseCommandValues/Whitespace.cs
@@ -0,0 +1,13 @@
+namespace ESCPOS_NET.Emitters.BaseCommandValues
+{
+ public static class Whitespace
+ {
+ // TODO: tabs?
+ public static readonly byte Linefeed = 0x0A;
+ public static readonly byte FeedLines = 0x64;
+ public static readonly byte FeedLinesReverse = 0x65;
+ public static readonly byte FeedDots = 0x4A;
+ public static readonly byte ResetLineSpacing = 0x32;
+ public static readonly byte LineSpacingInDots = 0x33;
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/EPSON.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/EPSON.cs
new file mode 100644
index 0000000..8648b63
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/EPSON.cs
@@ -0,0 +1,6 @@
+namespace ESCPOS_NET.Emitters
+{
+ public class EPSON : BaseCommandEmitter
+ {
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/2DCode.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/2DCode.cs
new file mode 100644
index 0000000..f1616c8
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/2DCode.cs
@@ -0,0 +1,30 @@
+namespace ESCPOS_NET.Emitters
+{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1025:Code should not contain multiple whitespace in a row", Justification = "Enums are easier to read if they have whitespace alignment.")]
+ public enum TwoDimensionCodeType
+ {
+ PDF417 = 0,
+ QRCODE_MODEL1 = 49,
+ QRCODE_MODEL2 = 50,
+ QRCODE_MICRO = 51,
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1025:Code should not contain multiple whitespace in a row", Justification = "Enums are easier to read if they have whitespace alignment.")]
+ public enum Size2DCode
+ {
+ TINY = 2,
+ SMALL = 3,
+ NORMAL = 4,
+ LARGE = 5,
+ EXTRA = 6,
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1025:Code should not contain multiple whitespace in a row", Justification = "Enums are easier to read if they have whitespace alignment.")]
+ public enum CorrectionLevel2DCode
+ {
+ PERCENT_7 = 48,
+ PERCENT_15 = 49,
+ PERCENT_25 = 50,
+ PERCENT_30 = 51,
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/Align.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/Align.cs
new file mode 100644
index 0000000..be7ef6b
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/Align.cs
@@ -0,0 +1,10 @@
+namespace ESCPOS_NET.Emitters
+{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1025:Code should not contain multiple whitespace in a row", Justification = "Enums are easier to read if they have whitespace alignment.")]
+ public enum Align
+ {
+ Left = 0,
+ Center = 1,
+ Right = 2,
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/BarLabelPrintPosition.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/BarLabelPrintPosition.cs
new file mode 100644
index 0000000..9c9d35e
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/BarLabelPrintPosition.cs
@@ -0,0 +1,11 @@
+namespace ESCPOS_NET.Emitters
+{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1025:Code should not contain multiple whitespace in a row", Justification = "Enums are easier to read if they have whitespace alignment..")]
+ public enum BarLabelPrintPosition
+ {
+ None = 0,
+ Above = 1,
+ Below = 2,
+ Both = 3,
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/BarWidth.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/BarWidth.cs
new file mode 100644
index 0000000..2023ec4
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/BarWidth.cs
@@ -0,0 +1,12 @@
+namespace ESCPOS_NET.Emitters
+{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1025:Code should not contain multiple whitespace in a row", Justification = "Enums are easier to read if they have whitespace alignment.")]
+ public enum BarWidth
+ {
+ Thinnest = 2,
+ Thin = 3,
+ Default = 4,
+ Thick = 5,
+ Thickest = 6,
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/BarcodeCode.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/BarcodeCode.cs
new file mode 100644
index 0000000..e515640
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/BarcodeCode.cs
@@ -0,0 +1,9 @@
+namespace ESCPOS_NET.Emitters
+{
+ public enum BarcodeCode
+ {
+ CODE_A = 0x41,
+ CODE_B = 0x42,
+ CODE_C = 0x43,
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/BarcodeType.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/BarcodeType.cs
new file mode 100644
index 0000000..3e7a5d7
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/BarcodeType.cs
@@ -0,0 +1,21 @@
+namespace ESCPOS_NET.Emitters
+{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1025:Code should not contain multiple whitespace in a row", Justification = "Enums are easier to read if they have whitespace alignment.")]
+ public enum BarcodeType // All from Function B
+ {
+ UPC_A = 0x41,
+ UPC_E = 0x42,
+ JAN13_EAN13 = 0x43,
+ JAN8_EAN8 = 0x44,
+ CODE39 = 0x45,
+ ITF = 0x46,
+ CODABAR_NW_7 = 0x47,
+ CODE93 = 0x48,
+ CODE128 = 0x49,
+ GS1_128 = 0x4A,
+ GS1_DATABAR_OMNIDIRECTIONAL = 0x4B,
+ GS1_DATABAR_TRUNCATED = 0x4C,
+ GS1_DATABAR_LIMITED = 0x4D,
+ GS1_DATABAR_EXPANDED = 0x4E,
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/CodePage.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/CodePage.cs
new file mode 100644
index 0000000..83e9eb1
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/CodePage.cs
@@ -0,0 +1,68 @@
+namespace ESCPOS_NET.Emitters
+{
+ #pragma warning disable SA1602 // Enumeration items should be documented
+ #pragma warning disable SA1600 // Elements should be documented
+ public enum CodePage
+ {
+ PC437_USA_STANDARD_EUROPE_DEFAULT = 0,
+ KATAKANA = 1,
+ PC850_MULTILINGUAL = 2,
+ PC860_PORTUGUESE = 3,
+ PC863_CANADIAN_FRENCH = 4,
+ PC865_NORDIC = 5,
+ HIRAGANA = 6,
+ ONE_PASS_KANJI = 7,
+ ONE_PASS_KANJI2 = 8,
+ PC851_GREEK = 11,
+ PC853_TURKISH = 12,
+ PC857_TURKISH = 13,
+ PC737_GREEK = 14,
+ ISO8859_7_GREEK = 15,
+ WPC1252 = 16,
+ PC866_CYRILLIC2 = 17,
+ PC852_LATIN2 = 18,
+ PC858_EURO = 19,
+ KU42_THAI = 20,
+ TIS11_THAI = 21,
+ TIS13_THAI = 22,
+ TIS14_THAI = 23,
+ TIS16_THAI = 24,
+ TIS17_THAI = 25,
+ TIS18_THAI = 26,
+ TCVN3_VIETNAMESE_L = 30,
+ TCVN3_VIETNAMESE_U = 31,
+ PC720_ARABIC = 32,
+ WPC775_BALTIC_RIM = 33,
+ PC855_CYRILLIC = 34,
+ PC861_ICELANDIC = 35,
+ PC862_HEBREW = 36,
+ PC864_ARABIC = 37,
+ PC869_GREEK = 38,
+ ISO8859_2_LATIN2 = 39,
+ ISO8859_15_LATIN9 = 40,
+ PC1098_FARSI = 41,
+ PC1118_LITHUANIAN = 42,
+ PC1119_LITHUANIAN = 43,
+ PC1125_UKRANIAN = 44,
+ WPC1250_LATIN2 = 45,
+ WPC1251_CYRILLIC = 46,
+ WPC1253_GREEK = 47,
+ WPC1254_TURKISH = 48,
+ WPC1255_HEBREW = 49,
+ WPC1256_ARABIC = 50,
+ WPC1257_BALTIC_RIM = 51,
+ WPC1258_VIETNAMESE = 52,
+ KZ1048_KAZAKHSTAN = 53,
+ DEVANAGARI = 66,
+ BENGALI = 67,
+ TAMIL = 68,
+ TELUGU = 69,
+ ASSAMESE = 70,
+ ORIYA = 71,
+ KANNADA = 72,
+ MALAYALAM = 73,
+ GUJARATI = 74,
+ PUNJABI = 75,
+ MARATHI = 82,
+ }
+}
\ No newline at end of file
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/Color.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/Color.cs
new file mode 100644
index 0000000..ed0055b
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/Color.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace ESCPOS_NET.Emitters
+{
+ [Flags]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1025:Code should not contain multiple whitespace in a row", Justification = "Enums are easier to read if they have whitespace alignment.")]
+ public enum Color
+ {
+ Black = 0,
+ Red = 1
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/PrintStyle.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/PrintStyle.cs
new file mode 100644
index 0000000..ab2b921
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/Enums/PrintStyle.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace ESCPOS_NET.Emitters
+{
+ [Flags]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1025:Code should not contain multiple whitespace in a row", Justification = "Enums are easier to read if they have whitespace alignment.")]
+ public enum PrintStyle
+ {
+ None = 0,
+ FontB = 1,
+ Bold = 1 << 3,
+ DoubleHeight = 1 << 4,
+ DoubleWidth = 1 << 5,
+ Underline = 1 << 7,
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Emitters/ICommandEmitter.cs b/PointOfSale/Utilities/ESCPOS_NET/Emitters/ICommandEmitter.cs
new file mode 100644
index 0000000..2641326
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Emitters/ICommandEmitter.cs
@@ -0,0 +1,92 @@
+namespace ESCPOS_NET.Emitters
+{
+ public interface ICommandEmitter
+ {
+ /* Print Commands */
+ byte[] PrintLine(string line = null);
+
+ byte[] Print(string line);
+
+ byte[] FeedLines(int lineCount);
+
+ byte[] FeedLinesReverse(int lineCount);
+
+ byte[] FeedDots(int dotCount);
+
+ /* Line Spacing Commands */
+ byte[] ResetLineSpacing();
+
+ byte[] SetLineSpacingInDots(int dots);
+
+ /* Operational Commands */
+ byte[] Initialize();
+
+ byte[] Enable();
+
+ byte[] Disable();
+
+ /* Cash Drawer Commands */
+ byte[] CashDrawerOpenPin2(int pulseOnTimeMs = 120, int pulseOffTimeMs = 240);
+
+ byte[] CashDrawerOpenPin5(int pulseOnTimeMs = 120, int pulseOffTimeMs = 240);
+
+ /* Character Commands */
+ byte[] SetStyles(PrintStyle style);
+
+ byte[] LeftAlign();
+
+ byte[] RightAlign();
+
+ byte[] CenterAlign();
+
+ byte[] ReverseMode(bool activate);
+
+ byte[] RightCharacterSpacing(int spaceCount);
+
+ byte[] UpsideDownMode(bool activate);
+
+ byte[] CodePage(CodePage codePage);
+
+ byte[] Color(Color color);
+
+ /* Action Commands */
+ byte[] FullCut();
+
+ byte[] PartialCut();
+
+ byte[] FullCutAfterFeed(int lineCount);
+
+ byte[] PartialCutAfterFeed(int lineCount);
+
+ /* Image Commands */
+ byte[] SetImageDensity(bool isHiDPI);
+
+ byte[] BufferImage(byte[] image, int maxWidth, bool isLegacy = false, int color = 1);
+
+ byte[] WriteImageFromBuffer();
+
+ byte[] PrintImage(byte[] image, bool isHiDPI, bool isLegacy = false, int maxWidth = -1, int color = 1);
+
+ /* Status Commands */
+ byte[] EnableAutomaticStatusBack();
+
+ byte[] EnableAutomaticInkStatusBack();
+
+ /* Barcode Commands */
+ byte[] PrintBarcode(BarcodeType type, string barcode, BarcodeCode code = BarcodeCode.CODE_B);
+
+ byte[] PrintQRCode(string data, TwoDimensionCodeType type = TwoDimensionCodeType.QRCODE_MODEL2, Size2DCode size = Size2DCode.NORMAL, CorrectionLevel2DCode correction = CorrectionLevel2DCode.PERCENT_7);
+
+ byte[] Print2DCode(TwoDimensionCodeType type, string data, Size2DCode size = Size2DCode.NORMAL, CorrectionLevel2DCode correction = CorrectionLevel2DCode.PERCENT_7);
+
+ byte[] SetBarcodeHeightInDots(int height);
+
+ byte[] SetBarWidth(BarWidth width);
+
+ byte[] SetBarLabelPosition(BarLabelPrintPosition position);
+
+ byte[] SetBarLabelFontB(bool fontB);
+
+ /* 2D-Code Commands */
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Extensions/ImageSharpExtensions.cs b/PointOfSale/Utilities/ESCPOS_NET/Extensions/ImageSharpExtensions.cs
new file mode 100644
index 0000000..a21cea9
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Extensions/ImageSharpExtensions.cs
@@ -0,0 +1,64 @@
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+
+namespace SixLabors.ImageSharp
+{
+ public static class ImageSharpExtensions
+ {
+ public static byte[] ToSingleBitPixelByteArray(this Image image, bool rasterFormat = true, int? maxWidth = null, int? maxHeight = null, float threshold = 0.5F)
+ {
+ image.Mutate(img =>
+ {
+ if (maxWidth.HasValue || maxHeight.HasValue)
+ {
+ img.Resize(new ResizeOptions
+ {
+ Mode = ResizeMode.Max,
+ Size = new Size(maxWidth ?? int.MaxValue, maxHeight ?? int.MaxValue),
+ });
+ }
+
+ img.BackgroundColor(Color.White);
+ img.Grayscale().BinaryDither(KnownDitherings.Stucki);
+
+ if (!rasterFormat)
+ {
+ img.Rotate(RotateMode.Rotate90);
+ img.Flip(FlipMode.Horizontal);
+ }
+ });
+
+ var result = image.ToSingleBitPixelByteArray();
+ return result;
+ }
+
+ private static byte[] ToSingleBitPixelByteArray(this Image image)
+ {
+ var bytesPerRow = (image.Width + 7 & -8) / 8;
+
+ var result = new byte[bytesPerRow * image.Height];
+ for (int y = 0; y < image.Height; y++)
+ {
+ var row = image.GetPixelRowSpan(y);
+ var rowStartPosition = y * bytesPerRow;
+
+ for (int x = 0; x < row.Length; x++)
+ {
+ if (!row[x].IsBlack())
+ {
+ continue;
+ }
+
+ result[rowStartPosition + (x / 8)] |= (byte)(0x01 << (7 - (x % 8)));
+ }
+ }
+
+ return result;
+ }
+
+ private static bool IsBlack(this Rgba32 rgba)
+ {
+ return rgba.R == 0 && rgba.G == 0 && rgba.B == 0;
+ }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Extensions/TaskExtensions.cs b/PointOfSale/Utilities/ESCPOS_NET/Extensions/TaskExtensions.cs
new file mode 100644
index 0000000..230af68
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Extensions/TaskExtensions.cs
@@ -0,0 +1,24 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ESCPOS_NET.Extensions
+{
+ public static class TaskExtensions
+ {
+ public static void LogExceptions(this Task task)
+ {
+ task.ContinueWith(t =>
+ {
+ var aggException = t.Exception.Flatten();
+ foreach (var exception in aggException.InnerExceptions)
+ {
+ Logging.Logger?.LogError(exception, "Uncaught Thread Exception.");
+ }
+ },
+ TaskContinuationOptions.OnlyOnFaulted);
+ }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Extensions/Util.cs b/PointOfSale/Utilities/ESCPOS_NET/Extensions/Util.cs
new file mode 100644
index 0000000..c1d7f1c
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Extensions/Util.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+
+namespace ESCPOS_NET.Extensions
+{
+ public static class Util
+ {
+ ///
+ /// Adds to the list of T items and/or enumerables of T. All other types will be ignored and not added to the list.
+ ///
+ /// List's items type.
+ /// List to be added the items.
+ /// Items to be added.
+ /// True if no item was ignored, otherwise False.
+ public static bool AddRange(this List list, params object[] items)
+ {
+ bool ignoredItems = false;
+ foreach (var item in items)
+ {
+ if (item is T itemT)
+ {
+ list.Add(itemT);
+ }
+ else if (item is IEnumerable arrayT)
+ {
+ list.AddRange(arrayT);
+ }
+ else
+ {
+ ignoredItems = true;
+ }
+ }
+
+ return !ignoredItems;
+ }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/GlobalSuppressions.cs b/PointOfSale/Utilities/ESCPOS_NET/GlobalSuppressions.cs
new file mode 100644
index 0000000..b404a03
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/GlobalSuppressions.cs
@@ -0,0 +1,9 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1200:Using directives should be placed correctly", Justification = "Placing usings within namespaces is not default in VS or in generated code.")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Visual Studio by default recommends removing prefixed 'this'.")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:Field names should not begin with underscore", Justification = "MS style guide uses _ for private members.")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1025:Code should not contain multiple whitespace in a row", Justification = "", Scope = "type", Target = "~T:ESCPOS_NET.Emitters.CodePage")]
diff --git a/PointOfSale/Utilities/ESCPOS_NET/LICENSE b/PointOfSale/Utilities/ESCPOS_NET/LICENSE
new file mode 100644
index 0000000..5631092
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Luke Paireepinart
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Logging/BaseLogger.cs b/PointOfSale/Utilities/ESCPOS_NET/Logging/BaseLogger.cs
new file mode 100644
index 0000000..a7ce46c
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Logging/BaseLogger.cs
@@ -0,0 +1,19 @@
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+
+namespace ESCPOS_NET
+{
+ public static class Logging
+ {
+ public static ILogger Logger { get; set; }
+ static Logging()
+ {
+
+ TaskScheduler.UnobservedTaskException += (s, e) =>
+ {
+ //LogUnhandledException(e.Exception, "TaskScheduler.UnobservedTaskException");
+ e.SetObserved();
+ };
+ }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Printers/BasePrinter.cs b/PointOfSale/Utilities/ESCPOS_NET/Printers/BasePrinter.cs
new file mode 100644
index 0000000..c5d3386
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Printers/BasePrinter.cs
@@ -0,0 +1,378 @@
+using ESCPOS_NET.Emitters.BaseCommandValues;
+using ESCPOS_NET.Utilities;
+using ESCPOS_NET.Utils;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Timers;
+
+namespace ESCPOS_NET
+{
+ public abstract partial class BasePrinter : IPrinter, IDisposable
+ {
+ private bool disposed = false;
+
+ //private volatile bool _isMonitoring;
+
+ private CancellationTokenSource _readCancellationTokenSource;
+ private CancellationTokenSource _writeCancellationTokenSource;
+
+ private readonly int _maxBytesPerWrite = 15000; // max byte chunks to write at once.
+
+ public PrinterStatusEventArgs Status { get; private set; } = new PrinterStatusEventArgs();
+
+ public event EventHandler StatusChanged;
+ public event EventHandler Disconnected;
+ public event EventHandler Connected;
+ //public event EventHandler WriteFailed;
+ //public event EventHandler Idle;
+
+ protected BinaryWriter Writer { get; set; }
+
+ protected BinaryReader Reader { get; set; }
+
+ protected ConcurrentQueue ReadBuffer { get; set; } = new ConcurrentQueue();
+
+ protected ConcurrentQueue WriteBuffer { get; set; } = new ConcurrentQueue();
+
+ protected int BytesWrittenSinceLastFlush { get; set; } = 0;
+
+ protected volatile bool IsConnected = true;
+
+ public string PrinterName { get; protected set; }
+
+ protected BasePrinter()
+ {
+ PrinterName = Guid.NewGuid().ToString();
+ Init();
+ }
+ protected BasePrinter(string printerName)
+ {
+ if (string.IsNullOrEmpty(printerName))
+ {
+ printerName = Guid.NewGuid().ToString();
+ }
+ PrinterName = printerName;
+ Init();
+ }
+ private void Init()
+ {
+ _readCancellationTokenSource = new CancellationTokenSource();
+ _writeCancellationTokenSource = new CancellationTokenSource();
+ Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Initializing Task Threads...", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ //Task.Factory.StartNew(MonitorPrinterStatusLongRunningTask, _connectivityCancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false);
+ Task.Factory.StartNew(WriteLongRunningTask, _writeCancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false);
+ Task.Factory.StartNew(ReadLongRunningTask, _readCancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false);
+ // TODO: read and status monitoring probably won't work for fileprinter, should let printer types disable this feature.
+ Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Task Threads started", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ }
+
+
+ protected void InvokeConnect()
+ {
+ Task.Run(() => Connected?.Invoke(this, new ConnectionEventArgs() { IsConnected = true }));
+ }
+ protected void InvokeDisconnect()
+ {
+ Task.Run(() => Disconnected?.Invoke(this, new ConnectionEventArgs() { IsConnected = false }));
+ }
+
+ protected virtual void Reconnect()
+ {
+ // Implemented in the network printer
+ }
+ protected virtual async void WriteLongRunningTask()
+ {
+ while (true)
+ {
+ if (_writeCancellationTokenSource != null && _writeCancellationTokenSource.IsCancellationRequested)
+ {
+ Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Write Long-Running Task Cancellation was requested.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ break;
+ }
+
+ await Task.Delay(100);
+ if (!IsConnected)
+ {
+ continue;
+ }
+
+ try
+ {
+ var didDequeue = WriteBuffer.TryDequeue(out var nextBytes);
+ if (didDequeue && nextBytes?.Length > 0)
+ {
+ WriteToBinaryWriter(nextBytes);
+ }
+ }
+ catch (IOException ex)
+ {
+ // Thrown if the printer times out the socket connection
+ // default is 90 seconds
+ //Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Swallowing IOException... sometimes happens with network printers. Should get reconnected automatically.");
+ }
+ catch (Exception ex)
+ {
+ // Swallow the exception
+ //Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Swallowing generic read exception... sometimes happens with serial port printers.");
+ }
+ }
+ }
+
+ protected virtual async void ReadLongRunningTask()
+ {
+ while (true)
+ {
+ if (_readCancellationTokenSource != null && _readCancellationTokenSource.IsCancellationRequested)
+ {
+ Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Read Long-Running Task Cancellation was requested.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ break;
+ }
+
+ await Task.Delay(100);
+
+ if (Reader == null) continue;
+ if (!IsConnected) continue;
+
+ try
+ {
+ // Sometimes the serial port lib will throw an exception and read past the end of the queue if a
+ // status changes while data is being written. We just ignore these bytes.
+ var b = Reader.BaseStream.ReadByte();
+ if (b >= 0 && b <= 255)
+ {
+ ReadBuffer.Enqueue((byte)b);
+ DataAvailable();
+ }
+ }
+
+ catch (Exception ex)
+ {
+ // Swallow the exception
+ //Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Swallowing generic read exception... sometimes happens with serial port printers.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ }
+ }
+ }
+
+ public virtual void Write(params byte[][] arrays)
+ {
+ Write(ByteSplicer.Combine(arrays));
+ }
+
+ public virtual void Write(byte[] bytes)
+ {
+ WriteBuffer.Enqueue(bytes);
+ }
+
+ protected virtual void WriteToBinaryWriter(byte[] bytes)
+ {
+
+ if (!IsConnected)
+ {
+ Logging.Logger?.LogInformation("[{Function}]:[{PrinterName}] Attempted to write but printer isn't connected. Attempting to reconnect...", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ Reconnect();
+ }
+
+ if (!IsConnected)
+ {
+ Logging.Logger?.LogError("[{Function}]:[{PrinterName}] Unrecoverable connectivity error writing to printer.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ throw new IOException("Unrecoverable connectivity error writing to printer.");
+ }
+
+ int bytePointer = 0;
+ int bytesLeft = bytes.Length;
+ bool hasFlushed = false;
+ while (bytesLeft > 0)
+ {
+
+ int count = Math.Min(_maxBytesPerWrite, bytesLeft);
+ try
+ {
+ Writer.Write(bytes, bytePointer, count);
+ }
+ catch (IOException e)
+ {
+ Reconnect();
+ if (!IsConnected)
+ {
+ Logging.Logger?.LogError(e, "[{Function}]:[{PrinterName}] Unrecoverable connectivity error writing to printer.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ }
+ Writer.Write(bytes, bytePointer, count);
+ }
+ BytesWrittenSinceLastFlush += count;
+ if (BytesWrittenSinceLastFlush >= 200)
+ {
+ // Immediately trigger a flush before proceeding so the output buffer will not be delayed.
+ hasFlushed = true;
+ Flush(null, null);
+ }
+
+ bytePointer += count;
+ bytesLeft -= count;
+ }
+
+ if (!hasFlushed)
+ {
+ Task.Run(async () => { await Task.Delay(50); Flush(null, null); });
+ }
+ }
+
+ protected virtual void Flush(object sender, ElapsedEventArgs e)
+ {
+ try
+ {
+
+ BytesWrittenSinceLastFlush = 0;
+ Writer.Flush();
+ }
+ catch (Exception ex)
+ {
+ Logging.Logger?.LogError(ex, "[{Function}]:[{PrinterName}] Flush threw exception.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ }
+ }
+
+ public virtual void DataAvailable()
+ {
+ if (ReadBuffer.Count() % 4 == 0)
+ {
+ var bytes = new byte[4];
+ for (int i = 0; i < 4; i++)
+ {
+ if (!ReadBuffer.TryDequeue(out bytes[i]))
+ {
+ // Ran out of bytes unexpectedly.
+ return;
+ }
+ }
+
+ TryUpdatePrinterStatus(bytes);
+
+ // TODO: call other update handlers.
+ }
+ }
+
+ private void TryUpdatePrinterStatus(byte[] bytes)
+ {
+ var bytesToString = BitConverter.ToString(bytes);
+
+ Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] TryUpdatePrinterStatus: Received flag values {bytesToString}", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName, bytesToString);
+
+ // Check header bits 0, 1 and 7 are 0, and 4 is 1
+ if (bytes[0].IsBitNotSet(0) && bytes[0].IsBitNotSet(1) && bytes[0].IsBitSet(4) && bytes[0].IsBitNotSet(7))
+ {
+ Status = new PrinterStatusEventArgs()
+ {
+ // byte[0] == 20 cash drawer closed
+ // byte[0] == 16 cash drawer open
+ // Note some cash drawers do not close properly.
+ IsCashDrawerOpen = bytes[0].IsBitNotSet(2),
+ IsPrinterOnline = bytes[0].IsBitNotSet(3),
+ IsCoverOpen = bytes[0].IsBitSet(5),
+ IsPaperCurrentlyFeeding = bytes[0].IsBitSet(6),
+ IsWaitingForOnlineRecovery = bytes[1].IsBitSet(0),
+ IsPaperFeedButtonPushed = bytes[1].IsBitSet(1),
+ DidRecoverableNonAutocutterErrorOccur = bytes[1].IsBitSet(2),
+ DidAutocutterErrorOccur = bytes[1].IsBitSet(3),
+ DidUnrecoverableErrorOccur = bytes[1].IsBitSet(5),
+ DidRecoverableErrorOccur = bytes[1].IsBitSet(6),
+ IsPaperLow = bytes[2].IsBitSet(0) && bytes[2].IsBitSet(1),
+ IsPaperOut = bytes[2].IsBitSet(2) && bytes[2].IsBitSet(3),
+ };
+ }
+
+ if (StatusChanged != null)
+ {
+ Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Invoking Status Changed Event Handler...", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ StatusChanged?.Invoke(this, Status);
+ }
+ }
+
+ ~BasePrinter()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void OverridableDispose() // This method should only be called by the Dispose method. // It allows synchronous disposing of derived class dependencies with base class disposes.
+ {
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ try
+ {
+ _readCancellationTokenSource?.Cancel();
+ }
+ catch (Exception e)
+ {
+ Logging.Logger?.LogDebug(e, "[{Function}]:[{PrinterName}] Dispose Issue during cancellation token cancellation call.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ }
+ try
+ {
+ Reader?.Close();
+ }
+ catch (Exception e)
+ {
+ Logging.Logger?.LogDebug(e, "[{Function}]:[{PrinterName}] Dispose Issue closing reader.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ }
+ try
+ {
+ Reader?.Dispose();
+ }
+ catch (Exception e)
+ {
+ Logging.Logger?.LogDebug(e, "[{Function}]:[{PrinterName}] Dispose Issue disposing reader.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ }
+ try
+ {
+ Writer?.Close();
+ }
+ catch (Exception e)
+ {
+ Logging.Logger?.LogDebug(e, "[{Function}]:[{PrinterName}] Dispose Issue closing writer.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ }
+ try
+ {
+ Writer?.Dispose();
+ }
+ catch (Exception e)
+ {
+ Logging.Logger?.LogDebug(e, "[{Function}]:[{PrinterName}] Dispose Issue disposing writer.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ }
+ try
+ {
+ OverridableDispose();
+ }
+ catch (Exception e)
+ {
+ Logging.Logger?.LogDebug(e, "[{Function}]:[{PrinterName}] Dispose Issue during overridable dispose.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName);
+ }
+ }
+
+ disposed = true;
+ }
+
+ public PrinterStatusEventArgs GetStatus()
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Printers/ConnectionEventArgs.cs b/PointOfSale/Utilities/ESCPOS_NET/Printers/ConnectionEventArgs.cs
new file mode 100644
index 0000000..3cfde19
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Printers/ConnectionEventArgs.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace ESCPOS_NET
+{
+ public class ConnectionEventArgs : EventArgs
+ {
+ public bool IsConnected;
+ }
+}
\ No newline at end of file
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Printers/FilePrinter.cs b/PointOfSale/Utilities/ESCPOS_NET/Printers/FilePrinter.cs
new file mode 100644
index 0000000..d49ad23
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Printers/FilePrinter.cs
@@ -0,0 +1,36 @@
+using System.IO;
+
+namespace ESCPOS_NET
+{
+ public class FilePrinter : BasePrinter
+ {
+ private readonly FileStream _file;
+
+ // TODO: default values to their default values in ctor.
+ public FilePrinter(string filePath, bool createIfNotExists = false)
+ : base()
+ {
+ if (createIfNotExists)
+ {
+ _file = File.Open(filePath, FileMode.OpenOrCreate);
+ }
+ else
+ {
+ _file = File.Open(filePath, FileMode.Open);
+ }
+ Writer = new BinaryWriter(_file);
+ Reader = new BinaryReader(_file);
+ }
+
+ ~FilePrinter()
+ {
+ Dispose(false);
+ }
+
+ protected override void OverridableDispose()
+ {
+ _file?.Close();
+ _file?.Dispose();
+ }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Printers/IPrinter.cs b/PointOfSale/Utilities/ESCPOS_NET/Printers/IPrinter.cs
new file mode 100644
index 0000000..e51805d
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Printers/IPrinter.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace ESCPOS_NET
+{
+ public interface IPrinter
+ {
+ PrinterStatusEventArgs GetStatus();
+ void Write(params byte[][] arrays);
+ void Write(byte[] bytes);
+ event EventHandler StatusChanged;
+ event EventHandler Disconnected;
+ event EventHandler Connected;
+ //event EventHandler WriteFailed;
+ //event EventHandler Idle;
+ //event EventHandler IdleDisconnected; is this useful? to know that it disconnected because of idle? probably better to have this as info in disconnected event object instead.
+ }
+}
\ No newline at end of file
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Printers/MemoryPrinter.cs b/PointOfSale/Utilities/ESCPOS_NET/Printers/MemoryPrinter.cs
new file mode 100644
index 0000000..11060f1
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Printers/MemoryPrinter.cs
@@ -0,0 +1,33 @@
+using System.IO;
+
+namespace ESCPOS_NET
+{
+ public class MemoryPrinter : BasePrinter
+ {
+ private readonly MemoryStream _ms;
+
+ // TODO: default values to their default values in ctor.
+ public MemoryPrinter()
+ : base()
+ {
+ _ms = new MemoryStream();
+ Writer = new BinaryWriter(_ms);
+ }
+
+ ~MemoryPrinter()
+ {
+ Dispose(false);
+ }
+
+ public byte[] GetAllData()
+ {
+ return _ms.ToArray();
+ }
+
+ protected override void OverridableDispose()
+ {
+ _ms?.Close();
+ _ms?.Dispose();
+ }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Printers/NetworkPrinter.cs b/PointOfSale/Utilities/ESCPOS_NET/Printers/NetworkPrinter.cs
new file mode 100644
index 0000000..4d9ca19
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Printers/NetworkPrinter.cs
@@ -0,0 +1,101 @@
+using Microsoft.Extensions.Logging;
+using SimpleTcp;
+using System;
+using System.IO;
+using System.Net;
+using System.Text.Json;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Reflection;
+
+namespace ESCPOS_NET
+{
+ public class NetworkPrinterSettings
+ {
+ // Connection string is of the form printer_name:port or ip:port
+ public string ConnectionString { get; set; }
+ public EventHandler ConnectedHandler { get; set; }
+ public EventHandler DisconnectedHandler { get; set; }
+ //public bool ReconnectOnTimeout { get; set; }
+ //public uint? ReceiveTimeoutMs { get; set; }
+ //public uint? SendTimeoutMs { get; set; }
+ //public uint? ConnectTimeoutMs { get; set; }
+ //public uint? ReconnectTimeoutMs { get; set; }
+ //public uint? MaxReconnectAttempts { get; set; }
+ public string PrinterName { get; set; }
+ }
+ public class NetworkPrinter : BasePrinter
+ {
+ private readonly NetworkPrinterSettings _settings;
+ private TCPConnection _tcpConnection;
+
+ public NetworkPrinter(NetworkPrinterSettings settings) : base(settings.PrinterName)
+ {
+ _settings = settings;
+ if (settings.ConnectedHandler != null)
+ {
+ Connected += settings.ConnectedHandler;
+ }
+ if (settings.DisconnectedHandler != null)
+ {
+ Disconnected += settings.DisconnectedHandler;
+ }
+ Connect();
+ }
+
+ private void ConnectedEvent(object sender, ClientConnectedEventArgs e)
+ {
+ Logging.Logger?.LogInformation("[{Function}]:[{PrinterName}] Connected successfully to network printer! Connection String: {ConnectionString}", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName, _settings.ConnectionString);
+ IsConnected = true;
+ InvokeConnect();
+ }
+ private void DisconnectedEvent(object sender, ClientDisconnectedEventArgs e)
+ {
+ IsConnected = false;
+ InvokeDisconnect();
+ Logging.Logger?.LogWarning("[{Function}]:[{PrinterName}] Network printer connection terminated. Attempting to reconnect. Connection String: {ConnectionString}", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName, _settings.ConnectionString);
+ Connect();
+ }
+ private void AttemptReconnectInfinitely()
+ {
+ try
+ {
+ //_tcpConnection.ConnectWithRetries(300000);
+ _tcpConnection.ConnectWithRetries(3000);
+ }
+ catch (Exception e)
+ {
+ //Logging.Logger?.LogWarning("[{Function}]:[{PrinterName}] Network printer unable to connect after 5 minutes. Attempting to reconnect. Settings: {Settings}", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName, JsonSerializer.Serialize(_settings));
+ Task.Run(async () => { await Task.Delay(250); Connect(); });
+ }
+ }
+
+ private void Connect()
+ {
+ if (_tcpConnection != null)
+ {
+ _tcpConnection.Connected -= ConnectedEvent;
+ _tcpConnection.Disconnected -= DisconnectedEvent;
+ }
+
+ // instantiate
+ _tcpConnection = new TCPConnection(_settings.ConnectionString);
+
+ // set events
+ _tcpConnection.Connected += ConnectedEvent;
+ _tcpConnection.Disconnected += DisconnectedEvent;
+
+ Reader = new BinaryReader(_tcpConnection.ReadStream);
+ Writer = new BinaryWriter(_tcpConnection.WriteStream);
+
+ Task.Run(() => { AttemptReconnectInfinitely(); });
+ }
+
+ protected override void OverridableDispose()
+ {
+ _tcpConnection = null;
+ }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Printers/PrinterOptions.cs b/PointOfSale/Utilities/ESCPOS_NET/Printers/PrinterOptions.cs
new file mode 100644
index 0000000..dbac2be
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Printers/PrinterOptions.cs
@@ -0,0 +1,8 @@
+namespace ESCPOS_NET
+{
+ public class PrinterOptions
+ {
+ int IdleTimeoutSeconds { get; set; } = 60;
+ int StatusPollingIntervalMilliseconds { get; set; } = 500;
+ }
+}
\ No newline at end of file
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Printers/PrinterStatusEventArgs.cs b/PointOfSale/Utilities/ESCPOS_NET/Printers/PrinterStatusEventArgs.cs
new file mode 100644
index 0000000..2e3148e
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Printers/PrinterStatusEventArgs.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace ESCPOS_NET
+{
+ public class PrinterStatusEventArgs : EventArgs
+ {
+ public bool? IsWaitingForOnlineRecovery { get; set; }
+
+ public bool? IsPaperCurrentlyFeeding { get; set; }
+
+ public bool? IsPaperFeedButtonPushed { get; set; }
+
+ public bool? IsPrinterOnline { get; set; }
+
+ public bool? IsCashDrawerOpen { get; set; }
+
+ public bool? IsCoverOpen { get; set; }
+
+ public bool? IsPaperLow { get; set; }
+
+ public bool? IsPaperOut { get; set; }
+
+ public bool? IsInErrorState => (DidRecoverableErrorOccur ?? false) || (DidUnrecoverableErrorOccur ?? false) || (DidAutocutterErrorOccur ?? false) || (DidRecoverableNonAutocutterErrorOccur ?? false);
+
+ public bool? DidRecoverableErrorOccur { get; set; }
+
+ public bool? DidUnrecoverableErrorOccur { get; set; }
+
+ public bool? DidAutocutterErrorOccur { get; set; }
+
+ public bool? DidRecoverableNonAutocutterErrorOccur { get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Printers/SerialPrinter.cs b/PointOfSale/Utilities/ESCPOS_NET/Printers/SerialPrinter.cs
new file mode 100644
index 0000000..39a0b42
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Printers/SerialPrinter.cs
@@ -0,0 +1,32 @@
+using System.IO;
+using System.IO.Ports;
+using System.Threading.Tasks;
+
+namespace ESCPOS_NET
+{
+ public class SerialPrinter : BasePrinter
+ {
+ private readonly SerialPort _serialPort;
+
+ public SerialPrinter(string portName, int baudRate)
+ : base()
+ {
+ _serialPort = new SerialPort(portName, baudRate);
+ _serialPort.Open();
+ Writer = new BinaryWriter(_serialPort.BaseStream);
+ Reader = new BinaryReader(_serialPort.BaseStream);
+ }
+
+ protected override void OverridableDispose()
+ {
+ _serialPort?.Close();
+ _serialPort?.Dispose();
+ Task.Delay(250).Wait(); // Based on MSDN Documentation, should sleep after calling Close or some functionality will not be determinant.
+ }
+
+ public void Disable()
+ {
+ OverridableDispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Printers/VirtualPrinter.cs b/PointOfSale/Utilities/ESCPOS_NET/Printers/VirtualPrinter.cs
new file mode 100644
index 0000000..c7e692e
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Printers/VirtualPrinter.cs
@@ -0,0 +1,9 @@
+// TODO: Implement this class
+
+namespace ESCPOS_NET
+{
+ public class VirtualPrinter
+ {
+ public byte[] Output { get; private set; }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Utils/Consts.cs b/PointOfSale/Utilities/ESCPOS_NET/Utils/Consts.cs
new file mode 100644
index 0000000..595fa75
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Utils/Consts.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ESCPOS_NET.Utils
+{
+ public static class Consts
+ {
+ public static readonly string LIBNAME = "ESCPOS_NET";
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Utils/EchoStream.cs b/PointOfSale/Utilities/ESCPOS_NET/Utils/EchoStream.cs
new file mode 100644
index 0000000..7013a12
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Utils/EchoStream.cs
@@ -0,0 +1,185 @@
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Collections.Concurrent;
+
+namespace ESCPOS_NET.Utils
+{
+
+ public class EchoStream : Stream
+ {
+ public override bool CanTimeout { get; } = true;
+ public override int ReadTimeout { get; set; } = Timeout.Infinite;
+ public override int WriteTimeout { get; set; } = Timeout.Infinite;
+ public override bool CanRead { get; } = true;
+ public override bool CanSeek { get; } = false;
+ public override bool CanWrite { get; } = true;
+
+ public bool CopyBufferOnWrite { get; set; } = false;
+
+ private readonly object _lock = new object();
+
+ // Default underlying mechanism for BlockingCollection is ConcurrentQueue, which is what we want
+ private readonly BlockingCollection _Buffers;
+ private int _maxQueueDepth = 10;
+
+ private byte[] m_buffer = null;
+ private int m_offset = 0;
+ private int m_count = 0;
+
+ private bool m_Closed = false;
+ public override void Close()
+ {
+ m_Closed = true;
+
+ // release any waiting writes
+ _Buffers.CompleteAdding();
+ }
+
+ public bool DataAvailable
+ {
+ get
+ {
+ return _Buffers.Count > 0;
+ }
+ }
+
+ private long _Length = 0L;
+ public override long Length
+ {
+ get
+ {
+ return _Length;
+ }
+ }
+
+ private long _Position = 0L;
+ public override long Position
+ {
+ get
+ {
+ return _Position;
+ }
+ set
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public EchoStream() : this(10)
+ {
+ }
+
+ public EchoStream(int maxQueueDepth)
+ {
+ _maxQueueDepth = maxQueueDepth;
+ _Buffers = new BlockingCollection(_maxQueueDepth);
+ }
+
+ // we override the xxxxAsync functions because the default base class shares state between ReadAsync and WriteAsync, which causes a hang if both are called at once
+ public new Task WriteAsync(byte[] buffer, int offset, int count)
+ {
+ return Task.Run(() => Write(buffer, offset, count));
+ }
+
+ // we override the xxxxAsync functions because the default base class shares state between ReadAsync and WriteAsync, which causes a hang if both are called at once
+ public new Task ReadAsync(byte[] buffer, int offset, int count)
+ {
+ return Task.Run(() =>
+ {
+ return Read(buffer, offset, count);
+ });
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (m_Closed || buffer.Length - offset < count || count <= 0)
+ return;
+
+ byte[] newBuffer;
+ if (!CopyBufferOnWrite && offset == 0 && count == buffer.Length)
+ newBuffer = buffer;
+ else
+ {
+ newBuffer = new byte[count];
+ System.Buffer.BlockCopy(buffer, offset, newBuffer, 0, count);
+ }
+ if (!_Buffers.TryAdd(newBuffer, WriteTimeout))
+ throw new TimeoutException("EchoStream Write() Timeout");
+
+ _Length += count;
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (count == 0)
+ return 0;
+ lock (_lock)
+ {
+ if (m_count == 0 && _Buffers.Count == 0)
+ {
+ if (m_Closed)
+ return -1;
+
+ if (_Buffers.TryTake(out m_buffer, ReadTimeout))
+ {
+ m_offset = 0;
+ m_count = m_buffer.Length;
+ }
+ else
+ return m_Closed ? -1 : 0;
+ }
+
+ int returnBytes = 0;
+ while (count > 0)
+ {
+ if (m_count == 0)
+ {
+ if (_Buffers.TryTake(out m_buffer, 0))
+ {
+ m_offset = 0;
+ m_count = m_buffer.Length;
+ }
+ else
+ break;
+ }
+
+ var bytesToCopy = (count < m_count) ? count : m_count;
+ System.Buffer.BlockCopy(m_buffer, m_offset, buffer, offset, bytesToCopy);
+ m_offset += bytesToCopy;
+ m_count -= bytesToCopy;
+ offset += bytesToCopy;
+ count -= bytesToCopy;
+
+ returnBytes += bytesToCopy;
+ }
+
+ _Position += returnBytes;
+
+ return returnBytes;
+ }
+ }
+
+ public override int ReadByte()
+ {
+ byte[] returnValue = new byte[1];
+ return (Read(returnValue, 0, 1) <= 0 ? -1 : (int)returnValue[0]);
+ }
+
+ public override void Flush()
+ {
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Utils/InterceptableWriteMemoryStream.cs b/PointOfSale/Utilities/ESCPOS_NET/Utils/InterceptableWriteMemoryStream.cs
new file mode 100644
index 0000000..3d02a16
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Utils/InterceptableWriteMemoryStream.cs
@@ -0,0 +1,51 @@
+using System;
+using System.IO;
+
+namespace ESCPOS_NET
+{
+ public class InterceptableWriteMemoryStream : Stream
+ {
+ private MemoryStream _innerStream = new MemoryStream();
+ public override bool CanRead => _innerStream.CanRead;
+
+ public override bool CanSeek => _innerStream.CanSeek;
+
+ public override bool CanWrite => _innerStream.CanWrite;
+
+ public override long Length => _innerStream.Length;
+
+ public override long Position { get => _innerStream.Position; set => _innerStream.Position = value; }
+
+ private Action _writeAction { get; set; }
+ public InterceptableWriteMemoryStream(Action writeAction)
+ {
+ _writeAction = writeAction;
+ }
+ public override void Flush()
+ {
+ _innerStream.Flush();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return _innerStream.Read(buffer, offset, count);
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ return _innerStream.Seek(offset, origin);
+ }
+
+ public override void SetLength(long value)
+ {
+ _innerStream.SetLength(value);
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ var writeArray = new byte[count];
+ Buffer.BlockCopy(buffer, offset, writeArray, 0, count);
+ _writeAction(writeArray);
+ }
+ }
+}
diff --git a/PointOfSale/Utilities/ESCPOS_NET/Utils/TCPConnection.cs b/PointOfSale/Utilities/ESCPOS_NET/Utils/TCPConnection.cs
new file mode 100644
index 0000000..55c31f7
--- /dev/null
+++ b/PointOfSale/Utilities/ESCPOS_NET/Utils/TCPConnection.cs
@@ -0,0 +1,59 @@
+using ESCPOS_NET.Utils;
+using SimpleTcp;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace ESCPOS_NET
+{
+ public class TCPConnection
+ {
+ public Stream ReadStream { get; private set; } = new EchoStream();
+ public Stream WriteStream { get; private set; }
+ public event EventHandler Connected;
+ public event EventHandler Disconnected;
+ public bool IsConnected => _client?.IsConnected ?? false;
+ private SimpleTcpClient _client;
+ //public event EventHandler DataReceived;
+ public TCPConnection(string destination)
+ {
+ _client = new SimpleTcpClient(destination);
+ _client.Events.Connected += ConnectedEventHandler;
+ _client.Events.Disconnected += DisconnectedEventHandler;
+ _client.Events.DataReceived += DataReceivedEventHandler;
+ _client.Keepalive = new SimpleTcpKeepaliveSettings() { EnableTcpKeepAlives = true, TcpKeepAliveInterval = 1, TcpKeepAliveTime = 1, TcpKeepAliveRetryCount = 3 };
+ ReadStream.ReadTimeout = 1500;
+ WriteStream = new InterceptableWriteMemoryStream(bytes => _client.Send(bytes));
+ }
+ private void ConnectedEventHandler(object sender, ClientConnectedEventArgs e)
+ {
+ Connected?.Invoke(sender, e);
+ }
+ private void DisconnectedEventHandler(object sender, ClientDisconnectedEventArgs e)
+ {
+ Disconnected?.Invoke(sender, e);
+ }
+ private void DataReceivedEventHandler(object sender, DataReceivedEventArgs e)
+ {
+ ReadStream.Write(e.Data, 0, e.Data.Length);
+ }
+ public void ConnectWithRetries(int timeoutMs)
+ {
+ _client.ConnectWithRetries(timeoutMs);
+ }
+
+ ~TCPConnection()
+ {
+ try
+ {
+ _client.Events.DataReceived -= DataReceivedEventHandler;
+ _client.Events.Connected -= ConnectedEventHandler;
+ _client.Events.Disconnected -= DisconnectedEventHandler;
+ _client?.Dispose();
+ }
+ catch { }
+ }
+
+ }
+}
diff --git a/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM.zip b/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM.zip
new file mode 100644
index 0000000..2f2d667
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM.zip differ
diff --git a/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/APD6_Install_en_revC.pdf b/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/APD6_Install_en_revC.pdf
new file mode 100644
index 0000000..49dc34e
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/APD6_Install_en_revC.pdf differ
diff --git a/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/APD6_Printer_en_revA.pdf b/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/APD6_Printer_en_revA.pdf
new file mode 100644
index 0000000..f1c1c8c
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/APD6_Printer_en_revA.pdf differ
diff --git a/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/APD6_Spec_T20IIIL_en_revA.pdf b/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/APD6_Spec_T20IIIL_en_revA.pdf
new file mode 100644
index 0000000..9d598a6
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/APD6_Spec_T20IIIL_en_revA.pdf differ
diff --git a/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/APD_604_T20IIIL.exe b/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/APD_604_T20IIIL.exe
new file mode 100644
index 0000000..ecab169
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/APD_604_T20IIIL.exe differ
diff --git a/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/README_EN.TXT b/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/README_EN.TXT
new file mode 100644
index 0000000..4063745
--- /dev/null
+++ b/PointOfSale/Utilities/Epson printer drivers/APD_604_T20IIIL_WM/README_EN.TXT
@@ -0,0 +1,74 @@
+================================================================================
+EPSON Advanced Printer Driver 6 for TM-T20IIIL
+
+ Copyright (C) 2019-2021 Seiko Epson Corporation
+================================================================================
+
+1. About this software
+ This package includes the following software program to use with an EPSON TM series printer.
+
+ 1) Windows Printer Driver
+ This driver is a Windows Printer Driver for the TM printer.
+ This driver makes it possible to print from a Windows application.
+
+ 2) Supported OS
+ The driver operation has been supported with the following operating systems.
+
+ Windows 10
+ Windows 8.1
+ Windows 8
+ Windows 7 SP1
+ Windows Server 2019
+ Windows Server 2016
+ Windows Server 2012 R2
+ Windows Server 2012
+ Windows Server 2008 R2 SP1
+ Windows Server 2008 SP2
+ Windows Embedded 8.1 Industry
+ Windows Embedded Standard 7
+ Windows Embedded POSReady 7
+
+ 3) Supported Printers
+ EPSON TM-T20IIIL
+
+ The printer driver operates regardless of the pritner firmware version.
+
+
+2. Supplied Files
+ - APD_604_T20IIIL_WM.zip : Printer Driver installer with manuals
+ +--- APD_604_T20IIIL.exe : Installer
+ +--- APD6_Install_en_revC.pdf : Install Manual
+ +--- APD6_Printer_en_revA.pdf : Printer Manual
+ +--- APD6_Spec_T20IIIL_en_revA.pdf : Printer Specification
+ +--- README_EN.txt : This document
+
+ How to install the printer driver, refer to the install manual.
+
+
+3. Remarks
+ 1) For detailed information, please read each manual.
+
+ 2) When an error message is displayed during installation, the following procedure may enable to
+ recover from the error.
+ 1) Specify the uninstall option, and execute the installer "APD_604_T20IIIL.exe /uninstall".
+ 2) Remove the error factor.
+ 3) Execute the installer "APD_604_T20IIIL.exe".
+
+ 3) Please use the same version of EPSON Advanced Printer Driver for each host when a network printer
+ is shared among two or more hosts.
+
+ 4) Please install "StatusAPI for EPSON Advanced Printer Driver 6", if you would like to use "StatusAPI".
+
+
+4.Modification from the old version
+ Ver6.04
+ - The following bugs have been fixed.
+ - When printing half-width characters with multilingual fonts, it may not be possible to
+ print up to the maximum print width.
+
+ Ver6.01
+ - The following bugs have been fixed.
+ - Part of UI displayed during installation is garbled.
+
+ Ver6.00
+ - New release.
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b.zip b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b.zip
new file mode 100644
index 0000000..5763014
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b.zip differ
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/Setup.exe b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/Setup.exe
new file mode 100644
index 0000000..24a093e
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/Setup.exe differ
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/DPInst.exe b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/DPInst.exe
new file mode 100644
index 0000000..c853b9d
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/DPInst.exe differ
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/TMUSB64.inf b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/TMUSB64.inf
new file mode 100644
index 0000000..db64a81
--- /dev/null
+++ b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/TMUSB64.inf
@@ -0,0 +1,81 @@
+;
+; Installation inf for the EPSON USB Controller for TM/BA/EU Printers
+;
+; Copyright(C) SEIKO EPSON CORPORATION 1999-2019. All rights reserved.
+;
+
+;----------------------------------------------------------------------------
+[Version]
+Signature = "$Windows NT$"
+Class = USB
+ClassGUID = {36FC9E60-C465-11CF-8056-444553540000}
+Provider = %Mfg%
+DriverVer = 02/28/2019, 8.0.0.0
+CatalogFile = TMUSB64.CAT
+
+;----------------------------------------------------------------------------
+[DestinationDirs]
+; [DefaultDestDir=dirid[,subdir]]
+; [file-list-section=dirid[,subdir]] ...
+DefaultDestDir = 12
+NTCopyFiles = 12
+
+;----------------------------------------------------------------------------
+;
+[NTCopyFiles]
+TMUSB64.SYS
+
+;----------------------------------------------------------------------------
+[Manufacturer]
+; %manufacturer-name% = models-section-name
+%Mfg% = Models,NTamd64
+
+;----------------------------------------------------------------------------
+[ControlFlags]
+; ExcludeFromSelect=device-id-str[,device-id-str]
+ExcludeFromSelect=USB\VID_04B8&PID_0201,USB\VID_04B8&PID_0202
+
+;----------------------------------------------------------------------------
+[Models.NTamd64]
+; device-description = install-section-name, hw-id[, compatible-id...]
+%Model1% = TMUSB_Inst.NT, USB\VID_04B8&PID_0201 ; UB-UXX
+%Model2% = TMUSB_Inst.NT, USB\VID_04B8&PID_0202 ; UB-UXX
+
+;----------------------------------------------------------------------------
+; Win NT DDInstall Section
+;
+[TMUSB_Inst.NT]
+CopyFiles = NTCopyFiles
+
+[TMUSB_Inst.NT.Services]
+; AddService=ServiceName,[flags],service-install-section[,
+; event-log-install-section[,[EventLogType][,EventName]]]
+Addservice = TMUSB, 0x00000002, TMUSB_Service_Inst
+
+[TMUSB_Service_Inst]
+DisplayName = %TMUSB_Inst.SvcDesc%
+ServiceType = 1 ; SERVICE_KERNEL_DRIVER
+StartType = 3 ; SERVICE_DEMAND_START
+ErrorControl = 1 ; SERVICE_ERROR_NORMAL
+ServiceBinary = %12%\TMUSB64.SYS ; 12 = nt/System32/Drivers
+LoadOrderGroup = Base
+
+;----------------------------------------------------------------------------
+[SourceDisksNames.amd64]
+; diskid = disk-description[,[tag-or-cab-file],[unused,path][,flags]]
+1 = %TMUSBDisk%,,,
+
+;----------------------------------------------------------------------------
+[SourceDisksFiles.amd64]
+; filename = diskid[,[ subdir][, size]]
+TMUSB64.SYS = 1,,
+
+;----------------------------------------------------------------------------
+[Strings]
+; = ""
+Mfg = "EPSON"
+Model1 = "EPSON USB Controller for TM/BA/EU Printers"
+Model2 = "EPSON USB Controller for TM/BA/EU Printers"
+TMUSB_Inst.SvcDesc = "EPSON USB Device Driver for TM/BA/EU Printers"
+TMUSBDisk = "EPSON USB Device Driver for TM/BA/EU Printers Installation Disk"
+
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/TMUSB64.sys b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/TMUSB64.sys
new file mode 100644
index 0000000..2a8595b
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/TMUSB64.sys differ
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/dpinst.xml b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/dpinst.xml
new file mode 100644
index 0000000..008827b
--- /dev/null
+++ b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/dpinst.xml
@@ -0,0 +1,31 @@
+
+
+
+
+ EPSON TMUSB Driver Ver.8.00 Installer
+ EPSON TMUSB Driver Ver.8.00 の インストールウィザードの開始
+ このウィザードでは、EPSON TM/BA/EUシリーズ用のUSBデバイスドライバ(EPSON TMUSB Driver)のインストールを開始します。
+
+ ソフトウェア使用許諾契約
+ 使用許諾契約に同意します。(&A)
+ 使用許諾契約に同意しません。(&D)
+
+
+ EPSON TMUSB Driver Ver.8.00 をインストールしています...
+ EPSON TMUSB Driver Ver.8.00 のインストールに成功しました。
+
+
+
+ EPSON TMUSB Driver Ver.8.00 Installer
+ EPSON TMUSB Driver Ver.8.00 Installation Wizard
+ This wizard will walk you through updating the USB device driver (EPSON TMUSB Driver) for your EPSON TM/BA/EU Series.
+
+ Software License Agreement
+ I &accept this SLA
+ I do ¬ accept this SLA
+
+
+ EPSON TMUSB driver Ver.8.00 is now installing...
+ Completing EPSON TMUSB driver Ver.8.00 Installation Wizard
+
+
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/licenseE.txt b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/licenseE.txt
new file mode 100644
index 0000000..01eb77f
--- /dev/null
+++ b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/licenseE.txt
@@ -0,0 +1,67 @@
+Please read the following Software License Agreement ("SLA"). Use the scroll bar to view the entire SLA.
+----------------------------------------------------
+
+SOFTWARE LICENSE AGREEMENT
+
+IMPORTANT! READ THIS SOFTWARE LICENSE AGREEMENT CAREFULLY. The computer software product and/or data, including any accompanying explanatory written materials (the "Software") should only be installed or used by the Licensee ("you") on the condition you agree with SEIKO EPSON CORPORATION ("EPSON") to the terms and conditions set forth in this SLA. By installing or using the Software, you are representing to agree all the terms and conditions set forth in this SLA. You should read this SLA carefully before installing or using the Software. If you do not agree with the terms and conditions of this SLA, you are not permitted to install or use the Software.
+
+If you agree to and accept all the terms and conditions of this SLA, EPSON and its suppliers grant to you a nonexclusive license to use the Software in accordance with the following conditions:
+
+1. Use of the Software
+(a) You may use the Software for the purpose of development and execution of your application software which operates the EPSON hardware products designated in a text file included in the Software. Your application software to be used with the Software shall be referred to as the "Application Software."
+(b) You may use the Software by installing to computers which you intend to use directly or via network; provided, however, that the Software shall be used only on a computer in which operating systems as designated in a text file included in the Software has been installed.
+(c) You may distribute the Software as a portion of the Application Software to a third party by the following way; provided, that you ensure that you will first obtain the agreement of the third party under your own license agreement which complies with the terms and conditions of this SLA. You agree not to distribute the Software on a standalone.
+(i) By storage medium, including, but not limited to, CD-ROM
+(ii) You allow a third party to download from a server which is under your control
+(iii) You sell your hardware products pre-installed the Application Software
+
+2. Restrictions on Usage
+(a) You may not copy the Software, except for one copy for backup purposes.
+(b) You may not to attempt to reverse engineer, decompile, disassemble or modify the Software.
+(c) You may not share, rent, lease, encumber or lend the Software.
+(d) You acknowledge that the Software (including manuals thereof) contains valuable, confidential information and trade secrets, that unauthorized use and copying are harmful to EPSON and its suppliers, and that you have a confidentiality obligation as to such valuable information and trade secrets.
+(e) You may not to use the Software in any manner other than expressly provided herein.
+
+3. Prohibition on Transfer
+You agree not to assign, share, rent, lend, lease or otherwise transfer the Software itself or your right to use the Software.
+
+4. Copyright
+ Title, ownership rights, and intellectual property rights in and to the Software and any copies thereof shall remain with EPSON or its suppliers. There is no transfer to you of any title to or ownership of the Software and this License shall not be construed as a sale of any rights in the Software. The Software is protected by Japanese Copyright Law and international copyright treaties, as well as other intellectual property laws and treaties. Except as otherwise provided in this SLA, you may not copy the Software. You also agree not to remove or alter any copyright and other proprietary notices on any copies of the Software.
+
+5. Limited Warranty
+(a) In case of that you obtained the Software by media from EPSON or a dealer, EPSON warrants that the media on which the Software is recorded will be free from defects in workmanship and materials under normal use for a period of 90 days from the date of delivery to you. If the media is returned to EPSON or the dealer from which the media was obtained within 90 days of the date of delivery to you, and if EPSON determines the media to be defective and provided the media was not subject to misuse, abuse, misapplication or use in defective equipment, EPSON will replace the media, upon your return to EPSON of the Software, including all copies of any portions thereof. ALL IMPLIED WARRANTIES ON THE MEDIA, INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE LIMITED TO THE DURATION OF THE EXPRESS WARRANTY SET FORTH ABOVE.
+(b) You acknowledge and agree that the use of the Software is at your sole risk. THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY WARRANTY OF ANY KIND. EPSON AND ITS SUPPLIERS DO NOT AND CANNOT WARRANT THE PERFORMANCE OR RESULTS YOU MAY OBTAIN BY USING THE SOFTWARE. THE FOREGOING STATES THE SOLE AND EXCLUSIVE REMEDIES FOR EPSON'S AND ITS SUPPLIERS' BREACH OF WARRANTY. EXCEPT FOR THE FOREGOING LIMITED WARRANTY, EPSON AND ITS SUPPLIERS MAKE NO WARRANTIES, EXPRESS OR IMPLIED, AS TO NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE. Some states or jurisdictions do not allow the exclusion of implied warranties or limitations on how long an implied warranty may last, so the above limitations may not apply to you. This warranty gives you specific legal rights. You may have other rights which vary from state to state or jurisdiction to jurisdiction.
+(c) IN NO EVENT WILL EPSON OR ITS SUPPLIERS BE LIABLE TO YOU, WHETHER ARISING UNDER CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, BREACH OR WARRANTY, MISREPRESENTATION OR OTHERWISE, FOR ANY DIRECT, CONSEQUENTIAL, INCIDENTAL OR SPECIAL DAMAGES, INCLUDING ANY LOST PROFITS OR LOST SAVINGS, EVEN IF EPSON, ITS SUPPLIERS OR ANY REPRESENTATIVE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, OR FOR ANY CLAIM BY ANY THIRD PARTY. Some states or jurisdictions, however, do not allow the exclusion or limitation of incidental, consequential or special damages, so the above limitations may not apply to you.
+(d) EPSON'S AGGREGATE LIABILITY TO YOU WITH RESPECT TO ANY SUBJECT MATTER OF THIS SLA WHETHER ARISING UNDER TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, BREACH OF WARRANTY, OR MISREPRESENTATION SHALL NOT EXCEED THE AMOUNTS PAID BY YOU TO EPSON TO USE THE SOFTWARE.
+(e) You are solely responsible for use and distribution of the Software and the Application Software and/or provision of support to your customers of such Software and Application Software.
+
+6. Termination
+This SLA shall automatically terminate when:
+(a) You stop to use of the Software.
+(b) You violate any of the terms or conditions set forth in this SLA.
+SLA shall automatically terminate upon failure by you to comply with its terms. You may also terminate this SLA at any time by uninstalling and destroying the Software and all copies thereof.
+
+7. Obligations after Termination
+Upon termination of this SLA, you agree to stop using the Software and delete the Software from the computers described in Article 1(b) above and return the original Software and all copies thereof to EPSON or destroy such Software and copies thereof so that you and a third party cannot use it, and then discard thosecopies.
+
+8. Indemnification by You
+You agree to fully defend, indemnify and hold harmless EPSON and its suppliers against all damages, loss, costs and/or expenses (including reasonable attorney's fees, expert witness fees and other related expenses) arising out of and/or resulting from any claim, proceeding or dispute in connection with the Software, Application Software and/or your failure to comply with any provision in this SLA.
+
+9. Others
+(a) EPSON may change the specification of the Software without notice to you.
+(b) EPSON has no obligation to provide you with any updates, new versions or releases of the Software.
+(c) You may not export the Software except as permitted by the applicable laws and/or regulations of Japan, or the laws and/or regulations of the jurisdiction in which the Software is obtained.
+(d) This SLA shall be governed and construed under by the laws of Japan (without giving effect to its conflicts of laws provisions). The Tokyo District Court shall have exclusive jurisdiction over this SLA and the parties thereto. If any provision in this SLA is ruled invalid by a court of competent jurisdiction, the remaining provisions shall remain in full force and effect.
+(e) If you are acquiring the Software on behalf of any unit or agency of the United States Government, the following provisions apply. The Government agrees:
+ (i) if the Software is supplied to the Department of Defense (DoD), the Software is classified as "Commercial Computer Software" and the Government is acquiring only "restricted rights" in the Software and its documentation as that term is defined in Clause 252.227-7013(c)(1) of the DFARS; and (ii) if the Software is supplied to any unit or agency of the United States Government other than DoD, the Government's rights in the Software and its documentation will be as defined in Clause 52.227-19(c)(2) of the FAR or, in the case of NASA, in Clause 18-52.227-86(d) of the NASA supplement to the FAR.
+
+
+
+Do you agree with and accept all the terms and conditions of this Software License Agreement and wish to continue installing?
+If you do so agree and accept, choose " I accept this SLA".
+If you do not so agree and accept, choose " I do not accept this SLA".
+
+
+Copyright (C) SEIKO EPSON CORPORATION 2000-2007.
+All rights reserved.
+
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/licenseJ.txt b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/licenseJ.txt
new file mode 100644
index 0000000..7d52490
--- /dev/null
+++ b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/licenseJ.txt
@@ -0,0 +1,70 @@
+\tgEFAgp_
+
+̃\tgEFAgpOɖ{\tgEFAgp_iȉu{_vƂ܂jTdɂǂ݂B̃\tgEFACXg[AȂ̕@ŎgpꂽꍇA{_ׂ̂Ă̏ɍS]ƂɓӂƂ݂Ȃ܂B{_ɓӂłȂꍇ́Ã\tgEFA̎gpB
+
+ZCR[Gv\ЁiȉAuЁvƂ܂jт̃TvC[́AqlɑẴ\tgEFAiȉAu{\tgEFAvƂ܂jL̏ɊÂgpƐIȖ̌܂B
+
+L
+P. gp
+(a) qĺA{\tgEFAɊ܂܂eLXgt@CŎw肷铖Ѓn[hEFAi𐧌䂷邨ql̃AvP[V\tgEFAiȉAu{AvP[V\tgEFAvƂ܂j쐬юsړÎ߂ɂ̂݁A{\tgEFAgp邱Ƃł܂B
+(b) qĺA{\tgEFAqlgp镡̃Rs[^A܂͂ql̊Ǘlbg[Nɐڑꂽ̃Rs[^ɃCXg[Ďgp邱Ƃł܂B̏ꍇAql͖{\tgEFAɊ܂܂eLXgt@CŎw肷Iy[eBOVXe𓋍ڂRs[^ɂ̂݃CXg[Ďgp̂Ƃ܂B
+(c) qĺAȉ̌`ԂŁA{AvP[V\tgEFAƈꏏɖ{\tgEFAO҂ɔЕz邱Ƃł܂B
+̏ꍇAql͓YO҂ɑAql{_ɂǂ`Ɠȏ̋`킹邱ƂƂ܂B
+(i) bc̋L}̂ɕĔЕz邱
+(ii) ql̊ǗT[o[Ɋi[AO҂Ƀ_E[hĔЕz邱
+(iii) ql̔n[hEFAiɃCXg[āAYn[hEFAi̔邱ƂɂЕz邱
+
+QD֎~
+(a) qĺAobNAbvpɖ{\tgEFAP邱Ƃł܂AȊO̖ړIŖ{\tgEFA邱Ƃ͂ł܂B
+(b) qĺA{\tgEFAo[XGWjAOA͂܂͉ϓ邱Ƃ͂ł܂B
+(c) qĺA{\tgEFAA^A[XAݕt邱Ƃ͂ł܂B
+qĺA{_ŋĂȂ@Ŗ{\tgEFAgpA̕肷邱Ƃ͂ł܂B
+(d) qĺA{\tgEFAɊ܂܂vOу}jAЂт̃TvC[̔閧ƂĈ̂ƂAO҂Ɉ؊JȂ̂Ƃ܂B
+
+R. n̋֎~
+qĺAO҂ɑĖ{\tgEFAт̕nA܂͂̑̕@łړ]邱Ƃ͂ł܂B܂AqĺAO҂ɑĖ{_ɊÂ{\tgEFAɊւ錠n邱Ƃł܂B
+
+S. 쌠
+{\tgEFA̒쌠́AЂт̃TvC[ɂꂼA܂BȂAql͖{\tgEFAт̕AЂ܂͂̃TvC[̒쌠\̒߂菜Ƃ͂ł܂B
+
+T. ӔC̐
+(a) ql̂{\tgEFÃvOL^Ătbs[fBXN܂͂bc|qnl̃fBAɕIȌׂꍇAЂ́AqlɑAfBA̒XOԂɌ薳œYוi邩A܂͑i܂B
+(b) Ђ́ALŒ߂ȊOɁA{\tgEFAɂĕiAセ̑ȂۏsȂ̂Ƃ܂B
+(c) qĺAȂ̐ӔCɂĖ{\tgEFAgp邱ƁA܂A{\tgEFÁÂ܂ܒ@Ȃނ̕ۏȂƂFAӂ̂Ƃ܂B
+(d) Ђ́A{\tgEFA̎gpɕt܂͔hĂqlɐY̐NQAv̑rA̍YɑNQAƖ̒f̑{\tgEFA̎gpɕt͔hāA܂̗͂p̌ʂƂĐ钼ړI܂͊ԐړIȑAQɂĈؐӔCȂ̂Ƃ܂B
+(e) OɋK肷鑹AQƂ́AqĺAql̐ӔCɂ̑ΏsAЂт̃TvC[ɂȂfyڂȂ̂Ƃ܂B
+(f) @ȂꍇɂĂAЂɐӔCꍇ̏́AqlЂɎx{\tgEFȂzȂ̂Ƃ܂B
+(g) qĺA{\tgEFAі{AvP[V\tgEFAɊւs܂͕iۏ؏̖蓙ꍇAȂ̐ӔCɂĂ̂ƂAЂт̃TvC[ɑĈؑQyڂȂ̂Ƃ܂B
+
+UD_̏I
+{_́Aȉ̂ꂩɊYꍇɏI܂B
+(i) qlA{\tgEFA̎gpI邱Ƃ]ƂB
+(ii) qlA{_ɋK肷邢ꂩ̏ɈᔽƂB
+
+VD_I̋`
+{_̏IAql͖{\tgEFA̎gpɒ~AqlCXg[{\tgEFAׂč폜ƋɁA{\tgEFAт̕ЂɕԋpĒAql܂͑O҂gpłȂԂɔĵppĒ̂Ƃ܂B
+
+WDQ
+qlA{_ɋK肷邢ꂩ̏ɈᔽAЂ܂͂̃TvC[ɑAQƂ́Aql͒ɂ̂Ƃ܂B
+
+X. ̑
+(a) Ђ́A{\tgEFA̎dlAOɂqlɒʒm邱ƂȂύXꍇ܂B
+(b) Ђ́Aqlɖ{\tgEFÃo[WAbvł`܂B
+(c) qĺA{\tgEFA{̖@EKɂ苖ĂꍇA܂͖{\tgEFA肵ꏊ̖@EKɂ苖ĂꍇėAo邱Ƃ͂ł܂B
+(d) {_ɊւíAnٔR̐ꑮINJٔƂ܂B܂A{_̐ь́AȂтɖ{_ɊւĔ̉߂їsɂẮA{̖@ɏ̂Ƃ܂BȂA{_̏ٔɂĖƔfꂽꍇłAc̏͌͂L̂Ƃ܂B
+(e) {_́A{\tgEFA̎gpɂāAqlƓЂ̊ԂŎ茈߂ꂽêׂĂLڂ̂łA{ɊւāA܂łɎ킵_iA̗܂݂܂jɗD悵ēKp̂łB{_ɊւāAAύXȂȂ̂Ƃ܂B
+(f) AJO{W҂gp҂̏ꍇ͈ȉǂ݂B
+Government End Users.
+If you are acquiring the Software on behalf of any unit or agency of the United States Government, the following provisions apply. The Government agrees:
+(i) if the Software is supplied to the Department of Defense (DoD), the Software is classified as "Commercial Computer Software" and the Government is acquiring only "restricted rights" in the Software and its documentation as that term is defined in Clause 252.227-7013(c)(1) of the DFARS; and
+(ii) if the Software is supplied to any unit or agency of the United States Government other than DoD, the Government's rights in the Software and its documentation will be as defined in Clause 52.227-19(c)(2) of the FAR or, in the case of NASA, in Clause 18-52.227-86(d) of the NASA Supplement to the FAR.
+
+------------- ȏ ------------------
+
+L_ɓӂāACXg[p܂H
+ӂꍇ́A"gp_ɓӂ܂B(A)"IĂB
+ӂȂꍇ́A"gp_ɓӂ܂B(D)"IĂB
+
+
+Copyright(C) SEIKO EPSON CORPORATION 1999-2007.
+All rights reserved.
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/tmusb64.cat b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/tmusb64.cat
new file mode 100644
index 0000000..a9595d6
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSB64/tmusb64.cat differ
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/DPInst.exe b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/DPInst.exe
new file mode 100644
index 0000000..b956915
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/DPInst.exe differ
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/TMUSB90.inf b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/TMUSB90.inf
new file mode 100644
index 0000000..58445ba
--- /dev/null
+++ b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/TMUSB90.inf
@@ -0,0 +1,80 @@
+;
+; Installation inf for the EPSON USB Controller for TM/BA/EU Printers
+;
+; Copyright(C) SEIKO EPSON CORPORATION 1999-2019. All rights reserved.
+;
+
+;----------------------------------------------------------------------------
+[Version]
+Signature = "$Windows NT$"
+Class = USB
+ClassGUID = {36FC9E60-C465-11CF-8056-444553540000}
+Provider = %Mfg%
+DriverVer = 02/28/2019, 8.0.0.0
+CatalogFile = TMUSBXP.CAT
+
+;----------------------------------------------------------------------------
+[DestinationDirs]
+; [DefaultDestDir=dirid[,subdir]]
+; [file-list-section=dirid[,subdir]] ...
+DefaultDestDir = 12
+NTCopyFiles = 12
+
+;----------------------------------------------------------------------------
+;
+[NTCopyFiles]
+TMUSBXP.SYS
+
+;----------------------------------------------------------------------------
+[Manufacturer]
+; %manufacturer-name% = models-section-name
+%Mfg% = Models
+
+;----------------------------------------------------------------------------
+[ControlFlags]
+; ExcludeFromSelect=device-id-str[,device-id-str]
+ExcludeFromSelect=USB\VID_04B8&PID_0201,USB\VID_04B8&PID_0202
+
+;----------------------------------------------------------------------------
+[Models]
+; device-description = install-section-name, hw-id[, compatible-id...]
+%Model1% = TMUSB_Inst.NT, USB\VID_04B8&PID_0201 ; UB-UXX
+%Model2% = TMUSB_Inst.NT, USB\VID_04B8&PID_0202 ; UB-UXX
+
+;----------------------------------------------------------------------------
+; Win NT DDInstall Section
+;
+[TMUSB_Inst.NT]
+CopyFiles = NTCopyFiles
+
+[TMUSB_Inst.NT.Services]
+; AddService=ServiceName,[flags],service-install-section[,
+; event-log-install-section[,[EventLogType][,EventName]]]
+Addservice = TMUSB, 0x00000002, TMUSB_Service_Inst
+
+[TMUSB_Service_Inst]
+DisplayName = %TMUSB_Inst.SvcDesc%
+ServiceType = 1 ; SERVICE_KERNEL_DRIVER
+StartType = 3 ; SERVICE_DEMAND_START
+ErrorControl = 1 ; SERVICE_ERROR_NORMAL
+ServiceBinary = %12%\TMUSBXP.SYS ; 12 = nt/System32/Drivers
+LoadOrderGroup = Base
+
+;----------------------------------------------------------------------------
+[SourceDisksNames]
+; diskid = disk-description[,[tag-or-cab-file],[unused,path][,flags]]
+1 = %TMUSBDisk%,,,
+
+;----------------------------------------------------------------------------
+[SourceDisksFiles]
+; filename = diskid[,[ subdir][, size]]
+TMUSBXP.SYS = 1,,
+
+;----------------------------------------------------------------------------
+[Strings]
+; = ""
+Mfg = "EPSON"
+Model1 = "EPSON USB Controller for TM/BA/EU Printers"
+Model2 = "EPSON USB Controller for TM/BA/EU Printers"
+TMUSB_Inst.SvcDesc = "EPSON USB Device Driver for TM/BA/EU Printers"
+TMUSBDisk = "EPSON USB Device Driver for TM/BA/EU Printers Installation Disk"
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/TMUSBXP.sys b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/TMUSBXP.sys
new file mode 100644
index 0000000..2a27cb8
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/TMUSBXP.sys differ
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/dpinst.xml b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/dpinst.xml
new file mode 100644
index 0000000..008827b
--- /dev/null
+++ b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/dpinst.xml
@@ -0,0 +1,31 @@
+
+
+
+
+ EPSON TMUSB Driver Ver.8.00 Installer
+ EPSON TMUSB Driver Ver.8.00 の インストールウィザードの開始
+ このウィザードでは、EPSON TM/BA/EUシリーズ用のUSBデバイスドライバ(EPSON TMUSB Driver)のインストールを開始します。
+
+ ソフトウェア使用許諾契約
+ 使用許諾契約に同意します。(&A)
+ 使用許諾契約に同意しません。(&D)
+
+
+ EPSON TMUSB Driver Ver.8.00 をインストールしています...
+ EPSON TMUSB Driver Ver.8.00 のインストールに成功しました。
+
+
+
+ EPSON TMUSB Driver Ver.8.00 Installer
+ EPSON TMUSB Driver Ver.8.00 Installation Wizard
+ This wizard will walk you through updating the USB device driver (EPSON TMUSB Driver) for your EPSON TM/BA/EU Series.
+
+ Software License Agreement
+ I &accept this SLA
+ I do ¬ accept this SLA
+
+
+ EPSON TMUSB driver Ver.8.00 is now installing...
+ Completing EPSON TMUSB driver Ver.8.00 Installation Wizard
+
+
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/licenseE.txt b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/licenseE.txt
new file mode 100644
index 0000000..01eb77f
--- /dev/null
+++ b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/licenseE.txt
@@ -0,0 +1,67 @@
+Please read the following Software License Agreement ("SLA"). Use the scroll bar to view the entire SLA.
+----------------------------------------------------
+
+SOFTWARE LICENSE AGREEMENT
+
+IMPORTANT! READ THIS SOFTWARE LICENSE AGREEMENT CAREFULLY. The computer software product and/or data, including any accompanying explanatory written materials (the "Software") should only be installed or used by the Licensee ("you") on the condition you agree with SEIKO EPSON CORPORATION ("EPSON") to the terms and conditions set forth in this SLA. By installing or using the Software, you are representing to agree all the terms and conditions set forth in this SLA. You should read this SLA carefully before installing or using the Software. If you do not agree with the terms and conditions of this SLA, you are not permitted to install or use the Software.
+
+If you agree to and accept all the terms and conditions of this SLA, EPSON and its suppliers grant to you a nonexclusive license to use the Software in accordance with the following conditions:
+
+1. Use of the Software
+(a) You may use the Software for the purpose of development and execution of your application software which operates the EPSON hardware products designated in a text file included in the Software. Your application software to be used with the Software shall be referred to as the "Application Software."
+(b) You may use the Software by installing to computers which you intend to use directly or via network; provided, however, that the Software shall be used only on a computer in which operating systems as designated in a text file included in the Software has been installed.
+(c) You may distribute the Software as a portion of the Application Software to a third party by the following way; provided, that you ensure that you will first obtain the agreement of the third party under your own license agreement which complies with the terms and conditions of this SLA. You agree not to distribute the Software on a standalone.
+(i) By storage medium, including, but not limited to, CD-ROM
+(ii) You allow a third party to download from a server which is under your control
+(iii) You sell your hardware products pre-installed the Application Software
+
+2. Restrictions on Usage
+(a) You may not copy the Software, except for one copy for backup purposes.
+(b) You may not to attempt to reverse engineer, decompile, disassemble or modify the Software.
+(c) You may not share, rent, lease, encumber or lend the Software.
+(d) You acknowledge that the Software (including manuals thereof) contains valuable, confidential information and trade secrets, that unauthorized use and copying are harmful to EPSON and its suppliers, and that you have a confidentiality obligation as to such valuable information and trade secrets.
+(e) You may not to use the Software in any manner other than expressly provided herein.
+
+3. Prohibition on Transfer
+You agree not to assign, share, rent, lend, lease or otherwise transfer the Software itself or your right to use the Software.
+
+4. Copyright
+ Title, ownership rights, and intellectual property rights in and to the Software and any copies thereof shall remain with EPSON or its suppliers. There is no transfer to you of any title to or ownership of the Software and this License shall not be construed as a sale of any rights in the Software. The Software is protected by Japanese Copyright Law and international copyright treaties, as well as other intellectual property laws and treaties. Except as otherwise provided in this SLA, you may not copy the Software. You also agree not to remove or alter any copyright and other proprietary notices on any copies of the Software.
+
+5. Limited Warranty
+(a) In case of that you obtained the Software by media from EPSON or a dealer, EPSON warrants that the media on which the Software is recorded will be free from defects in workmanship and materials under normal use for a period of 90 days from the date of delivery to you. If the media is returned to EPSON or the dealer from which the media was obtained within 90 days of the date of delivery to you, and if EPSON determines the media to be defective and provided the media was not subject to misuse, abuse, misapplication or use in defective equipment, EPSON will replace the media, upon your return to EPSON of the Software, including all copies of any portions thereof. ALL IMPLIED WARRANTIES ON THE MEDIA, INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE LIMITED TO THE DURATION OF THE EXPRESS WARRANTY SET FORTH ABOVE.
+(b) You acknowledge and agree that the use of the Software is at your sole risk. THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY WARRANTY OF ANY KIND. EPSON AND ITS SUPPLIERS DO NOT AND CANNOT WARRANT THE PERFORMANCE OR RESULTS YOU MAY OBTAIN BY USING THE SOFTWARE. THE FOREGOING STATES THE SOLE AND EXCLUSIVE REMEDIES FOR EPSON'S AND ITS SUPPLIERS' BREACH OF WARRANTY. EXCEPT FOR THE FOREGOING LIMITED WARRANTY, EPSON AND ITS SUPPLIERS MAKE NO WARRANTIES, EXPRESS OR IMPLIED, AS TO NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE. Some states or jurisdictions do not allow the exclusion of implied warranties or limitations on how long an implied warranty may last, so the above limitations may not apply to you. This warranty gives you specific legal rights. You may have other rights which vary from state to state or jurisdiction to jurisdiction.
+(c) IN NO EVENT WILL EPSON OR ITS SUPPLIERS BE LIABLE TO YOU, WHETHER ARISING UNDER CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, BREACH OR WARRANTY, MISREPRESENTATION OR OTHERWISE, FOR ANY DIRECT, CONSEQUENTIAL, INCIDENTAL OR SPECIAL DAMAGES, INCLUDING ANY LOST PROFITS OR LOST SAVINGS, EVEN IF EPSON, ITS SUPPLIERS OR ANY REPRESENTATIVE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, OR FOR ANY CLAIM BY ANY THIRD PARTY. Some states or jurisdictions, however, do not allow the exclusion or limitation of incidental, consequential or special damages, so the above limitations may not apply to you.
+(d) EPSON'S AGGREGATE LIABILITY TO YOU WITH RESPECT TO ANY SUBJECT MATTER OF THIS SLA WHETHER ARISING UNDER TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, BREACH OF WARRANTY, OR MISREPRESENTATION SHALL NOT EXCEED THE AMOUNTS PAID BY YOU TO EPSON TO USE THE SOFTWARE.
+(e) You are solely responsible for use and distribution of the Software and the Application Software and/or provision of support to your customers of such Software and Application Software.
+
+6. Termination
+This SLA shall automatically terminate when:
+(a) You stop to use of the Software.
+(b) You violate any of the terms or conditions set forth in this SLA.
+SLA shall automatically terminate upon failure by you to comply with its terms. You may also terminate this SLA at any time by uninstalling and destroying the Software and all copies thereof.
+
+7. Obligations after Termination
+Upon termination of this SLA, you agree to stop using the Software and delete the Software from the computers described in Article 1(b) above and return the original Software and all copies thereof to EPSON or destroy such Software and copies thereof so that you and a third party cannot use it, and then discard thosecopies.
+
+8. Indemnification by You
+You agree to fully defend, indemnify and hold harmless EPSON and its suppliers against all damages, loss, costs and/or expenses (including reasonable attorney's fees, expert witness fees and other related expenses) arising out of and/or resulting from any claim, proceeding or dispute in connection with the Software, Application Software and/or your failure to comply with any provision in this SLA.
+
+9. Others
+(a) EPSON may change the specification of the Software without notice to you.
+(b) EPSON has no obligation to provide you with any updates, new versions or releases of the Software.
+(c) You may not export the Software except as permitted by the applicable laws and/or regulations of Japan, or the laws and/or regulations of the jurisdiction in which the Software is obtained.
+(d) This SLA shall be governed and construed under by the laws of Japan (without giving effect to its conflicts of laws provisions). The Tokyo District Court shall have exclusive jurisdiction over this SLA and the parties thereto. If any provision in this SLA is ruled invalid by a court of competent jurisdiction, the remaining provisions shall remain in full force and effect.
+(e) If you are acquiring the Software on behalf of any unit or agency of the United States Government, the following provisions apply. The Government agrees:
+ (i) if the Software is supplied to the Department of Defense (DoD), the Software is classified as "Commercial Computer Software" and the Government is acquiring only "restricted rights" in the Software and its documentation as that term is defined in Clause 252.227-7013(c)(1) of the DFARS; and (ii) if the Software is supplied to any unit or agency of the United States Government other than DoD, the Government's rights in the Software and its documentation will be as defined in Clause 52.227-19(c)(2) of the FAR or, in the case of NASA, in Clause 18-52.227-86(d) of the NASA supplement to the FAR.
+
+
+
+Do you agree with and accept all the terms and conditions of this Software License Agreement and wish to continue installing?
+If you do so agree and accept, choose " I accept this SLA".
+If you do not so agree and accept, choose " I do not accept this SLA".
+
+
+Copyright (C) SEIKO EPSON CORPORATION 2000-2007.
+All rights reserved.
+
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/licenseJ.txt b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/licenseJ.txt
new file mode 100644
index 0000000..7d52490
--- /dev/null
+++ b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/licenseJ.txt
@@ -0,0 +1,70 @@
+\tgEFAgp_
+
+̃\tgEFAgpOɖ{\tgEFAgp_iȉu{_vƂ܂jTdɂǂ݂B̃\tgEFACXg[AȂ̕@ŎgpꂽꍇA{_ׂ̂Ă̏ɍS]ƂɓӂƂ݂Ȃ܂B{_ɓӂłȂꍇ́Ã\tgEFA̎gpB
+
+ZCR[Gv\ЁiȉAuЁvƂ܂jт̃TvC[́AqlɑẴ\tgEFAiȉAu{\tgEFAvƂ܂jL̏ɊÂgpƐIȖ̌܂B
+
+L
+P. gp
+(a) qĺA{\tgEFAɊ܂܂eLXgt@CŎw肷铖Ѓn[hEFAi𐧌䂷邨ql̃AvP[V\tgEFAiȉAu{AvP[V\tgEFAvƂ܂j쐬юsړÎ߂ɂ̂݁A{\tgEFAgp邱Ƃł܂B
+(b) qĺA{\tgEFAqlgp镡̃Rs[^A܂͂ql̊Ǘlbg[Nɐڑꂽ̃Rs[^ɃCXg[Ďgp邱Ƃł܂B̏ꍇAql͖{\tgEFAɊ܂܂eLXgt@CŎw肷Iy[eBOVXe𓋍ڂRs[^ɂ̂݃CXg[Ďgp̂Ƃ܂B
+(c) qĺAȉ̌`ԂŁA{AvP[V\tgEFAƈꏏɖ{\tgEFAO҂ɔЕz邱Ƃł܂B
+̏ꍇAql͓YO҂ɑAql{_ɂǂ`Ɠȏ̋`킹邱ƂƂ܂B
+(i) bc̋L}̂ɕĔЕz邱
+(ii) ql̊ǗT[o[Ɋi[AO҂Ƀ_E[hĔЕz邱
+(iii) ql̔n[hEFAiɃCXg[āAYn[hEFAi̔邱ƂɂЕz邱
+
+QD֎~
+(a) qĺAobNAbvpɖ{\tgEFAP邱Ƃł܂AȊO̖ړIŖ{\tgEFA邱Ƃ͂ł܂B
+(b) qĺA{\tgEFAo[XGWjAOA͂܂͉ϓ邱Ƃ͂ł܂B
+(c) qĺA{\tgEFAA^A[XAݕt邱Ƃ͂ł܂B
+qĺA{_ŋĂȂ@Ŗ{\tgEFAgpA̕肷邱Ƃ͂ł܂B
+(d) qĺA{\tgEFAɊ܂܂vOу}jAЂт̃TvC[̔閧ƂĈ̂ƂAO҂Ɉ؊JȂ̂Ƃ܂B
+
+R. n̋֎~
+qĺAO҂ɑĖ{\tgEFAт̕nA܂͂̑̕@łړ]邱Ƃ͂ł܂B܂AqĺAO҂ɑĖ{_ɊÂ{\tgEFAɊւ錠n邱Ƃł܂B
+
+S. 쌠
+{\tgEFA̒쌠́AЂт̃TvC[ɂꂼA܂BȂAql͖{\tgEFAт̕AЂ܂͂̃TvC[̒쌠\̒߂菜Ƃ͂ł܂B
+
+T. ӔC̐
+(a) ql̂{\tgEFÃvOL^Ătbs[fBXN܂͂bc|qnl̃fBAɕIȌׂꍇAЂ́AqlɑAfBA̒XOԂɌ薳œYוi邩A܂͑i܂B
+(b) Ђ́ALŒ߂ȊOɁA{\tgEFAɂĕiAセ̑ȂۏsȂ̂Ƃ܂B
+(c) qĺAȂ̐ӔCɂĖ{\tgEFAgp邱ƁA܂A{\tgEFÁÂ܂ܒ@Ȃނ̕ۏȂƂFAӂ̂Ƃ܂B
+(d) Ђ́A{\tgEFA̎gpɕt܂͔hĂqlɐY̐NQAv̑rA̍YɑNQAƖ̒f̑{\tgEFA̎gpɕt͔hāA܂̗͂p̌ʂƂĐ钼ړI܂͊ԐړIȑAQɂĈؐӔCȂ̂Ƃ܂B
+(e) OɋK肷鑹AQƂ́AqĺAql̐ӔCɂ̑ΏsAЂт̃TvC[ɂȂfyڂȂ̂Ƃ܂B
+(f) @ȂꍇɂĂAЂɐӔCꍇ̏́AqlЂɎx{\tgEFȂzȂ̂Ƃ܂B
+(g) qĺA{\tgEFAі{AvP[V\tgEFAɊւs܂͕iۏ؏̖蓙ꍇAȂ̐ӔCɂĂ̂ƂAЂт̃TvC[ɑĈؑQyڂȂ̂Ƃ܂B
+
+UD_̏I
+{_́Aȉ̂ꂩɊYꍇɏI܂B
+(i) qlA{\tgEFA̎gpI邱Ƃ]ƂB
+(ii) qlA{_ɋK肷邢ꂩ̏ɈᔽƂB
+
+VD_I̋`
+{_̏IAql͖{\tgEFA̎gpɒ~AqlCXg[{\tgEFAׂč폜ƋɁA{\tgEFAт̕ЂɕԋpĒAql܂͑O҂gpłȂԂɔĵppĒ̂Ƃ܂B
+
+WDQ
+qlA{_ɋK肷邢ꂩ̏ɈᔽAЂ܂͂̃TvC[ɑAQƂ́Aql͒ɂ̂Ƃ܂B
+
+X. ̑
+(a) Ђ́A{\tgEFA̎dlAOɂqlɒʒm邱ƂȂύXꍇ܂B
+(b) Ђ́Aqlɖ{\tgEFÃo[WAbvł`܂B
+(c) qĺA{\tgEFA{̖@EKɂ苖ĂꍇA܂͖{\tgEFA肵ꏊ̖@EKɂ苖ĂꍇėAo邱Ƃ͂ł܂B
+(d) {_ɊւíAnٔR̐ꑮINJٔƂ܂B܂A{_̐ь́AȂтɖ{_ɊւĔ̉߂їsɂẮA{̖@ɏ̂Ƃ܂BȂA{_̏ٔɂĖƔfꂽꍇłAc̏͌͂L̂Ƃ܂B
+(e) {_́A{\tgEFA̎gpɂāAqlƓЂ̊ԂŎ茈߂ꂽêׂĂLڂ̂łA{ɊւāA܂łɎ킵_iA̗܂݂܂jɗD悵ēKp̂łB{_ɊւāAAύXȂȂ̂Ƃ܂B
+(f) AJO{W҂gp҂̏ꍇ͈ȉǂ݂B
+Government End Users.
+If you are acquiring the Software on behalf of any unit or agency of the United States Government, the following provisions apply. The Government agrees:
+(i) if the Software is supplied to the Department of Defense (DoD), the Software is classified as "Commercial Computer Software" and the Government is acquiring only "restricted rights" in the Software and its documentation as that term is defined in Clause 252.227-7013(c)(1) of the DFARS; and
+(ii) if the Software is supplied to any unit or agency of the United States Government other than DoD, the Government's rights in the Software and its documentation will be as defined in Clause 52.227-19(c)(2) of the FAR or, in the case of NASA, in Clause 18-52.227-86(d) of the NASA Supplement to the FAR.
+
+------------- ȏ ------------------
+
+L_ɓӂāACXg[p܂H
+ӂꍇ́A"gp_ɓӂ܂B(A)"IĂB
+ӂȂꍇ́A"gp_ɓӂ܂B(D)"IĂB
+
+
+Copyright(C) SEIKO EPSON CORPORATION 1999-2007.
+All rights reserved.
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/tmusbxp.cat b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/tmusbxp.cat
new file mode 100644
index 0000000..109b0dc
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/TMUSBXP/tmusbxp.cat differ
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/readme_e.txt b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/readme_e.txt
new file mode 100644
index 0000000..7c6e608
--- /dev/null
+++ b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/readme_e.txt
@@ -0,0 +1,139 @@
+
+ TMUSB device driver version 8.00b
+
+ 09/13/2019
+ SEIKO EPSON Corporation
+
+1. INTRODUCTION
+---------------
+ TMUSB is a USB device driver for EPSON TM/EU/BA Series.
+ Although the device driver installation is generally done by the device,
+ wizard, this setup utility also supports a silent installation.
+
+2. ENVIRONMENT
+--------------
+2.1 Support OS
+ + Microsoft(R) Windows 10(R) 32bit Version
+ + Microsoft(R) Windows 10(R) 64bit Version
+ + Microsoft(R) Windows Server(R) 2016 64bit Version
+ + Microsoft(R) Windows Server(R) 2019 64bit Version
+
+ NOTE:
+ + It support Embedded OS of OS-based of the above.
+ + Please install TMUSB device driver version 7.10b on Windows 8.1 or earlier.
+
+
+2.2 Support USB driver stacks
+ + Microsoft(R) USB driver stack which is included in OS.
+ + TMUSB doesn't support any third party driver stacks.
+
+ NOTE:
+ + Please use the latest version of Microsoft(R) USB driver stack.
+
+2.3 Supported USB HUBs
+ USB 1.1 Full Speed HUB
+ USB 2.0 High Speed HUB
+ USB 3.0 Super Speed HUB
+
+ NOTE
+ + TMUSB doesn't support USB 1.0 HUB.
+
+ + A High Speed connection becomes available when a Full Speed device is
+ connected via the USB 2.0 HUB, even if your EPSON TM/EU/BA series device
+ does not support USB 2.0 High Speed
+ (only when your host PC has an EHCI host controller).
+
+ Again, see the requirements in "When the device is connected
+ via USB 2.0 High Speed connection" described earlier.
+
+2.4 Support Device
+ EPSON TM/EU/BA Series
+
+2.5 Support USB connection
+ Max stage of USB hub : 5 stages
+ NOTE:
+ Need to use cable and hub which are comply with USB 2.0 specification.
+
+3. FILES
+----------
+ This archive file contains these folders.
+
+ + TMUSB800b
+ + SETUP.exe
+ + TMUSBXP .....Folder containing TMUSB Driver files for 32Bit version
+ + TMUSB64 .....Folder containing TMUSB Driver files for 64Bit version
+ + ReadMeJ.txt
+ + ReadMeE.txt
+
+4. HOW TO INSTALL THE TMUSB DEVICE DRIVER
+-------------------------------------
+ You have to run SETUP.exe as administrator.
+
+4.1 How to install/update
+
+ 1) Power off all devices which are connected to the system by a USB cable.
+ 2) Execute the setup program on the command prompt. Attach start-up options as necessary.
+ For details on start-up options, see 4.2 Options for TMUSBSetup.
+ 3) Power on the device when installation is complete.
+
+ Setup program copies both INF file and SYS file to the system.
+ Setup program return a result of installation.
+
+ You can refer the result by ERRORLEVEL variable.
+ 0 : Succeeded
+ 2 : Installation files are not found.
+ 1151 : Not supported OS.
+ 1223 : A user canceled installation.
+ 3010 : Installation is successful.Changes will not be effective until
+ the system is rebooted.
+
+4.2 Options for TMUSBSetup
+
+ -s2 Silent installation
+ Don't install the specified version of TMUSB when a newer version
+ is installed already.
+ -p1 Enable Device Power Saving.
+ -p2 Disable Device Power Saving.
+
+4.3 Notes for installation of a TM-C100
+
+ Please be sure to install the printer driver of TM-C100 first before
+ connecting a TM-C100 to the Host PC.
+
+ When "USB Printing Support" is displayed on the device manager,
+ please do the following:
+
+ 1) Connect the TM-C100 to the PC and turn on the power supply.
+ 2) If "USB Printing Support" is displayed in the Device Manager,
+ right-click "USB Printing Support" and select "Delete".
+ 3) Turn the power for the TM-C100 off and then back on again.
+ 4) Confirm that "EPSON USB Controller for TM/BA/EU Series" is
+ displayed in the Device Manager.
+
+5. RESTRICTIONS
+---------------
+ + You must run SETUP.exe as administrator.
+ + All applications that use the TMUSB device driver must be closed before
+ executing the setup program.
+ + The device cannot be powered of, or the USB cable disconnected while the
+ device is printing.
+ + Windows(R) must be restarted, or else the device powered off and then
+ powered back on again, in order for the system to recognize that a new
+ device driver is installed.
+ + You need wait 5 seconds to power on after device's power is off.
+ This time is required to ensure that the device driver is unloaded.
+ + When you login as an guest, the system reports that there is no driver's
+ signature.
+ + In order to use the setup program, Internet Explorer version 4.0 or
+ higher is required.
+ + If the device is powered off or the USB cable disconnected while the OS is
+ entering stand by/hibernation mode, the system will not operate normally.
+
+ Please close printing applications, or else power off the device before
+ setting the OS to stand by/hibernation mode.
+
+6. HISTORY
+----------
+ + 09/13/2019 Version 8.00b Support Windows 10 security features. Windows 8.1 or earlier was excluded from support OS.
+
+--- EOF ---
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/readme_j.txt b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/readme_j.txt
new file mode 100644
index 0000000..37f2a3a
--- /dev/null
+++ b/PointOfSale/Utilities/Epson printer drivers/TMUSB800b/readme_j.txt
@@ -0,0 +1,137 @@
+
+ TMUSB device driver version 8.00b
+
+ 2019/09/13
+ ZCR[Gv\
+
+PD͂߂
+------------
+ TMUSB EPSON TM/EU/BAV[ỸfoCXƒʐM邽߂ USB foCXh
+ Co[łB
+ foCXhCo[̃CXg[̓foCXEBU[hɏ]čs܂A{
+ [eBeBɂ TMUSB foCXhCo[̃TCgCXg[s܂
+ B
+
+QDTMUSB
+------------------
+2.1 OS
+ E Microsoft(R) Windows 10(R) 32bit Version
+ E Microsoft(R) Windows 10(R) 64bit Version
+ E Microsoft(R) Windows Server(R) 2016 64bit Version
+ E Microsoft(R) Windows Server(R) 2019 64bit Version
+
+
+ LOSx[XƂEmbeddednOSɂΉĂ܂B
+ Windows 8.1ȑOOSɂTMUSB device driver version 7.10bCXg[ĂB
+ Microsoft, Windows, Windows ServeŕAč Microsoft Corporation ́Ač
+ A{т̑̍ɂo^W܂͏WłB
+ CéAAJOт̑̍ɂIntel Corporation܂͂
+ ̎qЂ̏W܂͓o^WłB
+ pЖEíAeЂ̏W܂͓o^WłB
+
+
+2.2 USB hCo[X^bN
+ Microsoft(R) (OSW)̃hCo[X^bNgB
+ ܂AMicrosoft(R) USB hCo[X^bN͍ŐV̂̂gp
+ BT[hp[eB USB hCo[X^bNł͕̓ۏĂ܂
+ B
+
+
+2.3 USB HUB
+ E USB 1.1 KiHUB(Full Speed Ή)
+ E USB 2.0 KiHUB(High Speed Ή)
+ E USB 3.0 KiHUB(Super Speed Ή)
+ USB 1.0 Ki USB HUB͕̓ۏĂ܂B
+ USB 2.0 High Speed ɑΉ PC ɁAUSB 2.0 HUBoRŃfoCXڑ
+ ƁAUSB 2.0 High Speed ɑΉĂȂfoCXłAUSB 2.0 High Spe
+ ed ڑƂȂ܂̂ŁAUSB 2.0 High Speed ڑ̎gpɏ]Ă
+ B
+
+2.4 foCX
+ EEPSON TM/EU/BAV[ỸfoCX
+
+2.5 USB ڑ
+ Eő HUB 5 i
+ USB 2.0 KiɓKĂ USB P[uAUSB HUBgp邱
+
+
+RDt@C̐
+------------------
+ ȉ̂悤ȃtH_Et@C\ƂȂĂ܂B
+
+ + TMUSB800b
+ + SETUP.exe
+ + TMUSBXP ..................32bitOSpTMUSBĂtH_
+ + TMUSB64 ..................64bitOSpTMUSBĂtH_
+ + ReadMeJ.txt
+ + ReadMeE.txt
+
+SDfoCXhCo[̃CXg[@
+----------------------------------------
+
+ CXg[/Abvf[gƂsꍇ͊ǗҌŎsKv܂B
+
+4.1 hCo[̃CXg[/Abvf[g菇
+
+ 1) USB P[uŐڑꂽׂẴfoCX̓dOFFɂ܂
+ 2) R}hvvgSETUP vOs܂BKvɉċNIvVĂB
+ NIvV̏ڍׂ4.2DNIvVQƂB
+ 3) CXg[foCX̓d𓊓܂
+
+ gp_\܂BemFӂꍇ́Augp_
+ ɓӂvIACXg[{^ĂB
+
+ SETUP vO hCot@C(INF)ƃhCot@C(SYS)VXe
+ ɃRs[܂B
+
+ ZbgAbvvO̓CXg[ʂ ϐ ERRORLEVEL ɕԂ܂
+ 0 :
+ 2 : CXg[t@CȂ
+ 1151 : T|[gĂȂ OS Ŏs
+ 1223 : [UCXg[LZ
+ 3010 : CXg[͐AVXẽu[gKv
+
+4.2DNIvV
+
+ -s2 TCgCXg[
+ ɃCXg[ĂhCoVCXg[Ȃ
+ -p1 foCXȓd͋@\Lɂ܂
+ -p2 foCXȓd͋@\ɂ܂
+
+4.3. TM-C100̃CXg[̒
+
+ TM-C100 {̂zXgPCɐڑOɕKATMUSB SETUP vO
+ sA܂́ATM-C100 p̃v^hCoɃCXg[ĂB
+ ATM-C100 PC ɐڑdĂfoCX}l[W
+ uEPSON USB Controller for TM/BA/EU Seriesv\Ȃꍇ́Aȉ
+ ̎菇ŕAĂB
+
+ 1) TM-C100 PC ɐڑd܂
+ 2) foCX}l[WɁuUSB Supportv\ĂꍇuUSB Printi
+ ng SupportvENbNu폜vI܂
+ 3) TM-C100 ̓d꒼܂
+ 4) foCX}l[WɁuEPSON USB Controller for TM/BA/EU Seriesv\
+ 邩mF܂
+
+TD
+------------
+ E CXg[͕KǗҌōsĂ
+ E TMUSBhCo[𗘗pĂAvP[VIĂZbgAbvv
+ OsĂ
+ E v^[ɈɁAdOFF/ONAP[u̔͂Ȃł
+ E foCX̓dOFFɂATMUSBhCo[ACXg[鎞
+ (Tb)҂ĂfoCX̓dONĂ
+ E foCXhCo[hĂԁifoCX̓dON̏ԁjŃZbg
+ AbvvOsꍇɂ́ÃfoCXhCo̍XVs
+ ܂BfoCX̓dOFF/ON܂̓VXe̍ċNsĂ
+
+ ERs[^[X^oC/x~ԂֈڍsɃfoCX̓dA
+ USB̃P[uƁAOS̐ŃVXesɂȂ邱Ƃ܂B
+ AvP[VIA̓foCX̓dOFFɂĂRs
+ [^[X^oC/x~ԂɂĂ
+
+UDTMUSB hCo
+----------------------
+ E 2019/09/13 Version 8.00b Windows 10̃ZLeB@\ɑΉBWindows 8.1ȑOT|[gOS珜O
+
+--- EOF ---
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMVirtualPortDriver870a.zip b/PointOfSale/Utilities/Epson printer drivers/TMVirtualPortDriver870a.zip
new file mode 100644
index 0000000..60381a5
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/TMVirtualPortDriver870a.zip differ
diff --git a/PointOfSale/Utilities/Epson printer drivers/TMVirtualPortDriver870a/TMVirtualPortDriver870a.exe b/PointOfSale/Utilities/Epson printer drivers/TMVirtualPortDriver870a/TMVirtualPortDriver870a.exe
new file mode 100644
index 0000000..326bd74
Binary files /dev/null and b/PointOfSale/Utilities/Epson printer drivers/TMVirtualPortDriver870a/TMVirtualPortDriver870a.exe differ
diff --git a/PointOfSale/Utilities/SQL/Structure_Data_v1.sql b/PointOfSale/Utilities/SQL/Structure_Data_v1.sql
new file mode 100644
index 0000000..d6c2a68
--- /dev/null
+++ b/PointOfSale/Utilities/SQL/Structure_Data_v1.sql
@@ -0,0 +1,145 @@
+-- --------------------------------------------------------
+-- Host: 127.0.0.1
+-- Server version: 11.0.2-MariaDB - mariadb.org binary distribution
+-- Server OS: Win64
+-- HeidiSQL Version: 12.3.0.6589
+-- --------------------------------------------------------
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET NAMES utf8 */;
+/*!50503 SET NAMES utf8mb4 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+
+-- Dumping database structure for pointofsale
+DROP DATABASE IF EXISTS `pointofsale`;
+CREATE DATABASE IF NOT EXISTS `pointofsale` /*!40100 DEFAULT CHARACTER SET armscii8 COLLATE armscii8_bin */;
+USE `pointofsale`;
+
+-- Dumping structure for table pointofsale.employee
+DROP TABLE IF EXISTS `employee`;
+CREATE TABLE IF NOT EXISTS `employee` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Name` varchar(50) NOT NULL,
+ `IsArchived` tinyint(4) NOT NULL DEFAULT 0,
+ `IsModified` tinyint(4) DEFAULT NULL,
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- Dumping data for table pointofsale.employee: ~4 rows (approximately)
+DELETE FROM `employee`;
+INSERT INTO `employee` (`Id`, `Name`, `IsArchived`, `IsModified`) VALUES
+ (1, 'Jette', 0, 0),
+ (2, 'Lone', 1, 0),
+ (3, 'Emilie', 0, 0),
+ (4, 'Ny', 1, 0);
+
+-- Dumping structure for table pointofsale.payment
+DROP TABLE IF EXISTS `payment`;
+CREATE TABLE IF NOT EXISTS `payment` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `SaleId` int(11) NOT NULL DEFAULT 0,
+ `Amount` decimal(20,6) NOT NULL DEFAULT 0.000000,
+ `Type` tinytext NOT NULL,
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=armscii8 COLLATE=armscii8_bin;
+
+-- Dumping data for table pointofsale.payment: ~3 rows (approximately)
+DELETE FROM `payment`;
+INSERT INTO `payment` (`Id`, `SaleId`, `Amount`, `Type`) VALUES
+ (1, 1, 150.000000, 'Kontant'),
+ (2, 1, 50.000000, 'Kort'),
+ (3, 2, 200.000000, 'Kontant');
+
+-- Dumping structure for table pointofsale.product
+DROP TABLE IF EXISTS `product`;
+CREATE TABLE IF NOT EXISTS `product` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Name` tinytext NOT NULL,
+ `Price` decimal(10,2) NOT NULL,
+ `Description` mediumtext DEFAULT NULL,
+ `ProductGroupId` int(11) NOT NULL DEFAULT 0,
+ `IsArchived` tinyint(4) NOT NULL DEFAULT 0,
+ `Index` int(11) NOT NULL DEFAULT 0,
+ `IsModified` tinyint(4) DEFAULT NULL,
+ PRIMARY KEY (`Id`),
+ KEY `FK_Product_Categories` (`ProductGroupId`) USING BTREE,
+ CONSTRAINT `FK_Product_ProductGroup` FOREIGN KEY (`Id`) REFERENCES `productgroup` (`Id`) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- Dumping data for table pointofsale.product: ~6 rows (approximately)
+DELETE FROM `product`;
+INSERT INTO `product` (`Id`, `Name`, `Price`, `Description`, `ProductGroupId`, `IsArchived`, `Index`, `IsModified`) VALUES
+ (1, 'Buket 1', 0.00, NULL, 3, 0, 1, 1),
+ (2, '3D Print 1.2', 0.00, NULL, 2, 0, 1, 1),
+ (3, 'Jul 1', 0.00, NULL, 8, 0, 3, 1),
+ (4, 'Buket 2', 0.00, NULL, 3, 1, 4, 1),
+ (5, '3D Print 1.1', 0.00, NULL, 2, 0, 0, 1),
+ (6, '3D Print 1.3', 0.00, NULL, 2, 0, 2, 1);
+
+-- Dumping structure for table pointofsale.productgroup
+DROP TABLE IF EXISTS `productgroup`;
+CREATE TABLE IF NOT EXISTS `productgroup` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Name` tinytext NOT NULL,
+ `IsArchived` tinyint(4) NOT NULL DEFAULT 0,
+ `Index` int(11) NOT NULL,
+ `IsModified` tinyint(4) DEFAULT NULL,
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- Dumping data for table pointofsale.productgroup: ~8 rows (approximately)
+DELETE FROM `productgroup`;
+INSERT INTO `productgroup` (`Id`, `Name`, `IsArchived`, `Index`, `IsModified`) VALUES
+ (1, 'Buketter 1', 1, 0, 0),
+ (2, '3D Print', 0, 0, 0),
+ (3, 'Buket', 0, 1, 0),
+ (4, 'Begravelse', 0, 2, 0),
+ (5, 'Sammenplantning', 0, 3, 0),
+ (6, 'Planter', 0, 4, 0),
+ (7, 'Gravpynt', 1, 5, 0),
+ (8, 'Jul', 0, 5, 0);
+
+-- Dumping structure for table pointofsale.sale
+DROP TABLE IF EXISTS `sale`;
+CREATE TABLE IF NOT EXISTS `sale` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Time` datetime NOT NULL,
+ `EmployeeId` int(11) NOT NULL DEFAULT 0,
+ `IsModified` tinyint(4) DEFAULT NULL,
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=armscii8 COLLATE=armscii8_bin;
+
+-- Dumping data for table pointofsale.sale: ~2 rows (approximately)
+DELETE FROM `sale`;
+INSERT INTO `sale` (`Id`, `Time`, `EmployeeId`, `IsModified`) VALUES
+ (1, '2023-07-08 23:09:12', 1, 0),
+ (2, '2023-07-08 23:09:29', 1, 0);
+
+-- Dumping structure for table pointofsale.sale_line
+DROP TABLE IF EXISTS `sale_line`;
+CREATE TABLE IF NOT EXISTS `sale_line` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `SaleId` int(11) NOT NULL DEFAULT 0,
+ `Product` tinytext NOT NULL,
+ `Pieces` smallint(6) NOT NULL DEFAULT 0,
+ `Price` decimal(20,6) NOT NULL DEFAULT 0.000000,
+ `Total` decimal(20,6) NOT NULL DEFAULT 0.000000,
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=armscii8 COLLATE=armscii8_bin;
+
+-- Dumping data for table pointofsale.sale_line: ~2 rows (approximately)
+DELETE FROM `sale_line`;
+INSERT INTO `sale_line` (`Id`, `SaleId`, `Product`, `Pieces`, `Price`, `Total`) VALUES
+ (1, 1, 'Buket', 2, 75.000000, 150.000000),
+ (2, 2, '3D Print', 5, 30.000000, 150.000000);
+
+/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
+/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
+/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;
diff --git a/PointOfSale/Utilities/SQL/Structure_Simply_Data_v1.sql b/PointOfSale/Utilities/SQL/Structure_Simply_Data_v1.sql
new file mode 100644
index 0000000..c691b93
--- /dev/null
+++ b/PointOfSale/Utilities/SQL/Structure_Simply_Data_v1.sql
@@ -0,0 +1,128 @@
+-- --------------------------------------------------------
+-- Host: mysql22.unoeuro.com
+-- Server version: 5.7.42-46-log - Percona Server (GPL), Release 46, Revision e1995a8bb71
+-- Server OS: Linux
+-- HeidiSQL Version: 12.3.0.6589
+-- --------------------------------------------------------
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET NAMES utf8 */;
+/*!50503 SET NAMES utf8mb4 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+-- Dumping structure for table blomstertilalt_dk_db.employee
+DROP TABLE IF EXISTS `employee`;
+CREATE TABLE IF NOT EXISTS `employee` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Name` varchar(50) NOT NULL,
+ `IsArchived` tinyint(4) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
+
+-- Dumping data for table blomstertilalt_dk_db.employee: ~4 rows (approximately)
+DELETE FROM `employee`;
+INSERT INTO `employee` (`Id`, `Name`, `IsArchived`) VALUES
+ (1, 'Jette', 0),
+ (2, 'Lone', 0),
+ (3, 'Emilie', 0),
+ (4, 'Ny', 1);
+
+-- Dumping structure for table blomstertilalt_dk_db.payment
+DROP TABLE IF EXISTS `payment`;
+CREATE TABLE IF NOT EXISTS `payment` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `SaleId` int(11) NOT NULL DEFAULT '0',
+ `Amount` decimal(20,6) NOT NULL DEFAULT '0.000000',
+ `Type` tinytext COLLATE armscii8_bin NOT NULL,
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=armscii8 COLLATE=armscii8_bin;
+
+-- Dumping data for table blomstertilalt_dk_db.payment: ~3 rows (approximately)
+DELETE FROM `payment`;
+INSERT INTO `payment` (`Id`, `SaleId`, `Amount`, `Type`) VALUES
+ (1, 1, 150.000000, 'Kontant'),
+ (2, 1, 50.000000, 'Kort'),
+ (3, 2, 200.000000, 'Kontant');
+
+-- Dumping structure for table blomstertilalt_dk_db.product
+DROP TABLE IF EXISTS `product`;
+CREATE TABLE IF NOT EXISTS `product` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Name` tinytext NOT NULL,
+ `Price` decimal(10,2) NOT NULL,
+ `Description` mediumtext,
+ `ProductGroupId` int(11) NOT NULL DEFAULT '0',
+ `IsArchived` tinyint(4) NOT NULL DEFAULT '0',
+ `Index` int(11) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`Id`),
+ KEY `FK_Product_Categories` (`ProductGroupId`),
+ CONSTRAINT `FK_Product_ProductGroup` FOREIGN KEY (`Id`) REFERENCES `productgroup` (`Id`) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
+
+-- Dumping data for table blomstertilalt_dk_db.product: ~0 rows (approximately)
+DELETE FROM `product`;
+
+-- Dumping structure for table blomstertilalt_dk_db.productgroup
+DROP TABLE IF EXISTS `productgroup`;
+CREATE TABLE IF NOT EXISTS `productgroup` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Name` tinytext NOT NULL,
+ `IsArchived` tinyint(4) NOT NULL DEFAULT '0',
+ `Index` int(11) NOT NULL,
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4;
+
+-- Dumping data for table blomstertilalt_dk_db.productgroup: ~4 rows (approximately)
+DELETE FROM `productgroup`;
+INSERT INTO `productgroup` (`Id`, `Name`, `IsArchived`, `Index`) VALUES
+ (1, 'Buketter 1', 1, 0),
+ (2, '3D Print', 0, 0),
+ (3, 'Buket', 0, 1),
+ (4, 'Begravelse', 0, 2),
+ (5, 'Sammenplantning', 0, 3),
+ (6, 'Planter', 0, 4),
+ (7, 'Gravpynt', 1, 5),
+ (8, 'Jul', 0, 5);
+
+-- Dumping structure for table blomstertilalt_dk_db.sale
+DROP TABLE IF EXISTS `sale`;
+CREATE TABLE IF NOT EXISTS `sale` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Time` datetime NOT NULL,
+ `EmployeeId` int(11) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=armscii8 COLLATE=armscii8_bin;
+
+-- Dumping data for table blomstertilalt_dk_db.sale: ~2 rows (approximately)
+DELETE FROM `sale`;
+INSERT INTO `sale` (`Id`, `Time`, `EmployeeId`) VALUES
+ (1, '2023-07-08 23:09:12', 1),
+ (2, '2023-07-08 23:09:29', 1);
+
+-- Dumping structure for table blomstertilalt_dk_db.sale_line
+DROP TABLE IF EXISTS `sale_line`;
+CREATE TABLE IF NOT EXISTS `sale_line` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `SaleId` int(11) NOT NULL DEFAULT '0',
+ `Product` tinytext COLLATE armscii8_bin NOT NULL,
+ `Pieces` smallint(6) NOT NULL DEFAULT '0',
+ `Price` decimal(20,6) NOT NULL DEFAULT '0.000000',
+ `Total` decimal(20,6) NOT NULL DEFAULT '0.000000',
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=armscii8 COLLATE=armscii8_bin;
+
+-- Dumping data for table blomstertilalt_dk_db.sale_line: ~2 rows (approximately)
+DELETE FROM `sale_line`;
+INSERT INTO `sale_line` (`Id`, `SaleId`, `Product`, `Pieces`, `Price`, `Total`) VALUES
+ (1, 1, 'Buket', 2, 75.000000, 150.000000),
+ (2, 2, '3D Print', 5, 30.000000, 150.000000);
+
+/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
+/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
+/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;
diff --git a/PointOfSale/Utilities/SQL/Structure_Simply_v1.sql b/PointOfSale/Utilities/SQL/Structure_Simply_v1.sql
new file mode 100644
index 0000000..3f0d6b4
--- /dev/null
+++ b/PointOfSale/Utilities/SQL/Structure_Simply_v1.sql
@@ -0,0 +1,98 @@
+-- --------------------------------------------------------
+-- Host: mysql22.unoeuro.com
+-- Server version: 5.7.42-46-log - Percona Server (GPL), Release 46, Revision e1995a8bb71
+-- Server OS: Linux
+-- HeidiSQL Version: 12.3.0.6589
+-- --------------------------------------------------------
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET NAMES utf8 */;
+/*!50503 SET NAMES utf8mb4 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+-- Dumping structure for table blomstertilalt_dk_db.employee
+DROP TABLE IF EXISTS `employee`;
+CREATE TABLE IF NOT EXISTS `employee` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Name` varchar(50) NOT NULL,
+ `IsArchived` tinyint(4) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
+
+-- Data exporting was unselected.
+
+-- Dumping structure for table blomstertilalt_dk_db.payment
+DROP TABLE IF EXISTS `payment`;
+CREATE TABLE IF NOT EXISTS `payment` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `SaleId` int(11) NOT NULL DEFAULT '0',
+ `Amount` decimal(20,6) NOT NULL DEFAULT '0.000000',
+ `Type` tinytext COLLATE armscii8_bin NOT NULL,
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=armscii8 COLLATE=armscii8_bin;
+
+-- Data exporting was unselected.
+
+-- Dumping structure for table blomstertilalt_dk_db.product
+DROP TABLE IF EXISTS `product`;
+CREATE TABLE IF NOT EXISTS `product` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Name` tinytext NOT NULL,
+ `Price` decimal(10,2) NOT NULL,
+ `Description` mediumtext,
+ `ProductGroupId` int(11) NOT NULL DEFAULT '0',
+ `IsArchived` tinyint(4) NOT NULL DEFAULT '0',
+ `Index` int(11) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`Id`),
+ KEY `FK_Product_Categories` (`ProductGroupId`),
+ CONSTRAINT `FK_Product_ProductGroup` FOREIGN KEY (`Id`) REFERENCES `productgroup` (`Id`) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
+
+-- Data exporting was unselected.
+
+-- Dumping structure for table blomstertilalt_dk_db.productgroup
+DROP TABLE IF EXISTS `productgroup`;
+CREATE TABLE IF NOT EXISTS `productgroup` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Name` tinytext NOT NULL,
+ `IsArchived` tinyint(4) NOT NULL DEFAULT '0',
+ `Index` int(11) NOT NULL,
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4;
+
+-- Data exporting was unselected.
+
+-- Dumping structure for table blomstertilalt_dk_db.sale
+DROP TABLE IF EXISTS `sale`;
+CREATE TABLE IF NOT EXISTS `sale` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Time` datetime NOT NULL,
+ `EmployeeId` int(11) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=armscii8 COLLATE=armscii8_bin;
+
+-- Data exporting was unselected.
+
+-- Dumping structure for table blomstertilalt_dk_db.sale_line
+DROP TABLE IF EXISTS `sale_line`;
+CREATE TABLE IF NOT EXISTS `sale_line` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `SaleId` int(11) NOT NULL DEFAULT '0',
+ `Product` tinytext COLLATE armscii8_bin NOT NULL,
+ `Pieces` smallint(6) NOT NULL DEFAULT '0',
+ `Price` decimal(20,6) NOT NULL DEFAULT '0.000000',
+ `Total` decimal(20,6) NOT NULL DEFAULT '0.000000',
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=armscii8 COLLATE=armscii8_bin;
+
+-- Data exporting was unselected.
+
+/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
+/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
+/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;
diff --git a/PointOfSale/Utilities/SQL/Structure_v1.sql b/PointOfSale/Utilities/SQL/Structure_v1.sql
new file mode 100644
index 0000000..2490bb4
--- /dev/null
+++ b/PointOfSale/Utilities/SQL/Structure_v1.sql
@@ -0,0 +1,108 @@
+-- --------------------------------------------------------
+-- Host: 127.0.0.1
+-- Server version: 11.0.2-MariaDB - mariadb.org binary distribution
+-- Server OS: Win64
+-- HeidiSQL Version: 12.3.0.6589
+-- --------------------------------------------------------
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET NAMES utf8 */;
+/*!50503 SET NAMES utf8mb4 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+
+-- Dumping database structure for pointofsale
+DROP DATABASE IF EXISTS `pointofsale`;
+CREATE DATABASE IF NOT EXISTS `pointofsale` /*!40100 DEFAULT CHARACTER SET armscii8 COLLATE armscii8_bin */;
+USE `pointofsale`;
+
+-- Dumping structure for table pointofsale.employee
+DROP TABLE IF EXISTS `employee`;
+CREATE TABLE IF NOT EXISTS `employee` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Name` varchar(50) NOT NULL,
+ `IsArchived` tinyint(4) NOT NULL DEFAULT 0,
+ `IsModified` tinyint(4) DEFAULT NULL,
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- Data exporting was unselected.
+
+-- Dumping structure for table pointofsale.payment
+DROP TABLE IF EXISTS `payment`;
+CREATE TABLE IF NOT EXISTS `payment` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `SaleId` int(11) NOT NULL DEFAULT 0,
+ `Amount` decimal(20,6) NOT NULL DEFAULT 0.000000,
+ `Type` tinytext NOT NULL,
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=armscii8 COLLATE=armscii8_bin;
+
+-- Data exporting was unselected.
+
+-- Dumping structure for table pointofsale.product
+DROP TABLE IF EXISTS `product`;
+CREATE TABLE IF NOT EXISTS `product` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Name` tinytext NOT NULL,
+ `Price` decimal(10,2) NOT NULL,
+ `Description` mediumtext DEFAULT NULL,
+ `ProductGroupId` int(11) NOT NULL DEFAULT 0,
+ `IsArchived` tinyint(4) NOT NULL DEFAULT 0,
+ `Index` int(11) NOT NULL DEFAULT 0,
+ `IsModified` tinyint(4) DEFAULT NULL,
+ PRIMARY KEY (`Id`),
+ KEY `FK_Product_Categories` (`ProductGroupId`) USING BTREE,
+ CONSTRAINT `FK_Product_ProductGroup` FOREIGN KEY (`Id`) REFERENCES `productgroup` (`Id`) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- Data exporting was unselected.
+
+-- Dumping structure for table pointofsale.productgroup
+DROP TABLE IF EXISTS `productgroup`;
+CREATE TABLE IF NOT EXISTS `productgroup` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Name` tinytext NOT NULL,
+ `IsArchived` tinyint(4) NOT NULL DEFAULT 0,
+ `Index` int(11) NOT NULL,
+ `IsModified` tinyint(4) DEFAULT NULL,
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+-- Data exporting was unselected.
+
+-- Dumping structure for table pointofsale.sale
+DROP TABLE IF EXISTS `sale`;
+CREATE TABLE IF NOT EXISTS `sale` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `Time` datetime NOT NULL,
+ `EmployeeId` int(11) NOT NULL DEFAULT 0,
+ `IsModified` tinyint(4) DEFAULT NULL,
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=armscii8 COLLATE=armscii8_bin;
+
+-- Data exporting was unselected.
+
+-- Dumping structure for table pointofsale.sale_line
+DROP TABLE IF EXISTS `sale_line`;
+CREATE TABLE IF NOT EXISTS `sale_line` (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `SaleId` int(11) NOT NULL DEFAULT 0,
+ `Product` tinytext NOT NULL,
+ `Pieces` smallint(6) NOT NULL DEFAULT 0,
+ `Price` decimal(20,6) NOT NULL DEFAULT 0.000000,
+ `Total` decimal(20,6) NOT NULL DEFAULT 0.000000,
+ PRIMARY KEY (`Id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=armscii8 COLLATE=armscii8_bin;
+
+-- Data exporting was unselected.
+
+/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
+/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
+/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;