-
-
Save thinkbeforecoding/9217108 to your computer and use it in GitHub Desktop.
Here is a cleaner, shorter version.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
using System; | |
using System.Collections.Generic; | |
using System.Collections.Immutable; | |
using System.Linq; | |
namespace TDDCoverage | |
{ | |
public class Order | |
{ | |
public class Line | |
{ | |
public readonly String ArticleId; | |
public readonly decimal Amount; | |
public Line(String ArticleId, decimal Amount) | |
{ | |
this.ArticleId = ArticleId; | |
this.Amount = Amount; | |
} | |
} | |
public readonly IEnumerable<Line> Lines; | |
public Order(IEnumerable<Line> Lines) | |
{ | |
this.Lines = Lines; | |
} | |
public class LineList : List<Line> | |
{ | |
public void Add(String ArticleId, decimal Amount) | |
{ | |
this.Add(new Line(ArticleId,Amount)); | |
} | |
} | |
} | |
public class Invoice | |
{ | |
public class Item | |
{ | |
public readonly String Name; | |
public readonly decimal Amount, Price, VAT; | |
public Item(String Name, decimal Amount, decimal Price, decimal VAT) | |
{ | |
this.Name = Name; | |
this.Amount = Amount; | |
this.Price = Price; | |
this.VAT = VAT; | |
} | |
public override bool Equals(object obj) | |
{ | |
var x = obj as Item; | |
return x != null && | |
x.Name == Name && | |
x.Price == Price && | |
x.VAT == VAT && | |
x.Amount == Amount; | |
} | |
public override int GetHashCode() | |
{ | |
return Name.GetHashCode() ^ Price.GetHashCode() ^ VAT.GetHashCode() ^ Amount.GetHashCode(); | |
} | |
} | |
public readonly IEnumerable<Item> Items; | |
public Invoice(IEnumerable<Item> Items) | |
{ | |
this.Items = Items; | |
} | |
public class ItemList:List<Item> | |
{ | |
public void Add(String Name, decimal Amount, decimal Price, decimal VAT) | |
{ | |
this.Add(new Item(Name,Amount,Price,VAT)); | |
} | |
} | |
} | |
public enum VATCode | |
{ | |
Unknown, | |
Regular, | |
Food, | |
None | |
} | |
public class VATRates : Dictionary<VATCode, decimal> { } | |
public class Article | |
{ | |
public readonly string Id; | |
public readonly VATCode VATCode; | |
public readonly string Name; | |
public readonly decimal Price; | |
public Article(string Id, string Name, decimal Price, VATCode VAT) | |
{ | |
this.Id = Id; | |
this.Name = Name; | |
this.Price = Price; | |
this.VATCode = VAT; | |
} | |
public class Dictionary : Dictionary<string, Article> | |
{ | |
public void Add(string Id, string Name, int Price, VATCode VATCode) | |
{ | |
this.Add(Id, new Article(Id, Name, Price, VATCode)); | |
} | |
} | |
} | |
public class ArticleNotFoundException : Exception { } | |
public class VATRateNotFoundException : Exception { } | |
class Invoicer | |
{ | |
private VATRates VATRates; | |
private Article.Dictionary Articles; | |
public Invoicer(VATRates VATRates, Article.Dictionary Articles) | |
{ | |
this.VATRates = VATRates; | |
this.Articles = Articles; | |
} | |
public Invoice BillOrder(Order o) | |
{ | |
Invoice.ItemList items = new Invoice.ItemList(); | |
foreach (var orderline in o.Lines) | |
{ | |
Article article; | |
Decimal vatcode; | |
if (!Articles.TryGetValue(orderline.ArticleId, out article)) throw new ArticleNotFoundException(); | |
if (!VATRates.TryGetValue(article.VATCode, out vatcode)) throw new VATRateNotFoundException(); | |
var total = orderline.Amount * article.Price; | |
items.Add(article.Name, orderline.Amount, total, total * vatcode); | |
} | |
return new Invoice(items); | |
} | |
} | |
[TestClass] | |
public class Bill_order_tests | |
{ | |
Invoicer SUT; | |
Order.LineList OrderLines; | |
string unknown_article_id, article_id_with_unknown_vat; | |
[TestInitialize] | |
public void Setup() | |
{ | |
var rates = new VATRates(); | |
rates.Add(VATCode.Regular, .21M); | |
rates.Add(VATCode.Food, .06M); | |
rates.Add(VATCode.None, 0); | |
var articles = new Article.Dictionary(); | |
articles.Add("HTC-123", "HTC One Two TTT", 1000, VATCode.Regular); | |
articles.Add("APPL", "Apple blah", 1, VATCode.Food); | |
articles.Add("RNV", "Stairs renovation", 1500, VATCode.None); | |
articles.Add("???", "VAT Unknown", 123, VATCode.Unknown); | |
SUT = new Invoicer(rates, articles); | |
OrderLines = new Order.LineList(); | |
OrderLines.Add("HTC-123", 1); | |
OrderLines.Add("APPL", 12); | |
OrderLines.Add("RNV", 1); | |
unknown_article_id = "XYZ"; | |
article_id_with_unknown_vat = "???"; | |
} | |
[TestMethod] | |
public void Bill_an_order() | |
{ | |
var invoice = SUT.BillOrder(new Order(OrderLines)); | |
var invoiceItems = invoice.Items.ToArray(); | |
var expectedItems = new Invoice.ItemList(); | |
expectedItems.Add("HTC One Two TTT", 1, 1000, 210); | |
expectedItems.Add("Apple blah", 12, 12, 0.72M); | |
expectedItems.Add("Stairs renovation", 1, 1500, 0); | |
Assert.AreEqual(invoiceItems[0], expectedItems[0]); | |
Assert.AreEqual(invoiceItems[1], expectedItems[1]); | |
Assert.AreEqual(invoiceItems[2], expectedItems[2]); | |
} | |
[TestMethod] | |
[ExpectedException(typeof(ArticleNotFoundException))] | |
public void Bill_an_order_with_an_unkown_article() | |
{ | |
OrderLines.Add(unknown_article_id, 5); | |
SUT.BillOrder(new Order(OrderLines)); | |
} | |
[TestMethod] | |
[ExpectedException(typeof(VATRateNotFoundException))] | |
public void Bill_an_order_with_an_unknown_VAT_code() | |
{ | |
OrderLines.Add(article_id_with_unknown_vat, 5); | |
SUT.BillOrder(new Order(OrderLines)); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open System | |
type OrderLine = { ArticleId: string; Amount: decimal } | |
type Order = { Lines: OrderLine list} | |
type VATType = | Unknown | Regular | Food | NoVAT | |
type VatRates = Map<VATType, decimal> | |
type InvoiceItem = {Name:string; Amount: decimal; Price: decimal; VAT: decimal} | |
type Invoice = { Items: InvoiceItem list} | |
type Article = {ArticleId: string; Name: string; Price: decimal; VATCode: VATType} | |
type Articles = Article list | |
exception ArticleNotFoundException | |
module Invoicer = | |
let invoice amount article vat = | |
{ Name = article.Name | |
Amount = amount | |
Price = article.Price | |
VAT = vat} | |
let billOrder vatRates order articles = | |
let (|VAT|) article = Map.find article.VATCode vatRates | |
query { | |
for line in order.Lines do | |
leftOuterJoin article in articles on (line.ArticleId = article.ArticleId) into group | |
select (match Seq.toList group with | |
| [article & VAT vat] -> | |
invoice line.Amount article vat | |
| _ -> raise ArticleNotFoundException) } |
change getVat to just take code
And change invoice to take params in more natural order
Place record construction in invoice on multiple line to make it easier to read.
Since the VATCode is a close set, we can assume that the vatRates contain all value and move this concern out of the scope
This last version uses the active pattern VAT to get the vat for an article.. short and convenient :D
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Updated billOrder arguments for currying, and removed useless type annotation in billOrder