Files
point_of_sale_v2/PointOfSale/Pos.Service/SalesTransactionService.cs
2026-07-30 10:11:54 +02:00

172 lines
5.3 KiB
C#

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