Although this question has been asked a lot of times, I was hoping for a feedback on my approach.
Tax Calculator Basic sales tax is applicable at a rate of 10% on all goods, except books, food, and medical products that are exempt. Import duty is an additional sales tax applicable on all imported goods at a rate of 5%, with no exemptions.
When I purchase items I receive a receipt which lists the name of all the items and their price (including tax), finishing with the total cost of the items, and the total amounts of sales taxes paid. The rounding rules for sales tax are that for a tax rate of n%, a shelf price of p contains (np/100 rounded up to the nearest 0.05) amount of sales tax.
Write an application that prints out the receipt details ...
Input 1:
1 book at 12.49 1 music CD at 14.99 1 chocolate bar at 0.85
Output 1:
1 book: 12.49 1 music CD: 16.49 1 chocolate bar: 0.85 Sales Taxes: 1.50 Total: 29.83
Input 2:
1 imported box of chocolates at 10.00 1 imported bottle of perfume at 47.50
Output 2:
1 imported box of chocolates: 10.50 1 imported bottle of perfume: 54.65 Sales Taxes: 7.65 Total: 65.15
Input 3:
1 imported bottle of perfume at 27.99 1 bottle of perfume at 18.99 1 packet of headache pills at 9.75 1 box of imported chocolates at 11.25
Output 3:
1 imported bottle of perfume: 32.19 1 bottle of perfume: 20.89 1 packet of headache pills: 9.75 1 imported box of chocolates: 11.85 Sales Taxes: 6.70 Total: 74.68
TaxCalculatorApplication
class TaxCalculatorApplication {
Receipt generateReceipt(String[] inputs) {
List<Item> items = ItemFactory.from(inputs);
return new Receipt(items);
}
}
ItemFactory
public class ItemFactory {
public static List<Item> from(String[] inputs) {
return Arrays.stream(inputs)
.map(ItemFactory::from)
.collect(Collectors.toList());
}
private static Item from(String input) {
Item item = ItemAdapter.createItemFromString(input);
ItemTaxCalculator.applyTaxes(item);
return item;
}
}
ItemAdapter
public class ItemAdapter {
private static final String ITEM_ENTRY_REGEX = "(\\d+) ([\\w\\s]* )at (\\d+.\\d{2})";
public static Item createItemFromString(String input) {
Pattern pattern = Pattern.compile(ITEM_ENTRY_REGEX);
Matcher matcher = pattern.matcher(input);
matcher.find();
return new Item(matcher.group(1), matcher.group(2), matcher.group(3));
}
}
ItemTaxCalculator
public class ItemTaxCalculator {
private static final double SALES_TAX_PERCENT = 0.1;
private static final double ADDITIONAL_SALES_TAX_PERCENT = 0.05;
public static void applyTaxes(Item item) {
if (!item.isExempted()) {
item.setBasicSalesTaxAmount(SALES_TAX_PERCENT);
}
if (item.isImported()) {
item.setAdditionalSalesTax(ADDITIONAL_SALES_TAX_PERCENT);
}
}
}
Receipt
public class Receipt {
private double totalSalesTax = 0.0;
private double totalAmount = 0.0;
private String itemDetails;
public Receipt(List<Item> items) {
StringBuilder itemDetails = new StringBuilder();
for (Item item : items) {
itemDetails.append(item.toString()).append("\n");
totalSalesTax += item.getTaxAmount();
totalAmount += item.getFinalPrice();
}
totalAmount = MathUtils.roundOffAmount(totalAmount);
totalSalesTax = MathUtils.roundOffAmount(totalSalesTax);
this.itemDetails = itemDetails.toString();
}
public double getTotalAmount() {
return totalAmount;
}
public double getTotalSalesTax() {
return totalSalesTax;
}
@Override
public String toString() {
return "Receipt" + "\n"
+ itemDetails
+ "Sales Taxes: " + totalSalesTax + "\n"
+ "Total: " + totalAmount
+"\n*******************************\n";
}
}
Item
public class Item {
private String name;
private int quantity;
private double basePrice;
private double basicSalesTaxAmount;
private double additionalSalesTaxAmount;
public Item(String quantity, String name, String basePrice) {
this.name = name;
this.quantity = Integer.valueOf(quantity);
this.basePrice = Double.valueOf(basePrice);
}
public double getFinalPrice() {
return MathUtils.roundOffAmount(quantity * basePrice + getTaxAmount());
}
public double getTaxAmount() {
return quantity * (basicSalesTaxAmount + additionalSalesTaxAmount);
}
public boolean isImported() {
return name.contains("imported");
}
public boolean isExempted() {
return Stream.of("book", "chocolate", "pill")
.anyMatch(exemptedItem -> name.contains(exemptedItem));
}
public void setBasicSalesTaxAmount(double factor) {
basicSalesTaxAmount = basePrice * factor;
}
public void setAdditionalSalesTax(double additionalSalesTaxPercent) {
additionalSalesTaxAmount = MathUtils.roundOffTax(basePrice * additionalSalesTaxPercent);
}
public String toString() {
return String.valueOf(quantity) +
" " +
name +
" : " +
getFinalPrice();
}
}
MathUtils
public class MathUtils {
public static double roundOffTax(double number) {
return Math.ceil(number * 20) / 20;
}
public static double roundOffAmount(double number) {
return Math.round(number * 100.0) / 100.0;
}
}
The whole project is available at this link.