Add InventoryManagementSystem app
This commit is contained in:
7
InventoryManagementSystem/Dockerfile
Normal file
7
InventoryManagementSystem/Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
FROM openjdk:21-jdk-slim
|
||||||
|
RUN groupadd -r appuser && useradd -r -g appuser appuser
|
||||||
|
WORKDIR /app
|
||||||
|
COPY build/libs/simple-inventory-1.0.0.jar ./app.jar
|
||||||
|
RUN chown appuser:appuser ./app.jar
|
||||||
|
USER appuser
|
||||||
|
CMD ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
|
||||||
36
InventoryManagementSystem/build.gradle
Normal file
36
InventoryManagementSystem/build.gradle
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'application'
|
||||||
|
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClass = "inventory.Main"
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
archiveBaseName = 'simple-inventory'
|
||||||
|
manifest {
|
||||||
|
attributes(
|
||||||
|
'Main-Class': application.mainClass
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
|
||||||
|
testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}"
|
||||||
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package inventory;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static inventory.ProductFactory.BOOK_TYPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strategy pattern implementation for calculating different types of discounts.
|
||||||
|
* Requirements:
|
||||||
|
* - Student discount: 10% off books only
|
||||||
|
* - Bulk discount: 15% off when buying 5+ items
|
||||||
|
* - No discount option
|
||||||
|
* - Return discount amount and description
|
||||||
|
*/
|
||||||
|
public class DiscountCalculator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inner class to hold discount calculation results.
|
||||||
|
*/
|
||||||
|
public static class DiscountResult {
|
||||||
|
private final double discountAmount;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
public DiscountResult(double discountAmount, String description) {
|
||||||
|
if (discountAmount < 0) {
|
||||||
|
throw new IllegalArgumentException("Discount amount must be greater than 0");
|
||||||
|
}
|
||||||
|
this.discountAmount = discountAmount;
|
||||||
|
this.description = Objects.requireNonNullElse(description, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDiscountAmount() {
|
||||||
|
return discountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param product - the product being purchased
|
||||||
|
* @param quantity - quantity being purchased
|
||||||
|
* @param discountType - type of discount to apply (STUDENT, BULK, NONE)
|
||||||
|
* @return DiscountResult with amount and description
|
||||||
|
*/
|
||||||
|
public static DiscountResult calculateDiscount(Product product, int quantity, String discountType) {
|
||||||
|
|
||||||
|
double discountAmount = 0.0;
|
||||||
|
String description = "No discount applied";
|
||||||
|
|
||||||
|
switch (discountType.toUpperCase()) {
|
||||||
|
|
||||||
|
case "STUDENT":
|
||||||
|
if (isEligibleForStudentDiscount(product)) {
|
||||||
|
discountAmount = product.getPrice() * quantity * 0.10;
|
||||||
|
description = "Student discount: 10% off books";
|
||||||
|
} else {
|
||||||
|
description = "Student discount only applies to books";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "BULK":
|
||||||
|
if (isEligibleForBulkDiscount(quantity)) {
|
||||||
|
discountAmount = product.getPrice() * quantity * 0.15;
|
||||||
|
description = "Bulk discount: 15% off for 5+ items";
|
||||||
|
} else {
|
||||||
|
description = "Bulk discount requires 5+ items";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "EMPLOYEE":
|
||||||
|
discountAmount = product.getPrice() * quantity * 0.20;
|
||||||
|
description = "Employee discount: 20% off";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "NONE":
|
||||||
|
default:
|
||||||
|
// No discount
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DiscountResult(discountAmount, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isEligibleForStudentDiscount(Product product) {
|
||||||
|
return BOOK_TYPE.equals(product.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isEligibleForBulkDiscount(int quantity) {
|
||||||
|
return quantity >= 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package inventory;
|
||||||
|
|
||||||
|
import inventory.DiscountCalculator.DiscountResult;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main business logic class that manages inventory operations.
|
||||||
|
* Requirements:
|
||||||
|
* - Use ProductFactory for creating products
|
||||||
|
* - Use DiscountCalculator for sales with discounts
|
||||||
|
* - Maintain product collection
|
||||||
|
* - Provide inventory operations and statistics
|
||||||
|
*/
|
||||||
|
public class InventoryManager {
|
||||||
|
|
||||||
|
private final Map<String, Product> products;
|
||||||
|
|
||||||
|
public InventoryManager() {
|
||||||
|
products = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add product to inventory using Factory pattern
|
||||||
|
* @param id - product identifier
|
||||||
|
* @param name - product name
|
||||||
|
* @param type - product type
|
||||||
|
* @param price - product price
|
||||||
|
* @param quantity - initial quantity
|
||||||
|
* @return True if product was added, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean addProduct(String id, String name, String type, double price, int quantity) {
|
||||||
|
try {
|
||||||
|
Product product = ProductFactory.createProduct(id, name, type, price, quantity);
|
||||||
|
products.put(id, product);
|
||||||
|
System.out.println("Product added successfully: " + product);
|
||||||
|
return true;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
System.out.println("Error adding product: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id - product identifier
|
||||||
|
* @param quantity - quantity to sell
|
||||||
|
* @param discountType - type of discount to apply
|
||||||
|
* @return True if book was sold, False if there was an error
|
||||||
|
*/
|
||||||
|
public boolean sellProduct(String id, int quantity, String discountType) {
|
||||||
|
if (!productExists(id)) {
|
||||||
|
System.out.println("Product not found: " + id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Product product = products.get(id);
|
||||||
|
|
||||||
|
if (!product.isInStock() || product.getQuantity() < quantity) {
|
||||||
|
System.out.println("Insufficient stock for product: " + id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate discount using Strategy pattern
|
||||||
|
DiscountResult discount =
|
||||||
|
DiscountCalculator.calculateDiscount(product, quantity, discountType);
|
||||||
|
|
||||||
|
// Calculate total and apply discount
|
||||||
|
double totalPrice = product.getPrice() * quantity;
|
||||||
|
double finalPrice = totalPrice - discount.getDiscountAmount();
|
||||||
|
|
||||||
|
// Update inventory
|
||||||
|
product.sell(quantity);
|
||||||
|
|
||||||
|
// Display sale information
|
||||||
|
displaySalesSummary(product, quantity, totalPrice, finalPrice, discount);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add stock to existing product
|
||||||
|
* @param id - product identifier
|
||||||
|
* @param quantity - quantity to add
|
||||||
|
* @return True if stock was added, False if there was an error
|
||||||
|
*/
|
||||||
|
public boolean addStock(String id, int quantity) {
|
||||||
|
Product product = products.get(id);
|
||||||
|
if (product == null) {
|
||||||
|
System.out.println("Product not found: " + id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
product.addStock(quantity);
|
||||||
|
|
||||||
|
System.out.printf("Added %d items to product %s%n", quantity, product);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display all products in inventory
|
||||||
|
*/
|
||||||
|
public void viewInventory() {
|
||||||
|
System.out.println("Inventory:");
|
||||||
|
if (products.isEmpty()) {
|
||||||
|
System.out.println("Inventory is empty.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Product product : products.values().stream().sorted(Comparator.comparing(Product::getId)).toList()) {
|
||||||
|
System.out.printf("\t%s%n",product);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate total inventory value
|
||||||
|
* @return total value of all products
|
||||||
|
*/
|
||||||
|
public double getInventoryValue() {
|
||||||
|
return products.values().stream().mapToDouble(p -> p.getQuantity() * p.getPrice()).sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get products with low stock
|
||||||
|
* @param threshold - minimum stock level
|
||||||
|
* @return list of products below threshold
|
||||||
|
*/
|
||||||
|
public List<Product> getLowStockProducts(int threshold) {
|
||||||
|
return products.values().stream().filter(p -> p.getQuantity() < threshold).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display inventory statistics
|
||||||
|
*/
|
||||||
|
public void viewStatistics() {
|
||||||
|
System.out.printf("Total products: %d%n", products.size());
|
||||||
|
System.out.printf("Total inventory value: %f%n", getInventoryValue());
|
||||||
|
|
||||||
|
List<Product> lowStock = getLowStockProducts(5);
|
||||||
|
if (lowStock.isEmpty()) {
|
||||||
|
System.out.println("No products low on stock.");
|
||||||
|
} else {
|
||||||
|
System.out.println("Products low on stock (<5):");
|
||||||
|
for (Product product : lowStock) {
|
||||||
|
System.out.printf("\tProduct: %s (quantity: %d, price: %f)%n", product.getName(), product.getQuantity(), product.getPrice());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean productExists(String id) {
|
||||||
|
return id != null && products.containsKey(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displaySalesSummary(Product product, int quantity, double originalPrice, double finalPrice, DiscountResult discountInfo) {
|
||||||
|
System.out.printf("Sale completed:%n");
|
||||||
|
System.out.printf("Product: %s%n", product.getName());
|
||||||
|
System.out.printf("Quantity: %d%n", quantity);
|
||||||
|
System.out.printf("Original Price: $%.2f%n", originalPrice);
|
||||||
|
System.out.printf("Discount: $%.2f (%s)%n", discountInfo.getDiscountAmount(), discountInfo.getDescription());
|
||||||
|
System.out.printf("Final Price: $%.2f%n", finalPrice);
|
||||||
|
}
|
||||||
|
}
|
||||||
164
InventoryManagementSystem/src/main/java/inventory/Main.java
Normal file
164
InventoryManagementSystem/src/main/java/inventory/Main.java
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package inventory;
|
||||||
|
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main class providing command-line interface for the inventory system.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
* - Menu with 6 options: Add, View, Sell, Stock, Statistics, Exit
|
||||||
|
* - Input validation and error handling
|
||||||
|
* - Sample data for testing
|
||||||
|
* - User-friendly interface
|
||||||
|
*/
|
||||||
|
public class Main {
|
||||||
|
|
||||||
|
private static InventoryManager manager;
|
||||||
|
private static Scanner scanner;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("Welcome to Inventory Management System!");
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
manager = new InventoryManager();
|
||||||
|
loadSampleData();
|
||||||
|
|
||||||
|
try (Scanner scanner = new Scanner(System.in)) {
|
||||||
|
Main.scanner = scanner;
|
||||||
|
while (true) {
|
||||||
|
showMenu();
|
||||||
|
int choice = getChoice();
|
||||||
|
if (!handleChoice(choice)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
System.err.printf("Unexpected error occurred: %s", e.getMessage());
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
scanner = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load sample data for testing
|
||||||
|
*/
|
||||||
|
private static void loadSampleData() {
|
||||||
|
manager.addProduct("B001", "Java Programming", "BOOK", 29.99, 10);
|
||||||
|
manager.addProduct("B002", "Design Patterns", "BOOK", 35.50, 8);
|
||||||
|
manager.addProduct("E001", "Laptop", "ELECTRONICS", 999.99, 5);
|
||||||
|
manager.addProduct("E002", "Mouse", "ELECTRONICS", 25.99, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display main menu options
|
||||||
|
*/
|
||||||
|
private static void showMenu() {
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("Choices:");
|
||||||
|
System.out.println("\t1. Add Product");
|
||||||
|
System.out.println("\t2. View Inventory");
|
||||||
|
System.out.println("\t3. Sell Product");
|
||||||
|
System.out.println("\t4. Add Stock");
|
||||||
|
System.out.println("\t5. View Statistics");
|
||||||
|
System.out.println("\t6. Exit");
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user choice with input validation
|
||||||
|
* @return user's menu choice
|
||||||
|
*/
|
||||||
|
private static int getChoice() {
|
||||||
|
return getIntInput("Choice: ", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle user menu choice
|
||||||
|
* @param choice - user's selected option
|
||||||
|
* @return should application continue
|
||||||
|
*/
|
||||||
|
private static boolean handleChoice(int choice) {
|
||||||
|
switch (choice) {
|
||||||
|
case 1: addProduct(); break;
|
||||||
|
case 2: manager.viewInventory(); break;
|
||||||
|
case 3: sellProduct(); break;
|
||||||
|
case 4: addStock(); break;
|
||||||
|
case 5: manager.viewStatistics(); break;
|
||||||
|
case 6: return false;
|
||||||
|
default: {
|
||||||
|
System.out.println("Invalid choice.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle adding new product
|
||||||
|
*/
|
||||||
|
private static void addProduct() {
|
||||||
|
String id = getStringInput("Product id: ");
|
||||||
|
String name = getStringInput("Product name: ");
|
||||||
|
|
||||||
|
String type;
|
||||||
|
do {
|
||||||
|
type = getStringInput("Product type (BOOK,ELECTRONICS): ").toUpperCase();
|
||||||
|
} while (!ProductFactory.isValidProductType(type));
|
||||||
|
|
||||||
|
int quantity = getIntInput("Product quantity: ", 1);
|
||||||
|
double price = getDoubleInput("Product price: ", 5.0);
|
||||||
|
|
||||||
|
manager.addProduct(id, name, type, price, quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle selling product
|
||||||
|
*/
|
||||||
|
private static void sellProduct() {
|
||||||
|
String id = getStringInput("Product id: ");
|
||||||
|
int quantity = getIntInput("Product quantity: ", 1);
|
||||||
|
String discountType = getStringInput("Discount type (STUDENT,BULK,NONE)[NONE]: ").toUpperCase();
|
||||||
|
|
||||||
|
manager.sellProduct(id,quantity,discountType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle adding stock
|
||||||
|
*/
|
||||||
|
private static void addStock() {
|
||||||
|
String id = getStringInput("Product id: ");
|
||||||
|
int quantity = getIntInput("Product quantity: ", 1);
|
||||||
|
|
||||||
|
manager.addStock(id, quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getStringInput(String prompt) {
|
||||||
|
System.out.print(prompt);
|
||||||
|
return scanner.nextLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getIntInput(String prompt, int min) {
|
||||||
|
int input = min - 1;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
input = Integer.parseInt(getStringInput(prompt));
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
System.err.println("Invalid input.");
|
||||||
|
}
|
||||||
|
} while (input < min);
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double getDoubleInput(String prompt, double min) {
|
||||||
|
double input = min - 1.0;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
input = Double.parseDouble(getStringInput(prompt));
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
System.err.println("Invalid input.");
|
||||||
|
}
|
||||||
|
} while (input < min);
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
123
InventoryManagementSystem/src/main/java/inventory/Product.java
Normal file
123
InventoryManagementSystem/src/main/java/inventory/Product.java
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package inventory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Product class representing items in the inventory.
|
||||||
|
* Requirements:
|
||||||
|
* - Private fields: id, name, type, price, quantity
|
||||||
|
* - Constructor with validation
|
||||||
|
* - Getter and setter methods
|
||||||
|
* - sell(), addStock(), isInStock() methods
|
||||||
|
* - toString() method
|
||||||
|
*/
|
||||||
|
public class Product {
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
private final String name;
|
||||||
|
private final String type;
|
||||||
|
private double price;
|
||||||
|
private int quantity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id - unique product identifier
|
||||||
|
* @param name - product name
|
||||||
|
* @param type - product category (BOOK or ELECTRONICS)
|
||||||
|
* @param price - product price (must be non-negative)
|
||||||
|
* @param quantity - stock quantity (must be non-negative)
|
||||||
|
* @throws IllegalArgumentException if price or quantity is negative
|
||||||
|
*/
|
||||||
|
public Product(String id, String name, String type, double price, int quantity) {
|
||||||
|
if (price <= 0) {
|
||||||
|
throw new IllegalArgumentException("Price cannot be negative or zero");
|
||||||
|
}
|
||||||
|
if (quantity <= 0) {
|
||||||
|
throw new IllegalArgumentException("Quantity cannot be negative or zero");
|
||||||
|
}
|
||||||
|
if (id == null) {
|
||||||
|
throw new IllegalArgumentException("Id cannot be null");
|
||||||
|
}
|
||||||
|
if (name == null) {
|
||||||
|
throw new IllegalArgumentException("Name cannot be null");
|
||||||
|
}
|
||||||
|
if (type == null) {
|
||||||
|
throw new IllegalArgumentException("Type cannot be null");
|
||||||
|
}
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
|
this.price = price;
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getPrice() {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQuantity() {
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrice(double price) {
|
||||||
|
if (price <= 0) {
|
||||||
|
throw new IllegalArgumentException("Price cannot be negative or zero");
|
||||||
|
}
|
||||||
|
this.price = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuantity(int quantity) {
|
||||||
|
if (quantity <= 0) {
|
||||||
|
throw new IllegalArgumentException("Quantity cannot be negative or zero");
|
||||||
|
}
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param amount - quantity to sell
|
||||||
|
* @return true if sale successful, false if insufficient stock
|
||||||
|
*/
|
||||||
|
public boolean sell(int amount) {
|
||||||
|
if (amount <= 0 || amount > quantity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
quantity -= amount;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param amount - quantity to add to stock
|
||||||
|
*/
|
||||||
|
public void addStock(int amount) {
|
||||||
|
if (amount <= 0) {
|
||||||
|
throw new IllegalArgumentException("Amount cannot be negative or zero");
|
||||||
|
}
|
||||||
|
quantity += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if product has stock available
|
||||||
|
*/
|
||||||
|
public boolean isInStock() {
|
||||||
|
return quantity > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return formatted string representation of the product
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Product{id='%s', name='%s', type='%s', price=%f, quantity=%d}"
|
||||||
|
.formatted(id, name, type, price, quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package inventory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory class for creating Product objects with business rules.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
* - Constants for product types
|
||||||
|
* - Business rules: Books minimum $5, Electronics minimum $10
|
||||||
|
* - Static factory method for product creation
|
||||||
|
* - Exception handling for invalid inputs
|
||||||
|
*/
|
||||||
|
public class ProductFactory {
|
||||||
|
|
||||||
|
public static final String BOOK_TYPE = "BOOK";
|
||||||
|
public static final String ELECTRONICS_TYPE = "ELECTRONICS";
|
||||||
|
public static final String FURNITURE_TYPE = "FURNITURE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method to create products with business rules
|
||||||
|
* @param id - product identifier
|
||||||
|
* @param name - product name
|
||||||
|
* @param type - product type (must be BOOK or ELECTRONICS)
|
||||||
|
* @param price - product price (must meet minimum requirements)
|
||||||
|
* @param quantity - initial stock quantity
|
||||||
|
* @return new Product instance
|
||||||
|
* @throws IllegalArgumentException if validation fails
|
||||||
|
*/
|
||||||
|
public static Product createProduct(String id, String name, String type, double price, int quantity) {
|
||||||
|
|
||||||
|
if (!isValidProductType(type)) {
|
||||||
|
throw new IllegalArgumentException("Product type '%s' not available.".formatted(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
validateBusinessRules(type, price);
|
||||||
|
|
||||||
|
return new Product(id, name, type, price, quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidProductType(String type) {
|
||||||
|
return BOOK_TYPE.equals(type) || ELECTRONICS_TYPE.equals(type) || FURNITURE_TYPE.equals(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateBusinessRules(String type, double price) {
|
||||||
|
if (BOOK_TYPE.equals(type) && price < 5.0) {
|
||||||
|
throw new IllegalArgumentException("Book price must be at least 5");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ELECTRONICS_TYPE.equals(type) && price < 10.0) {
|
||||||
|
throw new IllegalArgumentException("Electronics price must be at least 10");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FURNITURE_TYPE.equals(type) && price < 50.0) {
|
||||||
|
throw new IllegalArgumentException("Furniture price must be at least 50");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package inventory;
|
||||||
|
|
||||||
|
import inventory.DiscountCalculator.DiscountResult;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for DiscountCalculator class.
|
||||||
|
*/
|
||||||
|
class DiscountCalculatorTest {
|
||||||
|
|
||||||
|
private Product book;
|
||||||
|
private Product electronics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up test data before each test
|
||||||
|
*/
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
book = ProductFactory.createProduct("B001", "Test Book", "BOOK", 20.0, 10);
|
||||||
|
electronics = ProductFactory.createProduct("E001", "Test Electronics", "ELECTRONICS", 100.0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test student discount on books
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testStudentDiscountOnBooks() {
|
||||||
|
DiscountResult discount = DiscountCalculator.calculateDiscount(book, 1, "STUDENT");
|
||||||
|
assertNotNull(discount);
|
||||||
|
assertEquals(1 * book.getPrice() * 0.10, discount.getDiscountAmount());
|
||||||
|
assertEquals("Student discount: 10% off books", discount.getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test student discount on electronics (should not apply)
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testStudentDiscountOnElectronics() {
|
||||||
|
DiscountResult discount = DiscountCalculator.calculateDiscount(electronics, 1, "STUDENT");
|
||||||
|
assertNotNull(discount);
|
||||||
|
assertEquals(0, discount.getDiscountAmount());
|
||||||
|
assertEquals("Student discount only applies to books", discount.getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test bulk discount when quantity >= 5
|
||||||
|
*/
|
||||||
|
@ParameterizedTest(name = "quantity = {arguments}")
|
||||||
|
@ValueSource(ints = {5,10})
|
||||||
|
void testBulkDiscountValid(int quantity) {
|
||||||
|
DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, quantity, "BULK");
|
||||||
|
assertNotNull(discountBook);
|
||||||
|
assertEquals(quantity * book.getPrice() * 0.15, discountBook.getDiscountAmount());
|
||||||
|
assertEquals("Bulk discount: 15% off for 5+ items", discountBook.getDescription());
|
||||||
|
|
||||||
|
DiscountResult discountElectronics = DiscountCalculator.calculateDiscount(electronics, quantity, "BULK");
|
||||||
|
assertNotNull(discountElectronics);
|
||||||
|
assertEquals(quantity * electronics.getPrice() * 0.15, discountElectronics.getDiscountAmount());
|
||||||
|
assertEquals("Bulk discount: 15% off for 5+ items", discountElectronics.getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test bulk discount when quantity < 5
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testBulkDiscountInvalid() {
|
||||||
|
DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, 2, "BULK");
|
||||||
|
assertNotNull(discountBook);
|
||||||
|
assertEquals(0, discountBook.getDiscountAmount());
|
||||||
|
assertEquals("Bulk discount requires 5+ items", discountBook.getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test no discount option
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testNoDiscount() {
|
||||||
|
DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, 1, "NONE");
|
||||||
|
assertNotNull(discountBook);
|
||||||
|
assertEquals(0, discountBook.getDiscountAmount());
|
||||||
|
assertEquals("No discount applied", discountBook.getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test boundary conditions
|
||||||
|
*/
|
||||||
|
@ParameterizedTest(name = "quantity = {0}, discount(%) = {1}")
|
||||||
|
@CsvSource({"1,0", "5,0.15", "100,0.15", "1000,0.15", "10000,0.15"})
|
||||||
|
void testBoundaryConditions(int quantity, double discountPercentage) {
|
||||||
|
DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, quantity, "BULK");
|
||||||
|
assertNotNull(discountBook);
|
||||||
|
assertEquals(book.getPrice() * quantity * discountPercentage, discountBook.getDiscountAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test discount calculation accuracy
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testDiscountCalculationAccuracy() {
|
||||||
|
DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, 2, "STUDENT");
|
||||||
|
assertNotNull(discountBook);
|
||||||
|
assertEquals(4, discountBook.getDiscountAmount());
|
||||||
|
|
||||||
|
DiscountResult discountElectronics = DiscountCalculator.calculateDiscount(electronics, 5, "BULK");
|
||||||
|
assertNotNull(discountElectronics);
|
||||||
|
assertEquals(75, discountElectronics.getDiscountAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "type = {0}")
|
||||||
|
@ValueSource(strings = {"STUDENT", "student", "STUdent", "stuDent", "sTudenT"})
|
||||||
|
void testCaseSensitivityOfDiscountTypes(String type) {
|
||||||
|
DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, 1, type);
|
||||||
|
assertNotNull(discountBook);
|
||||||
|
assertEquals(2, discountBook.getDiscountAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "type = {0}")
|
||||||
|
@ValueSource(strings = {"", "--", "\n", "09999", "STUDENT1", "BULKSTUDENT"})
|
||||||
|
void testInvalidDiscountTypes(String type) {
|
||||||
|
DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, 1, type);
|
||||||
|
assertNotNull(discountBook);
|
||||||
|
assertEquals(0, discountBook.getDiscountAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDiscountZeroQuantity() {
|
||||||
|
DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, 0, "STUDENT");
|
||||||
|
assertNotNull(discountBook);
|
||||||
|
assertEquals(0, discountBook.getDiscountAmount());
|
||||||
|
|
||||||
|
DiscountResult discountElectronics = DiscountCalculator.calculateDiscount(electronics, 0, "BULK");
|
||||||
|
assertNotNull(discountElectronics);
|
||||||
|
assertEquals(0, discountElectronics.getDiscountAmount());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package inventory;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for InventoryManager class.
|
||||||
|
*/
|
||||||
|
class InventoryManagerTest {
|
||||||
|
|
||||||
|
private InventoryManager manager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up fresh instance before each test
|
||||||
|
*/
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
manager = new InventoryManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test adding products to inventory
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testAddProduct() {
|
||||||
|
boolean succeeded = manager.addProduct("TEST1", "Test Book", "BOOK", 10.0, 1);
|
||||||
|
assertTrue(succeeded);
|
||||||
|
assertTrue(manager.productExists("TEST1"));
|
||||||
|
assertEquals(10.00, manager.getInventoryValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test adding product with invalid parameters
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testAddInvalidProduct() {
|
||||||
|
boolean succeeded = manager.addProduct("TEST1", "Test Book", "BOOK", 0, 1);
|
||||||
|
assertFalse(succeeded);
|
||||||
|
assertEquals(0, manager.getInventoryValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test selling products
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testSellProduct() {
|
||||||
|
manager.addProduct("TEST1", "Test Book", "BOOK", 10.0, 10);
|
||||||
|
assertEquals(100.00, manager.getInventoryValue());
|
||||||
|
|
||||||
|
manager.sellProduct("TEST1", 2, "NONE");
|
||||||
|
assertEquals(80.00, manager.getInventoryValue());
|
||||||
|
|
||||||
|
manager.sellProduct("TEST1", 6, "BULK");
|
||||||
|
assertEquals(20.00, manager.getInventoryValue());
|
||||||
|
|
||||||
|
manager.sellProduct("TEST1", 2, "STUDENT");
|
||||||
|
assertEquals(0, manager.getInventoryValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test selling more than available stock
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testSellInsufficientStock() {
|
||||||
|
manager.addProduct("TEST1", "Test Book", "BOOK", 10.0, 10);
|
||||||
|
assertEquals(100.00, manager.getInventoryValue());
|
||||||
|
|
||||||
|
manager.sellProduct("TEST1", 12, "NONE");
|
||||||
|
assertEquals(100.00, manager.getInventoryValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test adding stock to existing product
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testAddStock() {
|
||||||
|
manager.addProduct("TEST1", "Test Book", "BOOK", 10.0, 1);
|
||||||
|
assertEquals(10.00, manager.getInventoryValue());
|
||||||
|
|
||||||
|
manager.addStock("TEST1", 1);
|
||||||
|
assertEquals(20.00, manager.getInventoryValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test inventory value calculation
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testInventoryValue() {
|
||||||
|
manager.addProduct("TEST1", "Test Book", "BOOK", 10.0, 2);
|
||||||
|
manager.addProduct("TEST2", "Test Book", "BOOK", 50.0, 1);
|
||||||
|
assertEquals(2 * 10.00 + 1 * 50.00, manager.getInventoryValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test low stock detection
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testLowStockProducts() {
|
||||||
|
manager.addProduct("TEST1", "Test Book", "BOOK", 10.0, 1);
|
||||||
|
manager.addProduct("TEST2", "Test Book", "BOOK", 10.0, 2);
|
||||||
|
manager.addProduct("TEST5", "Test Book", "BOOK", 10.0, 5);
|
||||||
|
manager.addProduct("TEST10", "Test Book", "BOOK", 10.0, 10);
|
||||||
|
|
||||||
|
List<Product> lowStockUnder5 = manager.getLowStockProducts(5);
|
||||||
|
assertEquals(2, lowStockUnder5.size());
|
||||||
|
assertTrue(lowStockUnder5.stream().anyMatch(p -> p.getId().equals("TEST1")));
|
||||||
|
assertTrue(lowStockUnder5.stream().anyMatch(p -> p.getId().equals("TEST2")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test operations on non-existent products
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testNonExistentProduct() {
|
||||||
|
boolean sellSucceeded = manager.sellProduct("NONE", 1, "BOOK");
|
||||||
|
assertFalse(sellSucceeded);
|
||||||
|
|
||||||
|
boolean addStockSucceeded = manager.addStock("NONE", 1);
|
||||||
|
assertFalse(addStockSucceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test complete workflow
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testCompleteWorkflow() {
|
||||||
|
|
||||||
|
boolean addSucceeded = manager.addProduct("TEST1", "Test Book", "BOOK", 10.0, 10);
|
||||||
|
assertTrue(addSucceeded);
|
||||||
|
assertEquals(100.00, manager.getInventoryValue());
|
||||||
|
|
||||||
|
boolean addStockSucceeded = manager.addStock("TEST1", 10);
|
||||||
|
assertTrue(addStockSucceeded);
|
||||||
|
assertEquals(200.00, manager.getInventoryValue());
|
||||||
|
|
||||||
|
boolean sellSucceeded = manager.sellProduct("TEST1", 18, "NONE");
|
||||||
|
assertTrue(sellSucceeded);
|
||||||
|
assertEquals(20.00, manager.getInventoryValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package inventory;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for ProductFactory class.
|
||||||
|
*/
|
||||||
|
class ProductFactoryTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test creating a valid book product
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testCreateValidBook() {
|
||||||
|
Product book = ProductFactory.createProduct("B001", "Java Book", "BOOK", 15.99, 5);
|
||||||
|
assertNotNull(book);
|
||||||
|
assertEquals("B001", book.getId());
|
||||||
|
assertEquals("BOOK", book.getType());
|
||||||
|
assertEquals(15.99, book.getPrice());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test creating a valid electronics product
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testCreateValidElectronics() {
|
||||||
|
Product electronics = ProductFactory.createProduct("E001", "Java Book", "ELECTRONICS", 15.99, 5);
|
||||||
|
assertNotNull(electronics);
|
||||||
|
assertEquals("E001", electronics.getId());
|
||||||
|
assertEquals("ELECTRONICS", electronics.getType());
|
||||||
|
assertEquals(15.99, electronics.getPrice());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test book minimum price validation
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testBookMinimumPriceValidation() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> {
|
||||||
|
ProductFactory.createProduct("B001", "Cheap Book", "BOOK", 3.99, 5);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test electronics minimum price validation
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testElectronicsMinimumPriceValidation() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> {
|
||||||
|
ProductFactory.createProduct("E001", "Cheap Electronics", "ELECTRONICS", 5.99, 5);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test invalid product type
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testInvalidProductType() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> {
|
||||||
|
ProductFactory.createProduct("S001", "Sheep", "SHEEP", 1.99, 5);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test boundary conditions
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testBoundaryConditions() {
|
||||||
|
Product book = ProductFactory.createProduct("B001", "Java Book", "BOOK", 5.00, 5);
|
||||||
|
assertNotNull(book);
|
||||||
|
Product electronics = ProductFactory.createProduct("B001", "Java Book", "ELECTRONICS", 10.00, 5);
|
||||||
|
assertNotNull(electronics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNullValues() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ProductFactory.createProduct(null, "Cheap Book", "BOOK", 1.99, 5));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ProductFactory.createProduct("B001", null, "BOOK", 3.99, 5));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ProductFactory.createProduct("B001", "Cheap Book", null, 3.99, 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEmptyValues() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ProductFactory.createProduct("", "Cheap Book", "BOOK", 1.99, 5));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ProductFactory.createProduct("B001", "", "BOOK", 3.99, 5));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ProductFactory.createProduct("B001", "Cheap Book", "", 3.99, 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNegativeAndZeroValues() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ProductFactory.createProduct("B001", "Cheap Book", "BOOK", 0, 5));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ProductFactory.createProduct("B001", "Cheap Book", "BOOK", 3.99, 0));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ProductFactory.createProduct("B001", "Cheap Book", "BOOK", -1, 5));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ProductFactory.createProduct("B001", "Cheap Book", "BOOK", 3.99, -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
1
gradle.properties
Normal file
1
gradle.properties
Normal file
@@ -0,0 +1 @@
|
|||||||
|
junitVersion=5.9.3
|
||||||
@@ -4,3 +4,4 @@ include 'Portfolio'
|
|||||||
include 'OnlineQuiz'
|
include 'OnlineQuiz'
|
||||||
include 'RetailManagementSystem:back-end'
|
include 'RetailManagementSystem:back-end'
|
||||||
include 'RetailManagementSystem:front-end'
|
include 'RetailManagementSystem:front-end'
|
||||||
|
include 'InventoryManagementSystem'
|
||||||
Reference in New Issue
Block a user