Quantcast
Channel: Reversible tax calculator class - Code Review Stack Exchange
Viewing all articles
Browse latest Browse all 4

Reversible tax calculator class

$
0
0

I have a class that contains 2 properties of the same type. decimal NetAmount and decimal GrossAmount

I would like to initialize it using either GrossAmount or NetAmount and based on the one specified calculate the second one.

Which way is the most elegant and why? (parameter validation is omitted for brevity)

1

public class TaxedPrice{    private TaxedPrice()    { }    public decimal NetAmount { get; private set; }    public decimal GrossAmount { get; private set; }    public static TaxedPrice FromNet(decimal netAmount, decimal taxRate)    {        return new TaxedPrice        {            NetAmount = decimal.Round(netAmount, 2, MidpointRounding.AwayFromZero),            GrossAmount = decimal.Round(netAmount.ApplyTax(taxRate), 2, MidpointRounding.AwayFromZero)        };    }    public static TaxedPrice FromGross(decimal grossAmount, decimal taxRate)    {        return new TaxedPrice        {            GrossAmount = decimal.Round(grossAmount, 2, MidpointRounding.AwayFromZero),            NetAmount = decimal.Round(grossAmount.RemoveTax(taxRate), 2, MidpointRounding.AwayFromZero)        };    }}

2

public class TaxedPrice{    public TaxedPrice(decimal netAmount, decimal grossAmount, decimal taxRate)    {        if (grossAmount != default)        {            GrossAmount = decimal.Round(grossAmount, 2, MidpointRounding.AwayFromZero);            NetAmount = decimal.Round(grossAmount.RemoveTax(taxRate), 2, MidpointRounding.AwayFromZero);        }        else if (netAmount != default)        {            NetAmount = decimal.Round(netAmount, 2, MidpointRounding.AwayFromZero);            GrossAmount = decimal.Round(netAmount.ApplyTax(taxRate), 2, MidpointRounding.AwayFromZero);        }        else        {            throw new InvalidOperationException($"Either {nameof(netAmount)} or {grossAmount} must be set.");        }    }    public decimal NetAmount { get; }    public decimal GrossAmount { get; }}

3

public class TaxedPrice{    public enum Type    {        Gross,        Net    }    public TaxedPrice(decimal amount, Type type, decimal taxRate)    {        if (type == Type.Gross)        {            GrossAmount = decimal.Round(amount, 2, MidpointRounding.AwayFromZero);            NetAmount = decimal.Round(amount.RemoveTax(taxRate), 2, MidpointRounding.AwayFromZero);        }        else if (type == Type.Net)        {            NetAmount = decimal.Round(amount, 2, MidpointRounding.AwayFromZero);            GrossAmount = decimal.Round(amount.ApplyTax(taxRate), 2, MidpointRounding.AwayFromZero);        }    }    public decimal NetAmount { get; }    public decimal GrossAmount { get; }}

4

public class TaxedPrice{    public TaxedPrice(decimal amount, bool fromGross, decimal taxRate)    {        if (fromGross)        {            GrossAmount = decimal.Round(amount, 2, MidpointRounding.AwayFromZero);            NetAmount = decimal.Round(amount.RemoveTax(taxRate), 2, MidpointRounding.AwayFromZero);        }        else        {            NetAmount = decimal.Round(amount, 2, MidpointRounding.AwayFromZero);            GrossAmount = decimal.Round(amount.ApplyTax(taxRate), 2, MidpointRounding.AwayFromZero);        }    }    public decimal NetAmount { get; }    public decimal GrossAmount { get; }}

How it looks like from caller's side:

// 1var taxedPrice = TaxedPrice.FromNet(2.123m, 0.23m);// 2var taxedPrice = new TaxedPrice(2.123m, default, 0.23m); // uses the first one to calculate the second onevar taxedPrice2 = new TaxedPrice(2.123m, 1.11m, 0.23m); // uses the first one to calculate the second onevar taxedPrice3 = new TaxedPrice(default, 1.11m, 0.23m); // uses the second one to calculate the first one// 3var taxedPrice = new TaxedPrice(2.123m, TaxedPrice.Type.Net, 0.23m);// 4 var taxedPrice = new TaxedPrice(2.123m, false, 0.23m);

Extensions for tax:

public static class TaxExtensions{    public static decimal ApplyTax(this decimal netPrice, decimal taxRate)    {        return netPrice * (taxRate + 1);    }    public static decimal RemoveTax(this decimal grossPrice, decimal taxRate)    {        return grossPrice / (taxRate + 1);    }}

Mapping perspective

In my upper layer I pass those prices in POCOs/DTOs like:

public class PriceDTO{    public decimal NetAmount { get; set; }    public decimal GrossAmount { get; set; }}

And I have to check there as well which one was passed to decide from which to calculate. So in case of 1 mapping would look like:

if (priceDto.GrossAmount != default)    return TaxedPrice.FromGross(priceDto.GrossAmount, taxRate);else if (priceDto.NetAmount != default)    return TaxedPrice.FromNet(priceDto.NetAmount, taxRate);else    // error

In case of 2 (no need to check in the mapping code)

return new TaxedPrice(priceDto.NetAmount, priceDto.GrossAmount, taxRate)

3 - there's a check as well

4 - same like 1 and 3

And I agree this could be a struct instead.


Viewing all articles
Browse latest Browse all 4

Latest Images

Trending Articles





Latest Images