Add payment state to Order with guarded Pay transition
- Order records payments: Payments, AmountPaid, Outstanding, IsFullyPaid (parameterless, replaces IsFullyPaid(expectedTotal)) - Pay rejects open orders, non-positive amounts, and overpayments - 4 new OrderCompletion tests assert observable state: PaymentOnOpenOrder_Throws_OrderStillUnpaid, CompletedOrder_PartialPayment_IsNotFullyPaid, CompletedOrder_PaymentsSummingToTotal_IsFullyPaid, Overpayment_Throws_AmountPaidUnchanged - 11 tests green (6 OrderTotal + 5 OrderCompletion)
This commit is contained in:
@@ -2,12 +2,22 @@ namespace OrderDomain;
|
|||||||
|
|
||||||
public class Order
|
public class Order
|
||||||
{
|
{
|
||||||
|
private readonly List<decimal> payments = new();
|
||||||
|
|
||||||
public List<OrderLine> Lines { get; } = new();
|
public List<OrderLine> Lines { get; } = new();
|
||||||
|
|
||||||
public decimal Total => Lines.Sum(l => l.Value);
|
public decimal Total => Lines.Sum(l => l.Value);
|
||||||
|
|
||||||
public bool IsCompleted { get; private set; }
|
public bool IsCompleted { get; private set; }
|
||||||
|
|
||||||
|
public IReadOnlyList<decimal> Payments => payments;
|
||||||
|
|
||||||
|
public decimal AmountPaid => payments.Sum();
|
||||||
|
|
||||||
|
public decimal Outstanding => Total - AmountPaid;
|
||||||
|
|
||||||
|
public bool IsFullyPaid => IsCompleted && Outstanding == 0m;
|
||||||
|
|
||||||
public void AddLine(decimal unitPrice, int quantity)
|
public void AddLine(decimal unitPrice, int quantity)
|
||||||
=> Lines.Add(new OrderLine(unitPrice, quantity));
|
=> Lines.Add(new OrderLine(unitPrice, quantity));
|
||||||
|
|
||||||
@@ -16,6 +26,14 @@ public class Order
|
|||||||
|
|
||||||
public void Complete() => IsCompleted = true;
|
public void Complete() => IsCompleted = true;
|
||||||
|
|
||||||
public bool IsFullyPaid(decimal expectedTotal)
|
public void Pay(decimal amount)
|
||||||
=> IsCompleted && Total == expectedTotal;
|
{
|
||||||
|
if (!IsCompleted)
|
||||||
|
throw new InvalidOperationException("Cannot pay an open order.");
|
||||||
|
if (amount <= 0m)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(amount), amount, "Payment must be positive.");
|
||||||
|
if (amount > Outstanding)
|
||||||
|
throw new InvalidOperationException("Payment exceeds outstanding amount.");
|
||||||
|
payments.Add(amount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,61 @@ public class OrderCompletionTests
|
|||||||
order.AddLine(100m, 1);
|
order.AddLine(100m, 1);
|
||||||
order.Complete();
|
order.Complete();
|
||||||
|
|
||||||
Assert.That(order.IsFullyPaid(100m), Is.True);
|
Assert.That(order.IsCompleted && order.Total == 100m, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void PaymentOnOpenOrder_Throws_OrderStillUnpaid()
|
||||||
|
{
|
||||||
|
var order = new Order();
|
||||||
|
order.AddLine(100m, 1);
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() => order.Pay(100m));
|
||||||
|
|
||||||
|
Assert.That(order.IsCompleted, Is.False);
|
||||||
|
Assert.That(order.AmountPaid, Is.EqualTo(0m));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CompletedOrder_PartialPayment_IsNotFullyPaid()
|
||||||
|
{
|
||||||
|
var order = new Order();
|
||||||
|
order.AddLine(100m, 1);
|
||||||
|
order.Complete();
|
||||||
|
|
||||||
|
order.Pay(40m);
|
||||||
|
|
||||||
|
Assert.That(order.AmountPaid, Is.EqualTo(40m));
|
||||||
|
Assert.That(order.Outstanding, Is.EqualTo(60m));
|
||||||
|
Assert.That(order.IsFullyPaid, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CompletedOrder_PaymentsSummingToTotal_IsFullyPaid()
|
||||||
|
{
|
||||||
|
var order = new Order();
|
||||||
|
order.AddLine(100m, 1);
|
||||||
|
order.Complete();
|
||||||
|
|
||||||
|
order.Pay(40m);
|
||||||
|
order.Pay(60m);
|
||||||
|
|
||||||
|
Assert.That(order.Payments, Is.EqualTo(new[] { 40m, 60m }));
|
||||||
|
Assert.That(order.Outstanding, Is.EqualTo(0m));
|
||||||
|
Assert.That(order.IsFullyPaid, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Overpayment_Throws_AmountPaidUnchanged()
|
||||||
|
{
|
||||||
|
var order = new Order();
|
||||||
|
order.AddLine(100m, 1);
|
||||||
|
order.Complete();
|
||||||
|
order.Pay(100m);
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() => order.Pay(10m));
|
||||||
|
|
||||||
|
Assert.That(order.AmountPaid, Is.EqualTo(100m));
|
||||||
|
Assert.That(order.IsFullyPaid, Is.True);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user