Skip to content

Instantly share code, notes, and snippets.

@thinkbeforecoding
Forked from ToJans/csharp.cs
Last active August 29, 2015 13:56
Show Gist options
  • Save thinkbeforecoding/9217108 to your computer and use it in GitHub Desktop.
Save thinkbeforecoding/9217108 to your computer and use it in GitHub Desktop.
Here is a cleaner, shorter version.
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));
}
}
}
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) }
@thinkbeforecoding
Copy link
Author

Updated billOrder arguments for currying, and removed useless type annotation in billOrder

@thinkbeforecoding
Copy link
Author

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.

@thinkbeforecoding
Copy link
Author

Since the VATCode is a close set, we can assume that the vatRates contain all value and move this concern out of the scope

@thinkbeforecoding
Copy link
Author

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