moved logic out of SalesWindows.xaml.cs to SaleTransactionServce

This commit is contained in:
Alex Pedersen
2026-07-30 09:41:47 +02:00
parent 236eb6792c
commit 97688542fe
3 changed files with 289 additions and 81 deletions

View File

@@ -17,6 +17,16 @@ namespace Database
public DbSet<SaleLineEntity> SalesLines { get; set; } public DbSet<SaleLineEntity> SalesLines { get; set; }
public DbSet<PaymentEntity> Payment { get; set; } public DbSet<PaymentEntity> Payment { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<SaleLineEntity>()
.Property(saleLine => saleLine.Pieces)
.HasConversion<short>()
.HasColumnType("smallint");
base.OnModelCreating(modelBuilder);
}
protected override void OnConfiguring( protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder) DbContextOptionsBuilder optionsBuilder)
{ {

View File

@@ -29,7 +29,8 @@ namespace Pos
private ObservableCollection<ProductGroupEntity> _productGroups; private ObservableCollection<ProductGroupEntity> _productGroups;
private ObservableCollection<SaleGridModel> _saleGridModels; private ObservableCollection<SaleGridModel> _saleGridModels;
private ObservableCollection<AmountGridModel> _paymentGridModels; private ObservableCollection<AmountGridModel> _paymentGridModels;
private List<PriceLineModel> _priceLineModels; private readonly SalesTransactionService _salesTransactionService =
new SalesTransactionService();
private ProductEntity _selectedProduct; private ProductEntity _selectedProduct;
private ProductGroupEntity _selectedProductGroup; private ProductGroupEntity _selectedProductGroup;
@@ -37,14 +38,10 @@ namespace Pos
private int _employeeId = -1; private int _employeeId = -1;
private int _productGroupId = -1; private int _productGroupId = -1;
private int _productId = -1; private int _productId = -1;
private int _numberOfProducts = 1;
private int _selectedSaleGridIndex = -1; private int _selectedSaleGridIndex = -1;
private int _maxCharOfName = 0; private int _maxCharOfName = 0;
private decimal _price = 0;
private decimal _totalPrice = 0;
private Button _prevEmpButton = new Button(); private Button _prevEmpButton = new Button();
private Button _prevProductGroupButton = new Button(); private Button _prevProductGroupButton = new Button();
private Button _prevProduct = new Button(); private Button _prevProduct = new Button();
@@ -61,7 +58,6 @@ namespace Pos
_employees = CacheService.GetEmployee() as ObservableCollection<EmployeeEntity>; _employees = CacheService.GetEmployee() as ObservableCollection<EmployeeEntity>;
_productGroups = CacheService.GetProductGroupsIncludeProducts() as ObservableCollection<ProductGroupEntity>; _productGroups = CacheService.GetProductGroupsIncludeProducts() as ObservableCollection<ProductGroupEntity>;
_saleGridModels = new ObservableCollection<SaleGridModel>(); _saleGridModels = new ObservableCollection<SaleGridModel>();
_priceLineModels = new List<PriceLineModel>();
_paymentGridModels = new ObservableCollection<AmountGridModel>(); _paymentGridModels = new ObservableCollection<AmountGridModel>();
SaleGrid.ItemsSource = _saleGridModels; SaleGrid.ItemsSource = _saleGridModels;
PaymentGrid.ItemsSource = _paymentGridModels; PaymentGrid.ItemsSource = _paymentGridModels;
@@ -150,11 +146,25 @@ namespace Pos
return; return;
decimal price = Convert.ToDecimal(Price.Text); decimal price = Convert.ToDecimal(Price.Text);
int number = Convert.ToInt32(NumberOfProducts.Text); int numberOfProducts = Convert.ToInt32(NumberOfProducts.Text);
try
{
BuildPrice(numberOfProducts, price);
}
catch (ArgumentException exception)
{
MessageBox.Show(
exception.Message,
"Ugyldig salgslinje",
MessageBoxButton.OK,
MessageBoxImage.Error);
return;
}
NumberOfProducts.Text = "1"; NumberOfProducts.Text = "1";
Price.Text = string.Empty; Price.Text = string.Empty;
TotalPrice.Content = "0"; TotalPrice.Content = "0";
BuildPrice(false);
_productId = -1; _productId = -1;
_productGroupId = -1; _productGroupId = -1;
ManipulateBuySection(); ManipulateBuySection();
@@ -162,16 +172,36 @@ namespace Pos
private void Payment_Click(object sender, RoutedEventArgs e) private void Payment_Click(object sender, RoutedEventArgs e)
{ {
//bool isValid = ValidateAmountInput(); decimal? amount = null;
//if (!isValid)
// return;
decimal amount = -1;
if (!string.IsNullOrEmpty(Amount.Text)) if (!string.IsNullOrEmpty(Amount.Text))
{
bool isValid = ValidateAmountInput();
if (!isValid)
return;
amount = Convert.ToDecimal(Amount.Text); amount = Convert.ToDecimal(Amount.Text);
}
Amount.Text = string.Empty; Amount.Text = string.Empty;
Button button = sender as Button; Button button = sender as Button;
BuildAmuntDataGrid(amount,(string)button.Tag); string paymentMethodName =
((string)button.Tag).Replace("PaymentMethod.", string.Empty);
if (!Enum.TryParse(paymentMethodName, out PaymentMethod paymentMethod))
return;
try
{
BuildAmuntDataGrid(amount, paymentMethod);
}
catch (ArgumentException exception)
{
MessageBox.Show(
exception.Message,
"Ugyldig betaling",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
} }
private void NumberOfProducts_LostFocus(object sender, RoutedEventArgs e) private void NumberOfProducts_LostFocus(object sender, RoutedEventArgs e)
@@ -249,61 +279,69 @@ namespace Pos
} }
} }
private void BuildPrice(bool returnProduct) private void BuildPrice(int numberOfProducts, decimal price)
{ {
PriceLineModel pModel = new PriceLineModel(); PriceLineModel priceLineModel;
pModel.NumberProducts = _numberOfProducts;
pModel.Price = _price;
if (_selectedProduct != null) if (_selectedProduct != null)
{ {
pModel.Id = _selectedProduct.Id; priceLineModel = _salesTransactionService.AddSaleLine(
pModel.Name = _selectedProduct.Name; _selectedProduct.Id,
pModel.IsProductGroup = false; _selectedProduct.Name,
numberOfProducts,
price,
false);
} }
else else
{ {
pModel.Id = _selectedProductGroup.Id; priceLineModel = _salesTransactionService.AddSaleLine(
pModel.Name = _selectedProductGroup.Name; _selectedProductGroup.Id,
pModel.IsProductGroup = true; _selectedProductGroup.Name,
numberOfProducts,
price,
true);
} }
BuildPriceDataGrid(pModel); BuildPriceDataGrid(priceLineModel);
//BuildEmployee(); //BuildEmployee();
BuildProductGroup(); BuildProductGroup();
} }
private void CalculateSubPrice() private void CalculateSubPrice()
{ {
_price = 0; bool isValid = decimal.TryParse(Price.Text, out decimal price);
bool isValid = decimal.TryParse(Price.Text, out _price);
if (!isValid) if (!isValid)
return; return;
isValid = Int32.TryParse(NumberOfProducts.Text, out _numberOfProducts); isValid = Int32.TryParse(
NumberOfProducts.Text,
out int numberOfProducts);
if (!isValid) if (!isValid)
return; return;
TotalPrice.Content = _price * _numberOfProducts; try
{
TotalPrice.Content =
_salesTransactionService.CalculateLineTotal(
price,
numberOfProducts);
}
catch (ArgumentOutOfRangeException)
{
TotalPrice.Content = "0";
}
} }
private void CalculateTotalLine() private void CalculateTotalLine()
{ {
_totalPrice = 0; Total.Content =
foreach (PriceLineModel model in _priceLineModels) $"At betale: Kr. {_salesTransactionService.TotalPrice}";
{
_totalPrice += model.Price * model.NumberProducts;
}
Total.Content = $"At betale: Kr. {_totalPrice}";
} }
private void BuildPriceDataGrid(PriceLineModel priceLineModel) private void BuildPriceDataGrid(PriceLineModel priceLineModel)
{ {
_selectedProduct = null; _selectedProduct = null;
_selectedProductGroup = null; _selectedProductGroup = null;
_numberOfProducts = 1;
_priceLineModels.Add(priceLineModel);
CalculateTotalLine(); CalculateTotalLine();
SaleGridModel model = new SaleGridModel(); SaleGridModel model = new SaleGridModel();
@@ -399,38 +437,14 @@ namespace Pos
TotalPrice.IsEnabled = enabled; TotalPrice.IsEnabled = enabled;
} }
private void BuildAmuntDataGrid(decimal amount, string paymentMethod) private void BuildAmuntDataGrid(
decimal? amount,
PaymentMethod paymentMethod)
{ {
if (amount == -1) AmountGridModel amountGridModel =
{ _salesTransactionService.AddPayment(paymentMethod, amount);
amount = 0;
foreach (PriceLineModel lineModel in _priceLineModels)
{
amount += (lineModel.Price * lineModel.NumberProducts);
}
}
AmountGridModel amountGridModel = new AmountGridModel(); amountGridModel.AmountText = $"Kr. {amountGridModel.Amount}";
amountGridModel.Amount = amount;
amountGridModel.AmountText = $"Kr. {amount}";
switch (paymentMethod)
{
case "PaymentMethod.Card":
amountGridModel.PaymentMethodText = "Kort";
break;
case "PaymentMethod.Cash":
amountGridModel.PaymentMethodText = "Kontant";
break;
case "PaymentMethod.GiftCard":
amountGridModel.PaymentMethodText = "Gavekort";
break;
case "PaymentMethod.MobilePay":
amountGridModel.PaymentMethodText = "MobilePay";
break;
}
_paymentGridModels.Add(amountGridModel); _paymentGridModels.Add(amountGridModel);
AmountPayed(); AmountPayed();
@@ -438,28 +452,25 @@ namespace Pos
private void AmountPayed() private void AmountPayed()
{ {
decimal amountPayed = 0; if (_salesTransactionService.AmountPaid ==
foreach (AmountGridModel paymentGridModel in _paymentGridModels) _salesTransactionService.TotalPrice)
{
amountPayed += paymentGridModel.Amount;
}
if (amountPayed == _totalPrice)
{ {
btnFinish_Click(null,null); btnFinish_Click(null,null);
return; return;
} }
if (amountPayed > _totalPrice) if (_salesTransactionService.Change > 0)
{ {
lblRecived.Content = $"Byttepenge: Kr. {amountPayed - _totalPrice}"; lblRecived.Content =
$"Byttepenge: Kr. {_salesTransactionService.Change}";
Style style = this.FindResource("BoldLabelWarningNoMargin") as Style; Style style = this.FindResource("BoldLabelWarningNoMargin") as Style;
lblRecived.Style = style; lblRecived.Style = style;
btnFinish.IsEnabled = true; btnFinish.IsEnabled = true;
} }
else else
{ {
lblRecived.Content = $"At betale: Kr. {_totalPrice - amountPayed}"; lblRecived.Content =
$"At betale: Kr. {_salesTransactionService.RemainingAmount}";
Style style = this.FindResource("BoldLabelNoMargin") as Style; Style style = this.FindResource("BoldLabelNoMargin") as Style;
lblRecived.Style = style; lblRecived.Style = style;
} }
@@ -473,21 +484,37 @@ namespace Pos
if (_selectedSaleGridIndex == -1) if (_selectedSaleGridIndex == -1)
return; return;
int index = _selectedSaleGridIndex; int index = _selectedSaleGridIndex;
//_saleGridModels.RemoveAt(_selectedSaleGridIndex); _salesTransactionService.RemoveSaleLineAt(index);
_priceLineModels.RemoveAt(index); _saleGridModels.RemoveAt(index);
_selectedSaleGridIndex = -1; _selectedSaleGridIndex = -1;
CalculateTotalLine(); CalculateTotalLine();
if (_salesTransactionService.AmountPaid > 0)
AmountPayed();
} }
} }
private void btnFinish_Click(object sender, RoutedEventArgs e) private void btnFinish_Click(object sender, RoutedEventArgs e)
{ {
try
{
_salesTransactionService.EnsureCanComplete();
}
catch (InvalidOperationException exception)
{
MessageBox.Show(
exception.Message,
"Salget kan ikke afsluttes",
MessageBoxButton.OK,
MessageBoxImage.Error);
return;
}
SaleService saleService = new SaleService(); SaleService saleService = new SaleService();
saleService.Save(_saleGridModels.ToList(),_paymentGridModels.ToList(),_employeeId); saleService.Save(_saleGridModels.ToList(),_paymentGridModels.ToList(),_employeeId);
_paymentGridModels.Clear(); _paymentGridModels.Clear();
_priceLineModels.Clear(); _salesTransactionService.Reset();
UpdateWindowTitle(); UpdateWindowTitle();
_totalPrice = 0;
_saleGridModels.Clear(); _saleGridModels.Clear();
lblRecived.Content = string.Empty; lblRecived.Content = string.Empty;
_selectedSaleGridIndex = -1; _selectedSaleGridIndex = -1;

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