Skip to main content
deleted 13 characters in body; edited tags
Source Link
Jamal
  • 34.9k
  • 13
  • 133
  • 237

Taxes are calculated with a rate of 17.5%, rounding the result to the upper 0.05. Please note that medical products are exempt and an additional 1.25 fixed amount is added as an extra tax on CDs.

BasketBasket class:

CostCost class:

LineItemLineItem class:

ProductProduct class:

ProductServiceProductService class:

MapProductRepositoryMapProductRepository class:

ProductRepositoryProductRepository interface:

MiscTaxMiscTax class:

TaxTax interface:

Taxes are calculated with a rate of 17.5%, rounding the result to the upper 0.05. Please note that medical products are exempt and an additional 1.25 fixed amount is added as an extra tax on CDs

Basket class:

Cost class:

LineItem class:

Product class:

ProductService class:

MapProductRepository class:

ProductRepository interface:

MiscTax class:

Tax interface:

Taxes are calculated with a rate of 17.5%, rounding the result to the upper 0.05. Please note that medical products are exempt and an additional 1.25 fixed amount is added as an extra tax on CDs.

Basket class:

Cost class:

LineItem class:

Product class:

ProductService class:

MapProductRepository class:

ProductRepository interface:

MiscTax class:

Tax interface:

Tweeted twitter.com/StackCodeReview/status/833530188315164672

Basket class:

public class Basket {

    private final ProductService productService;
    private final TaxService taxService;
    private final Map<String, LineItem> items;

    public Basket(ProductService productService, TaxService taxService) {
        this.items = new HashMap<>();
        this.productService = productService;
        this.taxService = taxService;
    }

    public void addItems(String... names) throws ProductNotFoundException {

        for(String name : names) {
            if (items.containsKey(name.toLowerCase())) {
                updateExistingLineItem(name);
            } else {
                addNewLineItem(name);
            }
        }
    }

    private void updateExistingLineItem(String name) throws ProductNotFoundException {
        Cost cost = calculateCost(name);
        items.get(name.toLowerCase()).update(cost);
    }

    private void addNewLineItem(String name) throws ProductNotFoundException {
        Cost cost = calculateCost(name);
        LineItem lineItem = new LineItem(name, cost);
        items.put(name.toLowerCase(), lineItem);
    }

    private Cost calculateCost(String name) throws ProductNotFoundException {
        BigDecimal gross = productService.price(name);
        BigDecimal tax = taxService.calculateTax(name);
        return new Cost(gross, tax);
    }

    public BigDecimal getTotalPrice() {
        return items.values()
                .stream().map(LineItem::getNetPrice)
                .reduce(ZERO, BigDecimal::add);
    }

    public BigDecimal getSalesTax() {
        return items.values()
                .stream().map(LineItem::getTax)
                .reduce(ZERO, BigDecimal::add);
    }

    @Override
    public String toString() {
        String formattedItems = items.values().toString()
                .replace(",", "")
                .replace("[", "")
                .replace("]", "")
                .trim();

        return formattedItems + "\nSales Taxes: " + getSalesTax() + "\nTotal: " + getTotalPrice() + "\n";
    }
}

 

Cost class:

public class Cost {

    private final BigDecimal gross;
    private final BigDecimal tax;

    public Cost(BigDecimal gross, BigDecimal tax) {
        this.gross = gross;
        this.tax = tax;
    }

    public Cost add(Cost cost) {
        return new Cost(this.gross.add(cost.gross), this.tax.add(cost.tax));
    }

    public BigDecimal getGross() {
        return gross;
    }

    public BigDecimal getTax() {
        return tax;
    }
}

 

LineItem class:

public class LineItem {

    private final String name;
    private Integer quantity = 0;
    private Cost cost;

    public LineItem(String name, Integer quantity, Cost cost) {
        this.name = name;
        this.quantity = quantity;
        this.cost = cost;
    }

    public LineItem(String name, Cost cost) {
        this(name, 1, cost);
    }

    public void update(Cost cost) {
        this.cost = this.cost.add(cost);
        this.quantity++;
    }

    public BigDecimal getTax() {
        return this.cost.getTax();
    }

    public BigDecimal getNetPrice() {
        return cost.getGross().add(getTax());
    }

    @Override
    public String toString() {
        StringJoiner joiner = new StringJoiner(" ");
        joiner.add(quantity.toString());
        joiner.add(name);
        joiner.add(":");
        joiner.add(getNetPrice().toString());

        return "\n" + joiner.toString();
    }
}

 

Product class:

public class Product {

    private String name;
    private BigDecimal price;
    private List<TaxBand> applicableTaxes;

    public Product(String name, String price, TaxBand... applicableTaxes) {
        this.name = name;
        this.price = new BigDecimal(price);
        this.applicableTaxes = asList(applicableTaxes);
    }

    public String getName() {
        return name;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public List<TaxBand> getApplicableTaxes() {
        return applicableTaxes;
    }
}

 

ProductService class:

public class ProductService {

    private ProductRepository productRepository;

    public ProductService(ProductRepository productRepository, Product... items) {
        this.productRepository = productRepository;
        Arrays.stream(items).forEach(item -> this.productRepository.add(item));
    }

    public Product getProductByName(String name) throws ProductNotFoundException {
        return productRepository.findByProductName(name);
    }

    public BigDecimal price(String name) throws ProductNotFoundException {
        return productRepository.price(name);
    }
}

 

MapProductRepository class:

public class MapProductRepository implements ProductRepository {

    private final Map<String, Product> products = new HashMap<>();

    @Override
    public Product findByProductName(String name) throws ProductNotFoundException {
        Product product = products.get(name.toLowerCase());

        if(product == null) {
            throw new ProductNotFoundException(name + " not supported");
        }
        return product;
    }

    @Override
    public BigDecimal price(String name) throws ProductNotFoundException{
        return findByProductName(name.toLowerCase()).getPrice();
    }

    @Override
    public void add(Product product) {
        products.put(product.getName().toLowerCase(), product);
    }
}

 

ProductRepository interface:

public interface ProductRepository {

    Product findByProductName(String name) throws ProductNotFoundException;

    BigDecimal price(String name) throws ProductNotFoundException;

    void add(Product product);
}

public class CompactDiskTax implements Tax {

    public static final BigDecimal TAX = new BigDecimal("1.25");

    @Override
    public BigDecimal calculate(Product product) {

        if(!product.getApplicableTaxes().contains(CD)){
            return ZERO;
        }
        return TAX;
    }
}

 

MiscTax class:

public class MiscTax implements Tax {

    public static final BigDecimal ROUND_TO = new BigDecimal("0.05");
    public static final BigDecimal TAX_RATE = new BigDecimal("17.5");

    public BigDecimal calculate(Product product) {

        if(!product.getApplicableTaxes().contains(TaxBand.MISC)){
            return ZERO;
        }
        return percentage(product.getPrice(), TAX_RATE);
    }

    private BigDecimal percentage(BigDecimal base, BigDecimal percentage){
        return round(base.multiply(percentage).divide(new BigDecimal(100)));
    }

    private BigDecimal round(BigDecimal value) {
        BigDecimal divided = value.divide(ROUND_TO, 0, UP);
        return divided.multiply(ROUND_TO);
    }
}

 

Tax interface:

public interface Tax {

    BigDecimal calculate(Product product);
}

public enum TaxBand {
    MEDICAL, CD, MISC
}

public class TaxService {

    private ProductService productService;
    private List<Tax> taxes;

    public TaxService(ProductService productService, Tax... taxes) {
        this.productService = productService;
        this.taxes = Arrays.asList(taxes);
    }

    public BigDecimal calculateTax(String productName) throws ProductNotFoundException {
        Product product = productService.getProductByName(productName);

        return taxes.stream()
                .map(tax -> tax.calculate(product))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}
public class Basket {

    private final ProductService productService;
    private final TaxService taxService;
    private final Map<String, LineItem> items;

    public Basket(ProductService productService, TaxService taxService) {
        this.items = new HashMap<>();
        this.productService = productService;
        this.taxService = taxService;
    }

    public void addItems(String... names) throws ProductNotFoundException {

        for(String name : names) {
            if (items.containsKey(name.toLowerCase())) {
                updateExistingLineItem(name);
            } else {
                addNewLineItem(name);
            }
        }
    }

    private void updateExistingLineItem(String name) throws ProductNotFoundException {
        Cost cost = calculateCost(name);
        items.get(name.toLowerCase()).update(cost);
    }

    private void addNewLineItem(String name) throws ProductNotFoundException {
        Cost cost = calculateCost(name);
        LineItem lineItem = new LineItem(name, cost);
        items.put(name.toLowerCase(), lineItem);
    }

    private Cost calculateCost(String name) throws ProductNotFoundException {
        BigDecimal gross = productService.price(name);
        BigDecimal tax = taxService.calculateTax(name);
        return new Cost(gross, tax);
    }

    public BigDecimal getTotalPrice() {
        return items.values()
                .stream().map(LineItem::getNetPrice)
                .reduce(ZERO, BigDecimal::add);
    }

    public BigDecimal getSalesTax() {
        return items.values()
                .stream().map(LineItem::getTax)
                .reduce(ZERO, BigDecimal::add);
    }

    @Override
    public String toString() {
        String formattedItems = items.values().toString()
                .replace(",", "")
                .replace("[", "")
                .replace("]", "")
                .trim();

        return formattedItems + "\nSales Taxes: " + getSalesTax() + "\nTotal: " + getTotalPrice() + "\n";
    }
}

 public class Cost {

    private final BigDecimal gross;
    private final BigDecimal tax;

    public Cost(BigDecimal gross, BigDecimal tax) {
        this.gross = gross;
        this.tax = tax;
    }

    public Cost add(Cost cost) {
        return new Cost(this.gross.add(cost.gross), this.tax.add(cost.tax));
    }

    public BigDecimal getGross() {
        return gross;
    }

    public BigDecimal getTax() {
        return tax;
    }
}

 public class LineItem {

    private final String name;
    private Integer quantity = 0;
    private Cost cost;

    public LineItem(String name, Integer quantity, Cost cost) {
        this.name = name;
        this.quantity = quantity;
        this.cost = cost;
    }

    public LineItem(String name, Cost cost) {
        this(name, 1, cost);
    }

    public void update(Cost cost) {
        this.cost = this.cost.add(cost);
        this.quantity++;
    }

    public BigDecimal getTax() {
        return this.cost.getTax();
    }

    public BigDecimal getNetPrice() {
        return cost.getGross().add(getTax());
    }

    @Override
    public String toString() {
        StringJoiner joiner = new StringJoiner(" ");
        joiner.add(quantity.toString());
        joiner.add(name);
        joiner.add(":");
        joiner.add(getNetPrice().toString());

        return "\n" + joiner.toString();
    }
}

 public class Product {

    private String name;
    private BigDecimal price;
    private List<TaxBand> applicableTaxes;

    public Product(String name, String price, TaxBand... applicableTaxes) {
        this.name = name;
        this.price = new BigDecimal(price);
        this.applicableTaxes = asList(applicableTaxes);
    }

    public String getName() {
        return name;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public List<TaxBand> getApplicableTaxes() {
        return applicableTaxes;
    }
}

 public class ProductService {

    private ProductRepository productRepository;

    public ProductService(ProductRepository productRepository, Product... items) {
        this.productRepository = productRepository;
        Arrays.stream(items).forEach(item -> this.productRepository.add(item));
    }

    public Product getProductByName(String name) throws ProductNotFoundException {
        return productRepository.findByProductName(name);
    }

    public BigDecimal price(String name) throws ProductNotFoundException {
        return productRepository.price(name);
    }
}

 public class MapProductRepository implements ProductRepository {

    private final Map<String, Product> products = new HashMap<>();

    @Override
    public Product findByProductName(String name) throws ProductNotFoundException {
        Product product = products.get(name.toLowerCase());

        if(product == null) {
            throw new ProductNotFoundException(name + " not supported");
        }
        return product;
    }

    @Override
    public BigDecimal price(String name) throws ProductNotFoundException{
        return findByProductName(name.toLowerCase()).getPrice();
    }

    @Override
    public void add(Product product) {
        products.put(product.getName().toLowerCase(), product);
    }
}

 public interface ProductRepository {

    Product findByProductName(String name) throws ProductNotFoundException;

    BigDecimal price(String name) throws ProductNotFoundException;

    void add(Product product);
}

public class CompactDiskTax implements Tax {

    public static final BigDecimal TAX = new BigDecimal("1.25");

    @Override
    public BigDecimal calculate(Product product) {

        if(!product.getApplicableTaxes().contains(CD)){
            return ZERO;
        }
        return TAX;
    }
}

 public class MiscTax implements Tax {

    public static final BigDecimal ROUND_TO = new BigDecimal("0.05");
    public static final BigDecimal TAX_RATE = new BigDecimal("17.5");

    public BigDecimal calculate(Product product) {

        if(!product.getApplicableTaxes().contains(TaxBand.MISC)){
            return ZERO;
        }
        return percentage(product.getPrice(), TAX_RATE);
    }

    private BigDecimal percentage(BigDecimal base, BigDecimal percentage){
        return round(base.multiply(percentage).divide(new BigDecimal(100)));
    }

    private BigDecimal round(BigDecimal value) {
        BigDecimal divided = value.divide(ROUND_TO, 0, UP);
        return divided.multiply(ROUND_TO);
    }
}

 public interface Tax {

    BigDecimal calculate(Product product);
}

public enum TaxBand {
    MEDICAL, CD, MISC
}

public class TaxService {

    private ProductService productService;
    private List<Tax> taxes;

    public TaxService(ProductService productService, Tax... taxes) {
        this.productService = productService;
        this.taxes = Arrays.asList(taxes);
    }

    public BigDecimal calculateTax(String productName) throws ProductNotFoundException {
        Product product = productService.getProductByName(productName);

        return taxes.stream()
                .map(tax -> tax.calculate(product))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

Basket class:

public class Basket {

    private final ProductService productService;
    private final TaxService taxService;
    private final Map<String, LineItem> items;

    public Basket(ProductService productService, TaxService taxService) {
        this.items = new HashMap<>();
        this.productService = productService;
        this.taxService = taxService;
    }

    public void addItems(String... names) throws ProductNotFoundException {

        for(String name : names) {
            if (items.containsKey(name.toLowerCase())) {
                updateExistingLineItem(name);
            } else {
                addNewLineItem(name);
            }
        }
    }

    private void updateExistingLineItem(String name) throws ProductNotFoundException {
        Cost cost = calculateCost(name);
        items.get(name.toLowerCase()).update(cost);
    }

    private void addNewLineItem(String name) throws ProductNotFoundException {
        Cost cost = calculateCost(name);
        LineItem lineItem = new LineItem(name, cost);
        items.put(name.toLowerCase(), lineItem);
    }

    private Cost calculateCost(String name) throws ProductNotFoundException {
        BigDecimal gross = productService.price(name);
        BigDecimal tax = taxService.calculateTax(name);
        return new Cost(gross, tax);
    }

    public BigDecimal getTotalPrice() {
        return items.values()
                .stream().map(LineItem::getNetPrice)
                .reduce(ZERO, BigDecimal::add);
    }

    public BigDecimal getSalesTax() {
        return items.values()
                .stream().map(LineItem::getTax)
                .reduce(ZERO, BigDecimal::add);
    }

    @Override
    public String toString() {
        String formattedItems = items.values().toString()
                .replace(",", "")
                .replace("[", "")
                .replace("]", "")
                .trim();

        return formattedItems + "\nSales Taxes: " + getSalesTax() + "\nTotal: " + getTotalPrice() + "\n";
    }
}

Cost class:

public class Cost {

    private final BigDecimal gross;
    private final BigDecimal tax;

    public Cost(BigDecimal gross, BigDecimal tax) {
        this.gross = gross;
        this.tax = tax;
    }

    public Cost add(Cost cost) {
        return new Cost(this.gross.add(cost.gross), this.tax.add(cost.tax));
    }

    public BigDecimal getGross() {
        return gross;
    }

    public BigDecimal getTax() {
        return tax;
    }
}

LineItem class:

public class LineItem {

    private final String name;
    private Integer quantity = 0;
    private Cost cost;

    public LineItem(String name, Integer quantity, Cost cost) {
        this.name = name;
        this.quantity = quantity;
        this.cost = cost;
    }

    public LineItem(String name, Cost cost) {
        this(name, 1, cost);
    }

    public void update(Cost cost) {
        this.cost = this.cost.add(cost);
        this.quantity++;
    }

    public BigDecimal getTax() {
        return this.cost.getTax();
    }

    public BigDecimal getNetPrice() {
        return cost.getGross().add(getTax());
    }

    @Override
    public String toString() {
        StringJoiner joiner = new StringJoiner(" ");
        joiner.add(quantity.toString());
        joiner.add(name);
        joiner.add(":");
        joiner.add(getNetPrice().toString());

        return "\n" + joiner.toString();
    }
}

Product class:

public class Product {

    private String name;
    private BigDecimal price;
    private List<TaxBand> applicableTaxes;

    public Product(String name, String price, TaxBand... applicableTaxes) {
        this.name = name;
        this.price = new BigDecimal(price);
        this.applicableTaxes = asList(applicableTaxes);
    }

    public String getName() {
        return name;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public List<TaxBand> getApplicableTaxes() {
        return applicableTaxes;
    }
}

ProductService class:

public class ProductService {

    private ProductRepository productRepository;

    public ProductService(ProductRepository productRepository, Product... items) {
        this.productRepository = productRepository;
        Arrays.stream(items).forEach(item -> this.productRepository.add(item));
    }

    public Product getProductByName(String name) throws ProductNotFoundException {
        return productRepository.findByProductName(name);
    }

    public BigDecimal price(String name) throws ProductNotFoundException {
        return productRepository.price(name);
    }
}

MapProductRepository class:

public class MapProductRepository implements ProductRepository {

    private final Map<String, Product> products = new HashMap<>();

    @Override
    public Product findByProductName(String name) throws ProductNotFoundException {
        Product product = products.get(name.toLowerCase());

        if(product == null) {
            throw new ProductNotFoundException(name + " not supported");
        }
        return product;
    }

    @Override
    public BigDecimal price(String name) throws ProductNotFoundException{
        return findByProductName(name.toLowerCase()).getPrice();
    }

    @Override
    public void add(Product product) {
        products.put(product.getName().toLowerCase(), product);
    }
}

ProductRepository interface:

public interface ProductRepository {

    Product findByProductName(String name) throws ProductNotFoundException;

    BigDecimal price(String name) throws ProductNotFoundException;

    void add(Product product);
}

public class CompactDiskTax implements Tax {

    public static final BigDecimal TAX = new BigDecimal("1.25");

    @Override
    public BigDecimal calculate(Product product) {

        if(!product.getApplicableTaxes().contains(CD)){
            return ZERO;
        }
        return TAX;
    }
}

MiscTax class:

public class MiscTax implements Tax {

    public static final BigDecimal ROUND_TO = new BigDecimal("0.05");
    public static final BigDecimal TAX_RATE = new BigDecimal("17.5");

    public BigDecimal calculate(Product product) {

        if(!product.getApplicableTaxes().contains(TaxBand.MISC)){
            return ZERO;
        }
        return percentage(product.getPrice(), TAX_RATE);
    }

    private BigDecimal percentage(BigDecimal base, BigDecimal percentage){
        return round(base.multiply(percentage).divide(new BigDecimal(100)));
    }

    private BigDecimal round(BigDecimal value) {
        BigDecimal divided = value.divide(ROUND_TO, 0, UP);
        return divided.multiply(ROUND_TO);
    }
}

Tax interface:

public interface Tax {

    BigDecimal calculate(Product product);
}

public enum TaxBand {
    MEDICAL, CD, MISC
}

public class TaxService {

    private ProductService productService;
    private List<Tax> taxes;

    public TaxService(ProductService productService, Tax... taxes) {
        this.productService = productService;
        this.taxes = Arrays.asList(taxes);
    }

    public BigDecimal calculateTax(String productName) throws ProductNotFoundException {
        Product product = productService.getProductByName(productName);

        return taxes.stream()
                .map(tax -> tax.calculate(product))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}
deleted 263 characters in body; edited tags; edited title
Source Link
Jamal
  • 34.9k
  • 13
  • 133
  • 237

Shopping Basket With Discountsbasket with discounts and Taxtax

Examples Input:

Input:

  • oneOne book at 29.49
  • oneOne music CD at 15.99
  • List one chocolate snack at 0.75
  • oneOne bottle of wine at 20.99
  • oneOne box of tooth ache pills at 4.15
  • oneOne box of pins at 11.25
  • oneOne music CD at 14.99
  • 1 bottle of wine: 24.69

    1 bottle of wine: 24.69
  • 1 box of headache pills: 4.15

    1 box of headache pills: 4.15
  • 1 box of pins: 13.25

    1 box of pins: 13.25
  • 1 music CD: 18.89

    1 music CD: 18.89
  • Sales Taxes: 9.60

    Sales Taxes: 9.60
  • Total: 60.98

         public class Basket {
    
         private final ProductService productService;
         private final TaxService taxService;
         private final Map<String, LineItem> items;
    
         public Basket(ProductService productService, TaxService taxService) {
             this.items = new HashMap<>();
             this.productService = productService;
             this.taxService = taxService;
         }
    
         public void addItems(String... names) throws ProductNotFoundException {
    
             for(String name : names) {
                 if (items.containsKey(name.toLowerCase())) {
                     updateExistingLineItem(name);
                 } else {
                     addNewLineItem(name);
                 }
             }
         }
    
         private void updateExistingLineItem(String name) throws ProductNotFoundException {
             Cost cost = calculateCost(name);
             items.get(name.toLowerCase()).update(cost);
         }
    
         private void addNewLineItem(String name) throws ProductNotFoundException {
             Cost cost = calculateCost(name);
             LineItem lineItem = new LineItem(name, cost);
             items.put(name.toLowerCase(), lineItem);
         }
    
         private Cost calculateCost(String name) throws ProductNotFoundException {
             BigDecimal gross = productService.price(name);
             BigDecimal tax = taxService.calculateTax(name);
             return new Cost(gross, tax);
         }
    
         public BigDecimal getTotalPrice() {
             return items.values()
                     .stream().map(LineItem::getNetPrice)
                     .reduce(ZERO, BigDecimal::add);
         }
    
         public BigDecimal getSalesTax() {
             return items.values()
                     .stream().map(LineItem::getTax)
                     .reduce(ZERO, BigDecimal::add);
         }
    
         @Override
         public String toString() {
             String formattedItems = items.values().toString()
                     .replace(",", "")
                     .replace("[", "")
                     .replace("]", "")
                     .trim();
    
             return formattedItems + "\nSales Taxes: " + getSalesTax() + "\nTotal: " + getTotalPrice() + "\n";
         }
     }
    

    public class Cost {

     private final BigDecimal gross;
     private final BigDecimal tax;
    
     public Cost(BigDecimal gross, BigDecimal tax) {
         this.gross = gross;
         this.tax = tax;
     }
    
     public Cost add(Cost cost) {
         return new Cost(this.gross.add(cost.gross), this.tax.add(cost.tax));
     }
    
     public BigDecimal getGross() {
         return gross;
     }
    
     public BigDecimal getTax() {
         return tax;
     }
    

    }

    public class LineItem {

     private final String name;
     private Integer quantity = 0;
     private Cost cost;
    
     public LineItem(String name, Integer quantity, Cost cost) {
         this.name = name;
         this.quantity = quantity;
         this.cost = cost;
     }
    
     public LineItem(String name, Cost cost) {
         this(name, 1, cost);
     }
    
     public void update(Cost cost) {
         this.cost = this.cost.add(cost);
         this.quantity++;
     }
    
     public BigDecimal getTax() {
         return this.cost.getTax();
     }
    
     public BigDecimal getNetPrice() {
         return cost.getGross().add(getTax());
     }
    
     @Override
     public String toString() {
         StringJoiner joiner = new StringJoiner(" ");
         joiner.add(quantity.toString());
         joiner.add(name);
         joiner.add(":");
         joiner.add(getNetPrice().toString());
    
         return "\n" + joiner.toString();
     }
    

    }

    public class Product {

     private String name;
     private BigDecimal price;
     private List<TaxBand> applicableTaxes;
    
     public Product(String name, String price, TaxBand... applicableTaxes) {
         this.name = name;
         this.price = new BigDecimal(price);
         this.applicableTaxes = asList(applicableTaxes);
     }
    
     public String getName() {
         return name;
     }
    
     public BigDecimal getPrice() {
         return price;
     }
    
     public List<TaxBand> getApplicableTaxes() {
         return applicableTaxes;
     }
    

    }

    public class ProductService {

     private ProductRepository productRepository;
    
     public ProductService(ProductRepository productRepository, Product... items) {
         this.productRepository = productRepository;
         Arrays.stream(items).forEach(item -> this.productRepository.add(item));
     }
    
     public Product getProductByName(String name) throws ProductNotFoundException {
         return productRepository.findByProductName(name);
     }
    
     public BigDecimal price(String name) throws ProductNotFoundException {
         return productRepository.price(name);
     }
    

    }

    public class MapProductRepository implements ProductRepository {

     private final Map<String, Product> products = new HashMap<>();
    
     @Override
     public Product findByProductName(String name) throws ProductNotFoundException {
         Product product = products.get(name.toLowerCase());
    
         if(product == null) {
             throw new ProductNotFoundException(name + " not supported");
         }
         return product;
     }
    
     @Override
     public BigDecimal price(String name) throws ProductNotFoundException{
         return findByProductName(name.toLowerCase()).getPrice();
     }
    
     @Override
     public void add(Product product) {
         products.put(product.getName().toLowerCase(), product);
     }
    

    }

    public interface ProductRepository {

     Product findByProductName(String name) throws ProductNotFoundException;
    
     BigDecimal price(String name) throws ProductNotFoundException;
    
     void add(Product product);
    

    }

    public class CompactDiskTax implements Tax {

     public static final BigDecimal TAX = new BigDecimal("1.25");
    
     @Override
     public BigDecimal calculate(Product product) {
    
         if(!product.getApplicableTaxes().contains(CD)){
             return ZERO;
         }
         return TAX;
     }
    

    }

    public class MiscTax implements Tax {

     public static final BigDecimal ROUND_TO = new BigDecimal("0.05");
     public static final BigDecimal TAX_RATE = new BigDecimal("17.5");
    
     public BigDecimal calculate(Product product) {
    
         if(!product.getApplicableTaxes().contains(TaxBand.MISC)){
             return ZERO;
         }
         return percentage(product.getPrice(), TAX_RATE);
     }
    
     private BigDecimal percentage(BigDecimal base, BigDecimal percentage){
         return round(base.multiply(percentage).divide(new BigDecimal(100)));
     }
    
     private BigDecimal round(BigDecimal value) {
         BigDecimal divided = value.divide(ROUND_TO, 0, UP);
         return divided.multiply(ROUND_TO);
     }
    

    }

    public interface Tax {

     BigDecimal calculate(Product product);
    

    }

    public enum TaxBand { MEDICAL, CD, MISC }

    public class TaxService {

     private ProductService productService;
     private List<Tax> taxes;
    
     public TaxService(ProductService productService, Tax... taxes) {
         this.productService = productService;
         this.taxes = Arrays.asList(taxes);
     }
    
     public BigDecimal calculateTax(String productName) throws ProductNotFoundException {
         Product product = productService.getProductByName(productName);
    
         return taxes.stream()
                 .map(tax -> tax.calculate(product))
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
     }
    

    }

    Total: 60.98
public class Basket {

    private final ProductService productService;
    private final TaxService taxService;
    private final Map<String, LineItem> items;

    public Basket(ProductService productService, TaxService taxService) {
        this.items = new HashMap<>();
        this.productService = productService;
        this.taxService = taxService;
    }

    public void addItems(String... names) throws ProductNotFoundException {

        for(String name : names) {
            if (items.containsKey(name.toLowerCase())) {
                updateExistingLineItem(name);
            } else {
                addNewLineItem(name);
            }
        }
    }

    private void updateExistingLineItem(String name) throws ProductNotFoundException {
        Cost cost = calculateCost(name);
        items.get(name.toLowerCase()).update(cost);
    }

    private void addNewLineItem(String name) throws ProductNotFoundException {
        Cost cost = calculateCost(name);
        LineItem lineItem = new LineItem(name, cost);
        items.put(name.toLowerCase(), lineItem);
    }

    private Cost calculateCost(String name) throws ProductNotFoundException {
        BigDecimal gross = productService.price(name);
        BigDecimal tax = taxService.calculateTax(name);
        return new Cost(gross, tax);
    }

    public BigDecimal getTotalPrice() {
        return items.values()
                .stream().map(LineItem::getNetPrice)
                .reduce(ZERO, BigDecimal::add);
    }

    public BigDecimal getSalesTax() {
        return items.values()
                .stream().map(LineItem::getTax)
                .reduce(ZERO, BigDecimal::add);
    }

    @Override
    public String toString() {
        String formattedItems = items.values().toString()
                .replace(",", "")
                .replace("[", "")
                .replace("]", "")
                .trim();

        return formattedItems + "\nSales Taxes: " + getSalesTax() + "\nTotal: " + getTotalPrice() + "\n";
    }
}

public class Cost {

    private final BigDecimal gross;
    private final BigDecimal tax;

    public Cost(BigDecimal gross, BigDecimal tax) {
        this.gross = gross;
        this.tax = tax;
    }

    public Cost add(Cost cost) {
        return new Cost(this.gross.add(cost.gross), this.tax.add(cost.tax));
    }

    public BigDecimal getGross() {
        return gross;
    }

    public BigDecimal getTax() {
        return tax;
    }
}

public class LineItem {

    private final String name;
    private Integer quantity = 0;
    private Cost cost;

    public LineItem(String name, Integer quantity, Cost cost) {
        this.name = name;
        this.quantity = quantity;
        this.cost = cost;
    }

    public LineItem(String name, Cost cost) {
        this(name, 1, cost);
    }

    public void update(Cost cost) {
        this.cost = this.cost.add(cost);
        this.quantity++;
    }

    public BigDecimal getTax() {
        return this.cost.getTax();
    }

    public BigDecimal getNetPrice() {
        return cost.getGross().add(getTax());
    }

    @Override
    public String toString() {
        StringJoiner joiner = new StringJoiner(" ");
        joiner.add(quantity.toString());
        joiner.add(name);
        joiner.add(":");
        joiner.add(getNetPrice().toString());

        return "\n" + joiner.toString();
    }
}

public class Product {

    private String name;
    private BigDecimal price;
    private List<TaxBand> applicableTaxes;

    public Product(String name, String price, TaxBand... applicableTaxes) {
        this.name = name;
        this.price = new BigDecimal(price);
        this.applicableTaxes = asList(applicableTaxes);
    }

    public String getName() {
        return name;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public List<TaxBand> getApplicableTaxes() {
        return applicableTaxes;
    }
}

public class ProductService {

    private ProductRepository productRepository;

    public ProductService(ProductRepository productRepository, Product... items) {
        this.productRepository = productRepository;
        Arrays.stream(items).forEach(item -> this.productRepository.add(item));
    }

    public Product getProductByName(String name) throws ProductNotFoundException {
        return productRepository.findByProductName(name);
    }

    public BigDecimal price(String name) throws ProductNotFoundException {
        return productRepository.price(name);
    }
}

public class MapProductRepository implements ProductRepository {

    private final Map<String, Product> products = new HashMap<>();

    @Override
    public Product findByProductName(String name) throws ProductNotFoundException {
        Product product = products.get(name.toLowerCase());

        if(product == null) {
            throw new ProductNotFoundException(name + " not supported");
        }
        return product;
    }

    @Override
    public BigDecimal price(String name) throws ProductNotFoundException{
        return findByProductName(name.toLowerCase()).getPrice();
    }

    @Override
    public void add(Product product) {
        products.put(product.getName().toLowerCase(), product);
    }
}

public interface ProductRepository {

    Product findByProductName(String name) throws ProductNotFoundException;

    BigDecimal price(String name) throws ProductNotFoundException;

    void add(Product product);
}

public class CompactDiskTax implements Tax {

    public static final BigDecimal TAX = new BigDecimal("1.25");

    @Override
    public BigDecimal calculate(Product product) {

        if(!product.getApplicableTaxes().contains(CD)){
            return ZERO;
        }
        return TAX;
    }
}

public class MiscTax implements Tax {

    public static final BigDecimal ROUND_TO = new BigDecimal("0.05");
    public static final BigDecimal TAX_RATE = new BigDecimal("17.5");

    public BigDecimal calculate(Product product) {

        if(!product.getApplicableTaxes().contains(TaxBand.MISC)){
            return ZERO;
        }
        return percentage(product.getPrice(), TAX_RATE);
    }

    private BigDecimal percentage(BigDecimal base, BigDecimal percentage){
        return round(base.multiply(percentage).divide(new BigDecimal(100)));
    }

    private BigDecimal round(BigDecimal value) {
        BigDecimal divided = value.divide(ROUND_TO, 0, UP);
        return divided.multiply(ROUND_TO);
    }
}

public interface Tax {

    BigDecimal calculate(Product product);
}

public enum TaxBand {
    MEDICAL, CD, MISC
}

public class TaxService {

    private ProductService productService;
    private List<Tax> taxes;

    public TaxService(ProductService productService, Tax... taxes) {
        this.productService = productService;
        this.taxes = Arrays.asList(taxes);
    }

    public BigDecimal calculateTax(String productName) throws ProductNotFoundException {
        Product product = productService.getProductByName(productName);

        return taxes.stream()
                .map(tax -> tax.calculate(product))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

Shopping Basket With Discounts and Tax

Examples Input:

  • one book at 29.49
  • one music CD at 15.99
  • List one chocolate snack at 0.75
  • one bottle of wine at 20.99
  • one box of tooth ache pills at 4.15
  • one box of pins at 11.25
  • one music CD at 14.99
  • 1 bottle of wine: 24.69

  • 1 box of headache pills: 4.15

  • 1 box of pins: 13.25

  • 1 music CD: 18.89

  • Sales Taxes: 9.60

  • Total: 60.98

         public class Basket {
    
         private final ProductService productService;
         private final TaxService taxService;
         private final Map<String, LineItem> items;
    
         public Basket(ProductService productService, TaxService taxService) {
             this.items = new HashMap<>();
             this.productService = productService;
             this.taxService = taxService;
         }
    
         public void addItems(String... names) throws ProductNotFoundException {
    
             for(String name : names) {
                 if (items.containsKey(name.toLowerCase())) {
                     updateExistingLineItem(name);
                 } else {
                     addNewLineItem(name);
                 }
             }
         }
    
         private void updateExistingLineItem(String name) throws ProductNotFoundException {
             Cost cost = calculateCost(name);
             items.get(name.toLowerCase()).update(cost);
         }
    
         private void addNewLineItem(String name) throws ProductNotFoundException {
             Cost cost = calculateCost(name);
             LineItem lineItem = new LineItem(name, cost);
             items.put(name.toLowerCase(), lineItem);
         }
    
         private Cost calculateCost(String name) throws ProductNotFoundException {
             BigDecimal gross = productService.price(name);
             BigDecimal tax = taxService.calculateTax(name);
             return new Cost(gross, tax);
         }
    
         public BigDecimal getTotalPrice() {
             return items.values()
                     .stream().map(LineItem::getNetPrice)
                     .reduce(ZERO, BigDecimal::add);
         }
    
         public BigDecimal getSalesTax() {
             return items.values()
                     .stream().map(LineItem::getTax)
                     .reduce(ZERO, BigDecimal::add);
         }
    
         @Override
         public String toString() {
             String formattedItems = items.values().toString()
                     .replace(",", "")
                     .replace("[", "")
                     .replace("]", "")
                     .trim();
    
             return formattedItems + "\nSales Taxes: " + getSalesTax() + "\nTotal: " + getTotalPrice() + "\n";
         }
     }
    

    public class Cost {

     private final BigDecimal gross;
     private final BigDecimal tax;
    
     public Cost(BigDecimal gross, BigDecimal tax) {
         this.gross = gross;
         this.tax = tax;
     }
    
     public Cost add(Cost cost) {
         return new Cost(this.gross.add(cost.gross), this.tax.add(cost.tax));
     }
    
     public BigDecimal getGross() {
         return gross;
     }
    
     public BigDecimal getTax() {
         return tax;
     }
    

    }

    public class LineItem {

     private final String name;
     private Integer quantity = 0;
     private Cost cost;
    
     public LineItem(String name, Integer quantity, Cost cost) {
         this.name = name;
         this.quantity = quantity;
         this.cost = cost;
     }
    
     public LineItem(String name, Cost cost) {
         this(name, 1, cost);
     }
    
     public void update(Cost cost) {
         this.cost = this.cost.add(cost);
         this.quantity++;
     }
    
     public BigDecimal getTax() {
         return this.cost.getTax();
     }
    
     public BigDecimal getNetPrice() {
         return cost.getGross().add(getTax());
     }
    
     @Override
     public String toString() {
         StringJoiner joiner = new StringJoiner(" ");
         joiner.add(quantity.toString());
         joiner.add(name);
         joiner.add(":");
         joiner.add(getNetPrice().toString());
    
         return "\n" + joiner.toString();
     }
    

    }

    public class Product {

     private String name;
     private BigDecimal price;
     private List<TaxBand> applicableTaxes;
    
     public Product(String name, String price, TaxBand... applicableTaxes) {
         this.name = name;
         this.price = new BigDecimal(price);
         this.applicableTaxes = asList(applicableTaxes);
     }
    
     public String getName() {
         return name;
     }
    
     public BigDecimal getPrice() {
         return price;
     }
    
     public List<TaxBand> getApplicableTaxes() {
         return applicableTaxes;
     }
    

    }

    public class ProductService {

     private ProductRepository productRepository;
    
     public ProductService(ProductRepository productRepository, Product... items) {
         this.productRepository = productRepository;
         Arrays.stream(items).forEach(item -> this.productRepository.add(item));
     }
    
     public Product getProductByName(String name) throws ProductNotFoundException {
         return productRepository.findByProductName(name);
     }
    
     public BigDecimal price(String name) throws ProductNotFoundException {
         return productRepository.price(name);
     }
    

    }

    public class MapProductRepository implements ProductRepository {

     private final Map<String, Product> products = new HashMap<>();
    
     @Override
     public Product findByProductName(String name) throws ProductNotFoundException {
         Product product = products.get(name.toLowerCase());
    
         if(product == null) {
             throw new ProductNotFoundException(name + " not supported");
         }
         return product;
     }
    
     @Override
     public BigDecimal price(String name) throws ProductNotFoundException{
         return findByProductName(name.toLowerCase()).getPrice();
     }
    
     @Override
     public void add(Product product) {
         products.put(product.getName().toLowerCase(), product);
     }
    

    }

    public interface ProductRepository {

     Product findByProductName(String name) throws ProductNotFoundException;
    
     BigDecimal price(String name) throws ProductNotFoundException;
    
     void add(Product product);
    

    }

    public class CompactDiskTax implements Tax {

     public static final BigDecimal TAX = new BigDecimal("1.25");
    
     @Override
     public BigDecimal calculate(Product product) {
    
         if(!product.getApplicableTaxes().contains(CD)){
             return ZERO;
         }
         return TAX;
     }
    

    }

    public class MiscTax implements Tax {

     public static final BigDecimal ROUND_TO = new BigDecimal("0.05");
     public static final BigDecimal TAX_RATE = new BigDecimal("17.5");
    
     public BigDecimal calculate(Product product) {
    
         if(!product.getApplicableTaxes().contains(TaxBand.MISC)){
             return ZERO;
         }
         return percentage(product.getPrice(), TAX_RATE);
     }
    
     private BigDecimal percentage(BigDecimal base, BigDecimal percentage){
         return round(base.multiply(percentage).divide(new BigDecimal(100)));
     }
    
     private BigDecimal round(BigDecimal value) {
         BigDecimal divided = value.divide(ROUND_TO, 0, UP);
         return divided.multiply(ROUND_TO);
     }
    

    }

    public interface Tax {

     BigDecimal calculate(Product product);
    

    }

    public enum TaxBand { MEDICAL, CD, MISC }

    public class TaxService {

     private ProductService productService;
     private List<Tax> taxes;
    
     public TaxService(ProductService productService, Tax... taxes) {
         this.productService = productService;
         this.taxes = Arrays.asList(taxes);
     }
    
     public BigDecimal calculateTax(String productName) throws ProductNotFoundException {
         Product product = productService.getProductByName(productName);
    
         return taxes.stream()
                 .map(tax -> tax.calculate(product))
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
     }
    

    }

Shopping basket with discounts and tax

Examples:

Input:

  • One book at 29.49
  • One music CD at 15.99
  • List one chocolate snack at 0.75
  • One bottle of wine at 20.99
  • One box of tooth ache pills at 4.15
  • One box of pins at 11.25
  • One music CD at 14.99
  • 1 bottle of wine: 24.69
  • 1 box of headache pills: 4.15
  • 1 box of pins: 13.25
  • 1 music CD: 18.89
  • Sales Taxes: 9.60
  • Total: 60.98
public class Basket {

    private final ProductService productService;
    private final TaxService taxService;
    private final Map<String, LineItem> items;

    public Basket(ProductService productService, TaxService taxService) {
        this.items = new HashMap<>();
        this.productService = productService;
        this.taxService = taxService;
    }

    public void addItems(String... names) throws ProductNotFoundException {

        for(String name : names) {
            if (items.containsKey(name.toLowerCase())) {
                updateExistingLineItem(name);
            } else {
                addNewLineItem(name);
            }
        }
    }

    private void updateExistingLineItem(String name) throws ProductNotFoundException {
        Cost cost = calculateCost(name);
        items.get(name.toLowerCase()).update(cost);
    }

    private void addNewLineItem(String name) throws ProductNotFoundException {
        Cost cost = calculateCost(name);
        LineItem lineItem = new LineItem(name, cost);
        items.put(name.toLowerCase(), lineItem);
    }

    private Cost calculateCost(String name) throws ProductNotFoundException {
        BigDecimal gross = productService.price(name);
        BigDecimal tax = taxService.calculateTax(name);
        return new Cost(gross, tax);
    }

    public BigDecimal getTotalPrice() {
        return items.values()
                .stream().map(LineItem::getNetPrice)
                .reduce(ZERO, BigDecimal::add);
    }

    public BigDecimal getSalesTax() {
        return items.values()
                .stream().map(LineItem::getTax)
                .reduce(ZERO, BigDecimal::add);
    }

    @Override
    public String toString() {
        String formattedItems = items.values().toString()
                .replace(",", "")
                .replace("[", "")
                .replace("]", "")
                .trim();

        return formattedItems + "\nSales Taxes: " + getSalesTax() + "\nTotal: " + getTotalPrice() + "\n";
    }
}

public class Cost {

    private final BigDecimal gross;
    private final BigDecimal tax;

    public Cost(BigDecimal gross, BigDecimal tax) {
        this.gross = gross;
        this.tax = tax;
    }

    public Cost add(Cost cost) {
        return new Cost(this.gross.add(cost.gross), this.tax.add(cost.tax));
    }

    public BigDecimal getGross() {
        return gross;
    }

    public BigDecimal getTax() {
        return tax;
    }
}

public class LineItem {

    private final String name;
    private Integer quantity = 0;
    private Cost cost;

    public LineItem(String name, Integer quantity, Cost cost) {
        this.name = name;
        this.quantity = quantity;
        this.cost = cost;
    }

    public LineItem(String name, Cost cost) {
        this(name, 1, cost);
    }

    public void update(Cost cost) {
        this.cost = this.cost.add(cost);
        this.quantity++;
    }

    public BigDecimal getTax() {
        return this.cost.getTax();
    }

    public BigDecimal getNetPrice() {
        return cost.getGross().add(getTax());
    }

    @Override
    public String toString() {
        StringJoiner joiner = new StringJoiner(" ");
        joiner.add(quantity.toString());
        joiner.add(name);
        joiner.add(":");
        joiner.add(getNetPrice().toString());

        return "\n" + joiner.toString();
    }
}

public class Product {

    private String name;
    private BigDecimal price;
    private List<TaxBand> applicableTaxes;

    public Product(String name, String price, TaxBand... applicableTaxes) {
        this.name = name;
        this.price = new BigDecimal(price);
        this.applicableTaxes = asList(applicableTaxes);
    }

    public String getName() {
        return name;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public List<TaxBand> getApplicableTaxes() {
        return applicableTaxes;
    }
}

public class ProductService {

    private ProductRepository productRepository;

    public ProductService(ProductRepository productRepository, Product... items) {
        this.productRepository = productRepository;
        Arrays.stream(items).forEach(item -> this.productRepository.add(item));
    }

    public Product getProductByName(String name) throws ProductNotFoundException {
        return productRepository.findByProductName(name);
    }

    public BigDecimal price(String name) throws ProductNotFoundException {
        return productRepository.price(name);
    }
}

public class MapProductRepository implements ProductRepository {

    private final Map<String, Product> products = new HashMap<>();

    @Override
    public Product findByProductName(String name) throws ProductNotFoundException {
        Product product = products.get(name.toLowerCase());

        if(product == null) {
            throw new ProductNotFoundException(name + " not supported");
        }
        return product;
    }

    @Override
    public BigDecimal price(String name) throws ProductNotFoundException{
        return findByProductName(name.toLowerCase()).getPrice();
    }

    @Override
    public void add(Product product) {
        products.put(product.getName().toLowerCase(), product);
    }
}

public interface ProductRepository {

    Product findByProductName(String name) throws ProductNotFoundException;

    BigDecimal price(String name) throws ProductNotFoundException;

    void add(Product product);
}

public class CompactDiskTax implements Tax {

    public static final BigDecimal TAX = new BigDecimal("1.25");

    @Override
    public BigDecimal calculate(Product product) {

        if(!product.getApplicableTaxes().contains(CD)){
            return ZERO;
        }
        return TAX;
    }
}

public class MiscTax implements Tax {

    public static final BigDecimal ROUND_TO = new BigDecimal("0.05");
    public static final BigDecimal TAX_RATE = new BigDecimal("17.5");

    public BigDecimal calculate(Product product) {

        if(!product.getApplicableTaxes().contains(TaxBand.MISC)){
            return ZERO;
        }
        return percentage(product.getPrice(), TAX_RATE);
    }

    private BigDecimal percentage(BigDecimal base, BigDecimal percentage){
        return round(base.multiply(percentage).divide(new BigDecimal(100)));
    }

    private BigDecimal round(BigDecimal value) {
        BigDecimal divided = value.divide(ROUND_TO, 0, UP);
        return divided.multiply(ROUND_TO);
    }
}

public interface Tax {

    BigDecimal calculate(Product product);
}

public enum TaxBand {
    MEDICAL, CD, MISC
}

public class TaxService {

    private ProductService productService;
    private List<Tax> taxes;

    public TaxService(ProductService productService, Tax... taxes) {
        this.productService = productService;
        this.taxes = Arrays.asList(taxes);
    }

    public BigDecimal calculateTax(String productName) throws ProductNotFoundException {
        Product product = productService.getProductByName(productName);

        return taxes.stream()
                .map(tax -> tax.calculate(product))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}
Source Link
TheCoder
  • 223
  • 2
  • 7
Loading