package exceptions; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import se.kth.iv1350.pos.integration.InventorySystem; import static org.junit.jupiter.api.Assertions.*; class ProductIDMissingExceptionTest extends Exception{ private InventorySystem inventorySystem = InventorySystem.getInventorySystemInstance(); @AfterEach public void cleanup() { inventorySystem = InventorySystem.getInventorySystemInstance(); } @Test void testGetProductIDShouldThrowException() { int falseProductID = 12; RuntimeException productIDMissingException = assertThrows(RuntimeException.class, () -> inventorySystem.getProductDTOFromDB(falseProductID)); assertEquals(productIDMissingException.getMessage(),"Product with ID " + falseProductID + " not found in external inventory system.", "The exception message should match the expected message when a product ID is missing."); } @Test void testGetItemIDShouldNotThrowException() { int realProductID = 121; assertDoesNotThrow(() -> inventorySystem.getProductDTOFromDB(realProductID)); } }package exceptions; import org.junit.jupiter.api.*; import se.kth.iv1350.pos.integration.InventorySystem; import static org.junit.jupiter.api.Assertions.*; public class InventoryDBExceptionTest { private InventorySystem inventorySystem = InventorySystem.getInventorySystemInstance(); @AfterEach void cleanup(){ inventorySystem = InventorySystem.getInventorySystemInstance(); } @Test void testInventoryDataShouldThrowServerDownException(){ int productIdDown = 404; RuntimeException serverDownProductId = assertThrows(RuntimeException.class, () -> inventorySystem.getProductDTOFromDB(productIdDown)); assertEquals(serverDownProductId.getMessage(),"The inventory system server is currently unavailable.", "The exception message should match the expected message when the inventory server is down."); } @Test void testInventoryDataShouldNotThrowServerDownException(){ int productIDNotDown = 44; RuntimeException serverDownItemID = assertThrows(RuntimeException.class, () -> inventorySystem.getProductDTOFromDB(productIDNotDown)); assertNotEquals(serverDownItemID.getMessage(),"The inventory system server is currently unavailable."); } } package model; import org.junit.Before; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import se.kth.iv1350.pos.model.Product; import se.kth.iv1350.pos.model.Sale; import static org.junit.jupiter.api.Assertions.assertEquals; public class SaleTest { private Sale sale = new Sale(); @AfterEach public void cleanUp() { sale = new Sale(); } @Test void isProductInSaleFail() { Product productInSale = new Product(132,2,"socks",50,8); sale.addProductToSale(productInSale); Product productNotInSale = new Product(111,2,"apple", 50,8); boolean productIsInSale = sale.isProductInSale(productNotInSale); assertEquals(productIsInSale,false, "The product should not be in the sale if it has a different product ID."); } @Test void isProductInSaleSuccess() { Product productInSale = new Product(132,2,"socks",50,8); sale.addProductToSale(productInSale); Product productNotInSale = new Product(132,1,"socks",50,8); boolean productIsInSale = sale.isProductInSale(productNotInSale); assertEquals(productIsInSale,true); } @Test void addNewProductToSaleSuccess(){ Product productInSale = new Product(132,2,"socks",50,8); sale.addProductToSale(productInSale); Product nextProduct = new Product(sale.getCurrentProducts().get(0)); assertEquals(nextProduct.getProductId(),productInSale.getProductId()); } }package model; import org.junit.jupiter.api.Test; import se.kth.iv1350.pos.model.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; public class PaymentTest { private Payment payment = new Payment(); @Test void increaseAmountToPaySuccess(){ payment.addToPayAmount(10); double amountTest = payment.getPayAmount(); assertEquals(10, amountTest, "The pay amount should be increased by 10 after adding 10."); } @Test void increaseAmountToPayFail(){ payment.addToPayAmount(10); double amountTest = payment.getPayAmount(); assertNotEquals(9, amountTest, "The pay amount should not be 9 after adding 10."); } @Test void payAmountFail(){ payment.addToPayAmount(10); payment.calculateChange(9); double changeTest = payment.getChange(); assertNotEquals(2, changeTest); } @Test void payAmountSuccess(){ payment.addToPayAmount(10); payment.calculateChange(9); double changeTest = payment.getChange(); assertEquals(1, changeTest); } } package se.kth.iv1350.pos.integration; /** * This class represents a receipt printer used for printing receipts during a sale. */ public class ReceiptPrinter { /** * Creates a new instance of the ReceiptPrinter class. */ public ReceiptPrinter(){ } } package se.kth.iv1350.pos.integration; import se.kth.iv1350.pos.model.Product; import se.kth.iv1350.pos.model.ProductDTO; import se.kth.iv1350.pos.model.SaleDTO; import se.kth.iv1350.pos.exceptions.*; /** Represents an inventory system that is used for fetching products. */ public class InventorySystem { private Product[] productsInDB; private static final int SERVER_DOWN_PRODUCT_ID = 404; private static final InventorySystem instance = new InventorySystem(); private InventorySystem() { productsInDB = new Product[] { new Product(121, 3, "Eggs", 25, 30), new Product(123, 3, "Lemon",10, 3.33) }; } /** Updates the quantity of products in the inventory system based on the completed sale. @param processedSale The completed sale to update the product quantities for. */ public void updateQuantityFromSale(SaleDTO processedSale) { } /** Retrieves the {@link ProductDTO} from the inventory system based on the product ID. @param productID The ID of the product to be found in the inventory system. @return A {@link ProductDTO} representing the found product, or null if not found. @throws ProductIDMissingException If the given product ID was not found in the inventory system. @throws InventoryDBException If the inventory system is unavailable. */ public ProductDTO getProductDTOFromDB(int productID) throws ProductIDMissingException, InventoryDBException { if (productID == SERVER_DOWN_PRODUCT_ID) { throw new InventoryDBException(); } Product productToReturn = null; for (Product product : productsInDB) { if (product.getProductId() == productID) { productToReturn = product; } } if (productToReturn == null) { throw new ProductIDMissingException(productID); } return new ProductDTO(productToReturn); } /** Retrieves the singleton instance of the InventorySystem class. @return The singleton instance of the InventorySystem. */ public static InventorySystem getInventorySystemInstance() { return instance; } }package se.kth.iv1350.pos.integration; import se.kth.iv1350.pos.model.SaleDTO; /** * The AccountingSystem class provides methods to update the accounting system with sales information. */ public class AccountingSystem { private static final AccountingSystem accountingSystemInstance = new AccountingSystem(); /** * Creates a new instance of AccountingSystem. */ private AccountingSystem(){ } public static AccountingSystem getAccountingSystemInstance() { return accountingSystemInstance; } /** * Updates the accounting system with the sale information. * * @param saleDTOLog The {@link SaleDTO} object representing the sale to update the accounting system with. */ public void updateAccountingSystem(SaleDTO saleDTOLog){ } }package se.kth.iv1350.pos.integration; import se.kth.iv1350.pos.model.PaymentObserver; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.time.LocalTime; /** * A class that outputs total revenue to a file and implements the {@link PaymentObserver} interface. */ public class TotalRevenueFileOutput implements PaymentObserver { private double totalRevenue; private PrintWriter logStream; /** * Creates a new file in the reports folder to write the revenue log. */ public TotalRevenueFileOutput() { try { logStream = new PrintWriter(new FileWriter("revenue.txt"), true); } catch (IOException ioException) { System.out.println("Could not create revenue log file"); ioException.printStackTrace(); } } /** * Writes the updated total revenue to the revenue log file. * * @param newPayment the new payment that increases the total revenue */ @Override public void newRevenue(double newPayment) { totalRevenue += newPayment; logStream.println("Time: " + LocalTime.now() + " | Total Revenue: " + totalRevenue); } }package se.kth.iv1350.pos.integration; import java.util.*; /** * The DiscountSystem class provides discount information based on customer IDs. */ public class DiscountSystem { private Map<String, Integer> discountMap; /** * Creates a new instance of DiscountSystem and initializes the discount map. */ public DiscountSystem() { discountMap = new HashMap<>(); discountMap.put("123", 10); discountMap.put("456", 15); discountMap.put("789", 25); } /** * Gets the discount percentage for a customer based on their customer ID. * * @param customerId The ID of the customer to get the discount for. * @return The discount percentage as an integer value. If no discount is found for the customer, 0 is returned. */ public int getDiscount(String customerId) { return discountMap.getOrDefault(customerId, 0); } }package se.kth.iv1350.pos.integration; /** * A logger for developers that writes to the console. * When exceptions are thrown, the logger is written to. */ public class DeveloperLog { /** * Creates a new instance of the developer logger. */ public DeveloperLog() {} /** * Writes the exception message to the developer log, which in this case is the console. * * @param exceptionMessage the message from the exception to be logged */ public void logException(String exceptionMessage) { System.out.println("Developer log: " + exceptionMessage + "\n"); } }package se.kth.iv1350.pos.exceptions; /** * Exception thrown when an item with a specific ID is not found. */ public class ProductIDMissingException extends RuntimeException { private int productIDMissing; /** * Constructs a new instance of the exception with the specified product ID. * * @param productIDMissing the ID of the missing product */ public ProductIDMissingException(int productIDMissing) { super("Product with ID " + productIDMissing + " not found in external inventory system."); this.productIDMissing = productIDMissing; } /** * Returns the ID of the missing product. * * @return the ID as an integer */ public int getMissingProductID() { return productIDMissing; } }package se.kth.iv1350.pos.exceptions; /** * Exception thrown when the inventory database is not available. */ public class InventoryDBException extends RuntimeException{ /** * Creates a new instance of the exception with a default message. */ public InventoryDBException() { super("The inventory system server is currently unavailable."); } }package se.kth.iv1350.pos.controller; import se.kth.iv1350.pos.exceptions.*; import se.kth.iv1350.pos.integration.*; import se.kth.iv1350.pos.model.*; import java.math.BigDecimal; import java.util.*; /** * The Controller class is responsible for controlling the POS system flow and interacting with external systems. */ public class Controller { private Sale saleCont; private AccountingSystem accountingSCont; private ReceiptPrinter printerCont; private InventorySystem inventorySCont; private DiscountSystem discountSCont; private List<PaymentObserver> paymentObserverList = new ArrayList<>(); private TotalRevenueFileOutput totalRevenueFileOutput = new TotalRevenueFileOutput(); private DeveloperLog developerLog = new DeveloperLog(); /** * Creates a new instance of Controller. * * @param accountingS The accounting system. * @param inventoryS The inventory system. * @param receiptPrinter The receipt printer. */ public Controller(AccountingSystem accountingS, InventorySystem inventoryS, ReceiptPrinter receiptPrinter){ accountingSCont = AccountingSystem.getAccountingSystemInstance(); inventorySCont = inventoryS; printerCont = receiptPrinter; discountSCont = new DiscountSystem(); } /** * Adds a new observer for the CustomerPayment class to the controller. * * @param observer the observer to be added */ public void addPaymentObserver(PaymentObserver observer) { paymentObserverList.add(observer); } /** * Adds an item found in the InventorySystem connected by the itemID to the Sale. * * @param productId the productId to be searched for * @throws OperationFailedException thrown if there are server issues or if there is no match for productID in the inventory system */ public void scanProduct(int productId) throws OperationFailedException { ProductDTO productDTOFromDB = null; try { productDTOFromDB = inventorySCont.getProductDTOFromDB(productId); } catch (ProductIDMissingException productIDMissing) { throw new OperationFailedException("Product ID was not found in the inventory system.", productIDMissing); } catch (InventoryDBException inventoryDBDown) { developerLog.logException(inventoryDBDown.getMessage()); throw new OperationFailedException("Inventory system server is down.", inventoryDBDown); } Product productFromDB = new Product(productDTOFromDB); productFromDB.increaseQuantity(1); saleCont.addProductToSale(productFromDB); } /** * Start a new <code>Sale</code>. */ public void initializeNewSale() { saleCont = new Sale(); saleCont.getPayment().addPaymentObservers(paymentObserverList); saleCont.getPayment().setTotalRevenue(totalRevenueFileOutput); } /** * A method to end the <code>Sale</code> and update the external systems with the <code>Sale</code> details. */ public void finalizeSaleAndUpdateSystems(){ SaleDTO saleDTO = new SaleDTO(saleCont); inventorySCont.updateQuantityFromSale(saleDTO); AccountingSystem.getAccountingSystemInstance().updateAccountingSystem(saleDTO); } /** * Calculates the change for the customer's payment and returns it. * * @param amountPaid The amount paid by the customer. * @return The change to give back to the customer. */ public double processPaymentAndReturnChange(double amountPaid){ saleCont.getPayment().calculateChange(amountPaid); return saleCont.getPayment().getChange(); } /** * Get <code>SaleDTO</code> for current <code>Sale</code> linked to <code>Controller</code>. * * @return The <code>SaleDTO</code> object representing the current sale. */ public SaleDTO getSaleDTO(){ SaleDTO saleDTO = new SaleDTO(saleCont); return saleDTO; } // A method that creates a new sale and applies a discount to it and handles any exceptions that may occur public void createSaleAndApplyDiscount() { Sale sale = new Sale(); sale.setDiscounter(new QuantityDiscounter(3)); BigDecimal amount = sale.getTotalAmount(); BigDecimal discountedPrice = sale.applyDiscount(amount); System.out.println("The discounted price is: " + discountedPrice); } public void setDiscounter(Discounter discounter) { saleCont.setDiscounter(discounter); } }package se.kth.iv1350.pos.controller; /** * Exception thrown when an operation fails. */ public class OperationFailedException extends Exception { /** * Creates a new instance of the exception with a message and a cause. * * @param message the message for the exception * @param cause the cause of the exception */ public OperationFailedException(String message, Exception cause){ super(message, cause); } }package se.kth.iv1350.pos.model; import java.time.LocalTime; /** * This class represents a receipt and the information it contains. */ public class Receipt { private double saleTotal; private double change; private double totalVat; private String[] name = new String[0]; private double[] price = new double[0]; private double[] vat = new double[0]; private int[] quantity = new int[0]; private LocalTime receiptCreationTime; private int receiptRowPos = 0; /** * Creates a new instance of the Receipt class with the current time of creation. */ public Receipt(){ this.receiptCreationTime = LocalTime.now(); } public int[] getQuantity(){ return quantity; } public void setChange(double changeFromSale){ change = changeFromSale; } public double getChange(){ return change; } public String getProductNameBasedOnIndex(int indexInName){ return name[indexInName]; } public double[] getPrice() { return price; } public String[] getNameList(){ return name; } public double[] getVatList(){ return vat; } public double getTotalVat(){ return totalVat; } public double getSaleTotal() { return saleTotal; } public LocalTime getReceiptCreationTime(){ return receiptCreationTime; } /** * Adds a product to the receipt. * * @param addProductToReceipt the product to add to the receipt. */ public void addRow(Product addProductToReceipt){ } }package se.kth.iv1350.pos.model; /** * A Data Transfer Object (DTO) for a product in the sale. */ public class ProductDTO { private int productID; private double productVat; private int productQuantity; private double productPrice; private String productName; /** * Creates a new instance of the ProductDTO. * * @param productID the identification number of the product. * @param productPrice the price of the product. * @param productVat the VAT of the product. * @param productQuantity the quantity of the product. * @param productName the name of the product. */ public ProductDTO(int productID, double productPrice, double productVat, int productQuantity, String productName){ this.productID = productID; this.productPrice = productPrice; this.productVat = productVat; this.productQuantity = productQuantity; this.productName = productName; } /** * Creates a new instance of the ProductDTO based on a Product object. * * @param product the Product object to create the DTO from. */ public ProductDTO(Product product){ this.productName = product.getProductName(); this.productPrice = product.getProductPrice(); this.productID = product.getProductId(); this.productQuantity = product.getProductQuantity(); this.productVat = product.getVat(); } public int getProductID(){ return productID; } public double getProductPrice(){ return productPrice; } public double getProductVat(){ return productVat; } public String getProductName(){ return productName; } public int getProductQuantity(){ return productQuantity; } }package se.kth.iv1350.pos.model; import java.math.BigDecimal; // Another concrete strategy class that implements a discount algorithm based on the customer id public class CustomerDiscounter implements Discounter { @Override public BigDecimal applyDiscount(BigDecimal amount) { // Fetch the discount rate from the database based on the customer id BigDecimal discountRate = getDiscountRateFromDatabase(); // Apply the discount rate to the amount return amount.multiply(discountRate); } // A method that queries the database for the discount rate based on the customer id private BigDecimal getDiscountRateFromDatabase() { // Connect to the database and execute a query // For example, you can use JDBC or JPA to access the database // Return the discount rate as a BigDecimal // For example, if the customer id is 1234, return 0.9 as the discount rate (10% off) return null; } }package se.kth.iv1350.pos.model; public class Product { private int productId; private int productQuantity = 0; private String productName; private double vat; private double productPrice; /** * Creates a new instance of Product based on a ProductDTO. * * @param productDTO the product data transfer object. */ public Product(ProductDTO productDTO){ this.productName = productDTO.getProductName(); this.productId = productDTO.getProductID(); this.productPrice = productDTO.getProductPrice(); this.vat = productDTO.getProductVat(); } /** * Creates a new instance of Product based on another Product. * * @param product the product to create a new instance from. */ public Product(Product product){ this.productId = product.productId; this.productPrice = product.productPrice; this.productName = product.productName; this.vat = product.vat; } /** * Creates a new instance of Product with the specified parameters. * * @param productId the ID of the product. * @param productQuantity the quantity of the product. * @param productName the name of the product. * @param productPrice the price of the product. * @param vat the VAT of the product. */ public Product(int productId, int productQuantity, String productName, double productPrice, double vat){ this.productId = productId; this.vat = vat; this.productQuantity = productQuantity; this.productName = productName; this.productPrice = productPrice; } public double getProductPrice(){ return productPrice; } public int getProductId(){ return productId; } public double getVat(){ return vat; } public int getProductQuantity(){ return productQuantity; } public String getProductName(){ return productName; } /** * Increases the quantity of the product * @param moreQuantity The amount to increase the quantity by */ public void increaseQuantity (int moreQuantity){ this.productQuantity += moreQuantity; } }package se.kth.iv1350.pos.model; import java.time.LocalTime; /** * A Data Transfer Object (DTO) for the receipt of the sale. */ public class ReceiptDTO { private double saleTotal; private double change; private double totalVat; private String[] name; private double[] price; private double[] vat; private int[] quantity; private LocalTime date; /** * Creates a new instance of the <code>ReceiptDTO</code> class using a <code>Receipt</code> object. * * @param receipt the receipt to create a DTO from. */ public ReceiptDTO(Receipt receipt){ saleTotal = receipt.getSaleTotal(); change = receipt.getChange(); totalVat = receipt.getTotalVat(); name = receipt.getNameList(); price = receipt.getPrice(); vat = receipt.getVatList(); quantity = receipt.getQuantity(); date = receipt.getReceiptCreationTime(); } }package se.kth.iv1350.pos.model; import java.math.BigDecimal; import java.sql.SQLException; // A concrete strategy class that implements a discount algorithm based on the number of items bought public class QuantityDiscounter implements Discounter { // Add a field to store the number of items bought private int quantity; // Add a constructor that takes the number of items bought as a parameter public QuantityDiscounter(int quantity) { this.quantity = quantity; } @Override public BigDecimal applyDiscount(BigDecimal amount) { try { // Fetch the discount rate from the database based on the number of items bought // Pass the quantity field to the getDiscountRateFromDatabase method BigDecimal discountRate = getDiscountRateFromDatabase(quantity); // Check if the discount rate is null before multiplying it with the amount if (discountRate != null) { // Apply the discount rate to the amount return amount.multiply(discountRate); } else { // Return the original amount without any discount return amount; } } catch (DatabaseFailureException e) { // Wrap the checked exception in a runtime exception throw new RuntimeException(e); } } // A method that queries the database for the discount rate based on the number of items bought and throws an exception if there is a database failure private BigDecimal getDiscountRateFromDatabase(int quantity) throws DatabaseFailureException { // Connect to the database and execute a query // For example, you can use JDBC or JPA to access the database // Return the discount rate as a BigDecimal // For example, if the number of items bought is 3 or more, return 0.9 as the discount rate (10% off) // If there is no discount for the given quantity, return null switch (quantity) { case 1: return BigDecimal.valueOf(0.95); // 5% off for one item case 2: return BigDecimal.valueOf(0.9); // 10% off for two items case 3: return BigDecimal.valueOf(0.85); // 15% off for three items default: return null; // no discount for other quantities } } }package se.kth.iv1350.pos.model; import se.kth.iv1350.pos.integration.TotalRevenueFileOutput; import java.util.*; /** * This class represents a payment made by a customer. */ public class Payment { private double payAmount; private double change; private double paidAmount; private List<PaymentObserver> paymentObservers = new ArrayList<>(); private TotalRevenueFileOutput totalRevenueFileOutput; public static final double FIRST_PAYMENT = 250; public static final double SECOND_PAYMENT = 300; /** * Creates a new instance of Payment with payAmount and change initialized to 0. */ public Payment(){ this.payAmount = 0; this.change = 0; } /** * Increases the total amount paid by the customer by the specified amount. * * @param addPayAmount the amount to add to the total amount paid. */ public void addToPayAmount(double addPayAmount){ this.payAmount += addPayAmount; } /** * Returns the total amount paid by the customer. * * @return the total amount paid by the customer. */ public double getPayAmount(){ return payAmount; } /** * Calculates the change to give back to the customer based on the amount received. * * @param amountReceived the amount received from the customer. */ public void calculateChange(double amountReceived){ change = amountReceived - payAmount; paidAmount = amountReceived; notifyPaymentObservers(); updateRecordedPaymentsLog(); } /** * Sets a class that can output to a file for this class. * * @param totalRevenueFileOutput the class that outputs to a file. */ public void setTotalRevenue(TotalRevenueFileOutput totalRevenueFileOutput) { this.totalRevenueFileOutput = totalRevenueFileOutput; } /** * Registers observers that will be notified when Payment changes state. * * @param paymentObserver the observer that will be notified of the change. */ public void addPaymentObservers(List<PaymentObserver> paymentObserver) { paymentObservers.addAll(paymentObserver); } /** * Notifies observers for the Payment class with the amount for sale. */ private void notifyPaymentObservers(){ for (PaymentObserver paymentObserver : paymentObservers) { paymentObserver.newRevenue(paidAmount - change); } } /** * Updates the amount for sale that updates the log. */ private void updateRecordedPaymentsLog() { totalRevenueFileOutput.newRevenue(paidAmount - change); } /** * Returns the amount of change to give back to the customer. * * @return the amount of change to give back to the customer. */ public double getChange(){ return change; } /** * Returns the amount of money paid by the customer. * * @return the amount of money paid by the customer. */ public double getPaidAmount() { return paidAmount; } }package se.kth.iv1350.pos.model; /** * This class represents a data transfer object (DTO) for a payment. */ public class PaymentDTO { private double totalAmount; private double change; /** * Creates a new instance of the <code>PaymentDTO</code> class based on the information in a {@link Payment} object. * * @param paymentToDTO the payment object to convert to a DTO. */ public PaymentDTO(Payment paymentToDTO){ change = paymentToDTO.getChange(); totalAmount = paymentToDTO.getPayAmount(); } public double getTotalAmount() { return totalAmount; } }package se.kth.iv1350.pos.model; /** * Represents an interface for an observer that observes customer payments. */ public interface PaymentObserver { /** * Called by the observed object when a new payment is made. * * @param newPayment the amount of the new payment. */ void newRevenue(double newPayment); }package se.kth.iv1350.pos.model; import java.time.LocalTime; import java.util.ArrayList; /** * The SaleDTO class represents a data transfer object for a sale made in the point of sale system. * It contains information about the products included in the sale, any discounts applied, payment information, * time of sale, and the receipt for the sale. */ public class SaleDTO { /** * ArrayList that stores the current list of products in the sale. */ private ArrayList<ProductDTO> currentProducts = new ArrayList<>(); /** * The payment information related to the sale. */ private PaymentDTO paymentDTO; /** * The time when the sale was initiated. */ private LocalTime timeOfSale; /** * The receipt information related to the sale. */ private ReceiptDTO receiptDTO; /** * Constructs a new instance of SaleDTO based on the specified Sale object. * * @param saleToDTO the Sale object to create the SaleDTO from. */ public SaleDTO(Sale saleToDTO){ this.timeOfSale = saleToDTO.getTimeOfSale(); for(Product product : saleToDTO.getCurrentProducts()){ this.currentProducts.add(new ProductDTO(product)); } this.receiptDTO = new ReceiptDTO(saleToDTO.getSalesReceipt()); this.paymentDTO = new PaymentDTO(saleToDTO.getPayment()); } public ArrayList<ProductDTO> getCurrentProducts() { return currentProducts; } public PaymentDTO getPaymentDTO() { return paymentDTO; } public LocalTime getTimeOfSale() { return timeOfSale; } public ReceiptDTO getReceiptDTO() { return receiptDTO; } }package se.kth.iv1350.pos.model; import java.math.BigDecimal; // The strategy interface that declares a common method for all discount algorithms public interface Discounter { // Declare that the method may throw a DatabaseFailureException BigDecimal applyDiscount(BigDecimal amount) throws DatabaseFailureException; }package se.kth.iv1350.pos.model; import java.math.BigDecimal; import java.time.LocalTime; import java.util.ArrayList; /** * Represents a sale in the point of sale system. */ public class Sale { private LocalTime timeOfSale; private Payment payment; private Receipt salesReceipt; private String customerId; private Discounter discounter; private BigDecimal totalAmount; private ArrayList<Product> currentProducts = new ArrayList<>(); /** * Creates a new instance of the <code>Sale</code> class. */ public Sale(){ this.timeOfSale = LocalTime.now(); salesReceipt = new Receipt(); payment = new Payment(); this.customerId = customerId; } public LocalTime getTimeOfSale(){ return timeOfSale; } public Payment getPayment(){ return payment; } public ArrayList<Product> getCurrentProducts() { return currentProducts; } public Receipt getSalesReceipt(){ return salesReceipt; } /** * Adds a product to the current sale. * * @param productToAddToSale the <code>Product</code> to be added to the sale. */ public void addProductToSale(Product productToAddToSale){ if(isProductInSale(productToAddToSale)){ for (Product product : currentProducts){ if(product.getProductId() == productToAddToSale.getProductId()){ product.increaseQuantity(1); } } } else{ currentProducts.add(productToAddToSale); } payment.addToPayAmount(productToAddToSale.getProductPrice()); salesReceipt.addRow(productToAddToSale); } /** * Checks if a product is already in the current sale. * * @param product the <code>Product</code> to check. * @return <code>true</code> if the product is already in the sale, <code>false</code> otherwise. */ public boolean isProductInSale(Product product) { boolean isProductInSale = false; for(Product productSearch : currentProducts){ if(productSearch.getProductId() == product.getProductId()) isProductInSale = true; } return isProductInSale; } // A method that sets the strategy object public void setDiscounter(Discounter discounter) { this.discounter = discounter; } // A method that gets the total amount of the sale public BigDecimal getTotalAmount() { return totalAmount; } // A method that uses the strategy object to apply the discount and handles any exceptions that may occur public BigDecimal applyDiscount(BigDecimal amount) { try { // Use the discounter object to apply the discount // Convert the double value to a BigDecimal value // For example, you can use the BigDecimal.valueOf method BigDecimal payAmount = BigDecimal.valueOf(payment.getPayAmount()); return discounter.applyDiscount(payAmount); } catch (DatabaseFailureException e) { // Handle the exception here // For example, you can print or log an error message for developers and users System.out.println("Developer log: " + e.getMessage()); System.out.println("User message: There was an error when fetching discounts from database."); // You can also return a default value or do something else // For example, you can return the original amount without any discount return amount; } } }package se.kth.iv1350.pos.model; // A checked exception class that indicates a database failure public class DatabaseFailureException extends Exception { public DatabaseFailureException(String message) { super(message); } } package se.kth.iv1350.pos.view; import se.kth.iv1350.pos.model.PaymentObserver; /** * Represents a view that displays the total revenue. */ public class TotalRevenueObserver implements PaymentObserver { private double totalRevenue; /** * The TotalRevenueObserver class represents a view that displays the total revenue. */ public TotalRevenueObserver() { totalRevenue = 0; } /** * Updates the total revenue and prints the current state. * * @param newPayment the amount of payment to add to the total revenue */ @Override public void newRevenue(double newPayment) { totalRevenue += newPayment; printCurrentState(); } /** * Prints the current state of the total revenue. */ private void printCurrentState() { System.out.println("\n---------------------"); System.out.println("Total revenue: " + totalRevenue); System.out.println("---------------------\n"); } }package se.kth.iv1350.pos.view; import se.kth.iv1350.pos.controller.Controller; import se.kth.iv1350.pos.model.Payment; import se.kth.iv1350.pos.model.ProductDTO; import se.kth.iv1350.pos.model.SaleDTO; /** * The View class represents the cashier's view. */ public class View { private Controller cont; int firstProductId; int secondProductId; /** * Creates a new instance of the View class. * * @param cont the controller instance to be associated with this view */ public View(Controller cont) { this.cont = cont; cont.addPaymentObserver(new TotalRevenueObserver()); secondProductId = 404; firstProductId = 101; } private void getProductById(int productId) { try { cont.scanProduct(productId); } catch (Exception e) { System.out.println(e.getMessage()); } } /** * Simulates a sale process with three purchased products, * where two share the same product ID and payment, * and prints to the terminal. */ public void simulateSaleProcess() { cont.initializeNewSale(); getProductById(firstProductId); getProductById(secondProductId); getProductById(firstProductId); cont.finalizeSaleAndUpdateSystems(); sendProductInfoToDisplay(); cont.createSaleAndApplyDiscount(); cont.processPaymentAndReturnChange(Payment.FIRST_PAYMENT); cont.initializeNewSale(); getProductById(firstProductId); getProductById(firstProductId); getProductById(121); cont.finalizeSaleAndUpdateSystems(); sendProductInfoToDisplay(); cont.createSaleAndApplyDiscount(); cont.processPaymentAndReturnChange(Payment.SECOND_PAYMENT); } private void sendProductInfoToDisplay() { showProductsInSale(cont.getSaleDTO()); showPriceOfProductsInSale(cont.getSaleDTO()); showQuantityOfProductsInSale(cont.getSaleDTO()); showProductPrice(cont.getSaleDTO()); } /** * Displays the products in the sale. * * @param saleDTO the SaleDTO object containing the products in the sale */ public void showProductsInSale(SaleDTO saleDTO) { System.out.println("Products in sale:"); for (ProductDTO product : saleDTO.getCurrentProducts()) System.out.println(product.getProductName()); } /** * Displays the price of the products in the sale. * * @param saleDTO the SaleDTO object containing the products in the sale */ public void showPriceOfProductsInSale(SaleDTO saleDTO) { System.out.println("Price of products in sale: "); for (ProductDTO productDTO : saleDTO.getCurrentProducts()) { System.out.println(productDTO.getProductPrice()); } } /** * Displays the quantity of the products in the sale. * * @param saleDTO the SaleDTO object containing the products in the sale */ public void showQuantityOfProductsInSale(SaleDTO saleDTO) { System.out.println("Quantity of products in sale: "); for (ProductDTO productDTO : saleDTO.getCurrentProducts()) { System.out.println(productDTO.getProductQuantity()); } } /** * Displays the total price of the products in the sale. * * @param saleDTO the SaleDTO object containing the payment information */ public void showProductPrice(SaleDTO saleDTO) { System.out.println("Price of products in sale: " + saleDTO.getPaymentDTO().getTotalAmount()); } }package se.kth.iv1350.pos.startup; import java.lang.String; import se.kth.iv1350.pos.controller.Controller; import se.kth.iv1350.pos.integration.AccountingSystem; import se.kth.iv1350.pos.integration.InventorySystem; import se.kth.iv1350.pos.integration.ReceiptPrinter; import se.kth.iv1350.pos.model.Discounter; import se.kth.iv1350.pos.model.QuantityDiscounter; import se.kth.iv1350.pos.view.View; /** * The Main class contains the main method that starts up the program. */ public class Main { /** * * @param args contains the arguments sent in command line when running the program. * This won't be used since no commands will be sent in this case. */ public static void main(String[] args) { AccountingSystem accountingSystem = AccountingSystem.getAccountingSystemInstance(); InventorySystem inventorySystem = InventorySystem.getInventorySystemInstance(); ReceiptPrinter receiptPrinter = new ReceiptPrinter(); Controller cont = new Controller(accountingSystem, inventorySystem, receiptPrinter); View view = new View(cont); cont.initializeNewSale(); Discounter discounter = new QuantityDiscounter(1); cont.setDiscounter(discounter); view.simulateSaleProcess(); } }