diff --git a/RefactorThis.Domain.Tests/InvoicePaymentProcessorTests.cs b/RefactorThis.Domain.Tests/InvoicePaymentProcessorTests.cs index 3a607fd..a9787df 100644 --- a/RefactorThis.Domain.Tests/InvoicePaymentProcessorTests.cs +++ b/RefactorThis.Domain.Tests/InvoicePaymentProcessorTests.cs @@ -1,247 +1,248 @@ using System; using System.Collections.Generic; using NUnit.Framework; +using NUnit.Framework.Legacy; using RefactorThis.Persistence; namespace RefactorThis.Domain.Tests { - [TestFixture] - public class InvoicePaymentProcessorTests - { - [Test] - public void ProcessPayment_Should_ThrowException_When_NoInoiceFoundForPaymentReference( ) - { - var repo = new InvoiceRepository( ); - - Invoice invoice = null; - var paymentProcessor = new InvoiceService( repo ); - - var payment = new Payment( ); - var failureMessage = ""; - - try - { - var result = paymentProcessor.ProcessPayment( payment ); - } - catch ( InvalidOperationException e ) - { - failureMessage = e.Message; - } - - Assert.AreEqual( "There is no invoice matching this payment", failureMessage ); - } - - [Test] - public void ProcessPayment_Should_ReturnFailureMessage_When_NoPaymentNeeded( ) - { - var repo = new InvoiceRepository( ); - - var invoice = new Invoice( repo ) - { - Amount = 0, - AmountPaid = 0, - Payments = null - }; - - repo.Add( invoice ); - - var paymentProcessor = new InvoiceService( repo ); - - var payment = new Payment( ); - - var result = paymentProcessor.ProcessPayment( payment ); - - Assert.AreEqual( "no payment needed", result ); - } - - [Test] - public void ProcessPayment_Should_ReturnFailureMessage_When_InvoiceAlreadyFullyPaid( ) - { - var repo = new InvoiceRepository( ); - - var invoice = new Invoice( repo ) - { - Amount = 10, - AmountPaid = 10, - Payments = new List - { - new Payment - { - Amount = 10 - } - } - }; - repo.Add( invoice ); - - var paymentProcessor = new InvoiceService( repo ); - - var payment = new Payment( ); - - var result = paymentProcessor.ProcessPayment( payment ); - - Assert.AreEqual( "invoice was already fully paid", result ); - } - - [Test] - public void ProcessPayment_Should_ReturnFailureMessage_When_PartialPaymentExistsAndAmountPaidExceedsAmountDue( ) - { - var repo = new InvoiceRepository( ); - var invoice = new Invoice( repo ) - { - Amount = 10, - AmountPaid = 5, - Payments = new List - { - new Payment - { - Amount = 5 - } - } - }; - repo.Add( invoice ); - - var paymentProcessor = new InvoiceService( repo ); - - var payment = new Payment( ) - { - Amount = 6 - }; - - var result = paymentProcessor.ProcessPayment( payment ); - - Assert.AreEqual( "the payment is greater than the partial amount remaining", result ); - } - - [Test] - public void ProcessPayment_Should_ReturnFailureMessage_When_NoPartialPaymentExistsAndAmountPaidExceedsInvoiceAmount( ) - { - var repo = new InvoiceRepository( ); - var invoice = new Invoice( repo ) - { - Amount = 5, - AmountPaid = 0, - Payments = new List( ) - }; - repo.Add( invoice ); - - var paymentProcessor = new InvoiceService( repo ); - - var payment = new Payment( ) - { - Amount = 6 - }; - - var result = paymentProcessor.ProcessPayment( payment ); - - Assert.AreEqual( "the payment is greater than the invoice amount", result ); - } - - [Test] - public void ProcessPayment_Should_ReturnFullyPaidMessage_When_PartialPaymentExistsAndAmountPaidEqualsAmountDue( ) - { - var repo = new InvoiceRepository( ); - var invoice = new Invoice( repo ) - { - Amount = 10, - AmountPaid = 5, - Payments = new List - { - new Payment - { - Amount = 5 - } - } - }; - repo.Add( invoice ); - - var paymentProcessor = new InvoiceService( repo ); - - var payment = new Payment( ) - { - Amount = 5 - }; - - var result = paymentProcessor.ProcessPayment( payment ); - - Assert.AreEqual( "final partial payment received, invoice is now fully paid", result ); - } - - [Test] - public void ProcessPayment_Should_ReturnFullyPaidMessage_When_NoPartialPaymentExistsAndAmountPaidEqualsInvoiceAmount( ) - { - var repo = new InvoiceRepository( ); - var invoice = new Invoice( repo ) - { - Amount = 10, - AmountPaid = 0, - Payments = new List( ) { new Payment( ) { Amount = 10 } } - }; - repo.Add( invoice ); - - var paymentProcessor = new InvoiceService( repo ); - - var payment = new Payment( ) - { - Amount = 10 - }; - - var result = paymentProcessor.ProcessPayment( payment ); - - Assert.AreEqual( "invoice was already fully paid", result ); - } - - [Test] - public void ProcessPayment_Should_ReturnPartiallyPaidMessage_When_PartialPaymentExistsAndAmountPaidIsLessThanAmountDue( ) - { - var repo = new InvoiceRepository( ); - var invoice = new Invoice( repo ) - { - Amount = 10, - AmountPaid = 5, - Payments = new List - { - new Payment - { - Amount = 5 - } - } - }; - repo.Add( invoice ); - - var paymentProcessor = new InvoiceService( repo ); - - var payment = new Payment( ) - { - Amount = 1 - }; - - var result = paymentProcessor.ProcessPayment( payment ); - - Assert.AreEqual( "another partial payment received, still not fully paid", result ); - } - - [Test] - public void ProcessPayment_Should_ReturnPartiallyPaidMessage_When_NoPartialPaymentExistsAndAmountPaidIsLessThanInvoiceAmount( ) - { - var repo = new InvoiceRepository( ); - var invoice = new Invoice( repo ) - { - Amount = 10, - AmountPaid = 0, - Payments = new List( ) - }; - repo.Add( invoice ); - - var paymentProcessor = new InvoiceService( repo ); - - var payment = new Payment( ) - { - Amount = 1 - }; - - var result = paymentProcessor.ProcessPayment( payment ); - - Assert.AreEqual( "invoice is now partially paid", result ); - } - } + [TestFixture] + public class InvoicePaymentProcessorTests + { + [Test] + public void ProcessPayment_Should_ThrowException_When_NoInoiceFoundForPaymentReference() + { + var repo = new InvoiceRepository(); + + Invoice invoice = null; + var paymentProcessor = new InvoiceService(repo); + + var payment = new Payment(); + var failureMessage = ""; + + try + { + var result = paymentProcessor.ProcessPayment(payment); + } + catch (InvalidOperationException e) + { + failureMessage = e.Message; + } + + ClassicAssert.AreEqual("There is no invoice matching this payment", failureMessage); + } + + [Test] + public void ProcessPayment_Should_ReturnFailureMessage_When_NoPaymentNeeded() + { + var repo = new InvoiceRepository(); + + var invoice = new Invoice(repo) + { + Amount = 0, + AmountPaid = 0, + Payments = null + }; + + repo.Add(invoice); + + var paymentProcessor = new InvoiceService(repo); + + var payment = new Payment(); + + var result = paymentProcessor.ProcessPayment(payment); + + ClassicAssert.AreEqual("no payment needed", result); + } + + [Test] + public void ProcessPayment_Should_ReturnFailureMessage_When_InvoiceAlreadyFullyPaid() + { + var repo = new InvoiceRepository(); + + var invoice = new Invoice(repo) + { + Amount = 10, + AmountPaid = 10, + Payments = new List + { + new Payment + { + Amount = 10 + } + } + }; + repo.Add(invoice); + + var paymentProcessor = new InvoiceService(repo); + + var payment = new Payment(); + + var result = paymentProcessor.ProcessPayment(payment); + + ClassicAssert.AreEqual("invoice was already fully paid", result); + } + + [Test] + public void ProcessPayment_Should_ReturnFailureMessage_When_PartialPaymentExistsAndAmountPaidExceedsAmountDue() + { + var repo = new InvoiceRepository(); + var invoice = new Invoice(repo) + { + Amount = 10, + AmountPaid = 5, + Payments = new List + { + new Payment + { + Amount = 5 + } + } + }; + repo.Add(invoice); + + var paymentProcessor = new InvoiceService(repo); + + var payment = new Payment() + { + Amount = 6 + }; + + var result = paymentProcessor.ProcessPayment(payment); + + ClassicAssert.AreEqual("the payment is greater than the partial amount remaining", result); + } + + [Test] + public void ProcessPayment_Should_ReturnFailureMessage_When_NoPartialPaymentExistsAndAmountPaidExceedsInvoiceAmount() + { + var repo = new InvoiceRepository(); + var invoice = new Invoice(repo) + { + Amount = 5, + AmountPaid = 0, + Payments = new List() + }; + repo.Add(invoice); + + var paymentProcessor = new InvoiceService(repo); + + var payment = new Payment() + { + Amount = 6 + }; + + var result = paymentProcessor.ProcessPayment(payment); + + ClassicAssert.AreEqual("the payment is greater than the invoice amount", result); + } + + [Test] + public void ProcessPayment_Should_ReturnFullyPaidMessage_When_PartialPaymentExistsAndAmountPaidEqualsAmountDue() + { + var repo = new InvoiceRepository(); + var invoice = new Invoice(repo) + { + Amount = 10, + AmountPaid = 5, + Payments = new List + { + new Payment + { + Amount = 5 + } + } + }; + repo.Add(invoice); + + var paymentProcessor = new InvoiceService(repo); + + var payment = new Payment() + { + Amount = 5 + }; + + var result = paymentProcessor.ProcessPayment(payment); + + ClassicAssert.AreEqual("final partial payment received, invoice is now fully paid", result); + } + + [Test] + public void ProcessPayment_Should_ReturnFullyPaidMessage_When_NoPartialPaymentExistsAndAmountPaidEqualsInvoiceAmount() + { + var repo = new InvoiceRepository(); + var invoice = new Invoice(repo) + { + Amount = 10, + AmountPaid = 0, + Payments = new List() { new Payment() { Amount = 10 } } + }; + repo.Add(invoice); + + var paymentProcessor = new InvoiceService(repo); + + var payment = new Payment() + { + Amount = 10 + }; + + var result = paymentProcessor.ProcessPayment(payment); + + ClassicAssert.AreEqual("invoice was already fully paid", result); + } + + [Test] + public void ProcessPayment_Should_ReturnPartiallyPaidMessage_When_PartialPaymentExistsAndAmountPaidIsLessThanAmountDue() + { + var repo = new InvoiceRepository(); + var invoice = new Invoice(repo) + { + Amount = 10, + AmountPaid = 5, + Payments = new List + { + new Payment + { + Amount = 5 + } + } + }; + repo.Add(invoice); + + var paymentProcessor = new InvoiceService(repo); + + var payment = new Payment() + { + Amount = 1 + }; + + var result = paymentProcessor.ProcessPayment(payment); + + ClassicAssert.AreEqual("another partial payment received, still not fully paid", result); + } + + [Test] + public void ProcessPayment_Should_ReturnPartiallyPaidMessage_When_NoPartialPaymentExistsAndAmountPaidIsLessThanInvoiceAmount() + { + var repo = new InvoiceRepository(); + var invoice = new Invoice(repo) + { + Amount = 10, + AmountPaid = 0, + Payments = new List() + }; + repo.Add(invoice); + + var paymentProcessor = new InvoiceService(repo); + + var payment = new Payment() + { + Amount = 1 + }; + + var result = paymentProcessor.ProcessPayment(payment); + + ClassicAssert.AreEqual("invoice is now partially paid", result); + } + } } \ No newline at end of file diff --git a/RefactorThis.Domain.Tests/RefactorThis.Domain.Tests.csproj b/RefactorThis.Domain.Tests/RefactorThis.Domain.Tests.csproj index f118007..adbbb38 100644 --- a/RefactorThis.Domain.Tests/RefactorThis.Domain.Tests.csproj +++ b/RefactorThis.Domain.Tests/RefactorThis.Domain.Tests.csproj @@ -1,67 +1,159 @@  - - - Debug - AnyCPU - {7971BDEC-EAD1-4FB8-A4F5-B1F67E4F6355} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Properties - RefactorThis.Domain.Tests - RefactorThis.Domain.Tests - v4.7.2 - 512 - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - ..\packages\NUnit.3.5.0\lib\net45\nunit.framework.dll - - - - - - - - - {5310b2fe-e26d-414e-b656-1f74c5a70368} - RefactorThis.Domain - - - {33cdc796-ff75-449c-9637-59c2efc46361} - RefactorThis.Persistence - - - - - - + \ No newline at end of file diff --git a/RefactorThis.Domain.Tests/packages.config b/RefactorThis.Domain.Tests/packages.config index c108d44..f9cf07b 100644 --- a/RefactorThis.Domain.Tests/packages.config +++ b/RefactorThis.Domain.Tests/packages.config @@ -1,4 +1,22 @@  - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/RefactorThis.Domain/InvoiceService.cs b/RefactorThis.Domain/InvoiceService.cs index fbd674c..a3ae5f4 100644 --- a/RefactorThis.Domain/InvoiceService.cs +++ b/RefactorThis.Domain/InvoiceService.cs @@ -1,149 +1,77 @@ using System; -using System.Linq; +using System.Collections.Generic; using RefactorThis.Persistence; namespace RefactorThis.Domain { - public class InvoiceService - { - private readonly InvoiceRepository _invoiceRepository; + public class InvoiceService + { + private readonly InvoiceRepository _invoiceRepository; - public InvoiceService( InvoiceRepository invoiceRepository ) - { - _invoiceRepository = invoiceRepository; - } + public InvoiceService(InvoiceRepository invoiceRepository) + { + _invoiceRepository = invoiceRepository; + } - public string ProcessPayment( Payment payment ) - { - var inv = _invoiceRepository.GetInvoice( payment.Reference ); + private string ValidateInvoice(Invoice invoice, Payment payment) + { + if (invoice == null) + { + throw new InvalidOperationException("There is no invoice matching this payment"); + } + if (invoice.Amount == 0 && invoice.HasPayments) + { + throw new InvalidOperationException("The invoice is in an invalid state, it has an amount of 0 and it has payments."); + } - var responseMessage = string.Empty; + List allowedInvoiceTypes = new List() { InvoiceType.Commercial, InvoiceType.Commercial }; + if (allowedInvoiceTypes.Contains(invoice.Type)) + { + throw new ArgumentOutOfRangeException(); + } - if ( inv == null ) - { - throw new InvalidOperationException( "There is no invoice matching this payment" ); - } - else - { - if ( inv.Amount == 0 ) - { - if ( inv.Payments == null || !inv.Payments.Any( ) ) - { - responseMessage = "no payment needed"; - } - else - { - throw new InvalidOperationException( "The invoice is in an invalid state, it has an amount of 0 and it has payments." ); - } - } - else - { - if ( inv.Payments != null && inv.Payments.Any( ) ) - { - if ( inv.Payments.Sum( x => x.Amount ) != 0 && inv.Amount == inv.Payments.Sum( x => x.Amount ) ) - { - responseMessage = "invoice was already fully paid"; - } - else if ( inv.Payments.Sum( x => x.Amount ) != 0 && payment.Amount > ( inv.Amount - inv.AmountPaid ) ) - { - responseMessage = "the payment is greater than the partial amount remaining"; - } - else - { - if ( ( inv.Amount - inv.AmountPaid ) == payment.Amount ) - { - switch ( inv.Type ) - { - case InvoiceType.Standard: - inv.AmountPaid += payment.Amount; - inv.Payments.Add( payment ); - responseMessage = "final partial payment received, invoice is now fully paid"; - break; - case InvoiceType.Commercial: - inv.AmountPaid += payment.Amount; - inv.TaxAmount += payment.Amount * 0.14m; - inv.Payments.Add( payment ); - responseMessage = "final partial payment received, invoice is now fully paid"; - break; - default: - throw new ArgumentOutOfRangeException( ); - } - - } - else - { - switch ( inv.Type ) - { - case InvoiceType.Standard: - inv.AmountPaid += payment.Amount; - inv.Payments.Add( payment ); - responseMessage = "another partial payment received, still not fully paid"; - break; - case InvoiceType.Commercial: - inv.AmountPaid += payment.Amount; - inv.TaxAmount += payment.Amount * 0.14m; - inv.Payments.Add( payment ); - responseMessage = "another partial payment received, still not fully paid"; - break; - default: - throw new ArgumentOutOfRangeException( ); - } - } - } - } - else - { - if ( payment.Amount > inv.Amount ) - { - responseMessage = "the payment is greater than the invoice amount"; - } - else if ( inv.Amount == payment.Amount ) - { - switch ( inv.Type ) - { - case InvoiceType.Standard: - inv.AmountPaid = payment.Amount; - inv.TaxAmount = payment.Amount * 0.14m; - inv.Payments.Add( payment ); - responseMessage = "invoice is now fully paid"; - break; - case InvoiceType.Commercial: - inv.AmountPaid = payment.Amount; - inv.TaxAmount = payment.Amount * 0.14m; - inv.Payments.Add( payment ); - responseMessage = "invoice is now fully paid"; - break; - default: - throw new ArgumentOutOfRangeException( ); - } - } - else - { - switch ( inv.Type ) - { - case InvoiceType.Standard: - inv.AmountPaid = payment.Amount; - inv.TaxAmount = payment.Amount * 0.14m; - inv.Payments.Add( payment ); - responseMessage = "invoice is now partially paid"; - break; - case InvoiceType.Commercial: - inv.AmountPaid = payment.Amount; - inv.TaxAmount = payment.Amount * 0.14m; - inv.Payments.Add( payment ); - responseMessage = "invoice is now partially paid"; - break; - default: - throw new ArgumentOutOfRangeException( ); - } - } - } - } - } - - inv.Save(); + if (invoice.Amount == 0) + { + return "no payment needed"; + } - return responseMessage; - } - } + if (invoice.Amount == invoice.PaymentsTotalAmount) + { + return "invoice was already fully paid"; + } + + if (payment.Amount > invoice.AmountToPay) + { + return invoice.HasPayments ? "the payment is greater than the partial amount remaining" : "the payment is greater than the invoice amount"; + } + + return string.Empty; + } + + public string ProcessPayment(Payment payment) + { + var invoice = _invoiceRepository.GetInvoice(payment.Reference); + + var responseMessage = this.ValidateInvoice(invoice, payment); + + if (string.IsNullOrWhiteSpace(responseMessage)) + { + string partialPaymentMessage = invoice.HasPayments ? "another partial payment received, still not fully paid" : "invoice is now partially paid"; + string fullPaymentMessage = invoice.HasPayments ? "final partial payment received, invoice is now fully paid" : "invoice is now fully paid"; + bool fullyPaid = invoice.AmountToPay == payment.Amount; + responseMessage = fullyPaid ? fullPaymentMessage : partialPaymentMessage; + + invoice.AmountPaid += payment.Amount; + if (invoice.Type == InvoiceType.Commercial) + { + invoice.TaxAmount += payment.Amount * 0.14m; + } + invoice.Payments.Add(payment); + } + + invoice.Save(); + + return responseMessage; + } + } } \ No newline at end of file diff --git a/RefactorThis.Persistence/Invoice.cs b/RefactorThis.Persistence/Invoice.cs index bd4370d..bdef20b 100644 --- a/RefactorThis.Persistence/Invoice.cs +++ b/RefactorThis.Persistence/Invoice.cs @@ -1,31 +1,64 @@ using System.Collections.Generic; +using System.Linq; namespace RefactorThis.Persistence { - public class Invoice - { - private readonly InvoiceRepository _repository; - public Invoice( InvoiceRepository repository ) - { - _repository = repository; - } - - public void Save( ) - { - _repository.SaveInvoice( this ); - } - - public decimal Amount { get; set; } - public decimal AmountPaid { get; set; } - public decimal TaxAmount { get; set; } - public List Payments { get; set; } - - public InvoiceType Type { get; set; } - } - - public enum InvoiceType - { - Standard, - Commercial - } + public class Invoice + { + private readonly InvoiceRepository _repository; + public Invoice(InvoiceRepository repository) + { + _repository = repository; + } + + public void Save() + { + _repository.SaveInvoice(this); + } + + public decimal Amount { get; set; } + public decimal AmountPaid { get; set; } + public decimal TaxAmount { get; set; } + public List Payments { get; set; } + + public InvoiceType Type { get; set; } + + public bool HasPayments + { + get + { + return Payments != null && Payments.Any(); + } + } + + public decimal PaymentsTotalAmount + { + get + { + return HasPayments ? Payments.Sum(x => x.Amount) : 0; + } + } + + public decimal PartialAmountRemaining + { + get + { + return Amount - AmountPaid; + } + } + + public decimal AmountToPay + { + get + { + return HasPayments ? PartialAmountRemaining : Amount; + } + } + } + + public enum InvoiceType + { + Standard, + Commercial + } } \ No newline at end of file