My Solution for Design a Warehouse Management System with Score: 8/10

by yield_radiant616

Requirements

Warehouse owner

1- Add new partition with a priority rank depending on the partition place.

2- Change the priority or size of the partition.

3- Add new product to the warehouse in a specific partition depending on the product priority rank and size and the partition rank and size.

4- Changing the product partition.

5- Track the stock level and the amount of each product is remaining on the warehouse.

6- Track the expiry date for the batch of products and deliver them using First expired, first out technique

7- manage any incoming or outgoing shipments.

8- Track the total statistics and the net profit after each outgoing shipment.


Define Core Objects

Warehouse model object

Product model object

Product Batch model object

Partition model object

Shipment Item object

Shipment model object

Statistics service object

Order model object

Person model object


Analyze Relationships

1- One-to-Many relationship between Product and Product batch

2- One-to-Many relationship between product and Order Item

3- One-to-Many relationship between Order and Order Item

4- One-to-Many relationship between Person and Shipment

5- Many-to-Many relationship between Shipment and Order

6- One-to-Many relationship between Partition and product


Establish Hierarchy

Person => Client

=> Manager

Shipment => incoming Shipment

=> outgoing Shipment


Design Patterns

Singleton design pattern → for the Statistical service, to ensure the service has a single global entry point.

Factory design pattern → for creating a new Person or Shipment, to hide the complex initialization from the client.

Strategy design pattern → for generating a new Shipment at runtime, depending on the shipment type.

Observer design pattern → to ensure all parts of the system are notified when a new Order is created.


Define Class Members (write code)

Attributes: For each class, define the attributes (data) it will hold...

Methods: Define the methods (functions) that operate on the attributes. Ensure they align with the object's responsibilities and adhere to the principle of encapsulation.


public class Person { private String name; private int age; public Person(String name, int age){ this.name = name; this.age = age; } public int getAge() {return age;} public String getName() {return name;} public void setAge(int age) {this.age = age;} public void setName(String name) {this.name = name;} } public class Client extends Person { private String clientType; public Client(String name, int age, String clientType){ super(name, age); this.clientType = clientType; } public String getClientType() {return clientType;} public void setClientType(String clientType) {this.clientType = clientType;} } public class Manager extends Person { private String managerType; private int workPeriod; public Manager(String name, int age, String managerType, int workPeriod){ super(name, age); this.managerType = managerType; this.workPeriod = workPeriod; } public String getManagerType() { return managerType; } public void setManagerType(String managerType) { this.managerType = managerType; } public int getWorkPeriod() { return workPeriod; } public void setWorkPeriod(int workPeriod) { this.workPeriod = workPeriod; } } public class PersonFactory { public Person generateNewPerson(String type, String name, int age, String role){ if(Objects.equals(type, "Manager")){ return new Manager(name, age, role, 8); }else{ return new Client(name, age, role); } } } public class Product { private String productId; private String name; private float price; private int quantity; private float size; private Partition partition; public Product(String productId, String name, float price, int quantity, Partition partition, float size){ this.name = name; this.quantity = quantity; this.productId = productId; this.price = price; this.partition = partition; this.size = size; } public String getProductId() {return productId;} public void setProductId(String productId) {this.productId = productId;} public String getName() {return name;} public void setName(String name) {this.name = name;} public float getPrice() {return price;} public void setPrice(float price) {this.price = price;} public int getQuantity() {return quantity;} public void setQuantity(int quantity) {this.quantity = quantity;} public Partition getPartition() {return partition;} public float getSize() { return size; } public void setSize(float size) { this.size = size; } } import java.time.LocalDate; public class ProductBatch { private String productBatchId; private String productId; private LocalDate expiryDate; private int quantity; public ProductBatch(String productBatchId, String productId, LocalDate expiryDate, int quantity) { this.productBatchId = productBatchId; this.productId = productId; this.expiryDate = expiryDate; this.quantity = quantity; } public String getProductBatchId() {return productBatchId;} public void setProductBatchId(String productBatchId) {this.productBatchId = productBatchId;} public String getProductId() {return productId;} public void setProductId(String productId) {this.productId = productId;} public LocalDate getExpiryDate() {return expiryDate;} public void setExpiryDate(LocalDate expiryDate) {this.expiryDate = expiryDate;} public int getQuantity() {return quantity;} public void setQuantity(int quantity) {this.quantity = quantity;} } public class Order { private String orderId; private List<OrderItem> orders = new ArrayList<>(); private float totalCost = 0; public Order(String orderId, List<OrderItem> orders){ this.orderId = orderId; this.orders = orders; for(OrderItem orderItem: orders){ totalCost += orderItem.getQuantity() * orderItem.getProduct().getPrice(); } } public float getTotalCost(){ return totalCost; } public void getOrderDetails(){ for(OrderItem orderItem: orders){ System.out.println("The product name is: " + orderItem.getProduct().getName() + " and the quantity is: " + orderItem.getQuantity()); } } public String getOrderId() { return orderId; } } public class OrderItem { private String orderItemId; private Product product; private Client client; private LocalDate orderDate; private int quantity; public OrderItem(String orderItemId, Product product, Client client, LocalDate orderDate, int quantity){ this.product = product; this.orderDate = orderDate; this.orderItemId = orderItemId; this.quantity = quantity; this.client = client; } public String getOrderItemId() { return orderItemId; } public int getQuantity() { return quantity; } public LocalDate getOrderDate() { return orderDate; } public Client getClient() { return client; } public void setOrderItemId(String orderItemId) { this.orderItemId = orderItemId; } public void setClient(Client client) { this.client = client; } public void setOrderDate(LocalDate orderDate) { this.orderDate = orderDate; } public void setQuantity(int quantity) { this.quantity = quantity; } public void setProduct(Product product) { this.product = product; } public Product getProduct() { return product; } } public class StatisticsService implements ObserverInterface{ private static StatisticsService statisticsService; private float totalProfit; private List<Order> totalOrders = new ArrayList<>(); private float totalNumberOfOrders; static public StatisticsService getInstance(){ if(statisticsService == null){ statisticsService = new StatisticsService(); } return statisticsService; } public float getTotalProfit() { return totalProfit; } public float getTotalNumberOfOrders() { return totalNumberOfOrders; } public void addProfit(float profit) { this.totalProfit += profit; } public void getOrdersDetails(){ for(Order order: totalOrders){ System.out.println("The order id is: " + order.getOrderId() + " and the total order cost is: " + order.getTotalCost()); } } public void addNewOrder(Order order){ totalOrders.add(order); } @Override public void update(){ this.totalNumberOfOrders++; } } public interface ShipmentInterface { public void makeShipmentLogic(); } abstract public class Shipment implements ShipmentInterface { private final String shipmentId; private Order order; private float shipmentCost; private String shipmentLocation; public Shipment(String shipmentId, Order order, float shipmentCost, String shipmentLocation){ this.shipmentId = shipmentId; this.order = order; this.shipmentCost = shipmentCost; this.shipmentLocation = shipmentLocation; } public float getShipmentCost() { return shipmentCost; } public String getShipmentId() { return shipmentId; } public Order getOrder() { return order; } public String getShipmentLocation() { return shipmentLocation; } public void setOrder(Order order) { this.order = order; } public void setShipmentCost(float shipmentCost) { this.shipmentCost = shipmentCost; } public void setShipmentLocation(String shipmentLocation) { this.shipmentLocation = shipmentLocation; } } public class IncomingShipment extends Shipment implements ObserverInterface{ public IncomingShipment(String shipmentId, Order order, float shipmentCost, String shipmentLocation){ super(shipmentId, order, shipmentCost, shipmentLocation); } @Override public void makeShipmentLogic(){ System.out.println("Make incoming shipment logic"); } @Override public void update(){ StatisticsService.getInstance().addProfit(-this.getOrder().getTotalCost()); } } public class OutgoingShipment extends Shipment implements ObserverInterface{ public OutgoingShipment(String shipmentId, Order order, float shipmentCost, String shipmentLocation){ super(shipmentId, order, shipmentCost, shipmentLocation); } @Override public void makeShipmentLogic(){ System.out.println("Make outgoing shipment logic"); } @Override public void update(){ StatisticsService.getInstance().addProfit(this.getOrder().getTotalCost()); } } public interface ObserverInterface { public void update(); } public interface ObserverInterface { public void update(); } public class Partition { private float size; private float remainingSize; private String location; private int rank; private List<Product> productList = new ArrayList<>(); public Partition(float size, int rank, String location){ this.size = size; this.location = location; this.rank = rank; } public void setSize(float size) { this.size = size; } public float getSize() { return size; } public float getRemainingSize() { return remainingSize; } public int getRank() { return rank; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public void setRank(int rank) { this.rank = rank; } public void decreaseRemainingSize(float size) { this.remainingSize -= size; } public void increaseRemainingSize(float size) { this.remainingSize += size; } public void addProduct(Product product) { this.productList.add(product); this.decreaseRemainingSize(product.getQuantity() * product.getSize()); } public void removeProduct(Product product){ this.productList.remove(product); this.increaseRemainingSize(product.getQuantity() * product.getSize()); } } public class Warehouse { private List<Partition> partitions = new ArrayList<>(); public void addPartition(Partition partition) { partitions.add(partition); } public void addNewProduct(Product product, int rank){ for(Partition partition: partitions){ if( partition.getRank() == rank && partition.getRemainingSize() > product.getQuantity() * product.getSize() ){ partition.addProduct(product); return; } } throw new RuntimeException("There is no available partition with this rank and the required size"); } public void removeProduct(Product product){ product.getPartition().removeProduct(product); } }




Adhere to SOLID Guidelines

The implementation follow the SOLID principles

  • Each object has a single responsibility.
  • The system is open for extension and closed for modification, and the use of the Strategy design pattern for the shipment logic is an example of this principle.
  • The Liskov Substitution, Interface Segregation, and Dependency Inversion principles are also considered in the implementation.


Consider Scalability and Flexibility

  • The implementation is designed to be scalable and flexible, allowing for future extensions and growth.


Create/Explain your diagram(s)

Try creating a class, flow, state and/or sequence diagram using the diagramming tool. Mermaid flow diagrams can be used to represent system use cases. You can ask the interviewer bot to create a starter diagram if unfamiliar with the tool. Briefly explain your diagrams if necessary...






Future improvements

We can make some improvements, such as adding an expiry date to products and automatically removing them from the partitions once the expiry date is exceeded.