Initial commit

Initial commit til Git.
V2 er deployed
This commit is contained in:
2026-06-13 17:31:50 +02:00
parent 9fcd2b145e
commit 41e23b6184
375 changed files with 15956 additions and 0 deletions

362
PointOfSale/.gitignore vendored Normal file
View File

@@ -0,0 +1,362 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd

View File

@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EpsonPrinter.Model;
using EpsonPrinter.Services;
namespace EpsonPrinter.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class PosPrinterController : ControllerBase
{
/// <summary>
/// PrintReceipt receipt, so far only for all Epson Thermal Printers
/// </summary>
/// PrintStyle is:
/// None
/// FontB
/// Bold
/// DoubleHeight
/// DoubleWidth
/// Underline
/// There is support for any combination of PrintStyles
/// ----------------------------------------
///
///
/// <param name="receiptModel"></param>
[HttpPost]
[Route("Receipt")]
public void PrintReceipt([FromServices] EpsonPrintService epsonPrint, ReceiptModel receiptModel)
{
epsonPrint.PrintReceipt(receiptModel);
}
[HttpPost]
[Route("SaleOfDay")]
public void PrintSaleOfDay([FromServices] EpsonPrintService epsonPrint, SaleOfDayModel saleOfDayModel)
{
epsonPrint.PrintSaleOfDay(saleOfDayModel);
}
}
}

View File

@@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace EpsonPrinter.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
[Route("HelloWorld")]
public string Get()
{
return "HelloWorld";
}
}
}

View File

@@ -0,0 +1,9 @@
namespace EpsonPrinter.Enums
{
public enum AlignmentEnum
{
Left,
Center,
Right
}
}

View File

@@ -0,0 +1,12 @@
namespace EpsonPrinter.Enums
{
public enum PrintStylesEnum
{
None,
FontB,
Bold,
DoubleHeight,
DoubleWidth,
UnderLine
}
}

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\Pos</DockerfileContext>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Remove="ESC-POS\**" />
<Content Remove="ESC-POS\**" />
<EmbeddedResource Remove="ESC-POS\**" />
<None Remove="ESC-POS\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
</ItemGroup>
</Project>

View File

@@ -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<BodyProductModel> 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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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 HeaderModel
{
public string Value { get; set; }
public PrintStyleModel PrintStyles { get; set; }
public AlignmentEnum TextAlignment { get; set; }
public int FeedingLines { get; set; }
}
}

View File

@@ -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; }
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
namespace EpsonPrinter.Model
{
public class ReceiptModel
{
public string LogoBase64 { get; set; }
public List<HeaderModel> Header { get; set; }
public BodyModel BodyModel { get; set; }
public List<FooterModel> Footer { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace EpsonPrinter.Model
{
public class SaleOfDayDetail
{
public string Category { get; set; }
public decimal TotalSale { get; set; }
}
}

View File

@@ -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> SaleOfDayDetail { get; set; } = new();
}
}

View File

@@ -0,0 +1,23 @@
using EpsonPrinter.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// Services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddScoped<EpsonPrintService>();
var app = builder.Build();
// Swagger (altid aktiv vi er på en Pi, ikke i produktion)
app.UseSwagger();
app.UseSwaggerUI();
// Routing
app.MapControllers();
app.Run();

View File

@@ -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"
}
}
}
}

View File

@@ -0,0 +1,18 @@
Linux USB version of the EpsonPrinter API.
What changed:
- Keeps the incoming JSON models for /api/PosPrinter/Receipt and /api/PosPrinter/SaleOfDay
- Replaces COM/SerialPrinter transport with raw writes to Linux USB printer device
- Uses /dev/usb/lp0 by default from appsettings.json
- Builds a standard receipt when parts of the payload are missing
- Pulls fallback values from appsettings.json under PrintSettings:Defaults
Typical run:
dotnet restore
dotnet run
Typical endpoint:
POST /api/PosPrinter/Receipt
Important setting:
PrintSettings:DevicePath = /dev/usb/lp0

View File

@@ -0,0 +1,248 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using EpsonPrinter.Model;
using Microsoft.Extensions.Configuration;
namespace EpsonPrinter.Services
{
public class EpsonPrintService
{
private readonly IConfiguration _config;
private readonly string _devicePath;
public EpsonPrintService(IConfiguration config)
{
_config = config;
_devicePath = _config["PrintSettings:DevicePath"] ?? "/dev/usb/lp0";
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
}
public void PrintReceipt(ReceiptModel model)
{
ICommandEmitter e = new EPSON();
InitAndWrite(e);
List<byte[][]> printBytes = new List<byte[][]>();
PrintHeader printHeader = new PrintHeader(e);
PrintBody printBody = new PrintBody(e, _config);
PrintFooter printFooter = new PrintFooter(e);
var header = (model.Header != null && model.Header.Any()) ? model.Header : BuildDefaultHeader();
var body = model.BodyModel ?? BuildDefaultBody();
if (string.IsNullOrWhiteSpace(body.Staff))
{
body.Staff = _config["PrintSettings:Defaults:Staff"] ?? "Ukendt";
}
if (string.IsNullOrWhiteSpace(body.ReceiptTime))
{
body.ReceiptTime = DateTime.Now.ToString("dd-MM-yyyy HH:mm:ss");
}
if (body.ReceiptNumber <= 0)
{
body.ReceiptNumber = Math.Abs(Environment.TickCount % 1000000);
}
body.Products ??= new List<BodyProductModel>();
if (!body.Products.Any())
{
body.Products.Add(new BodyProductModel
{
Product = "Standard vare",
NoOfProduct = 1,
Price = 99.00m,
TotalPrice = 99.00m
});
}
if (body.TotalPrice <= 0)
{
body.TotalPrice = body.Products.Sum(x => x.TotalPrice <= 0 ? x.Price * x.NoOfProduct : x.TotalPrice);
}
if (body.TotalVat <= 0)
{
body.TotalVat = Math.Round(body.TotalPrice * 0.25m, 2);
}
var footer = (model.Footer != null && model.Footer.Any()) ? model.Footer : BuildDefaultFooter();
printBytes.AddRange(printHeader.Print(header));
printBytes.AddRange(printBody.Print(body));
printBytes.AddRange(printFooter.Print(footer));
byte[][] array = printBytes.SelectMany(c => c).ToArray();
Write(array);
Write(e.FeedLines(5));
Write(e.FullCut());
}
public void PrintSaleOfDay(SaleOfDayModel model)
{
ICommandEmitter e = new EPSON();
InitAndWrite(e);
PrintSaleOfDay(model, e);
}
private void InitAndWrite(ICommandEmitter e)
{
Write(e.Initialize());
Write(e.Enable());
Write(e.CodePage(CodePage.ISO8859_15_LATIN9));
Write(e.EnableAutomaticStatusBack());
}
private void PrintSaleOfDay(SaleOfDayModel model, ICommandEmitter e)
{
List<byte[][]> data = new List<byte[][]>();
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 = $"{saleOfDayDetail.Category} : {saleOfDayDetail.TotalSale.ToString("#.#0", CultureInfo.InvariantCulture)}";
byte[][] totalPrice =
{
e.SetStyles(PrintStyle.DoubleWidth),
e.LeftAlign(),
e.PrintLine(pS)
};
data.Add(totalPrice);
}
data.Add(seperater);
string totalSale = $"Total salg: {model.TotalSale.ToString("#.#0", CultureInfo.InvariantCulture)}";
byte[][] tSBytes =
{
e.SetStyles(PrintStyle.DoubleWidth),
e.LeftAlign(),
e.PrintLine(totalSale)
};
data.Add(tSBytes);
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();
Write(array);
Write(e.FeedLines(5));
Write(e.FullCut());
}
private List<HeaderModel> BuildDefaultHeader()
{
return new List<HeaderModel>
{
new HeaderModel
{
Value = _config["PrintSettings:Defaults:StoreName"] ?? "TableTop3D",
TextAlignment = Enums.AlignmentEnum.Center,
FeedingLines = 0,
PrintStyles = new PrintStyleModel { Bold = true, DoubleWidth = true, DoubleHeight = true }
},
new HeaderModel
{
Value = _config["PrintSettings:Defaults:AddressLine1"] ?? string.Empty,
TextAlignment = Enums.AlignmentEnum.Center,
FeedingLines = 0,
PrintStyles = new PrintStyleModel { Bold = true }
},
new HeaderModel
{
Value = _config["PrintSettings:Defaults:AddressLine2"] ?? string.Empty,
TextAlignment = Enums.AlignmentEnum.Center,
FeedingLines = 0,
PrintStyles = new PrintStyleModel()
},
new HeaderModel
{
Value = _config["PrintSettings:Defaults:Cvr"] ?? string.Empty,
TextAlignment = Enums.AlignmentEnum.Center,
FeedingLines = 1,
PrintStyles = new PrintStyleModel()
}
}.Where(x => !string.IsNullOrWhiteSpace(x.Value)).ToList();
}
private FooterModel CreateFooterLine(string value, bool bold = false)
{
return new FooterModel
{
Value = value,
TextAlignment = Enums.AlignmentEnum.Center,
FeedingLines = 0,
PrintStyles = new PrintStyleModel { Bold = bold }
};
}
private List<FooterModel> BuildDefaultFooter()
{
var values = new[]
{
_config["PrintSettings:Defaults:FooterLine1"] ?? "Tak for dit køb!",
_config["PrintSettings:Defaults:FooterLine2"] ?? string.Empty,
_config["PrintSettings:Defaults:Phone"] ?? string.Empty
}.Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
return values.Select((x, i) => CreateFooterLine(x, i == 0)).ToList();
}
private BodyModel BuildDefaultBody()
{
return new BodyModel
{
ReceiptNumber = Math.Abs(Environment.TickCount % 1000000),
ReceiptTime = DateTime.Now.ToString("dd-MM-yyyy HH:mm:ss"),
Staff = _config["PrintSettings:Defaults:Staff"] ?? "Ukendt",
Products = new List<BodyProductModel>
{
new BodyProductModel
{
Product = "Standard vare",
NoOfProduct = 1,
Price = 99.00m,
TotalPrice = 99.00m
}
},
TotalPrice = 99.00m,
TotalVat = 24.75m
};
}
private void Write(byte[] data)
{
using var stream = new FileStream(_devicePath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
stream.Write(data, 0, data.Length);
stream.Flush();
}
private void Write(byte[][] data)
{
foreach (var segment in data)
{
Write(segment);
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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<byte[][]> Print(BodyModel model)
{
List<byte[][]> data = new List<byte[][]>();
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;
}
}
}

View File

@@ -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<byte[][]> Print(List<FooterModel> model)
{
List<byte[][]> data = new List<byte[][]>();
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;
}
}
}

View File

@@ -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<Byte[][]> Print(List<HeaderModel> model)
{
List<byte[][]> data = new List<byte[][]>();
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;
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace EpsonPrinter.Services
{
public class PrintString
{
/// <summary>
/// An appropriate interval is converted into the length of
/// the tab about two texts. And make a printing data.
/// </summary>
/// <param name="iLineChars">
/// The width of the territory which it prints on is converted into the number of
/// characters, and that value is specified.
/// </param>
/// <param name="strBuf">
/// It is necessary as an information for deciding the interval of the text.
/// </param>
/// <param name="strPrice">
/// It is necessary as an information for deciding the interval of the text, too.
/// </param>
/// <returns>printing data.
/// </returns>
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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@@ -0,0 +1,26 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"PrintSettings": {
"DevicePath": "/dev/usb/lp0",
"PrintWidth": 48,
"CodePage": "ISO8859_15_LATIN9",
"Defaults": {
"StoreName": "Blomster Til Alt",
"AddressLine1": "Adelgade 88",
"AddressLine2": "5400 Bogense",
"Cvr": "CVR: 37144436",
"Phone": "Tlf: 41 82 71 66",
"FooterLine1": "Tak for dit køb!",
"FooterLine2": "Vi håber du bliver glad for dine varer.",
"Staff": "Ukendt",
"ReceiptPrefix": "BON"
}
}
}

View File

@@ -0,0 +1,73 @@
{
"logoBase64": "string",
"header": [
{
"value": "Første 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
}
]
}

View File

@@ -0,0 +1,7 @@
namespace Pos.Api.Database
{
public class Class1
{
}
}

View File

@@ -0,0 +1,162 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Pos.Api.Database.Models;
namespace Pos.Api.Database.Data;
public partial class PosApiContext : DbContext
{
public PosApiContext(DbContextOptions<PosApiContext> options)
: base(options)
{
}
public virtual DbSet<Employee> Employees { get; set; }
public virtual DbSet<LastUpdate> LastUpdates { get; set; }
public virtual DbSet<Payment> Payments { get; set; }
public virtual DbSet<Product> Products { get; set; }
public virtual DbSet<Productgroup> Productgroups { get; set; }
public virtual DbSet<Sale> Sales { get; set; }
public virtual DbSet<SaleLine> SaleLines { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.UseCollation("latin1_swedish_ci")
.HasCharSet("latin1");
modelBuilder.Entity<Employee>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("employee")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.Property(e => e.Id).HasColumnType("int(11)");
entity.Property(e => e.IsArchived).HasColumnType("tinyint(4)");
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(50);
});
modelBuilder.Entity<LastUpdate>(entity =>
{
entity
.HasNoKey()
.ToTable("LastUpdate");
entity.Property(e => e.DateTime).HasColumnType("datetime");
});
modelBuilder.Entity<Payment>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("payment")
.HasCharSet("armscii8")
.UseCollation("armscii8_bin");
entity.Property(e => e.Id).HasColumnType("int(11)");
entity.Property(e => e.Amount).HasPrecision(20, 6);
entity.Property(e => e.SaleId).HasColumnType("int(11)");
entity.Property(e => e.Type)
.IsRequired()
.HasColumnType("tinytext");
});
modelBuilder.Entity<Product>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("product")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.ProductGroupId, "FK_Product_Categories");
entity.Property(e => e.Id)
.ValueGeneratedOnAdd()
.HasColumnType("int(11)");
entity.Property(e => e.Description).HasColumnType("mediumtext");
entity.Property(e => e.Index).HasColumnType("int(11)");
entity.Property(e => e.IsArchived).HasColumnType("tinyint(4)");
entity.Property(e => e.Name)
.IsRequired()
.HasColumnType("tinytext");
entity.Property(e => e.Price).HasPrecision(10, 2);
entity.Property(e => e.ProductGroupId).HasColumnType("int(11)");
entity.HasOne(d => d.IdNavigation).WithOne(p => p.Product)
.HasForeignKey<Product>(d => d.Id)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Product_ProductGroup");
});
modelBuilder.Entity<Productgroup>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("productgroup")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.Property(e => e.Id).HasColumnType("int(11)");
entity.Property(e => e.Index).HasColumnType("int(11)");
entity.Property(e => e.IsArchived).HasColumnType("tinyint(4)");
entity.Property(e => e.Name)
.IsRequired()
.HasColumnType("tinytext");
});
modelBuilder.Entity<Sale>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("sale")
.HasCharSet("armscii8")
.UseCollation("armscii8_bin");
entity.Property(e => e.Id).HasColumnType("int(11)");
entity.Property(e => e.EmployeeId).HasColumnType("int(11)");
entity.Property(e => e.Time).HasColumnType("datetime");
});
modelBuilder.Entity<SaleLine>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("sale_line")
.HasCharSet("armscii8")
.UseCollation("armscii8_bin");
entity.Property(e => e.Id).HasColumnType("int(11)");
entity.Property(e => e.Pieces).HasColumnType("smallint(6)");
entity.Property(e => e.Price).HasPrecision(20, 6);
entity.Property(e => e.Product)
.IsRequired()
.HasColumnType("tinytext");
entity.Property(e => e.SaleId).HasColumnType("int(11)");
entity.Property(e => e.Total).HasPrecision(20, 6);
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

View File

@@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pos.Api.Database.Data
{
public partial class PosApiContext
{
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
{
LoadConfig l = new LoadConfig();
IConfiguration config = l.ByEnvironment();
string connectionString = config["MySQL"].ToString();
optionsBuilder
.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
.UseLoggerFactory(LoggerFactory.Create(b => b
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
base.OnConfiguring(optionsBuilder);
}
}
}

View File

@@ -0,0 +1,55 @@
using Microsoft.Extensions.Configuration;
namespace Pos.Api.Database
{
public class LoadConfig
{
private string dir;
public LoadConfig()
{
dir = AppDomain.CurrentDomain.BaseDirectory;
}
public IConfiguration ByEnvironment()
{
//Console.Out.WriteLine($"Json path: {dir}");
#if DEBUG
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true)
.Build();
return config;
#else
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true)
.Build();
return config;
#endif
//Running in a environment that not is supported in this setup
throw new Exception("HostingEnvironment is not supported! This config setup only supports Development or Production");
}
public IConfiguration DebugConfiguration()
{
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true)
.Build();
return config;
}
public IConfiguration ReleaseConfiguration()
{
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true)
.Build();
return config;
}
}
}

View File

@@ -0,0 +1,15 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
namespace Pos.Api.Database.Models;
public partial class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public sbyte IsArchived { get; set; }
}

View File

@@ -0,0 +1,11 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
namespace Pos.Api.Database.Models;
public partial class LastUpdate
{
public DateTime? DateTime { get; set; }
}

View File

@@ -0,0 +1,17 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
namespace Pos.Api.Database.Models;
public partial class Payment
{
public int Id { get; set; }
public int SaleId { get; set; }
public decimal Amount { get; set; }
public string Type { get; set; }
}

View File

@@ -0,0 +1,25 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
namespace Pos.Api.Database.Models;
public partial class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Description { get; set; }
public int ProductGroupId { get; set; }
public sbyte IsArchived { get; set; }
public int Index { get; set; }
public virtual Productgroup IdNavigation { get; set; }
}

View File

@@ -0,0 +1,19 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
namespace Pos.Api.Database.Models;
public partial class Productgroup
{
public int Id { get; set; }
public string Name { get; set; }
public sbyte IsArchived { get; set; }
public int Index { get; set; }
public virtual Product Product { get; set; }
}

View File

@@ -0,0 +1,15 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
namespace Pos.Api.Database.Models;
public partial class Sale
{
public int Id { get; set; }
public DateTime Time { get; set; }
public int EmployeeId { get; set; }
}

View File

@@ -0,0 +1,21 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
namespace Pos.Api.Database.Models;
public partial class SaleLine
{
public int Id { get; set; }
public int SaleId { get; set; }
public string Product { get; set; }
public short Pieces { get; set; }
public decimal Price { get; set; }
public decimal Total { get; set; }
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,66 @@
{
"CodeGenerationMode": 3,
"ContextClassName": "PosApiContext",
"ContextNamespace": null,
"DefaultDacpacSchema": null,
"FilterSchemas": false,
"IncludeConnectionString": false,
"ModelNamespace": null,
"OutputContextPath": "Data",
"OutputPath": "Models",
"PreserveCasingWithRegex": true,
"ProjectRootNamespace": "Pos.Api.Database",
"Schemas": null,
"SelectedHandlebarsLanguage": 0,
"SelectedToBeGenerated": 0,
"Tables": [
{
"Name": "employee",
"ObjectType": 0
},
{
"Name": "LastUpdate",
"ObjectType": 0
},
{
"Name": "payment",
"ObjectType": 0
},
{
"Name": "product",
"ObjectType": 0
},
{
"Name": "productgroup",
"ObjectType": 0
},
{
"Name": "sale",
"ObjectType": 0
},
{
"Name": "sale_line",
"ObjectType": 0
}
],
"UiHint": "simply.com",
"UncountableWords": null,
"UseBoolPropertiesWithoutDefaultSql": false,
"UseDatabaseNames": false,
"UseDateOnlyTimeOnly": false,
"UseDbContextSplitting": false,
"UseFluentApiOnly": true,
"UseHandleBars": false,
"UseHierarchyId": false,
"UseInflector": true,
"UseLegacyPluralizer": false,
"UseManyToManyEntity": false,
"UseNoDefaultConstructor": false,
"UseNoObjectFilter": false,
"UseNodaTime": false,
"UseNullableReferences": false,
"UseSchemaFolders": false,
"UseSchemaNamespaces": false,
"UseSpatial": false,
"UseT4": false
}

View File

@@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Pos.Api.Database;
namespace Pos.Api
{
public class BaseController : Controller
{
private readonly IConfiguration _config;
public BaseController(IConfiguration config)
{
_config = config;
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
HttpRequest request = Request;
string apiKey = request.Headers["ApiKey"].ToString();
string? apiKeyRequest = _config["ApiKey"];
if (apiKeyRequest == null)
{
throw new NullReferenceException("ApiKey header is missing");
}
if (!apiKeyRequest.Equals(apiKey))
throw new ArgumentException("ApiKey is wrong");
}
}
}

View File

@@ -0,0 +1,151 @@
using System.Diagnostics.Eventing.Reader;
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Pos.Api.Database.Data;
using Pos.Api.Database.Models;
namespace Pos.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class PosController : Controller
{
private readonly PosApiContext _context;
private readonly IConfiguration _config;
public PosController(IConfiguration config, PosApiContext context)
{
_context = context;
_config = config;
}
//[HttpGet("/update")]
//public DateTime LastUpdate()
//{
// LastUpdate? lastUpdate = _context.LastUpdates.SingleOrDefault();
// if (lastUpdate == null)
// {
// return DateTime.Now;
// }
// DateTime last = lastUpdate.DateTime.Value;
// lastUpdate.DateTime = DateTime.Now;
// _context.SaveChanges();
// return last;
//}
[HttpPost("/employee")]
public void Employee(Employee employee)
{
ValidateApiKey();
Employee? dbEmployee = _context.Employees.SingleOrDefault(c => c.Id == employee.Id);
if (dbEmployee == null)
{
dbEmployee = new Employee();
dbEmployee.Id = employee.Id;
dbEmployee.IsArchived = employee.IsArchived;
_context.Employees.Add(dbEmployee);
}
dbEmployee.Name = employee.Name;
_context.SaveChanges();
}
[HttpPost("/productgroup")]
public void Product(Productgroup productGroup)
{
ValidateApiKey();
Productgroup dbProductGroup = _context.Productgroups.SingleOrDefault(c => c.Id == productGroup.Id);
if (dbProductGroup == null)
{
dbProductGroup = new Productgroup();
dbProductGroup.Id = productGroup.Id;
_context.Productgroups.Add(dbProductGroup);
}
dbProductGroup.IsArchived = productGroup.IsArchived;
dbProductGroup.Name = productGroup.Name;
dbProductGroup.Index = productGroup.Index;
_context.SaveChanges();
}
[HttpPost("/sale")]
public void Sale(Sale sale)
{
ValidateApiKey();
Sale? dbSale = _context.Sales.SingleOrDefault(c => c.Id == sale.Id);
if (dbSale == null)
{
dbSale = new Sale();
dbSale.Id = sale.Id;
_context.Sales.Add(dbSale);
}
dbSale.EmployeeId = sale.EmployeeId;
dbSale.Time = sale.Time;
_context.SaveChanges();
}
[HttpPost("/saleline")]
public void SaleLine(SaleLine saleLine)
{
ValidateApiKey();
SaleLine? dbSaleLine = _context.SaleLines.SingleOrDefault(c => c.Id == saleLine.Id);
if (dbSaleLine == null)
{
dbSaleLine = new SaleLine();
dbSaleLine.Id = saleLine.Id;
_context.SaleLines.Add(dbSaleLine);
}
dbSaleLine.SaleId = saleLine.SaleId;
dbSaleLine.Price = saleLine.Price;
dbSaleLine.Pieces = saleLine.Pieces;
dbSaleLine.Price = saleLine.Price;
dbSaleLine.Product = saleLine.Product;
dbSaleLine.Total = saleLine.Total;
_context.SaveChanges();
}
[HttpPost("/payment")]
public void Payment(Payment payment)
{
ValidateApiKey();
Payment? dbPayment = _context.Payments.SingleOrDefault(c => c.Id == payment.Id);
if (dbPayment == null)
{
dbPayment = new Payment();
dbPayment.Id = payment.Id;
_context.Payments.Add(dbPayment);
}
dbPayment.Amount = payment.Amount;
dbPayment.SaleId = payment.SaleId;
dbPayment.Type = payment.Type;
_context.SaveChanges();
}
private void ValidateApiKey()
{
IHeaderDictionary headers = Request.Headers;
if (!headers.ContainsKey("ApiKey"))
{
throw new MissingFieldException("ApiKey missing");
}
StringValues apiKeyHeader = headers["ApiKey"];
string? apiKey = _config["ApiKey"];
if (!apiKeyHeader.Equals(apiKey))
{
throw new MissingFieldException("ApiKey is wrong");
}
}
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Mvc;
namespace Pos.Api.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Pos.Api.Database;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Pos.Api.Helper
{
public class ApiKeyHeaderOperationFilter : IOperationFilter
{
private readonly IConfiguration _config;
public ApiKeyHeaderOperationFilter(IConfiguration config)
{
_config = config;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
string? apiKey = _config["ApiKey"];
operation.Parameters.Add(new OpenApiParameter
{
Name = "ApiKey",
In = ParameterLocation.Header,
Required = true,
Schema = new OpenApiSchema
{
Type = "string",
Default = new OpenApiString(apiKey)
}
});
}
}
}

View File

@@ -0,0 +1,55 @@
using Microsoft.Extensions.Configuration;
namespace Pos.Api.Helper
{
public class LoadConfig
{
private string dir;
public LoadConfig()
{
dir = AppDomain.CurrentDomain.BaseDirectory;
}
public IConfiguration ByEnvironment()
{
//Console.Out.WriteLine($"Json path: {dir}");
#if DEBUG
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true)
.Build();
return config;
#else
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true)
.Build();
return config;
#endif
//Running in a environment that not is supported in this setup
throw new Exception("HostingEnvironment is not supported! This config setup only supports Development or Production");
}
public IConfiguration DebugConfiguration()
{
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true)
.Build();
return config;
}
public IConfiguration ReleaseConfiguration()
{
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true)
.Build();
return config;
}
}
}

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.7" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Pos.Api.Database\Pos.Api.Database.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,32 @@
using Pos.Api.Database;
using Pos.Api.Database.Data;
using Pos.Api.Helper;
using LoadConfig = Pos.Api.Helper.LoadConfig;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
LoadConfig loadConfig = new LoadConfig();
IConfiguration config = loadConfig.ByEnvironment();
builder.Services.AddSingleton(config);
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
c.OperationFilter<ApiKeyHeaderOperationFilter>()
);
builder.Services.AddDbContext<PosApiContext>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@@ -0,0 +1,31 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:53151",
"sslPort": 0
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5297",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,13 @@
namespace Pos.Api
{
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,11 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"MySQL": "Data Source=mysql22.unoeuro.com;Initial Catalog=blomstertilalt_dk_db;Persist Security Info=False;User ID=blomstertil_dk;Password=amccpy6l",
"ApiKey": "ad0YfMYm5bdVGjmXkJBOdNggQaWtkB9nzyQv68GAcB7mpf9onGBP9j3DJ46S7go30NwaQgoZBNS7hZDOM79KTyU3K2ysMW2x4mWGHOkETJmWadaMXBTGpoWn0ef9KiUN"
}

View File

@@ -0,0 +1,55 @@
using Microsoft.Extensions.Configuration;
namespace Pos.Dispatcher.Database
{
public class LoadConfig
{
private string dir;
public LoadConfig()
{
dir = AppDomain.CurrentDomain.BaseDirectory;
}
public IConfiguration ByEnvironment()
{
//Console.Out.WriteLine($"Json path: {dir}");
#if DEBUG
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true)
.Build();
return config;
#else
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true)
.Build();
return config;
#endif
//Running in a environment that not is supported in this setup
throw new Exception("HostingEnvironment is not supported! This config setup only supports Development or Production");
}
public IConfiguration DebugConfiguration()
{
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true)
.Build();
return config;
}
public IConfiguration ReleaseConfiguration()
{
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true)
.Build();
return config;
}
}
}

View File

@@ -0,0 +1,75 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Pos.Dispatcher.Database.Models;
namespace Pos.Dispatcher.Database.Model;
public partial class PosDispatcherContext : DbContext
{
public PosDispatcherContext(DbContextOptions<PosDispatcherContext> options)
: base(options)
{
}
public virtual DbSet<Employee> Employees { get; set; }
public virtual DbSet<Payment> Payments { get; set; }
public virtual DbSet<Product> Products { get; set; }
public virtual DbSet<Productgroup> Productgroups { get; set; }
public virtual DbSet<Sale> Sales { get; set; }
public virtual DbSet<SaleLine> SaleLines { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.UseCollation("armscii8_bin")
.HasCharSet("armscii8");
modelBuilder.Entity<Employee>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
});
modelBuilder.Entity<Payment>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
});
modelBuilder.Entity<Product>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity.Property(e => e.Id).ValueGeneratedOnAdd();
entity.HasOne(d => d.IdNavigation).WithOne(p => p.Product)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Product_ProductGroup");
});
modelBuilder.Entity<Productgroup>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
});
modelBuilder.Entity<Sale>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
});
modelBuilder.Entity<SaleLine>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Pos.Dispatcher.Database.Model
{
public partial class PosDispatcherContext
{
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
{
LoadConfig l = new LoadConfig();
IConfiguration config = l.ByEnvironment();
string connectionString = config["MariaSqlServer"].ToString();
optionsBuilder
.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
.UseLoggerFactory(LoggerFactory.Create(b => b
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
base.OnConfiguring(optionsBuilder);
}
}
}

View File

@@ -0,0 +1,29 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Pos.Dispatcher.Database.Models;
[Table("employee")]
[MySqlCharSet("utf8mb4")]
[MySqlCollation("utf8mb4_general_ci")]
public partial class Employee
{
[Key]
[Column(TypeName = "int(11)")]
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Column(TypeName = "tinyint(4)")]
public sbyte IsArchived { get; set; }
[Column(TypeName = "tinyint(4)")]
public sbyte? IsModified { get; set; }
}

View File

@@ -0,0 +1,27 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Pos.Dispatcher.Database.Models;
[Table("payment")]
public partial class Payment
{
[Key]
[Column(TypeName = "int(11)")]
public int Id { get; set; }
[Column(TypeName = "int(11)")]
public int SaleId { get; set; }
[Precision(20, 6)]
public decimal Amount { get; set; }
[Required]
[Column(TypeName = "tinytext")]
public string Type { get; set; }
}

View File

@@ -0,0 +1,46 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Pos.Dispatcher.Database.Models;
[Table("product")]
[Index("ProductGroupId", Name = "FK_Product_Categories")]
[MySqlCharSet("utf8mb4")]
[MySqlCollation("utf8mb4_general_ci")]
public partial class Product
{
[Key]
[Column(TypeName = "int(11)")]
public int Id { get; set; }
[Required]
[Column(TypeName = "tinytext")]
public string Name { get; set; }
[Precision(10, 2)]
public decimal Price { get; set; }
[Column(TypeName = "mediumtext")]
public string Description { get; set; }
[Column(TypeName = "int(11)")]
public int ProductGroupId { get; set; }
[Column(TypeName = "tinyint(4)")]
public sbyte IsArchived { get; set; }
[Column(TypeName = "int(11)")]
public int Index { get; set; }
[Column(TypeName = "tinyint(4)")]
public sbyte? IsModified { get; set; }
[ForeignKey("Id")]
[InverseProperty("Product")]
public virtual Productgroup IdNavigation { get; set; }
}

View File

@@ -0,0 +1,35 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Pos.Dispatcher.Database.Models;
[Table("productgroup")]
[MySqlCharSet("utf8mb4")]
[MySqlCollation("utf8mb4_general_ci")]
public partial class Productgroup
{
[Key]
[Column(TypeName = "int(11)")]
public int Id { get; set; }
[Required]
[Column(TypeName = "tinytext")]
public string Name { get; set; }
[Column(TypeName = "tinyint(4)")]
public sbyte IsArchived { get; set; }
[Column(TypeName = "int(11)")]
public int Index { get; set; }
[Column(TypeName = "tinyint(4)")]
public sbyte? IsModified { get; set; }
[InverseProperty("IdNavigation")]
public virtual Product Product { get; set; }
}

View File

@@ -0,0 +1,26 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Pos.Dispatcher.Database.Models;
[Table("sale")]
public partial class Sale
{
[Key]
[Column(TypeName = "int(11)")]
public int Id { get; set; }
[Column(TypeName = "datetime")]
public DateTime Time { get; set; }
[Column(TypeName = "int(11)")]
public int EmployeeId { get; set; }
[Column(TypeName = "tinyint(4)")]
public sbyte? IsModified { get; set; }
}

View File

@@ -0,0 +1,33 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Pos.Dispatcher.Database.Models;
[Table("sale_line")]
public partial class SaleLine
{
[Key]
[Column(TypeName = "int(11)")]
public int Id { get; set; }
[Column(TypeName = "int(11)")]
public int SaleId { get; set; }
[Required]
[Column(TypeName = "tinytext")]
public string Product { get; set; }
[Column(TypeName = "smallint(6)")]
public short Pieces { get; set; }
[Precision(20, 6)]
public decimal Price { get; set; }
[Precision(20, 6)]
public decimal Total { get; set; }
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,62 @@
{
"CodeGenerationMode": 3,
"ContextClassName": "PosDispatcherContext",
"ContextNamespace": null,
"DefaultDacpacSchema": null,
"FilterSchemas": false,
"IncludeConnectionString": false,
"ModelNamespace": null,
"OutputContextPath": "Model",
"OutputPath": "Models",
"PreserveCasingWithRegex": true,
"ProjectRootNamespace": "Pos.Dispatcher.Database",
"Schemas": null,
"SelectedHandlebarsLanguage": 0,
"SelectedToBeGenerated": 0,
"Tables": [
{
"Name": "employee",
"ObjectType": 0
},
{
"Name": "payment",
"ObjectType": 0
},
{
"Name": "product",
"ObjectType": 0
},
{
"Name": "productgroup",
"ObjectType": 0
},
{
"Name": "sale",
"ObjectType": 0
},
{
"Name": "sale_line",
"ObjectType": 0
}
],
"UiHint": "localhost",
"UncountableWords": null,
"UseBoolPropertiesWithoutDefaultSql": false,
"UseDatabaseNames": false,
"UseDateOnlyTimeOnly": false,
"UseDbContextSplitting": false,
"UseFluentApiOnly": false,
"UseHandleBars": false,
"UseHierarchyId": false,
"UseInflector": true,
"UseLegacyPluralizer": false,
"UseManyToManyEntity": false,
"UseNoDefaultConstructor": false,
"UseNoObjectFilter": false,
"UseNodaTime": false,
"UseNullableReferences": false,
"UseSchemaFolders": false,
"UseSchemaNamespaces": false,
"UseSpatial": false,
"UseT4": false
}

View File

@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="appsettings.json" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="RestSharp" Version="110.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Pos.Dispatcher.Database\Pos.Dispatcher.Database.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,21 @@
// See https://aka.ms/new-console-template for more information
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Pomelo.EntityFrameworkCore;
using Pos.Dispatcher;
using Pos.Dispatcher.Database;
using Pos.Dispatcher.Database.Model;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
LoadConfig loadConfig = new LoadConfig();
IConfiguration config = loadConfig.ByEnvironment();
builder.Services.AddSingleton(config);
builder.Services.AddDbContext<PosDispatcherContext>();
builder.Services.AddScoped<Synchronize>();
using IHost host = builder.Build();
Synchronize synchronize = host.Services.GetRequiredService<Synchronize>();
synchronize.Go();

View File

@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Pos.Dispatcher.Database.Model;
using Pos.Dispatcher.Database.Models;
using RestSharp;
namespace Pos.Dispatcher
{
public class Synchronize
{
private readonly PosDispatcherContext _context;
private readonly IConfiguration _config;
private string _apiUrl;
private string _apiKey;
public Synchronize(PosDispatcherContext context, IConfiguration config)
{
_context = context;
_config = config;
_apiUrl = _config["PosApi"];
_apiKey = _config["ApiKey"];
}
public void Go()
{
Employee();
ProductGroup();
Sale();
}
private void Employee()
{
List<Employee> employees = _context.Employees.Where(c => c.IsModified == 1).ToList();
foreach (Employee employee in employees)
{
RestClient client = new RestClient(_apiUrl);
client.AddDefaultHeader("ApiKey", _apiKey);
var request = new RestRequest("/employee", Method.Post);
request.AddJsonBody(employee);
RestResponse restResponse = client.Post(request);
employee.IsModified = 0;
}
_context.SaveChanges();
}
private void ProductGroup()
{
List<Productgroup> productgroups = _context.Productgroups.Where(c => c.IsModified == 1).ToList();
foreach (Productgroup product in productgroups)
{
RestClient client = new RestClient(_apiUrl);
client.AddDefaultHeader("ApiKey", _apiKey);
var request = new RestRequest("/productgroup", Method.Post);
request.AddJsonBody(product);
RestResponse response = client.Post(request);
product.IsModified = 0;
}
_context.SaveChanges();
}
public void Sale()
{
List<Sale> sales = _context.Sales.Where(c => c.IsModified == 1).ToList();
foreach (Sale sale in sales)
{
RestClient client = new RestClient(_apiUrl);
client.AddDefaultHeader("ApiKey", _apiKey);
var request = new RestRequest("/sale", Method.Post);
request.AddJsonBody(sale);
RestResponse response = client.Post(request);
sale.IsModified = 0;
List<SaleLine> saleLines = _context.SaleLines.Where(c => c.SaleId == sale.Id).ToList();
foreach (SaleLine saleLine in saleLines)
{
client = new RestClient(_apiUrl);
client.AddDefaultHeader("ApiKey", _apiKey);
request = new RestRequest("/saleline", Method.Post);
request.AddJsonBody(saleLine);
response = client.Post(request);
}
List<Payment> payments = _context.Payments.Where(c => c.SaleId == sale.Id).ToList();
foreach (Payment payment in payments)
{
client = new RestClient(_apiUrl);
client.AddDefaultHeader("ApiKey", _apiKey);
request = new RestRequest("/payment", Method.Post);
request.AddJsonBody(payment);
response = client.Post(request);
}
sale.IsModified = 0;
}
_context.SaveChanges();
}
}
}

View File

@@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"MariaSqlServer": "Data Source=localhost;Initial Catalog=PointOfSale;Persist Security Info=False;User ID=root",
"ApiKey": "ad0YfMYm5bdVGjmXkJBOdNggQaWtkB9nzyQv68GAcB7mpf9onGBP9j3DJ46S7go30NwaQgoZBNS7hZDOM79KTyU3K2ysMW2x4mWGHOkETJmWadaMXBTGpoWn0ef9KiUN",
"PosApi": "http://localhost:53151"
}

View File

@@ -0,0 +1,21 @@
using System.Text;
namespace EpsonReceiptPrinter;
public static class EncodingHelper
{
static EncodingHelper()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
}
public static Encoding GetEncodingForCodePage(PrinterCodePage codePage) =>
codePage switch
{
PrinterCodePage.PC865_Nordic => Encoding.GetEncoding(865),
PrinterCodePage.PC850_Multilingual => Encoding.GetEncoding(850),
PrinterCodePage.PC858_Euro => Encoding.GetEncoding(858),
PrinterCodePage.WPC1252 => Encoding.GetEncoding(1252),
_ => Encoding.GetEncoding(865)
};
}

View File

@@ -0,0 +1,42 @@
using System.Net.Sockets;
namespace EpsonReceiptPrinter;
public sealed class EpsonNetworkReceiptPrinter : IReceiptPrinter
{
private readonly string _host;
private readonly int _port;
private readonly PrinterCodePage _codePage;
public EpsonNetworkReceiptPrinter(string host, int port = 9100, PrinterCodePage codePage = PrinterCodePage.PC865_Nordic)
{
if (string.IsNullOrWhiteSpace(host))
{
throw new ArgumentException("Host must be provided.", nameof(host));
}
_host = host;
_port = port;
_codePage = codePage;
}
public async Task PrintAsync(Receipt receipt, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(receipt);
var data = ReceiptFormatter.Format(receipt, _codePage);
await PrintRawAsync(data, cancellationToken);
}
public async Task PrintRawAsync(byte[] data, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(data);
using var client = new TcpClient();
await client.ConnectAsync(_host, _port, cancellationToken);
await using var stream = client.GetStream();
await stream.WriteAsync(data, cancellationToken);
await stream.FlushAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,147 @@
using System.Text;
namespace EpsonReceiptPrinter;
public sealed class EscPosBuilder
{
private readonly List<byte> _buffer = new();
private readonly Encoding _encoding;
private PrinterCodePage _codePage;
public EscPosBuilder(PrinterCodePage codePage = PrinterCodePage.PC865_Nordic, Encoding? encoding = null)
{
_codePage = codePage;
_encoding = encoding ?? EncodingHelper.GetEncodingForCodePage(codePage);
}
public EscPosBuilder Initialize()
{
_buffer.AddRange(EscPosCommands.Initialize);
_buffer.AddRange(EscPosCommands.SelectCodePage(_codePage));
return this;
}
public EscPosBuilder SelectCodePage(PrinterCodePage codePage)
{
_codePage = codePage;
_buffer.AddRange(EscPosCommands.SelectCodePage(codePage));
return this;
}
public EscPosBuilder AlignLeft()
{
_buffer.AddRange(EscPosCommands.AlignLeft);
return this;
}
public EscPosBuilder AlignCenter()
{
_buffer.AddRange(EscPosCommands.AlignCenter);
return this;
}
public EscPosBuilder AlignRight()
{
_buffer.AddRange(EscPosCommands.AlignRight);
return this;
}
public EscPosBuilder BoldOn()
{
_buffer.AddRange(EscPosCommands.BoldOn);
return this;
}
public EscPosBuilder BoldOff()
{
_buffer.AddRange(EscPosCommands.BoldOff);
return this;
}
public EscPosBuilder UnderlineOn()
{
_buffer.AddRange(EscPosCommands.UnderlineOn);
return this;
}
public EscPosBuilder UnderlineOff()
{
_buffer.AddRange(EscPosCommands.UnderlineOff);
return this;
}
public EscPosBuilder SetTextSize(TextSize size)
{
_buffer.AddRange(EscPosCommands.TextSize(size));
return this;
}
public EscPosBuilder SetTextSize(int widthMultiplier, int heightMultiplier)
{
_buffer.AddRange(EscPosCommands.TextSize(widthMultiplier, heightMultiplier));
return this;
}
public EscPosBuilder Write(string text)
{
if (!string.IsNullOrEmpty(text))
{
_buffer.AddRange(_encoding.GetBytes(text));
}
return this;
}
public EscPosBuilder WriteLine(string? text = null)
{
if (!string.IsNullOrEmpty(text))
{
_buffer.AddRange(_encoding.GetBytes(text));
}
_buffer.Add(0x0A);
return this;
}
public EscPosBuilder Feed(int lines = 1)
{
_buffer.AddRange(EscPosCommands.Feed(lines));
return this;
}
public EscPosBuilder Cut(bool partial = false)
{
_buffer.AddRange(partial ? EscPosCommands.CutPartial() : EscPosCommands.CutFull());
return this;
}
public EscPosBuilder OpenCashDrawer()
{
_buffer.AddRange(EscPosCommands.OpenCashDrawer());
return this;
}
public EscPosBuilder Separator(int width = 42, char character = '-')
{
return WriteLine(new string(character, width));
}
public EscPosBuilder WriteColumns(string left, string? right, int totalWidth = 42)
{
left ??= string.Empty;
right ??= string.Empty;
if (left.Length + right.Length >= totalWidth)
{
WriteLine(left);
WriteLine(right.PadLeft(totalWidth));
return this;
}
var spaces = totalWidth - left.Length - right.Length;
WriteLine(left + new string(' ', spaces) + right);
return this;
}
public byte[] Build() => _buffer.ToArray();
}

View File

@@ -0,0 +1,63 @@
namespace EpsonReceiptPrinter;
public static class EscPosCommands
{
public static readonly byte[] Initialize = [0x1B, 0x40];
public static readonly byte[] BoldOn = [0x1B, 0x45, 0x01];
public static readonly byte[] BoldOff = [0x1B, 0x45, 0x00];
public static readonly byte[] UnderlineOn = [0x1B, 0x2D, 0x01];
public static readonly byte[] UnderlineOff = [0x1B, 0x2D, 0x00];
public static readonly byte[] AlignLeft = [0x1B, 0x61, 0x00];
public static readonly byte[] AlignCenter = [0x1B, 0x61, 0x01];
public static readonly byte[] AlignRight = [0x1B, 0x61, 0x02];
public static readonly byte[] FontA = [0x1B, 0x4D, 0x00];
public static readonly byte[] FontB = [0x1B, 0x4D, 0x01];
public static byte[] Feed(int lines)
{
if (lines < 0 || lines > 255)
{
throw new ArgumentOutOfRangeException(nameof(lines));
}
return [0x1B, 0x64, (byte)lines];
}
public static byte[] CutFull() => [0x1D, 0x56, 0x00];
public static byte[] CutPartial() => [0x1D, 0x56, 0x01];
public static byte[] OpenCashDrawer() => [0x1B, 0x70, 0x00, 0x19, 0xFA];
public static byte[] SelectCodePage(PrinterCodePage codePage) => [0x1B, 0x74, (byte)codePage];
public static byte[] TextSize(TextSize size) =>
size switch
{
TextSize.Normal => [0x1D, 0x21, 0x00],
TextSize.DoubleWidth => [0x1D, 0x21, 0x10],
TextSize.DoubleHeight => [0x1D, 0x21, 0x01],
TextSize.DoubleWidthAndHeight => [0x1D, 0x21, 0x11],
_ => [0x1D, 0x21, 0x00]
};
public static byte[] TextSize(int widthMultiplier, int heightMultiplier)
{
if (widthMultiplier < 1 || widthMultiplier > 8)
{
throw new ArgumentOutOfRangeException(nameof(widthMultiplier));
}
if (heightMultiplier < 1 || heightMultiplier > 8)
{
throw new ArgumentOutOfRangeException(nameof(heightMultiplier));
}
var value = (byte)(((widthMultiplier - 1) << 4) | (heightMultiplier - 1));
return [0x1D, 0x21, value];
}
}

View File

@@ -0,0 +1,7 @@
namespace EpsonReceiptPrinter;
public interface IReceiptPrinter
{
Task PrintAsync(Receipt receipt, CancellationToken cancellationToken = default);
Task PrintRawAsync(byte[] data, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,13 @@
namespace EpsonReceiptPrinter;
public enum PrinterCodePage
{
PC437_USA = 0,
Katakana = 1,
PC850_Multilingual = 2,
PC860_Portuguese = 3,
PC863_CanadianFrench = 4,
PC865_Nordic = 5,
WPC1252 = 16,
PC858_Euro = 19
}

View File

@@ -0,0 +1,31 @@
namespace EpsonReceiptPrinter;
public sealed class Receipt
{
public string? Header { get; set; }
public string? SubHeader { get; set; }
public List<ReceiptLine> Lines { get; } = new();
public string? Footer { get; set; }
public bool CutPaper { get; set; } = true;
public int PaperWidthCharacters { get; set; } = 42;
}
public sealed class ReceiptLine
{
public ReceiptLine()
{
}
public ReceiptLine(string left, string? right = null, bool emphasize = false, TextSize textSize = TextSize.Normal)
{
Left = left;
Right = right;
Emphasize = emphasize;
TextSize = textSize;
}
public string Left { get; set; } = string.Empty;
public string? Right { get; set; }
public bool Emphasize { get; set; }
public TextSize TextSize { get; set; } = TextSize.Normal;
}

View File

@@ -0,0 +1,72 @@
namespace EpsonReceiptPrinter;
public static class ReceiptFormatter
{
public static byte[] Format(Receipt receipt, PrinterCodePage codePage = PrinterCodePage.PC865_Nordic)
{
ArgumentNullException.ThrowIfNull(receipt);
var width = receipt.PaperWidthCharacters <= 0 ? 42 : receipt.PaperWidthCharacters;
var builder = new EscPosBuilder(codePage)
.Initialize()
.AlignCenter();
if (!string.IsNullOrWhiteSpace(receipt.Header))
{
builder
.SetTextSize(TextSize.DoubleWidthAndHeight)
.BoldOn()
.WriteLine(receipt.Header)
.BoldOff()
.SetTextSize(TextSize.Normal);
}
if (!string.IsNullOrWhiteSpace(receipt.SubHeader))
{
builder.WriteLine(receipt.SubHeader);
}
builder
.Feed(1)
.AlignLeft()
.Separator(width);
foreach (var line in receipt.Lines)
{
builder.SetTextSize(line.TextSize);
if (line.Emphasize)
{
builder.BoldOn();
}
else
{
builder.BoldOff();
}
builder.WriteColumns(line.Left, line.Right, width);
builder.SetTextSize(TextSize.Normal);
builder.BoldOff();
}
builder
.Separator(width)
.Feed(1)
.AlignCenter();
if (!string.IsNullOrWhiteSpace(receipt.Footer))
{
builder.WriteLine(receipt.Footer);
}
builder.Feed(3);
if (receipt.CutPaper)
{
builder.Cut();
}
return builder.Build();
}
}

View File

@@ -0,0 +1,9 @@
namespace EpsonReceiptPrinter;
public enum TextSize
{
Normal,
DoubleWidth,
DoubleHeight,
DoubleWidthAndHeight
}

View File

@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "5.0.9",
"commands": [
"dotnet-ef"
]
}
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
namespace Pos
{
public class LoadConfig
{
private string dir;
public LoadConfig()
{
dir = AppDomain.CurrentDomain.BaseDirectory;
}
public IConfiguration ByEnvironment()
{
//Console.Out.WriteLine($"Json path: {dir}");
#if DEBUG
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true)
.Build();
return config;
#else
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true)
.Build();
return config;
#endif
//Running in a environment that not is supported in this setup
throw new Exception("HostingEnvironment is not supported! This config setup only supports Development or Production");
}
public IConfiguration DebugConfiguration()
{
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true)
.Build();
return config;
}
public IConfiguration ReleaseConfiguration()
{
var config = new ConfigurationBuilder()
.SetBasePath(dir)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true)
.Build();
return config;
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Database.Models
{
[Table("employee")]
public class EmployeeEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public bool IsArchived { get; set; }
public bool IsModified { get; set; }
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Database.Models
{
[Table("payment")]
public class PaymentEntity
{
[Key]
public int Id { get; set; }
public int SaleId { get; set; }
public decimal Amount { get; set;}
public string Type { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Database.Models
{
[Table("product")]
public class ProductEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Description { get; set; }
public bool IsArchived { get; set; }
public int Index { get; set; }
public bool IsModified { get; set; }
public int ProductGroupId { get; set; }
[ForeignKey(nameof(ProductGroupId))]
public ProductGroupEntity ProductGroup { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Database.Models
{
[Table("productgroup")]
public class ProductGroupEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public bool IsArchived { get; set; }
public int Index { get; set; }
public ICollection<ProductEntity> Products { get; set; }
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Database.Models
{
[Table("sale")]
public class SaleEntity
{
[Key]
public int Id { get; set; }
public DateTime Time { get; set; }
public int EmployeeId { get; set; }
public bool IsModified { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Database.Models
{
[Table("sale_line")]
public class SaleLineEntity
{
[Key]
public int Id { get; set; }
public int SaleId { get; set; }
public string Product { get; set; }
public int Pieces { get; set; }
public decimal Price { get; set; }
public decimal Total { get; set; }
}
}

View File

@@ -0,0 +1,35 @@
using Database.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Pos;
namespace Database
{
public class PosDbContext : DbContext
{
public DbSet<EmployeeEntity> Employee { get; set; }
public DbSet<ProductGroupEntity> ProductGroups { get; set; }
public DbSet<ProductEntity> Products { get; set; }
public DbSet<SaleEntity> Sales { get; set; }
public DbSet<SaleLineEntity> SalesLines { get; set; }
public DbSet<PaymentEntity> Payment { get; set; }
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
{
LoadConfig l = new LoadConfig();
IConfiguration config = l.ByEnvironment();
string connectionString = config["MariaSqlServer"].ToString();
optionsBuilder
.UseMySql(connectionString,ServerVersion.AutoDetect(connectionString))
.UseLoggerFactory(LoggerFactory.Create(b => b
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
base.OnConfiguring(optionsBuilder);
}
}
}

View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Database.Models;
using Microsoft.Extensions.Configuration;
namespace Database.Repository
{
public class EmployeeRepository : IDisposable
{
public void Add(string name)
{
EmployeeEntity employee = new EmployeeEntity();
employee.Name = name;
employee.IsModified = true;
using PosDbContext context = new PosDbContext();
context.Employee.Add(employee);
context.SaveChanges();
}
public EmployeeEntity Get(int employeeId)
{
using PosDbContext context = new PosDbContext();
EmployeeEntity employeeEntity = context.Employee.First(c => c.Id == employeeId);
return employeeEntity;
}
public List<EmployeeEntity> GetAll()
{
using PosDbContext context = new PosDbContext();
List<EmployeeEntity> staffs = context.Employee.Where(c => c.IsArchived == false).ToList();
return staffs;
}
public void Edit(int id, string name)
{
using PosDbContext context = new PosDbContext();
EmployeeEntity employee = context.Employee.Single(c => c.Id == id);
employee.Name = name;
employee.IsModified = true;
context.SaveChanges();
}
public void Delete(int id)
{
using PosDbContext context = new PosDbContext();
EmployeeEntity employee = context.Employee.Single(c => c.Id == id);
employee.IsArchived = true;
employee.IsModified = true;
context.SaveChanges();
}
public void Dispose()
{
}
}
}

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Database.Models;
using Microsoft.EntityFrameworkCore;
namespace Database.Repository
{
public class ProductGroupRepository
{
public List<ProductGroupEntity> GetAll()
{
using PosDbContext context = new PosDbContext();
List<ProductGroupEntity> productGroups = context.ProductGroups
.Include(c => c.Products)
.Where(c => c.IsArchived == false)
.OrderBy(c => c.Index)
.ToList();
return productGroups;
}
public ProductGroupEntity Get(int id)
{
using PosDbContext context = new PosDbContext();
ProductGroupEntity product = context.ProductGroups.Single(c => c.Id == id);
return product;
}
public void Add(string name)
{
ProductGroupEntity productGroup = new ProductGroupEntity();
productGroup.Name = name;
using PosDbContext context = new PosDbContext();
//Get the highest index
ProductGroupEntity highest = context.ProductGroups.OrderByDescending(c => c.Index).Take(1).FirstOrDefault();
if(highest == null)
{
highest = new ProductGroupEntity();
highest.Index = 0;
}
productGroup.Index = highest.Index + 1;
context.ProductGroups.Add(productGroup);
context.SaveChanges();
}
public void Edit(string name, int id)
{
using PosDbContext context = new PosDbContext();
ProductGroupEntity productGroup = context.ProductGroups.Single(c => c.Id == id);
productGroup.Name = name;
context.SaveChanges();
}
public void Archive(int id)
{
using PosDbContext context = new PosDbContext();
ProductGroupEntity productGroup = context.ProductGroups.Single(c => c.Id == id);
productGroup.IsArchived = true;
context.SaveChanges();
}
public void SetIndex(int id, int index)
{
using PosDbContext context = new PosDbContext();
ProductGroupEntity productGroup = context.ProductGroups.Single(c => c.Id == id);
productGroup.Index = index;
context.SaveChanges();
}
public bool Any()
{
using PosDbContext context = new PosDbContext();
bool any = context.ProductGroups.Any();
return any;
}
}
}

View File

@@ -0,0 +1,87 @@
using System.Collections.Generic;
using System.Linq;
using Database.Models;
namespace Database.Repository
{
public class ProductRepository
{
public List<ProductEntity> GetAll()
{
using PosDbContext context = new PosDbContext();
List<ProductEntity> products = context.Products
.Where(c => c.IsArchived == false)
.OrderBy(c => c.Index)
.ToList();
return products;
}
public List<ProductEntity> GetByProductGroup(int id)
{
using PosDbContext context = new PosDbContext();
List<ProductEntity> products = context.Products
.Where(c => c.ProductGroupId == id)
.Where(c => c.IsArchived == false)
.OrderBy(c => c.Index)
.ToList();
return products;
}
public void SetIndex(int id, int index)
{
using PosDbContext context = new PosDbContext();
ProductEntity product = context.Products.Single(c => c.Id == id);
product.Index = index;
product.IsModified = true;
context.SaveChanges();
}
public ProductEntity GetById(int id)
{
using PosDbContext context = new PosDbContext();
ProductEntity product = context.Products.Single(c => c.Id == id);
return product;
}
public void Update(int id, int productGroupId, string name)
{
using PosDbContext context = new PosDbContext();
ProductEntity dbProduct = context.Products.Single(c => c.Id == id);
dbProduct.ProductGroupId = productGroupId;
dbProduct.Name = name;
dbProduct.IsModified = true;
context.SaveChanges();
}
public void Archive(int id)
{
using PosDbContext context = new PosDbContext();
ProductEntity dbProduct = context.Products.Single(c => c.Id == id);
dbProduct.IsArchived = true;
dbProduct.IsModified = true;
context.SaveChanges();
}
public void Add(string name, int productGroupId)
{
ProductEntity product = new ProductEntity();
product.Name = name;
product.ProductGroupId = productGroupId;
using PosDbContext context = new PosDbContext();
//Get the highest index
ProductEntity highest = context.Products.OrderByDescending(c => c.Index).Take(1).FirstOrDefault();
if (highest == null)
{
highest = new ProductEntity();
highest.Index = 0;
}
product.Index = highest.Index + 1;
product.IsModified = true;
context.Products.Add(product);
context.SaveChanges();
}
}
}

View File

@@ -0,0 +1,83 @@
using Database.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Database.Repository
{
public class SaleRepository
{
public SaleEntity New(int employeeId)
{
using PosDbContext context = new PosDbContext();
SaleEntity saleEntity = new SaleEntity();
saleEntity.Time = DateTime.Now;
saleEntity.EmployeeId = employeeId;
saleEntity.IsModified = true;
context.Sales.Add(saleEntity);
context.SaveChanges();
return saleEntity;
}
public SaleLineEntity SaveSaleLine(SaleLineEntity entity)
{
using PosDbContext context = new PosDbContext();
context.SalesLines.Add(entity);
context.SaveChanges();
return entity;
}
public List<SaleLineEntity> GetSaleLineBySaleId(int saleId)
{
using PosDbContext context = new PosDbContext();
List<SaleLineEntity> saleLineEntities = context.SalesLines.Where(c => c.SaleId == saleId).ToList();
return saleLineEntities;
}
public PaymentEntity SavePayment(PaymentEntity entity)
{
using PosDbContext context = new PosDbContext();
context.Payment.Add(entity);
context.SaveChanges();
return entity;
}
public List<PaymentEntity> GetPaymentBySaleId(int saleId)
{
using PosDbContext context = new PosDbContext();
List<PaymentEntity> paymentEntities = context.Payment.Where(c => c.SaleId == saleId).ToList();
return paymentEntities;
}
public SaleEntity GetLatest()
{
using PosDbContext context = new PosDbContext();
SaleEntity saleEntity = context.Sales.OrderByDescending(c => c.Id).Take(1).First();
return saleEntity;
}
public SaleEntity Get(int id)
{
using PosDbContext context = new PosDbContext();
SaleEntity saleEntity = context.Sales.Single(c => c.Id == id);
return saleEntity;
}
public List<SaleEntity> GetByDateRange(DateTime start, DateTime end)
{
using PosDbContext context = new PosDbContext();
List<SaleEntity> saleEntities = context.Sales.Where(c => c.Time.Date >= start.Date && c.Time.Date <= end.Date).OrderByDescending(c => c.Time).ToList();
return saleEntities;
}
public List<SaleEntity> GetByDateRange(DateTime start)
{
using PosDbContext context = new PosDbContext();
List<SaleEntity> saleEntities = context.Sales.Where(c => c.Time.Date == start.Date.Date).OrderByDescending(c => c.Time).ToList();
return saleEntities;
}
}
}

View File

@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EpsonPrinter.Model;
using EpsonPrinter.Services;
namespace EpsonPrinter.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class PosPrinterController : ControllerBase
{
/// <summary>
/// PrintReceipt receipt, so far only for all Epson Thermal Printers
/// </summary>
/// PrintStyle is:
/// None
/// FontB
/// Bold
/// DoubleHeight
/// DoubleWidth
/// Underline
/// There is support for any combination of PrintStyles
/// ----------------------------------------
///
///
/// <param name="receiptModel"></param>
[HttpPost]
[Route("Receipt")]
public void PrintReceipt([FromServices] EpsonPrintService epsonPrint, ReceiptModel receiptModel)
{
epsonPrint.PrintReceipt(receiptModel);
}
[HttpPost]
[Route("SaleOfDay")]
public void PrintSaleOfDay([FromServices] EpsonPrintService epsonPrint, SaleOfDayModel saleOfDayModel)
{
epsonPrint.PrintSaleOfDay(saleOfDayModel);
}
}
}

View File

@@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace EpsonPrinter.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
[Route("HelloWorld")]
public string Get()
{
return "HelloWorld";
}
}
}

View File

@@ -0,0 +1 @@
* text=auto

View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: ['lukevp']
#patreon: # Replace with a single Patreon username
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
#liberapay: # Replace with a single Liberapay username
#issuehunt: # Replace with a single IssueHunt username
#otechie: # Replace with a single Otechie username
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -0,0 +1,7 @@
.vs/
.idea/
**/obj/
**/bin/
push.ps1
/push.txt
/ESCPOS_NET/ESCPOS_NET.csproj.user

View File

@@ -0,0 +1,74 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ESCPOS_NET\ESCPOS_NET.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="abe-lincoln.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\abe-lincoln.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\kitten.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\pd-logo-100.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\pd-logo-200.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\pd-logo-300.bmp">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\pd-logo-300.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\pd-logo-300.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\pd-logo-300.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\pd-logo-400.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\pd-logo-500.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\pd-logo-600.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\pd-logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\pd-logo.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\Portal_Companion_Cube.jpg">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="kitten.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="pd-logo.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="test-files\output-juliogamasso.bin">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,267 @@
using ESCPOS_NET.Emitters;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Linq;
using Microsoft.Extensions.Logging;
using System.Threading;
namespace ESCPOS_NET.ConsoleTest
{
internal class Program
{
private static BasePrinter printer;
private static ICommandEmitter e;
static void Main(string[] args)
{
Console.WriteLine("Welcome to the ESCPOS_NET Test Application!");
Console.Write("Would you like to see all debug messages? (y/n): ");
var response = Console.ReadLine().Trim().ToLowerInvariant();
var logLevel = LogLevel.Information;
if (response.Length >= 1 && response[0] == 'y')
{
Console.WriteLine("Debugging enabled!");
logLevel = LogLevel.Trace;
}
var factory = LoggerFactory.Create(b => b.AddConsole().SetMinimumLevel(logLevel));
var logger = factory.CreateLogger<Program>();
ESCPOS_NET.Logging.Logger = logger;
Console.WriteLine("1 ) Test Serial Port");
Console.WriteLine("2 ) Test Network Printer");
Console.Write("Choice: ");
string comPort = "";
string baudRate;
string ip;
string networkPort;
response = Console.ReadLine();
var valid = new List<string> { "1", "2" };
if (!valid.Contains(response))
{
response = "1";
}
int choice = int.Parse(response);
if (choice == 1)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
while (!comPort.StartsWith("COM"))
{
Console.Write("COM Port (enter for default COM5): ");
comPort = Console.ReadLine();
if (string.IsNullOrWhiteSpace(comPort))
{
comPort = "COM5";
}
}
Console.Write("Baud Rate (enter for default 115200): ");
baudRate = Console.ReadLine();
if (string.IsNullOrWhiteSpace(baudRate))
{
baudRate = "115200";
}
printer = new SerialPrinter(portName: comPort, baudRate: 115200);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Console.Write("File / virtual com path (eg. /dev/usb/lp0): ");
comPort = Console.ReadLine();
if (string.IsNullOrWhiteSpace(comPort))
{
comPort = "/dev/usb/lp0";
}
printer = new FilePrinter(filePath: comPort, false);
}
}
else if (choice == 2)
{
Console.Write("IP Address (eg. 192.168.1.240): ");
ip = Console.ReadLine();
if (string.IsNullOrWhiteSpace(ip))
{
ip = "192.168.254.202";
}
Console.Write("TCP Port (enter for default 9100): ");
networkPort = Console.ReadLine();
if (string.IsNullOrWhiteSpace(networkPort))
{
networkPort = "9100";
}
printer = new NetworkPrinter(settings: new NetworkPrinterSettings() { ConnectionString = $"{ip}:{networkPort}" });
}
bool monitor = false;
Thread.Sleep(500);
Console.Write("Turn on Live Status Back Monitoring? (y/n): ");
response = Console.ReadLine().Trim().ToLowerInvariant();
if (response.Length >= 1 && response[0] == 'y')
{
monitor = true;
}
e = new EPSON();
var testCases = new Dictionary<Option, string>()
{
{ Option.SingleLinePrinting, "Single Line Printing" },
{ Option.MultiLinePrinting, "Multi-line Printing" },
{ Option.LineSpacing, "Line Spacing" },
{ Option.BarcodeStyles, "Barcode Styles" },
{ Option.BarcodeTypes, "Barcode Types" },
{ Option.TwoDimensionCodes, "2D Codes" },
{ Option.TextStyles, "Text Styles" },
{ Option.FullReceipt, "Full Receipt" },
{ Option.CodePages, "Code Pages (Euro, Katakana, Etc)" },
{ Option.Images, "Images" },
{ Option.LegacyImages, "Legacy Images" },
{ Option.LargeByteArrays, "Large Byte Arrays" },
{ Option.CashDrawerPin2, "Cash Drawer Pin2" },
{ Option.CashDrawerPin5, "Cash Drawer Pin5" },
{ Option.Exit, "Exit" }
};
while (true)
{
foreach (var item in testCases)
{
Console.WriteLine($"{(int)item.Key} : {item.Value}");
}
Console.Write("Execute Test: ");
if (!int.TryParse(Console.ReadLine(), out choice) || !Enum.IsDefined(typeof(Option), choice))
{
Console.WriteLine("Invalid entry. Please try again.");
continue;
}
var enumChoice = (Option)choice;
if (enumChoice == Option.Exit)
{
return;
}
Console.Clear();
if (monitor)
{
printer.Write(e.Initialize());
printer.Write(e.Enable());
printer.Write(e.EnableAutomaticStatusBack());
}
Setup(monitor);
printer?.Write(e.PrintLine($"== [ Start {testCases[enumChoice]} ] =="));
switch (enumChoice)
{
case Option.SingleLinePrinting:
printer.Write(Tests.SingleLinePrinting(e));
break;
case Option.MultiLinePrinting:
printer.Write(Tests.MultiLinePrinting(e));
break;
case Option.LineSpacing:
printer.Write(Tests.LineSpacing(e));
break;
case Option.BarcodeStyles:
printer.Write(Tests.BarcodeStyles(e));
break;
case Option.BarcodeTypes:
printer.Write(Tests.BarcodeTypes(e));
break;
case Option.TwoDimensionCodes:
printer.Write(Tests.TwoDimensionCodes(e));
break;
case Option.TextStyles:
printer.Write(Tests.TextStyles(e));
break;
case Option.FullReceipt:
printer.Write(Tests.Receipt(e));
break;
case Option.Images:
printer.Write(Tests.Images(e, false));
break;
case Option.LegacyImages:
printer.Write(Tests.Images(e, true));
break;
case Option.LargeByteArrays:
try
{
printer.Write(Tests.TestLargeByteArrays(e));
}
catch (Exception e)
{
Console.WriteLine($"Aborting print due to test failure. Exception: {e?.Message}, Stack Trace: {e?.GetBaseException()?.StackTrace}");
}
break;
case Option.CashDrawerPin2:
printer.Write(Tests.CashDrawerOpenPin2(e));
break;
case Option.CashDrawerPin5:
printer.Write(Tests.CashDrawerOpenPin5(e));
break;
default:
Console.WriteLine("Invalid entry.");
break;
}
Setup(monitor);
printer?.Write(e.PrintLine($"== [ End {testCases[enumChoice]} ] =="));
printer?.Write(e.PartialCutAfterFeed(5));
// TODO: also make an automatic runner that runs all tests (command line).
}
}
public enum Option
{
SingleLinePrinting = 1,
MultiLinePrinting,
LineSpacing,
BarcodeStyles,
BarcodeTypes,
TwoDimensionCodes,
TextStyles,
FullReceipt,
CodePages,
Images,
LegacyImages,
LargeByteArrays,
CashDrawerPin2,
CashDrawerPin5,
Exit = 99
}
private static void StatusChanged(object sender, EventArgs ps)
{
var status = (PrinterStatusEventArgs)ps;
if (status == null) { Console.WriteLine("Status was null - unable to read status from printer."); return; }
Console.WriteLine($"Printer Online Status: {status.IsPrinterOnline}");
Console.WriteLine(JsonConvert.SerializeObject(status));
}
private static bool _hasEnabledStatusMonitoring = false;
private static void Setup(bool enableStatusBackMonitoring)
{
if (printer != null)
{
// Only register status monitoring once.
if (!_hasEnabledStatusMonitoring)
{
printer.StatusChanged += StatusChanged;
_hasEnabledStatusMonitoring = true;
}
printer?.Write(e.Initialize());
printer?.Write(e.Enable());
if (enableStatusBackMonitoring)
{
printer.Write(e.EnableAutomaticStatusBack());
}
}
}
}
}

View File

@@ -0,0 +1,43 @@
using ESCPOS_NET.Emitters;
using ESCPOS_NET.Utilities;
namespace ESCPOS_NET.ConsoleTest
{
public static partial class Tests
{
private const string websiteString = "https://github.com/lukevp/ESC-POS-.NET/";
public static byte[][] TwoDimensionCodes(ICommandEmitter e) => new byte[][] {
e.PrintLine("PDF417:"),
e.Print2DCode(TwoDimensionCodeType.PDF417, websiteString),
e.PrintLine(),
e.PrintLine("PDF417 (TINY):"),
e.Print2DCode(TwoDimensionCodeType.PDF417, websiteString, Size2DCode.TINY),
e.PrintLine(),
e.PrintLine("PDF417 (LARGE):"),
e.Print2DCode(TwoDimensionCodeType.PDF417, websiteString, Size2DCode.LARGE),
e.PrintLine(),
e.PrintLine("QRCODE MODEL 1:"),
e.Print2DCode(TwoDimensionCodeType.QRCODE_MODEL1, websiteString),
e.PrintLine(),
e.PrintLine("QRCODE MODEL 2:"),
e.PrintQRCode(websiteString),
e.PrintLine(),
e.PrintLine("QRCODE MICRO:"),
e.Print2DCode(TwoDimensionCodeType.QRCODE_MICRO, "github.com/lukevp"),
e.PrintLine(),
e.PrintLine("QRCODE MODEL 1 (TINY):"),
e.Print2DCode(TwoDimensionCodeType.QRCODE_MODEL1, websiteString, Size2DCode.TINY),
e.PrintLine(),
e.PrintLine("QRCODE MODEL 1 (LARGE):"),
e.Print2DCode(TwoDimensionCodeType.QRCODE_MODEL1, websiteString, Size2DCode.LARGE),
e.PrintLine()
};
}
}

View File

@@ -0,0 +1,64 @@
using ESCPOS_NET.Emitters;
using ESCPOS_NET.Utilities;
namespace ESCPOS_NET.ConsoleTest
{
public static partial class Tests
{
public static byte[][] BarcodeStyles(ICommandEmitter e) => new byte[][] {
//TODO: test all widths and put bar in front in label
e.PrintLine("Thinnest Width:"),
e.SetBarcodeHeightInDots(300),
e.SetBarWidth(BarWidth.Thinnest),
e.PrintBarcode(BarcodeType.UPC_A, "012345678905"),
e.PrintLine("Thin Width:"),
e.SetBarcodeHeightInDots(300),
e.SetBarWidth(BarWidth.Thin),
e.PrintBarcode(BarcodeType.UPC_A, "012345678905"),
e.PrintLine("Default Width:"),
e.SetBarcodeHeightInDots(300),
e.SetBarWidth(BarWidth.Default),
e.PrintBarcode(BarcodeType.UPC_A, "012345678905"),
e.PrintLine("Thicker Width:"),
e.SetBarcodeHeightInDots(300),
e.SetBarWidth(BarWidth.Thick),
e.PrintBarcode(BarcodeType.UPC_A, "012345678905"),
e.PrintLine("Thickest Width:"),
e.SetBarcodeHeightInDots(300),
e.SetBarWidth(BarWidth.Thickest),
e.PrintBarcode(BarcodeType.UPC_A, "012345678905"),
e.PrintLine("Short (50 dots):"),
e.SetBarcodeHeightInDots(50),
e.SetBarWidth(BarWidth.Default),
e.PrintBarcode(BarcodeType.UPC_A, "012345678905"),
e.PrintLine("Tall (255 dots):"),
e.SetBarcodeHeightInDots(255),
e.PrintBarcode(BarcodeType.UPC_A, "012345678905"),
e.PrintLine("Label Above:"),
e.SetBarcodeHeightInDots(50),
e.SetBarLabelPosition(BarLabelPrintPosition.Above),
e.PrintBarcode(BarcodeType.UPC_A, "012345678905"),
e.PrintLine("Label Above and Below:"),
e.SetBarLabelPosition(BarLabelPrintPosition.Both),
e.PrintBarcode(BarcodeType.UPC_A, "012345678905"),
e.PrintLine("Label Below:"),
e.SetBarLabelPosition(BarLabelPrintPosition.Below),
e.PrintBarcode(BarcodeType.UPC_A, "012345678905"),
e.PrintLine("Font B Label Below:"),
e.SetBarLabelFontB(true),
e.PrintBarcode(BarcodeType.UPC_A, "012345678905")
};
}
}

Some files were not shown because too many files have changed in this diff Show More