Merge branch 'Funktion-zur-automatischen-Margenermittlung' into dev
This commit is contained in:
@@ -12,6 +12,7 @@ import de.rwu.easydrop.api.client.DataSourceFactory;
|
|||||||
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
|
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
|
||||||
import de.rwu.easydrop.data.connector.SQLiteConnector;
|
import de.rwu.easydrop.data.connector.SQLiteConnector;
|
||||||
import de.rwu.easydrop.model.ProductCatalogue;
|
import de.rwu.easydrop.model.ProductCatalogue;
|
||||||
|
import de.rwu.easydrop.service.processing.OrderManager;
|
||||||
import de.rwu.easydrop.service.retriever.CatalogueRetriever;
|
import de.rwu.easydrop.service.retriever.CatalogueRetriever;
|
||||||
import de.rwu.easydrop.service.retriever.ProductRetriever;
|
import de.rwu.easydrop.service.retriever.ProductRetriever;
|
||||||
import de.rwu.easydrop.service.writer.CatalogueWriter;
|
import de.rwu.easydrop.service.writer.CatalogueWriter;
|
||||||
@@ -58,5 +59,7 @@ public final class Main {
|
|||||||
String pCatStr = pCat.toString();
|
String pCatStr = pCat.toString();
|
||||||
LOGGER.info(pCatStr);
|
LOGGER.info(pCatStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OrderManager.createOrders(pCats);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package de.rwu.easydrop.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception that signifies the data of a Product are invalid.
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
public class InvalidCatalogueException extends RuntimeException {
|
||||||
|
/**
|
||||||
|
* Throws an exception that signifies the data of a Product are invalid.
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
public InvalidCatalogueException(final String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws an exception that signifies the data of a Product are invalid.
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* @param cause
|
||||||
|
*/
|
||||||
|
public InvalidCatalogueException(final String message, final Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/java/de/rwu/easydrop/model/ProductPair.java
Normal file
31
src/main/java/de/rwu/easydrop/model/ProductPair.java
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package de.rwu.easydrop.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associates two related Products to one another.
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ProductPair {
|
||||||
|
/**
|
||||||
|
* The first product.
|
||||||
|
*/
|
||||||
|
private final Product product1;
|
||||||
|
/**
|
||||||
|
* The second product.
|
||||||
|
*/
|
||||||
|
private final Product product2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a Product Pair.
|
||||||
|
*
|
||||||
|
* @param a First product
|
||||||
|
* @param b Second product
|
||||||
|
*/
|
||||||
|
public ProductPair(final Product a, final Product b) {
|
||||||
|
this.product1 = a;
|
||||||
|
this.product2 = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,87 @@
|
|||||||
package de.rwu.easydrop.service.processing;
|
package de.rwu.easydrop.service.processing;
|
||||||
|
|
||||||
/**
|
import java.util.List;
|
||||||
* Processes dropshipping orders.
|
|
||||||
*
|
|
||||||
* TODO implement
|
|
||||||
*/
|
|
||||||
public class OrderManager {
|
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import de.rwu.easydrop.exception.InvalidCatalogueException;
|
||||||
|
import de.rwu.easydrop.model.Product;
|
||||||
|
import de.rwu.easydrop.model.ProductCatalogue;
|
||||||
|
import de.rwu.easydrop.model.ProductPair;
|
||||||
|
import de.rwu.easydrop.util.FormattingUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates dropshipping orders based on price margin.
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
public final class OrderManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary logging instance.
|
||||||
|
*/
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(OrderManager.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor to prevent unwanted instantiation.
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException always
|
||||||
|
*/
|
||||||
|
private OrderManager() throws UnsupportedOperationException {
|
||||||
|
throw new UnsupportedOperationException("This is a stateless class, don't instantiate it.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates orders for products with sufficient margin.
|
||||||
|
*
|
||||||
|
* @param pCats Product Catalogues
|
||||||
|
*/
|
||||||
|
public static void createOrders(final List<ProductCatalogue> pCats) {
|
||||||
|
for (ProductCatalogue pCat : pCats) {
|
||||||
|
ProductPair pair = getHighestMarginProducts(pCat);
|
||||||
|
|
||||||
|
// #12: Create actual orders/transactions, remove logger
|
||||||
|
double margin = pair.getProduct2().getCurrentPrice()
|
||||||
|
- pair.getProduct1().getCurrentPrice();
|
||||||
|
String marginFormatted = FormattingUtil.formatEuro(margin);
|
||||||
|
LOGGER.info("{}: Margin {} ({} to {})",
|
||||||
|
pCat.getProductName(),
|
||||||
|
marginFormatted,
|
||||||
|
pair.getProduct1().getDataOrigin(),
|
||||||
|
pair.getProduct2().getDataOrigin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cheapest and most expensive product of a catalogue to guarantee
|
||||||
|
* the biggest margin as a pair.
|
||||||
|
*
|
||||||
|
* @param pCat Product Catalogue
|
||||||
|
* @return Cheapest Product, Most Expensive Product as a Product Pair
|
||||||
|
*/
|
||||||
|
public static ProductPair getHighestMarginProducts(final ProductCatalogue pCat) {
|
||||||
|
if (pCat.getProducts().size() < 2) {
|
||||||
|
throw new InvalidCatalogueException("Product Catalogue holds less than 2 products!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize indexes
|
||||||
|
Product cheapestProduct = pCat.getProducts().get(0);
|
||||||
|
Product mostExpensiveProduct = pCat.getProducts().get(0);
|
||||||
|
|
||||||
|
for (Product product : pCat.getProducts()) {
|
||||||
|
if (product.getCurrentPrice() < cheapestProduct.getCurrentPrice()) {
|
||||||
|
cheapestProduct = product;
|
||||||
|
}
|
||||||
|
if (product.getCurrentPrice() > mostExpensiveProduct.getCurrentPrice()) {
|
||||||
|
mostExpensiveProduct = product;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cheapestProduct.getCurrentPrice() == mostExpensiveProduct.getCurrentPrice()) {
|
||||||
|
throw new InvalidCatalogueException("Price margin is zero!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ProductPair(cheapestProduct, mostExpensiveProduct);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public final class FormattingUtil {
|
|||||||
* @return formatted price
|
* @return formatted price
|
||||||
*/
|
*/
|
||||||
public static String formatEuro(final double amount) {
|
public static String formatEuro(final double amount) {
|
||||||
return String.format(Locale.GERMAN, "%,.2f", amount) + " €";
|
return String.format(Locale.GERMAN, "%,.2f", amount) + " Euro";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package de.rwu.easydrop.exception;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class InvalidCatalogueExceptionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConstructorWithMessage_ExceptionWithMessageCreated() {
|
||||||
|
// Arrange
|
||||||
|
String message = "Invalid catalogue";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
InvalidCatalogueException exception = new InvalidCatalogueException(message);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assertions.assertEquals(message, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConstructorWithMessageAndCause_ExceptionWithMessageAndCauseCreated() {
|
||||||
|
// Arrange
|
||||||
|
String message = "Invalid catalogue";
|
||||||
|
Throwable cause = new RuntimeException("Cause exception");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
InvalidCatalogueException exception = new InvalidCatalogueException(message, cause);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assertions.assertEquals(message, exception.getMessage());
|
||||||
|
Assertions.assertEquals(cause, exception.getCause());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,8 +85,8 @@ class ProductCatalogueTest {
|
|||||||
String expectedString = "Product Catalogue: GPU\n" +
|
String expectedString = "Product Catalogue: GPU\n" +
|
||||||
"Description: Graphics Processing Units\n" +
|
"Description: Graphics Processing Units\n" +
|
||||||
"Products:\n" +
|
"Products:\n" +
|
||||||
"Product: [12345 from AmazonSeller (Amazon) at 0,00 € (available: no)]\n" +
|
"Product: [12345 from AmazonSeller (Amazon) at 0,00 Euro (available: no)]\n" +
|
||||||
"Product: [54321 from eBaySeller (eBay) at 0,00 € (available: no)]\n";
|
"Product: [54321 from eBaySeller (eBay) at 0,00 Euro (available: no)]\n";
|
||||||
|
|
||||||
Assertions.assertEquals(expectedString, productCatalogue.toString());
|
Assertions.assertEquals(expectedString, productCatalogue.toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class ProductTest {
|
|||||||
product1.setCurrentPrice(19.99);
|
product1.setCurrentPrice(19.99);
|
||||||
product1.setAvailable(true);
|
product1.setAvailable(true);
|
||||||
|
|
||||||
String expectedString1 = "Product: [12345 from Merchant A (Amazon) at 19,99 € (available: yes)]";
|
String expectedString1 = "Product: [12345 from Merchant A (Amazon) at 19,99 Euro (available: yes)]";
|
||||||
String result1 = product1.toString();
|
String result1 = product1.toString();
|
||||||
|
|
||||||
assertEquals(expectedString1, result1);
|
assertEquals(expectedString1, result1);
|
||||||
@@ -31,7 +31,7 @@ class ProductTest {
|
|||||||
product2.setCurrentPrice(9.99);
|
product2.setCurrentPrice(9.99);
|
||||||
product2.setAvailable(false);
|
product2.setAvailable(false);
|
||||||
|
|
||||||
String expectedString2 = "Product: [67890 from Merchant B (eBay) at 9,99 € (available: no)]";
|
String expectedString2 = "Product: [67890 from Merchant B (eBay) at 9,99 Euro (available: no)]";
|
||||||
String result2 = product2.toString();
|
String result2 = product2.toString();
|
||||||
|
|
||||||
assertEquals(expectedString2, result2);
|
assertEquals(expectedString2, result2);
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package de.rwu.easydrop.service.processing;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import de.rwu.easydrop.exception.InvalidCatalogueException;
|
||||||
|
import de.rwu.easydrop.model.Product;
|
||||||
|
import de.rwu.easydrop.model.ProductCatalogue;
|
||||||
|
import de.rwu.easydrop.model.ProductPair;
|
||||||
|
|
||||||
|
class OrderManagerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConstructorIsPrivate()
|
||||||
|
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||||
|
// Check for private constructor
|
||||||
|
Constructor<OrderManager> constructor = OrderManager.class.getDeclaredConstructor();
|
||||||
|
assertTrue(Modifier.isPrivate(constructor.getModifiers()));
|
||||||
|
|
||||||
|
// Make sure exception is thrown when instantiating
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
assertThrows(InvocationTargetException.class, () -> {
|
||||||
|
constructor.newInstance();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateOrders_ValidCatalogues_OrdersCreated() {
|
||||||
|
// Arrange
|
||||||
|
List<ProductCatalogue> catalogues = createSampleCatalogues();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
OrderManager.createOrders(catalogues);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetHighestMarginProducts_ValidCatalogue_CheapestAndMostExpensiveProductsReturned() {
|
||||||
|
// Arrange
|
||||||
|
ProductCatalogue catalogue = createSampleCatalogue();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
ProductPair pair = OrderManager.getHighestMarginProducts(catalogue);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertNotNull(pair);
|
||||||
|
assertNotNull(pair.getProduct1());
|
||||||
|
assertNotNull(pair.getProduct2());
|
||||||
|
assertEquals("Product2", pair.getProduct1().getProductId());
|
||||||
|
assertEquals("Product3", pair.getProduct2().getProductId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetHighestMarginProducts_InvalidCatalogue_ThrowsInvalidCatalogueException() {
|
||||||
|
// Arrange
|
||||||
|
ProductCatalogue catalogue = new ProductCatalogue("Catalogue1", "Sample catalogue");
|
||||||
|
catalogue.addProduct(new Product());
|
||||||
|
// The catalogue has only one product, which is invalid
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Exception e = assertThrows(InvalidCatalogueException.class,
|
||||||
|
() -> OrderManager.getHighestMarginProducts(catalogue));
|
||||||
|
|
||||||
|
assertEquals("Product Catalogue holds less than 2 products!", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetHighestMarginProducts_InvalidCatalogue_NoMargin() {
|
||||||
|
// Arrange
|
||||||
|
ProductCatalogue catalogue = new ProductCatalogue("Catalogue1", "Sample catalogue");
|
||||||
|
catalogue.addProduct(new Product());
|
||||||
|
catalogue.addProduct(new Product());
|
||||||
|
// The catalogue has only one product, which is invalid
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Exception e = assertThrows(InvalidCatalogueException.class,
|
||||||
|
() -> OrderManager.getHighestMarginProducts(catalogue));
|
||||||
|
|
||||||
|
assertEquals("Price margin is zero!", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ProductCatalogue> createSampleCatalogues() {
|
||||||
|
List<ProductCatalogue> catalogues = new ArrayList<>();
|
||||||
|
|
||||||
|
ProductCatalogue catalogue1 = createSampleCatalogue();
|
||||||
|
catalogues.add(catalogue1);
|
||||||
|
|
||||||
|
ProductCatalogue catalogue2 = new ProductCatalogue("Catalogue2", "Sample catalogue 2");
|
||||||
|
Product product4 = new Product();
|
||||||
|
product4.setProductId("Product4");
|
||||||
|
product4.setCurrentPrice(6.78);
|
||||||
|
catalogue2.addProduct(product4);
|
||||||
|
Product product5 = new Product();
|
||||||
|
product5.setProductId("Product5");
|
||||||
|
product5.setCurrentPrice(7.89);
|
||||||
|
catalogue2.addProduct(product5);
|
||||||
|
catalogues.add(catalogue2);
|
||||||
|
|
||||||
|
return catalogues;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProductCatalogue createSampleCatalogue() {
|
||||||
|
ProductCatalogue catalogue = new ProductCatalogue("Catalogue1", "Sample catalogue");
|
||||||
|
Product product1 = new Product();
|
||||||
|
product1.setProductId("Product1");
|
||||||
|
product1.setCurrentPrice(1.23);
|
||||||
|
catalogue.addProduct(product1);
|
||||||
|
Product product2 = new Product();
|
||||||
|
product2.setProductId("Product2");
|
||||||
|
product2.setCurrentPrice(0.89);
|
||||||
|
catalogue.addProduct(product2);
|
||||||
|
Product product3 = new Product();
|
||||||
|
product3.setProductId("Product3");
|
||||||
|
product3.setCurrentPrice(4.56);
|
||||||
|
catalogue.addProduct(product3);
|
||||||
|
return catalogue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ class FormattingUtilTest {
|
|||||||
@Test
|
@Test
|
||||||
void testFormatEuro_positiveAmount() {
|
void testFormatEuro_positiveAmount() {
|
||||||
double amount = 1234.56;
|
double amount = 1234.56;
|
||||||
String expectedFormattedAmount = "1.234,56 €";
|
String expectedFormattedAmount = "1.234,56 Euro";
|
||||||
|
|
||||||
String formattedAmount = FormattingUtil.formatEuro(amount);
|
String formattedAmount = FormattingUtil.formatEuro(amount);
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class FormattingUtilTest {
|
|||||||
@Test
|
@Test
|
||||||
void testFormatEuro_zeroAmount() {
|
void testFormatEuro_zeroAmount() {
|
||||||
double amount = 0.0;
|
double amount = 0.0;
|
||||||
String expectedFormattedAmount = "0,00 €";
|
String expectedFormattedAmount = "0,00 Euro";
|
||||||
|
|
||||||
String formattedAmount = FormattingUtil.formatEuro(amount);
|
String formattedAmount = FormattingUtil.formatEuro(amount);
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ class FormattingUtilTest {
|
|||||||
@Test
|
@Test
|
||||||
void testFormatEuro_negativeAmount() {
|
void testFormatEuro_negativeAmount() {
|
||||||
double amount = -789.12;
|
double amount = -789.12;
|
||||||
String expectedFormattedAmount = "-789,12 €";
|
String expectedFormattedAmount = "-789,12 Euro";
|
||||||
|
|
||||||
String formattedAmount = FormattingUtil.formatEuro(amount);
|
String formattedAmount = FormattingUtil.formatEuro(amount);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user