Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
target/
bin/

## Intellij IDEA ##
.idea
.iml

*.log
6 changes: 6 additions & 0 deletions Assumptions
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Notes:
1. Each offer is applied independently, offer applied previously have no bearing on next offer.
2. May be rules be externalized, may be a rules engine like Drools.
3. Possibly could have implemented visitor pattern.
4. Could have added more options at command line i.e show available products, offers or discount applied. but kept it to minimal to calculate the final price after applying discounts.
5.
6 changes: 6 additions & 0 deletions lombok.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
lombok.anyConstructor.addConstructorProperties=true
lombok.log.fieldName=LOGGER
lombok.log.log4j2.flagUsage=error
lombok.log.log4j.flagUsage=error
lombok.log.javaUtilLogging.flagUsage=error
lombok.log.apacheCommons.flagUsage=error
72 changes: 72 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.industriallogic.henrysgroceries</groupId>
<artifactId>PriceGroceryBasket</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.0</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.industriallogic.henrysgroceries;

import com.industriallogic.henrysgroceries.exception.ProductNotFoundException;
import com.industriallogic.henrysgroceries.service.ShoppingBasketServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;

@SpringBootApplication
@Slf4j
public class PriceShoppingBasketApplication implements CommandLineRunner {

@Autowired
private ShoppingBasketServiceImpl shoppingBasketService;

public static void main(String[] args) {
SpringApplication.run(PriceShoppingBasketApplication.class, args);
}

@Override
public void run(String... args) {
startShopping();
}

public void startShopping() {
try (Scanner scanner = new Scanner(System.in)) {
while (true) {
LOGGER.info("Enter 1. To add products to basket | Enter 2. To checkout | Enter X. To EXIT");
String option = scanner.next();
switch (option) {
case "1":
addProductsToBasket( scanner);
break;
case "2":
checkout( );
break;
case "X":
return;
default:
LOGGER.info("Invalid entry, please re-enter '1' OR '2' OR 'X'");
}
}
}
}

private void addProductsToBasket( Scanner scanner) {
LOGGER.info("Enter products separated with space to add to basket: ");
scanner.nextLine();
List<String> productsNameList = Arrays.asList(StringUtils.split(scanner.nextLine()));
productsNameList.forEach(productName -> {
try {
BigDecimal curntTotalAmount = shoppingBasketService.addProductToBasket(productName);
LOGGER.info(String.format("Added %s to basket. Current total amount %.2f " ,productName, curntTotalAmount));
} catch (ProductNotFoundException e) {
LOGGER.error("Product not found for .. " + productName);
}
});
}

private void checkout( ) {
BigDecimal toPay = shoppingBasketService.totalPriceToPay();
LOGGER.info("Total to Pay after discount " + toPay);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.industriallogic.henrysgroceries.exception;

public class ProductNotFoundException extends Exception {

private static final long serialVersionUID = 1L;

public ProductNotFoundException(String message) {
super(message);
}

public ProductNotFoundException(String message, Throwable cause) {
super(message, cause);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.industriallogic.henrysgroceries.model;

public enum MeasurementUnit {
TIN, LOAF, BOTTLE, SINGLE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.industriallogic.henrysgroceries.model;

import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import java.math.BigDecimal;

@RequiredArgsConstructor
@Data
public class Product {
@NonNull private String productCode;
@NonNull private String name;
@NonNull private BigDecimal price;
@NonNull private MeasurementUnit unit;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.industriallogic.henrysgroceries.model;


import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@NoArgsConstructor
@Component
public class ShoppingBasket {

private final Map<Product, Integer> basket = new HashMap<>();
private LocalDate shoppingDate = LocalDate.now();
private BigDecimal curTotalAmount = BigDecimal.ZERO;

public BigDecimal addProductToBasket(Product product) {
basket.compute(product,
(k, v) -> {
return v == null ? 1 : v + 1;
});
curTotalAmount = curTotalAmount.add(product.getPrice());
return curTotalAmount;
}

public LocalDate getShoppingDate() {
return this.shoppingDate;
}

public Map<Product, Integer> getProductsInBasket() {
return Collections.unmodifiableMap(basket);
}

public BigDecimal getCurTotalAmount() {
return curTotalAmount;
}

public void setShoppingDate(LocalDate shoppingDate) {
this.shoppingDate = shoppingDate;
}
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.industriallogic.henrysgroceries.offers;

import com.industriallogic.henrysgroceries.model.Product;
import com.industriallogic.henrysgroceries.model.ShoppingBasket;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import java.math.BigDecimal;
import java.time.LocalDate;

@RequiredArgsConstructor
@Data
public class ComboDiscountOffer implements Offer {

@NonNull
private String offerName;
@NonNull
private Product qualifyingProduct;
@NonNull
private Integer qualifyingProdMinQnty;
@NonNull
private Product offerOnProduct;
@NonNull
private BigDecimal discountFactor;
@NonNull
private LocalDate offerStartDate;
@NonNull
private LocalDate offerEndDate;

/**
* Applies Combo discount offer if basket qualifies for the offer
* @param basket - basket with products
* @return - discount amount to be applied on the basket
*/
@Override
public BigDecimal getDiscount(ShoppingBasket basket) {
if (isOfferStillValid(basket.getShoppingDate(), offerStartDate, offerEndDate)) {
Integer qualifyingProdCountInBasket = basket.getProductsInBasket().getOrDefault(qualifyingProduct, 0);
if (qualifyingProdCountInBasket >= qualifyingProdMinQnty) {
int purchaseQnty = basket.getProductsInBasket().getOrDefault(offerOnProduct, 0);
int eligibleOfferCount = Math.min(qualifyingProdCountInBasket/qualifyingProdMinQnty, purchaseQnty);
return offerOnProduct.getPrice().multiply(discountFactor).divide(ONE_HUNDRED ).multiply( new BigDecimal(eligibleOfferCount)).setScale(2);
}
}
return BigDecimal.ZERO;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.industriallogic.henrysgroceries.offers;

import com.industriallogic.henrysgroceries.model.ShoppingBasket;

import java.math.BigDecimal;
import java.time.LocalDate;

public interface Offer {

BigDecimal ONE_HUNDRED = new BigDecimal(100);

default boolean isOfferStillValid(LocalDate shoppingDate, LocalDate offerStartDate, LocalDate offerEndDate) {
return ( shoppingDate.compareTo(offerStartDate) >= 0 && shoppingDate.compareTo(offerEndDate) <= 0) ;
}

public BigDecimal getDiscount(ShoppingBasket basket);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.industriallogic.henrysgroceries.offers;

import com.industriallogic.henrysgroceries.model.Product;
import com.industriallogic.henrysgroceries.model.ShoppingBasket;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import java.math.BigDecimal;
import java.time.LocalDate;

@RequiredArgsConstructor
@Data
public class PercentageDiscountOffer implements Offer {
@NonNull
private String offerName;
@NonNull
private Product product;
@NonNull
private BigDecimal discountPercentage;
@NonNull
private LocalDate offerStartDate;
@NonNull
private LocalDate offerEndDate;

/**
* Apples percentage discount offer if basket qualifies for the offer
* @param basket - basket with products
* @return - discount amount to be applied on the basket
*/
public BigDecimal getDiscount(ShoppingBasket basket) {
if (isOfferStillValid(basket.getShoppingDate(), offerStartDate, offerEndDate)) {
Integer productQuantity = basket.getProductsInBasket().getOrDefault(product, 0);
return product.getPrice().multiply(discountPercentage).divide(ONE_HUNDRED ).multiply(new BigDecimal(productQuantity));
}
return BigDecimal.ZERO;
}
}
Loading