Move logic out the the class libs

This commit is contained in:
Alex Pedersen
2026-07-30 10:11:54 +02:00
parent 97688542fe
commit 6df185d519
21 changed files with 44 additions and 99 deletions

View File

@@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Database.Models;
using Database.Repository;
namespace Pos.Service
{
public static class CacheService
{
private static bool _invalidEmployee = false;
private static bool _invalidProductGroup = false;
private static List<EmployeeEntity> _employees = new();
private static List<ProductGroupEntity> _productGroups = new();
public static void Invalidate()
{
_invalidEmployee = true;
_invalidProductGroup = true;
}
public static ObservableCollection<EmployeeEntity> GetEmployee()
{
ObservableCollection<EmployeeEntity> obsEmployees = new ObservableCollection<EmployeeEntity>();
if (_invalidEmployee)
{
EmployeeRepository employeeRepository = new EmployeeRepository();
_employees = employeeRepository.GetAll();
foreach (EmployeeEntity employee in _employees)
{
obsEmployees.Add(employee);
}
_invalidEmployee = false;
}
return obsEmployees;
}
public static ObservableCollection<ProductGroupEntity> GetProductGroupsIncludeProducts()
{
ObservableCollection<ProductGroupEntity> obsProductGroups = new ObservableCollection<ProductGroupEntity>();
if(_invalidProductGroup)
{
ProductGroupRepository productGroupRepository = new ProductGroupRepository();
_productGroups = productGroupRepository.GetAll();
_invalidProductGroup = false;
foreach (ProductGroupEntity productGroup in _productGroups)
{
obsProductGroups.Add(productGroup);
}
}
return obsProductGroups;
}
}
}

View File

@@ -6,4 +6,14 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.10" />
<PackageReference Include="RestSharp" Version="108.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Pos.Models\Pos.Models.csproj" />
<ProjectReference Include="..\Pos.Ui\Database\Database.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,305 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Database.Models;
using Database.Repository;
using Microsoft.Extensions.Configuration;
using Pos.Json;
using Pos.Models;
using RestSharp;
using BodyModel = Pos.Json.BodyModel;
using FooterModel = Pos.Json.FooterModel;
using HeaderModel = Pos.Json.HeaderModel;
namespace Pos.Service
{
public class SaleService
{
private float _totalPrice = 0;
private SaleRepository _saleRepository = new SaleRepository();
public void Save(List<SaleGridModel> saleGridModels, List<AmountGridModel> amountGridModels, int employeeNo)
{
SaleRepository saleRepository = new SaleRepository();
SaleEntity saleEntity = saleRepository.New(employeeNo);
foreach (SaleGridModel model in saleGridModels)
{
if(String.IsNullOrEmpty(model.Navn))
continue;
SaleLineEntity saleLineEntity = new SaleLineEntity();
saleLineEntity.SaleId = saleEntity.Id;
saleLineEntity.Price = model.SaleGridInternalModel.InternPrice;
saleLineEntity.Product = model.Navn;
saleLineEntity.Pieces = model.SaleGridInternalModel.InternPieces;
saleLineEntity.Total = saleLineEntity.Pieces * saleLineEntity.Price;
saleRepository.SaveSaleLine(saleLineEntity);
}
foreach (AmountGridModel model in amountGridModels)
{
PaymentEntity payment = new PaymentEntity();
payment.Amount = model.Amount;
payment.Type = model.PaymentMethodText;
payment.SaleId = saleEntity.Id;
saleRepository.SavePayment(payment);
}
}
public void PrintReceipt()
{
SaleEntity sale = _saleRepository.GetLatest();
if (sale == null)
return;
BuildSaleReceipt(sale);
}
public void PrintReceipt(int saleId)
{
SaleEntity sale = _saleRepository.Get(saleId);
BuildSaleReceipt(sale);
}
public void PrintSaleOfDay(DateTime selectedDate, TotalSaleDetail totalSaleDetail)
{
SaleOfDayModel saleOfDayModel = new SaleOfDayModel();
saleOfDayModel.Date = selectedDate;
saleOfDayModel.TotalSale = totalSaleDetail.TotalSale;
saleOfDayModel.TotalCustomers = totalSaleDetail.TotalCustomer;
foreach (TotalSaleCategory category in totalSaleDetail.TotalSaleCategories)
{
SaleOfDayDetail saleOfDayDetail = new SaleOfDayDetail();
saleOfDayDetail.TotalSale = category.Sale;
saleOfDayDetail.Category = category.Category;
saleOfDayModel.SaleOfDayDetail.Add(saleOfDayDetail);
}
LoadConfig loadConfig = new LoadConfig();
IConfiguration config = loadConfig.ByEnvironment();
string url = config["API:URL"]
?? throw new InvalidOperationException(
"API:URL mangler i konfigurationen.");
RestClient restClient = new RestClient($"{url}/api/PosPrinter/SaleOfDay");
RestRequest request = new RestRequest();
request.AddJsonBody(saleOfDayModel);
request.Method = Method.Post;
EnsurePrintSucceeded(restClient.Post(request));
}
public TotalSaleDetail TotalSale(DateTime selectedDate)
{
List<SaleEntity> sales = _saleRepository.GetByDateRange(selectedDate);
TotalSaleDetail totalSaleDetail = new TotalSaleDetail();
totalSaleDetail.TotalCustomer = sales.Count;
foreach (SaleEntity sale in sales)
{
List<SaleLineEntity> saleLineBySaleId = _saleRepository.GetSaleLineBySaleId(sale.Id);
foreach (SaleLineEntity saleLine in saleLineBySaleId)
{
if (totalSaleDetail.TotalSaleCategories.Any(c => c.Category.Contains(saleLine.Product)))
{
TotalSaleCategory totalSaleCategory = totalSaleDetail.TotalSaleCategories.Single(c => c.Category.Contains(saleLine.Product));
totalSaleCategory.Sale += saleLine.Total;
totalSaleCategory.SaleString = "Kr. " + totalSaleCategory.Sale.ToString("0.00");
}
else
{
TotalSaleCategory totalSaleCategory = new TotalSaleCategory();
totalSaleCategory.Sale = saleLine.Total;
totalSaleCategory.SaleString ="Kr. " + totalSaleCategory.Sale.ToString("0.00");
totalSaleCategory.Category = saleLine.Product;
totalSaleDetail.TotalSaleCategories.Add(totalSaleCategory);
}
totalSaleDetail.TotalSale += saleLine.Total;
}
}
return totalSaleDetail;
}
public string LastAndTotalSale()
{
SaleEntity saleEntity = _saleRepository.GetLatest();
List<SaleLineEntity> saleLineBySaleId = _saleRepository.GetSaleLineBySaleId(saleEntity.Id);
decimal lastSale = 0;
foreach (SaleLineEntity saleLine in saleLineBySaleId)
{
lastSale += saleLine.Total;
}
TotalSaleDetail totalSale = TotalSale(DateTime.Now);
string todaySale = $"Sidste salg: kr. {lastSale.ToString("N")} - Dagens salg: kr. {totalSale.TotalSale.ToString("N")}";
return todaySale;
}
private void BuildSaleReceipt(SaleEntity sale)
{
EmployeeRepository employeeRepository = new EmployeeRepository();
EmployeeEntity employee = employeeRepository.Get(sale.EmployeeId);
List<SaleLineEntity> salesLines = _saleRepository.GetSaleLineBySaleId(sale.Id);
List<PaymentEntity> payments = _saleRepository.GetPaymentBySaleId(sale.Id);
PosReceipt posReceipt = new PosReceipt();
posReceipt.header = BuildReceiptHeader().ToArray();
ProductModel[] productModels = BuildSaleLines(salesLines).ToArray();
posReceipt.bodyModel = BuildBody(sale, employee.Name);
posReceipt.bodyModel.products = productModels;
posReceipt.footer = BuildFooter().ToArray();
posReceipt.logoBase64 = String.Empty;
LoadConfig loadConfig = new LoadConfig();
IConfiguration config = loadConfig.ByEnvironment();
string apiUrl = config["API:URL"]
?? throw new InvalidOperationException(
"API:URL mangler i konfigurationen.");
string url = $"{apiUrl}/api/PosPrinter/Receipt";
RestClient restClient = new RestClient(url);
RestRequest request = new RestRequest();
request.AddJsonBody(posReceipt);
request.Method = Method.Post;
EnsurePrintSucceeded(restClient.Post(request));
}
private static void EnsurePrintSucceeded(RestResponse response)
{
if (response.IsSuccessful)
return;
string message = response.ErrorMessage ?? response.Content ?? "Ukendt fejl";
throw new InvalidOperationException(
$"Printer-API kald fejlede ({(int)response.StatusCode} {response.StatusCode}): {message}",
response.ErrorException);
}
private List<FooterModel> BuildFooter()
{
List<FooterModel> footerModels = new List<FooterModel>();
FooterModel model = new FooterModel();
model.value = "Tak for handlen";
model.printStyles = new PrintStylesModel
{
bold = false,
fontB = false,
doubleHeight = false,
doubleWidth = false,
underline = false
};
model.feedingLines = 0;
model.textAlignment = 0;
footerModels.Add(model);
return footerModels;
}
private List<ProductModel> BuildSaleLines(List<SaleLineEntity> salesLines)
{
List<ProductModel> products = new List<ProductModel>();
foreach (SaleLineEntity salesLine in salesLines)
{
ProductModel productModel = new ProductModel();
productModel.noOfProduct = salesLine.Pieces.ToString();
productModel.price = (float)salesLine.Price;
productModel.totalPrice = salesLine.Pieces * productModel.price;
_totalPrice += productModel.totalPrice;
productModel.product = salesLine.Product;
products.Add(productModel);
}
return products;
}
private BodyModel BuildBody(SaleEntity sale, string employeeName)
{
BodyModel body = new BodyModel
{
receiptNumber = sale.Id,
receiptTime = sale.Time.ToString("HH:mm dd-MM-yyyy"),
staff = employeeName,
totalPrice = _totalPrice,
totalVat = (_totalPrice / 100) * 25
};
return body;
}
private List<HeaderModel> BuildReceiptHeader()
{
List<HeaderModel> headerModels = new List<HeaderModel>();
HeaderModel headerModel = new HeaderModel
{
feedingLines = 1,
textAlignment = 1,
value = "Blomster Til Alt"
};
PrintStylesModel printStylesModel = new PrintStylesModel
{
bold = true,
fontB = false,
doubleHeight = true,
doubleWidth = true,
underline = false
};
headerModel.printStyles = printStylesModel;
headerModels.Add(headerModel);
printStylesModel = new PrintStylesModel
{
bold = false,
fontB = false,
doubleHeight = false,
doubleWidth = false,
underline = false
};
headerModel = new HeaderModel
{
feedingLines = 0,
textAlignment = 1,
value = "Adelgade 91",
printStyles = printStylesModel
};
headerModels.Add(headerModel);
headerModel = new HeaderModel
{
feedingLines = 0,
textAlignment = 1,
value = "5400 Bogense",
printStyles = printStylesModel
};
headerModels.Add(headerModel);
headerModel = new HeaderModel
{
feedingLines = 0,
textAlignment = 1,
value = "Tlf: 41 82 71 66",
printStyles = printStylesModel
};
headerModels.Add(headerModel);
headerModel = new HeaderModel
{
feedingLines = 0,
textAlignment = 1,
value = "CVR: 37 14 44 36",
printStyles = printStylesModel
};
headerModels.Add(headerModel);
return headerModels;
}
}
}

View File

@@ -0,0 +1,171 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Pos.Models;
namespace Pos.Service
{
/// <summary>
/// Holder styr på den igangværende handel og dens beregninger.
/// Servicen har ingen afhængighed til WPF eller web.
/// </summary>
public class SalesTransactionService
{
private readonly List<PriceLineModel> _saleLines = new List<PriceLineModel>();
private readonly List<AmountGridModel> _payments = new List<AmountGridModel>();
public IReadOnlyList<PriceLineModel> SaleLines => _saleLines;
public IReadOnlyList<AmountGridModel> Payments => _payments;
public decimal TotalPrice =>
_saleLines.Sum(line =>
CalculateLineTotal(line.Price, line.NumberProducts));
public decimal AmountPaid =>
_payments.Sum(payment => payment.Amount);
public decimal RemainingAmount =>
Math.Max(TotalPrice - AmountPaid, 0);
public decimal Change =>
Math.Max(AmountPaid - TotalPrice, 0);
public bool IsFullyPaid =>
TotalPrice > 0 && AmountPaid >= TotalPrice;
/// <summary>
/// Beregner totalen for en enkelt salgslinje.
/// Parsing og visning af resultatet er fortsat UI-ansvar.
/// </summary>
public decimal CalculateLineTotal(decimal price, int numberOfProducts)
{
ValidatePriceAndQuantity(price, numberOfProducts);
return price * numberOfProducts;
}
public PriceLineModel AddSaleLine(
int id,
string name,
int numberOfProducts,
decimal price,
bool isProductGroup)
{
if (id <= 0)
throw new ArgumentOutOfRangeException(
nameof(id),
"Id skal være større end 0.");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException(
"Navn skal være udfyldt.",
nameof(name));
ValidatePriceAndQuantity(price, numberOfProducts);
PriceLineModel saleLine = new PriceLineModel
{
Id = id,
Name = name,
NumberProducts = numberOfProducts,
Price = price,
IsProductGroup = isProductGroup
};
_saleLines.Add(saleLine);
return saleLine;
}
public void RemoveSaleLineAt(int index)
{
if (index < 0 || index >= _saleLines.Count)
throw new ArgumentOutOfRangeException(nameof(index));
_saleLines.RemoveAt(index);
}
/// <summary>
/// Registrerer en betaling. Hvis amount er null, betales restbeløbet.
/// </summary>
public AmountGridModel AddPayment(
PaymentMethod paymentMethod,
decimal? amount = null)
{
if (_saleLines.Count == 0)
throw new InvalidOperationException(
"Der kan ikke registreres betaling på et tomt salg.");
decimal paymentAmount = amount ?? RemainingAmount;
if (paymentAmount <= 0)
throw new ArgumentOutOfRangeException(
nameof(amount),
"Betalingsbeløbet skal være større end 0.");
AmountGridModel payment = new AmountGridModel
{
Amount = paymentAmount,
PaymentMethodText = GetPaymentMethodText(paymentMethod)
};
_payments.Add(payment);
return payment;
}
public void RemovePaymentAt(int index)
{
if (index < 0 || index >= _payments.Count)
throw new ArgumentOutOfRangeException(nameof(index));
_payments.RemoveAt(index);
}
public void EnsureCanComplete()
{
if (_saleLines.Count == 0)
throw new InvalidOperationException(
"Et tomt salg kan ikke afsluttes.");
if (!IsFullyPaid)
throw new InvalidOperationException(
"Salget er ikke fuldt betalt.");
}
public void Reset()
{
_saleLines.Clear();
_payments.Clear();
}
private static void ValidatePriceAndQuantity(
decimal price,
int numberOfProducts)
{
if (price < 0)
throw new ArgumentOutOfRangeException(
nameof(price),
"Prisen må ikke være negativ.");
if (numberOfProducts <= 0)
throw new ArgumentOutOfRangeException(
nameof(numberOfProducts),
"Antal skal være større end 0.");
}
private static string GetPaymentMethodText(PaymentMethod paymentMethod)
{
switch (paymentMethod)
{
case PaymentMethod.Card:
return "Kort";
case PaymentMethod.Cash:
return "Kontant";
case PaymentMethod.GiftCard:
return "Gavekort";
case PaymentMethod.MobilePay:
return "MobilePay";
default:
throw new ArgumentOutOfRangeException(nameof(paymentMethod));
}
}
}
}