By: AY1920S2-CS2103-W14-2      Since: Feb 2020      Licence: MIT

1. Setting up

Refer to the guide here.

2. Design

2.1. Architecture

ArchitectureDiagram
Figure 1. Architecture Diagram

The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.

The .puml files used to create diagrams in this document can be found in the diagrams folder.

Main has two classes called Main and MainApp. It is responsible for,

  • At app launch: Initializes the components in the correct sequence, and connects them up with each other.

  • At shut down: Shuts down the components and invokes cleanup method where necessary.

Commons represents a collection of classes used by multiple other components. The following class plays an important role at the architecture level:

  • LogsCenter : Used by many classes to write log messages to the App’s log file.

The rest of the App consists of four components.

  • UI: The UI of the App.

  • Logic: The command executor.

  • Model: Holds the data of the App in-memory.

  • Storage: Reads data from, and writes data to, the hard disk.

Each of the four components

  • Defines its API in an interface with the same name as the Component.

  • Exposes its functionality using a {Component Name}Manager class.

For example, the Logic component (see the class diagram given below) defines it’s API in the Logic.java interface and exposes its functionality using the LogicManager.java class.

LogicClassDiagram
Figure 2. Class Diagram of the Logic Component

How the architecture components interact with each other (By Pang Jia Da)

The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete-s 1.

ArchitectureSequenceDiagram
Figure 3. Component interactions for delete-s 1 command

The sections below give more details of each component.

2.2. UI component (by Fang Shao Hua)

UiClassDiagram
Figure 4. Structure of the UI Component

API : Ui.java

The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, SupplierListPanel, StatusBarFooter etc. All these, including the MainWindow, inherit from the abstract UiPart class.

The UI component uses JavaFx UI framework. The layout of these UI parts are defined in matching .fxml files that are in the src/main/resources/view folder. For example, the layout of the MainWindow is specified in MainWindow.fxml

The UI component,

  • Executes user commands using the Logic component.

  • Listens for changes to Model data so that the UI can be updated with the modified data.

2.3. Logic component (by Fang Shao Hua)

LogicClassDiagram
Figure 5. Structure of the Logic Component

API : Logic.java

  1. Logic uses the InventoryManagerParser class to parse the user command.

  2. This results in a Command object which is executed by the LogicManager.

  3. The command execution can affect the Model (e.g. adding a supplier).

  4. The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui.

  5. In addition, the CommandResult object can also instruct the Ui to perform certain actions, such as displaying help to the user.

(By Pang Jia Da)

Given below is the Sequence Diagram for interactions within the Logic component for the execute("buy 1 g/Apple q/50") API call.

BuySequenceDiagram
Figure 6. Interactions Inside the Logic Component for the buy 1 g/Apple q/50 Command
The lifeline for BuyCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

2.4. Model component (by Nicholas Cristian Fernando)

API : Model.java

The Model,

  • stores a UserPref object that represents the user’s preferences.

  • stores three sets of versioned data: VersionedAddressBook, VersionedInventory and VersionedTransactionHistory, which inherit features from their non-versioned counterparts

  • exposes three unmodifiable lists: ObservableList<Supplier>, ObservableList<Good> and ObservableList<Transaction> to be observed and displayed by the UI.

  • does not depend on any of the other three components.

ModelClassDiagram
Figure 7. Structure of the Model Component

The AddressBook stores a list of Supplier objects, which each:

  • stores details of a supplier: Name, Phone, Address, Email and Offer.

  • can have variable number of Offer objects, representing an offer to sell a specific good at a specific price.

  • links to a GoodName and a Price via each of its Offer objects

SupplierModelClassDiagram
Figure 8. Structure of the AddressBook

The Inventory stores a list of Good objects, which each stores details of a good:

  • its name GoodName,

  • two quantities represented by two GoodQuantity objects, one indicating the current quantity and the other the minimum threshold quantity

GoodModelClassDiagram
Figure 9. Structure of the Inventory

The TransactionHistory stores a list of Transaction objects. Each Transaction stores common details of a transaction:

  • TransactionId for unique identification,

  • GoodName for the transaction good, and

  • GoodQuantity for the transaction quantity.

A Transaction can be either SellTransaction or BuyTransaction:

  • SellTransaction has a Price to indicate the price at which the goods is sold.

  • BuyTransaction has a Supplier and a Price to indicate the supplier and the price the goods is bought at respectively.

TransactionModelClassDiagram
Figure 10. Structure of the TransactionHistory

2.5. Storage component (by Fang Shao Hua)

StorageClassDiagram
Figure 11. Structure of the Storage Component

API : Storage.java

The Storage component,

  • can save UserPref objects in json format and read it back.

  • can save 3 sets of data: AddressBook, Inventory and TransactionHistory in json format, save them in separate json file and read the data back.

2.6. Common classes

Classes used by multiple components are in the cs2103_w14_2.inventory.commons package.

3. Implementation

This section describes some noteworthy details on how certain features are implemented and pitfalls to avoid when making modifications.

3.1. Buy and Sell Commands (By Pang Jia Da)

The buy and sell commands are the main ways a user is expected to interact with the Inventory Manager, and encompasses the bulk of the commands a user is going to enter into Inventory Manager.

3.1.1. Commonalities in Implementation of buy and sell

Both buy and sell commands are required to accomplish 3 things:

  1. Update the quantity in the inventory, subject to their respective validity checks.

  2. Create a transaction record of itself to be added to the transaction history.

  3. Commit the mutated model to facilitate proper functioning of undo and redo commands

The API that Model exposes has been structured to mirror the requirements of the commands. The above 3 requirements can be satisfied by calls to:

  1. Model#setGood()

  2. Model#addTransaction()

  3. Model#commit().

3.1.2. Specifics of buy Command

The two ways buy executes successfully depends on whether the good already exists in the inventory or not.

  • If the good does not already exist, then a new Good entry has to be created with the quantity bought.

  • If it does exist, then the existing good entry has to be retrieved, and the quantity updated.

Developers modifying existing Good related information must be aware that Goods are immutable. In updating only the quantity field, all other fields must be correctly duplicated to the updated Good entry created. Common mistakes are to omit those fields, resulting in loss of persistent Good related information. To make explicit this requirement to future Developers, multiple constructors have been created with their respective purposes documented: Good(), Good.newGoodEntry(), Good.goodWithName().
Buy Command Input Constraints

The following constraints have been put on buy command inputs. Their reasons are discussed in the following section. Users will not be allowed to:

  1. Purchase from a supplier that is not in the supplier list

  2. Purchase a good that the supplier does not offer.

  3. Purchase a quantity of good that would cause the total quantity of any individual good in the inventory to exceed the Good limit.

The maximum Good limit is 999,999.

Developers seeking to modify the buy command must respect the above 3 input validations as they form the basis of future features.

The buy command format is: buy SUPPLIER_DISPLAYED_INDEX g/GOODNAME q/QUANTITY

The current implementation of buy performs validation of the above respectively as follows:

  1. The SUPPLIER_DISPLAYED_INDEX must be within the length of list of suppliers returned by Model#getFilteredSupplierList().

  2. Get the offers of the supplier through Supplier#getOffers(). Iterate through the Set<Offer> of the selected supplier to find existence of an Offer with GOODNAME.

  3. Reuse the inbuilt quantity validation in the Good constructor to test if the resulting inventory quantity is valid. A Good with the new quantity is constructed. If the quantity is invalid, an error is thrown and the relevant feedback to the user returned.

3.1.3. Design Considerations

Aspect: Format of buy command
  • Alternative 1 (current choice): buy SUPPLIER_DISPLAYED_INDEX g/GOODNAME q/QUANTITY.

    • Pros:

      • Users would not have to type out the entire Supplier’s name in full and case sensitive. This increases command input speed and further optimizes usage for fast typists. Wasted time from typos in minimized.

      • Verification that a supplier exists in the supplier list is trivial. The supplier at the given index only needs to be retrieved.

    • Cons:

      • We lose the flexibility of having input parameters being unordered. All inputs with a prefix flag, e.g. g/, can be input in any order, but now the SUPPLIER_DISPLAYED_INDEX has to be the first parameter.

      • Additional cognitive burden on users to remember the buy command format’s first parameter.

  • Alternative 2: buy n/SUPPLIER_NAME g/GOODNAME q/QUANTITY.

    • Pros: Flexibility of having unordered input is maintained.

    • Cons: Testing revealed that command entry was tedious and error prone, especially since supplier names tended to be long and a mix of upper- and lower- case alphabets, reducing user-friendliness.

Aspect: buy Input Constraints:
  • Alternative 1 (current choice): Supplier has to exist in supplier list before purchase.

    • Pros:

      • Supports future data analytics commands. We can save all relevant transaction information with every particular supplier at the point of transaction because the supplier will have to exist in the supplier list. The feasible future features include: cost analysis and ranking of suppliers by certain parameters.

      • Users do not have to enter an additional parameter: purchase price, since this can be extracted from the Supplier’s offer under the hood.

    • Cons: If user has making a new purchase, he or she has to first perform data entry for the supplier and all it’s relevant information before the buy command can be executed.

  • Alternative 2: Supplier would be an optional parameter to the buy command.

    • Pros: Command usage is more fluid and user-friendly.

    • Cons: Cost and supplier related data would be incomplete, reducing comprehensiveness of data analytics commands.

3.1.4. Specifics of sell Command

Sell Command Input Constraints

The following constraints have been put on sell command inputs. Their reasons are discussed in the following section. Users will not be allowed to:

  1. Sell a good they do not currently have in inventory.

  2. Sell a quantity a larger quantity of a good than they currently have in inventory.

The minimum Good limit is 0.

Developers seeking to modify the sell command must respect the above 2 input validations as they form the basis of future features.

The sell command format is: sell GOOD_DISPLAYED_INDEX p/PRICE q/QUANTITY

The current implementation of sell performs validation as follows:

  1. The GOOD_DISPLAYED_INDEX must be within the length of list of Goods returned by Model#getFilteredGoodList().

  2. Reuse the inbuilt quantity validation in the Good constructor to test if the resulting inventory quantity is valid. A Good with the new quantity is constructed. If the quantity is invalid, an error is thrown and the relevant feedback to the user returned.

3.1.5. Design Considerations

Aspect: Format of sell command
  • Alternative 1 (current choice): sell GOOD_DISPLAYED_INDEX p/PRICE q/QUANTITY.

    • Pros:

      • Users would not have to type out the entire Good’s name in full and case sensitive. This increases command input speed and further optimizes usage for fast typists. Wasted time from typos in minimized.

      • Verification that a good exists in the inventory is trivial. The good at the given index only needs to be retrieved.

    • Cons:

      • We lose the flexibility of having input parameters being unordered. All inputs with a prefix flag, e.g. g/, can be input in any order, but now the GOOD_DISPLAYED_INDEX has to be the first parameter.

      • Additional cognitive burden on users to remember the sell command format’s first parameter.

  • Alternative 2: sell g/GOOD_NAME p/PRICE q/QUANTITY.

    • Pros: Flexibility of having unordered input is maintained.

    • Cons: Testing revealed that command entry was tedious and error prone, especially since Good names tended to be long and a mix of upper- and lower- case alphabets, reducing user-friendliness.

3.2. Undo/Redo feature (by Nicholas Cristian Fernando)

3.2.1. Implementation

The undo/redo mechanism is facilitated by three versioned databases VersionedInventory, VersionedAddressBook and VersionedTransactionHistory for Good, Supplier and Transaction data respectively. These versioned classes extend their non-versioned counterparts. These classes also implement the Versionable interface, which has these methods:

  • Versionable#commit() — Adds the current state to the tracked states.

  • Versionable#undo() — Restores the previous database state.

  • Versionable#redo() — Restores the most recently undone database state.

These operations are exposed in the Model interface, which extends Versionable as well. Each call of these methods will call the respective methods of each of the versioned classes.

The class diagram below shows how the classes are related to each other.

VersionClassDiagram

The three versioned classes use the same logic for versioning, so only VersionedInventory will be mentioned in subsequent examples and diagrams.

The sequence diagram below illustrates the events that occur when a user calls the undo command assuming that there is a state to return to. VersionedAddressBook#undo() and VersionedTransactionHistory#undo() are called as well, but omitted for brevity.

UndoSequenceDiagram

Currently, VersionedInventory uses LinearHistory for versioning, and delegates all Versionable methods to it. LinearHistory can store objects of Inventory class, which has implemented the Copyable interface to allow creation of independent copies for storage. On the other hand, LinearHistory implements the interface Version, which extends from Versionable and has the following additional method:

  • Version#getCurrentState() — Returns the current state of the stored object

The class diagram below shows how the classes are connected such that VersionedInventory is able to use LinearHistory.

LinearHistoryClassDiagram
The lifeline for UndoCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. For simplicity, goods are each represented with strings containing their name and quantity.

Step 1. The user launches the application for the first time. The VersionedInventory will be created with a list of Good objects from storage, while creating a LinearHistory that stores a copy of this state, and also stores another copy in its history. Using copy() method from Copyable ensures currentState and saved0 are independent Inventory objects.

UndoRedoState0

Step 2. The user executes delete-g 3 command to delete the 3rd good in the inventory list. The delete-g command first deletes the 3rd good in the currentState of the LinearHistory, exposed by VersionedInventory. Then, the command calls Model#commit() since it modifies the data. LinearHistory then makes a copy of the modified currentState and stores it in the history, moving the statePointer up.

UndoRedoState1

Step 3. The user executes buy 1 g/apple q/5 to buy 5 apples from the first supplier. Let us assume that the first supplier sells apples. The buy command also calls Model#commit() as it modifies the data, causing LinearHistory to save a copy of the modified currentState.

UndoRedoState2
If a command fails its execution, it will not call Model#commit(), so the currentState will not be saved into the history.

Step 4. The user now decides that buying the apples was a mistake, and decides to undo that action by executing the undo command. The undo command will call Model#undo(), which will shift the statePointer one step backward, pointing it to the previous saved state saved1, and updates currentState with saved1.

UndoRedoState3
If the currentStatePointer is pointing to the first state saved0, then there is no state to return to. In this case, it will return an error to the user rather than attempting to perform the undo.

The redo command does the opposite — it calls Model#redo(), which shifts the currentStatePointer one step forward, pointing to the previously undone state, and restores the currentState to that state.

If the currentStatePointer is pointing to the latest state, then there are no states to go to. Thus, it will return an error to the user rather than attempting to perform the redo.

Step 5. The user then decides to execute the command list-t. Commands that do not modify the data, such as list-t, will not call Model#commit(). Thus, the history and currentState in LinearHistory remains unchanged.

UndoRedoState4

Step 6. The user executes sell 2 q/1 p/5 to sell 1 of the second goods in the list, banana. This calls Model#commit(). Since there is a branching in history, all states after the state pointed by statePointer will be purged. Many mainstream editing software exhibit this behaviour, which would condition the user to expect this behavior.

UndoRedoState5

The activity diagram below shows the conditions under which Model#commit() is called by a command, and its effects. As shown, only undoable commands that are successfully executed will call Model#commit() and purge the "future" states. This behavior in command execution guards against unwanted states being saved during invalid commands and confusing the user. In addition, the guard against invalid execution at the start helps to keep the currentState free of changes when the command will be invalid. Thus, the correctness of the commit() behavior is tied to the correct command execution protocol.

CommitActivityDiagram

3.2.2. Design Considerations

Aspect: How undo & redo executes
  • Alternative 1 (current choice): Saves the entire state of the database.

    • Pros: Trivial implementation.

    • Cons: May encounter performance issues due to memory load, especially with three different databases.

  • Alternative 2: Individual command knows how to undo/redo by itself.

    • Pros: Will use less memory (e.g. for delete-s, just save the supplier being deleted).

    • Cons: We must ensure that the implementation of each individual command are correct.

Aspect: When to save history
  • Alternative 1 (current choice) : Save all three databases even when only one database is modified.

    • Pros: Easy to implement.

    • Cons: Inefficient memory usage, especially when only one database is being modified in each action.

  • Alternative 2: Save a database only when that database is modified.

    • Pros: Saves memory usage that could be used for performance.

    • Cons: Requires information on which databases are affected by a command, which breaks abstraction on both the versioned databases and commands.

Aspect: How storage of states is implemented
  • Alternative 1 (current choice) : Store states as objects during Java runtime

    • Pros: Simple implementation and automatic cleanup.

    • Cons: Segmentation fault may occur for very long sessions and large databases.

  • Alternative 2: Store states in an external file

    • Pros: Less memory usage, leading to better performance.

    • Cons: File I/O may incur comparable overhead, and abrupt termination of the application may result in temporary files being left behind and cluttering space.

3.3. Supplier Feature (By Liu Chao)

InventoryManager allows users (store managers) to record the suppliers whom they buy goods from. Users could add and store the information of suppliers such as company name, company address, registered phone number, official email address and goods that company is selling.

A supplier can be stored as a supplier object in the InventoryManager. The main components are 1) company name, 2) company address, 3) registered phone number, 4) official email address and 5) good-price pairs of goods that company is selling. Company name is used to identify the company since every company needs to have an unique name which is registered legally with Accounting and Corporate Regulatory Authority. A Good-price pair of good is a combination of records of a good’s name and a selling price. Good-price pairs are stored as an offer object in the InventoryManager. The following is a class diagram of Supplier feature.

SupplierModelClassDiagram
Figure 12. Class diagram of Supplier Feature, displaying only directly related classes

3.3.1. Edit Supplier Feature

Users could edit and update the information of registered supplier should there be any changes in InventoryManager by using the EditSupplierCommand. The edit supplier command format: edit-s INDEX (must be a positive integer) [n/NAME] [c/PHONE] [e/EMAIL] [a/ADDRESS] [gp/GOOD_PRICE_PAIR]…

Implementation

The following sequence diagram shows how the information of existing suppliers (supplierToEdit) could be updated by the EditSupplierCommand. EditSupplierCommandParser creates a EditSupplierDescriptor object to store all the information that needs to be updated to a supplier. Then both index of the supplier that aims to be edited and the EditSupplierDescriptor object are passed to the EditSupplierCommand where a new supplier object (editedSupplier) will be created by combining the information of the supplierToEdit, which has gotten by searching through the address book by using index, and the new information stored in the EditSupplierDescriptor object. The original supplier (supplierToEdit) is replaced by the newly created supplier object (editedSupplier) by using the model.setSupplier.

SequentialDiagramEditSupplier
Figure 13. Sequence diagram of Edit Supplier Feature
The sequence diagram is generated using PowerPoint. There may be some formatting issues and drawings may not be to scale.

The detailed activity diagram of editing a supplier is shown below.

ActivityDiagramEditSupplier
Figure 14. Activity Diagram of Edit Supplier Feature
The activity diagram is generated using PowerPoint. There may be some formatting issues and drawings may not be to scale.
Design Considerations

Users could involve any number of fields that is a positive number to edit the existing supplier. Fields include 1) company name, 2) company address, 3) registered phone number, 4) official email address and 5) good-price pairs of goods that company is selling. If the good is not present in the existing supplier’s list, the good will be added into the supplier’s list as a new good price pair entry. Otherwise, if the good is already present in the supplier’s list, then the good price pair of that good in the supplier’s list will be edited and updated to the entered values. This feature is also used to add another good price pair for the supplier.

We choose to allow users to edit any number of fields is to provide convenience for the users as the users could edit multiple fields at once with flexibility. We choose to use this feature to add a new good price pair for the supplier is to prevent having an additional command (e.g. include good price pair), which may confuse the users if there is too many commands involved in the application. In addition, using this feature will prevent users from adding a new entry of the same good that is already existed in the supplier’s list. Furthermore, we are using index to allocate suppliers (edit-s 1) instead of using company name (edit-s NTUC) is to provide convenience for the users who could find it challenging to type full company name and could easily allocate index of supplier from the supplier panel.

3.3.2. Delete Good Price Pair from Supplier

The delete-gp command is the main way a user could delete an entry or entries of good price pair(s) in a supplier’s list. The user could delete multiple entries of good price pairs by using multiple good names in one command.

The delete good price pair command format: delete-gp INDEX (must be a positive integer) g/GOOD_NAME [g/MORE_GOOD_NAME]…​

The current implementation of delete good price pair command performs validation as follows:

  1. INDEX must be within the length of list of suppliers returned by Model#getFilteredSupplierList().

  2. Get the offers of the supplier through Supplier#getOffers(). Iterate through the Set<Offer> of the selected supplier to find existence of an Offer with GOODNAME to be deleted.

  3. Prefix of good name of good to be deleted is validated with GoodName class to check the validity of good name.

  4. If the user input contains good name. if users do not include good name, the command will throw exception that at least one good name must be included.

3.3.3. Commonalities in Implementation of delete-gp command

The delete-gp command is required to accomplish 5 things:

  1. Allocate the supplier that is targeted by using the index subject to validity check.

  2. Find and allocate the good price pair(s) specific by the input good name(s).

  3. Delete the necessary good price pair(s) in the supplier’s list

  4. Inform users which goods are successfully deleted and which goods could not be found at the CommandResult.

  5. Commit the mutated model to facilitate proper functioning of undo and redo commands

3.3.4. Design Considerations

Aspect: format of delete good price pair command

  • Alternative 1 (current choice): delete-gp INDEX (must be a positive integer) g/GOOD_NAME [g/MORE_GOOD_NAME]…​

    • Pros

      • It is able to check if the user includes good name in the command.

      • User will not accidently delete a supplier entry by omitting good name like alternative 2.

    • Cons

      • Users need to remember another command

  • Alternative 2 (modifying delete supplier’s command): delete-s INDEX (must be a positive integer) g/GOOD_NAME [g/MORE_GOOD_NAME]…​ (This is such a way that if user does not include good price pair, the command will delete the supplier entry. If the user includes the good price pair, the command will not delete the supplier’s entry but rather edits the good price pairs in the supplier’s list like alternative 1.)

    • Pros

      • Users do not need to remember another command.

    • Cons

      • Users could accidently delete a supplier entry by forgetting to input good name.

Aspect: Ability to delete multiple good price pairs at one command

  • Alternative 1 (current choice): Users could delete multiple good price pairs by using one command

    • Pros

      • Convenience for the Users

    • Cons

      • Harder to implement than alternative 2

      • More prone to errors and bugs.

      • Users may forget what the good price pairs are deleted and do not know what are the good price pairs that could not be found if there is a large number of entries.

  • Alternative 2: Users could only delete one good price pair by using one command.

    • Pros

      • Easy to implement

      • Less prone to errors and bugs

      • At every command, users could know which good price pair entry is successfully deleted and which good price pair could not be found.

    • Cons

      • Inconvenience for the users.

The current implementation will give a summary of all good price pairs that are successfully deleted and all good price pairs that could not be found to assist the users.

3.4. [Proposed] Automatic Batch Expiry and Warning (By Pang Jia Da)

The primary aim of inventory management is to ensure that there is always sufficient stock of goods. Out-of-stock situations cost the company needless revenue losses.

When stores sell fast moving consumer goods with short shelf lives, this problem becomes hard to solve when every individual batch of purchases have their respective expiry dates.

This feature aims to augment every buy command with it’s respective EXPIRY_DATE. When the expiry date approaches, unsold goods from that batch would automatically be removed from the inventory, the user would be notified of the expiration and warned if that causes the good to fall below it’s stipulated threshold. The command to source for suppliers who sell that good can also be triggered to facilitate restocking of that good.

3.4.1. Proposed Implementation

Proposed Changes to Good class

Inventory Manager v1.4 currently stores the name, current quantity, and threshold quantity of every good in the inventory. An expiryDates field will be added to store all distinct expiry dates, from closest to furthest, and the number of units expiring on that date. Java’s built-in Date class will suffice.

GoodWithExpiryDate

Proposed Changes to buy Command

The buy command will include an expiry date for every purchase goods. A possible format would be: buy SUPPLIER_DISPLAYED_INDEX q/QUANTITY g/GOOD_NAME x/EXPIRY_DATE. This assigns the EXPIRY_DATE to all QUANTITY units of GOOD_NAME bought.

The correct Good entry can be retrieved from the InventoryManager. If there is currently no expiry dates on EXPIRY_DATE, a new Map.entry will be created indicating that QUANTITY many units will expire on EXPIRY_DATE. Else, the current Map.entry will be updated.

Proposed Changes to sell Command

Under this implementation, the sell command must sell goods in a First-In-First-Out (FIFO) manner. When any valid sell command is entered, the earliest expiry dates are removed first. This is accomplished by reducing the values that are mapped to the earliest expiry dates.

Expiry Detection

Upon Inventory Manager Program startup, the expiryDates of all Good s in the inventory is checked with the current System Date. When any expiry date is found to be earlier than the System date, the mapped number of goods will expire and be removed from inventory.

Possible Extensions

Possible extensions of usefulness are listed below:

  1. Make any expiry event generate it’s respective transaction record in the transaction history.

  2. If goods fall below their warning threshold as a result of expiry, have a notification to the user and display the list of suppliers that sell that particular good, sorted by increasing price.

3.4.2. Design Considerations

Aspect: Data Structure for expiryDates
  • Alternative 1 (current choice): Use a TreeMap<Date, Integer>

    • Pros: Memory efficient.

    • Cons:

      • TreeMap navigation is more complex than a linear data structure.

      • Updating is more complex for sell commands, especially if goods with multiple expiry dates are being sold.

  • Alternative 2: Maintain an ordered LinkedList<Date>.

    • Pros: Simple to implement and update.

      • Buying QUANTITY of a good would correspond to inserting QUANTITY of EXPIRY_DATE into the list and sorting it.

      • Selling QUANTITY of a good would correspond to removing the first QUANTITY elements.

      • Finding all expired items can be done be traversing down the list until the first non-expired item is found. Everything traversed has expired.

    • Cons:

      • Extremely memory inefficient, especially since each Good can contain up to 999,999 quantity, and there will be 1 Date for each good stored.

3.5. Find transaction (By Fang Shao Hua)

Inventory Manager has a find transaction feature which allows the user to filter transactions, so that it saves time for the user to look for specific transactions among the transaction history.

User can provide 3 different types of filter, or combination of filters to filter the transaction list. These 3 types of filter are TransactionType, Name and GoodName.

To extract out these filter specifications, ArgumentMultimap is needed. TransactionType will be stored in the preamble, Name will stored in the value under prefix n/ and GoodName will be stored in the value under prefix g/.

FindTransactionCommandParser will called ArgumentMultimap to parse the user input into respective values. These values will then set up filters in the Predicate for the model to filter the transaction list.

If the user did not specify a particular type of filter, that particular type of filter will not be activated. The feature requires at least one filter to be able to functional.

For the Name and GoodName filters, these filters can take in multiple Name and GoodName respectively. This means that there can be multiple Name keywords in the Name filter, and the transaction only need to match any of the Name to pass the filter. Same goes for the GoodName filter.

The transaction has to fulfill all active filters to be added into the filtered list.

Here is a sample activity diagram that shows the general flow:

FindTransactionActivityDiagram

This feature mainly involves within Logic, but also require interaction with Model to update the filter list.

Here is a sample activity diagram that shows the flow when user inputs: find-t buy n/alice g/apple:

FindTransactionSequenceDiagram

3.5.1. Design Considerations

Aspect: Multiple filters
  • Alternative 1 (current choice): Enables multiple filters to filter the transaction list

    • Pros: Enable easier and flexible search.

    • Cons: More complex to implement, need to take care of multiple cases.

  • Alternative 2: Decompose search function into multiple functions, each consist of single filter

    • Pros: Easier to implement, less complexity

    • Cons: More code need to be written, and the code will have high degree of duplication. Less flexible search.

3.6. Set threshold for good (By Fang Shao Hua)

Inventory Manager has a set threshold feature which allows the user to set the threshold quantity for goods in Inventory, so that it can alert the user when a particular good fall below its threshold quantity.

The alert mechanism is to resort the goods in the inventory such that those fall below their their threshold will be display first, and their current quantity will be display with red background.

Every command that changes the quantity of good or set new threshold for the good in the inventory will trigger a check and update the filtered list accordingly.

By default, any newly added good in the inventory will be set with threshold quantity of zero.

Here is a sample activity diagram that shows the flow when user inputs: warn 5 q/100:

SetThresholdSequenceDiagram
SetThresholdSequenceDiagram2

3.7. Logging

We are using java.util.logging package for logging. The LogsCenter class is used to manage the logging levels and logging destinations.

  • The logging level can be controlled using the logLevel setting in the configuration file (See Section 3.8, “Configuration”)

  • The Logger for a class can be obtained using LogsCenter.getLogger(Class) which will log messages according to the specified logging level

  • Currently log messages are output through: Console and to a .log file.

Logging Levels

  • SEVERE : Critical problem detected which may possibly cause the termination of the application

  • WARNING : Can continue, but with caution

  • INFO : Information showing the noteworthy actions by the App

  • FINE : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size

3.8. Configuration

Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: config.json).

4. Documentation

Refer to the guide here.

5. Testing

Refer to the guide here.

6. Dev Ops

Refer to the guide here.

Appendix A: Product Scope

Target user profile:

  • has a need to manage a large number of fast-moving consumer goods (FMCG) which arrives in batches

  • has a need to manage a large number of suppliers

  • has a need to draw insights from analysing transactions with suppliers and customers

  • prefer desktop apps over other types

  • can type fast

  • prefers typing over mouse input

  • is reasonably comfortable using CLI apps

Value proposition: manage an FMCG store faster than a typical mouse/GUI driven app

Appendix B: User Stories

Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *

Priority As a …​ I want to …​ So that I can…​

* * *

new user

see usage instructions

refer to instructions when I forget how to use InventoryManager

* * *

user

add a new supplier

* * *

user

add a new goods to supplier

* * *

user

delete a supplier

remove entries that I no longer need

* * *

user

see goods that are low in stock

know what to buy

* * *

user

see goods that are low in stock

buy more before running out

* * *

user

update inventory with the transaction records

avoid keeping track of the inventory personally

* * *

user

update prices of goods offered by suppliers

account for changes in supply agreement or prices

* * *

clumsy user

undo previous actions

fix mistakes in inputs or spelling

* * *

user

be notified of goods falling below a set quantity threshold

buy expected goods in advance

* * *

user

be notified of goods that are above a set quantity threshold

avoid expiration of large number of goods

* *

user

create a set purchase order automatically on a regular basis

simulate supply contracts

* *

user

find a supplier by goods sold

locate the relevant suppliers without having to go through the entire list

* *

user

find a goods by name

locate the relevant goods without having to go through the entire list

* *

user

hide transaction details by default

minimize chance of someone else seeing them by accident

* *

user

set expiry event for a batch of goods

account for expiration of goods

* *

user

change names of goods

avoid confusion when producers change the name of their products

* *

user

have a summary of the transactions throughout the day

determine performance of the day

* *

expanding user

see a performance tracker

find points of improvement in business activity

*

clumsy user

receive suggestion for the next words

avoid misspelling and be reminded of syntax

Appendix C: Use Cases

(For all use cases below, the System is the InventoryManager and the Actor is the user, unless specified otherwise)

Use case: UC1 - listing all suppliers

MSS

  1. User requests to list suppliers.

  2. InventoryManager shows a list of suppliers.

    Use case ends.

Extensions

  • 2a. The list is empty.

    • 2a1. InventoryManager shows a message to inform that there are no suppliers.

      Use case ends.

Use case: UC2 - listing all goods

MSS

  1. User requests to list goods.

  2. InventoryManager shows a list of goods.

    Use case ends.

Extensions

  • 2a. The list is empty.

    • 2a1. InventoryManager shows a message to inform that there are no goods.

      Use case ends.

Use case: UC3 - adding a supplier

MSS

  1. User requests to add a supplier with given details.

  2. InventoryManager creates a supplier with the given details.

    Use case ends.

Extensions

  • 1a. The given details of the supplier is incomplete.

    • 1a1. Inventory Manager shows an error message to indicate the incomplete details.

      Use case ends.

  • 1b. The given details of the supplier is invalid.

    • 1b1. Inventory Manager shows an error message to indicate the invalid details.

      Use case ends.

  • 1c. The given details contains a non-supported parameter e.g. age.

    • 1c1. Inventory Manager shows an error message to indicate the non-supported parameter.

      Use case ends.

  • 1d. The specified supplier already exists.

    • 1d1. Inventory Manager shows an error message to indicate that the supplier already exists.

      Use case ends.

Use case: UC4 - deleting a supplier

MSS

  1. User lists all suppliers (UC1).

  2. User selects a supplier from the list and requests to delete the supplier by the index shown on the list.

  3. InventoryManager deletes the supplier.

    Use case ends.

Extensions

  • 1a. The list is empty.

    • Use case ends.

  • 2a. The given index is invalid.

    • 2a1. InventoryManager shows an error message to indicate the invalid index.

      Use case ends.

Use case: UC5 - deleting a good from supplier’s list

MSS

  1. User lists all suppliers (UC1).

  2. User requests to delete a good from a supplier’s list and give the good’s name.

  3. InventoryManager confirms the deletion.

  4. InventoryManager deletes the good from the supplier’s good list.

    Use case ends.

Extensions

  1. The required good is not found.

    • InventoryManager informs there is no such good found.

      Use case ends.

Use case: UC6 - editing a supplier

MSS

  1. User lists all suppliers (UC1)

  2. User requests to edit a supplier specified by the index and gives the new parameters

  3. InventoryManager updates the details of the supplier.

    Use case ends.

Extensions

  1. There is existing good in the list.

    • The latest information of good will be updated.

      Use case ends.

  2. The given index is invalid.

    • InventoryManager shows an error message to indicate the invalid index.

      Use case ends.

  3. The given details of the supplier is incomplete.

    • Inventory Manager shows an error message to indicate the incomplete details.

      Use case ends.

  4. The given details of the supplier is invalid.

    • Inventory Manager shows an error message to indicate the invalid details.

      Use case ends.

  5. The given details contains a non-supported parameter e.g. age.

    • Inventory Manager shows an error message to indicate the non-supported parameter.

      Use case ends.

  6. The good is not found in the existing supplier’s good list.

    • Inventory Manager will include the good as a new good in the supplier’s good list.

      Use case ends.

Use case: UC7 - finding a supplier for a particular goods

MSS

  1. User lists all goods (UC2).

  2. User requests to list the suppliers supplying the goods with a specified name.

  3. InventoryManager shows a list of suppliers providing this goods.

    Use case ends.

Extensions

  • 1a. The list is empty.

    • Use case ends.

  • 2a. The goods with the given name does not exist.

    • 2a1. InventoryManager shows an error message to indicate the goods does not exist.

      Use case ends.

  • 3a. The list is empty.

    • 3a1. InventoryManager informs the user that there are no suppliers providing this goods.

      Use case ends.

Use case: UC8 - buying a particular goods

MSS

  1. User lists all the suppliers for a particular good (UC7).

  2. User requests to make a buy order for a quantity of the particular goods from a supplier.

  3. InventoryManager adds the order and adds the quantity to the total number of that particular goods.

    Use case ends.

Extensions

  • 1a. The list is empty.

    • Use case ends.

  • 2a. The goods with the given name does not exist.

    • 2a1. InventoryManager shows an error message to indicate the goods does not exist.

      Use case ends.

  • 2b. The supplier with the given name does not exist.

    • 2b1. InventoryManager shows an error message to indicate the supplier does not exist.

      Use case ends.

  • 2c. The quantity given is invalid.

    • 2c1. InventoryManager shows an error message to indicate the quantity is invalid.

      Use case ends.

  • 2d. One or more parameters are missing.

    • 2d1. InventoryManager shows an error message to indicate the missing parameters.

      Use case ends.

Use case: UC9 - selling a particular goods

MSS

  1. User lists all goods (UC2).

  2. User requests to make a selling order of a quantity of a particular goods.

  3. InventoryManager adds the sell order and deducts the quantity in the selling order to the total number of the particular goods.

    Use case ends.

Extensions

  • 1a. The list is empty.

    • Use case ends.

  • 2a. The goods with the given name does not exist.

    • 2a1. InventoryManager shows an error message to indicate the goods does not exist.

      Use case ends.

  • 2b. The quantity given is invalid.

    • 2b1. InventoryManager shows an error message to indicate the quantity is invalid.

      Use case ends.

  • 2c. The quantity given exceeds current amount in inventory.

    • 2c1. InventoryManager shows an error message to indicate insufficient quantity.

      Use case ends.

  • 2d. One or more parameters are missing.

    • 2d1. InventoryManager shows an error message to indicate the missing parameters.

      Use case ends.

Use case: UC10 - set lower threshold quantity of goods

MSS

  1. User lists all goods (UC2).

  2. User sets a lower quantity threshold for a particular goods.

    Use case ends.

Extensions

  • 1a. The list is empty.

    • Use case ends.

  • 2a. The quantity is invalid.

    • 2a1. InventoryManager shows an error message to indicate the quantity is invalid.

      Use case ends.

  • 2b. The quantity is above the upper threshold, if it exists.

    • 2b1. InventoryManager shows an error message to indicate the quantity is above the upper threshold.

      Use case ends.

  • 2c. The given index is out of bounds.

    • 2c1. InventoryManager shows an error message to indicate the index is out of bounds.

      Use case ends.

Use case: UC11 - listing all past transactions

MSS

  1. User requests to list all past transactions.

  2. InventoryManager lists all past transactions.

    Use case ends.

Extensions

  • 2a. The list is empty.

    • 2a1. InventoryManager informs the user that there are no past transactions.

      Use case ends.

Use case: UC12 - search transactions with specific criteria

MSS

  1. User requests to find transactions with the specific criteria.

  2. InventoryManager lists all transactions fulfill the specific criteria.

    Use case ends.

Extensions

  • 2a. The list is empty.

    • 2a1. InventoryManager informs the user that there are no transactions.

      Use case ends.

  • 2b. Any of the criteria is in invalid format

    • 2b1. InventoryManager informs the user that the criteria input is invalid.

      Use case ends.

Use case: UC13 - undoing a command

MSS

  1. User enters the undo command through the command line.

  2. InventoryManager moves to the state before the latest modifying command e.g. add supplier.

  3. InventoryManager shows a message indicating success.

    Use case ends.

Extensions

  • 2a. InventoryManager is at the oldest recorded state and thus is unable to move to a previous state.

    • 2a1. InventoryManager informs the user that it is unable to undo from the oldest recorded state.

      Use case ends.

Use case: UC14 - redoing a command

MSS

  1. User enters the redo command through the command line.

  2. InventoryManager moves to the state before the latest undo command.

  3. InventoryManager shows a message indicating success.

    Use case ends.

Extensions

  • 2a. InventoryManager is unable to move to the next state as it is already at the latest state.

    • 2a1. InventoryManager informs the user that it is unable to redo from the latest state.

      Use case ends.

Appendix D: Non Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 11 or above installed.

  2. Should be able to hold up to 1000 suppliers and goods without a noticeable sluggishness in performance for typical usage.

  3. Should run without any internet connection.

  4. Should have a human-editable storage text file.

  5. Should not require a database.

  6. Should not require an installer to use.

  7. Should not exceed 100MB in application size.

  8. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.

Appendix E: Glossary

Fast-moving consumer goods

Goods that are characterised by large inventory quantities, high turnover rate, numerous suppliers and short shelf-life.

Transaction record

A record of an event that results in change in the quantity of goods i.e. buying/selling.

Mainstream OS

Windows, Linux, Unix, OS-X.

Appendix F: Instructions for Manual Testing

Given below are instructions to test the app manually.

These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.

F.1. Launch and Shutdown

  1. Initial launch

    1. Download the jar file and copy into an empty folder

    2. Double-click the jar file
      Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. Mac OS users may need to give permission to open JAR files initially.

  2. Saving window preferences

    1. Resize the window to an optimum size. Move the window to a different location. Close the window.

    2. Re-launch the app by double-clicking the jar file.
      Expected: The most recent window size and location is retained.

F.2. Deleting a supplier

  1. Deleting a supplier while all suppliers are listed

    1. Prerequisites: List all suppliers using the list command. Multiple suppliers in the list.

    2. Test case: delete 1
      Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.

    3. Test case: delete 0
      Expected: No supplier is deleted. Error details shown in the status message. Status bar remains the same.

    4. Other incorrect delete commands to try: delete, delete x (where x is larger than the list size) {give more}
      Expected: Similar to previous.

F.3. Undo and Redo

  1. Undo at initial state

    1. Prerequisites: The application is newly opened i.e. no undoable commands has been done

    2. Test case: undo
      Expected: An error is shown, indicating that undo cannot be done from initial state.

    3. Test case: Add a valid supplier followed by undo
      Expected: The supplier is removed

    4. Test case: list-t followed by undo
      Expected: An error is shown, indicating that undo cannot be done from initial state. This is because list-t is not an undoable command

  2. Redo from latest state

    1. Prerequisites: One undoable command e.g. add-s has been successfully executed as the latest command

    2. Test case: redo
      An error is shown, indicating that redo cannot be done from latest state.

    3. Test case: undo followed by redo
      The changes done by the latest undoable command is removed, then reinstated.

  3. Overwriting history

    1. Prerequisites: None

    2. Test case: Add a valid supplier, undo, add a different valid supplier, and finally redo
      An error is shown, indicating that redo cannot be done from latest state.

F.4. Buying goods

  1. Prerequisites:

    1. A supplier at displayed index 1 in the supplier list that sells a good named Rice.

    2. There should either not be any Rice in the inventory or have an amount that will be strictly less than 1 million after buying.

  2. Test case: buy 1 g/Rice q/10
    Expected: The quantity of Rice in the inventory should increase by 10. If Rice was not previously present, a new entry would be created.

F.5. Selling goods

  1. Prerequisites:

    1. Have a good named Rice in the inventory with at least 10 quantity.

    2. Rice should be at displayed index 1 in the inventory.

  2. Test case: sell 1 q/3 p/3
    Expected: The quantity of rice in the inventory should decrease by 3.

F.6. Set threshold quantity for good

  1. Prerequisites: One buy command to add good into the inventory. Buy quantity set as 100.

  2. Test case: warn 1 q/10000
    Expected: First good will be under alert with red colour background, as the quantity is lower than the threshold quantity.

  3. Test case: warn 1 q/99
    Expected: No effect is shown, as the current quantity of good is greater than the threshold quantity.

  4. Test case: warn 1 q/99 followed by sell command to sell quantity of 10
    Expected: First good will be under alert with red colour background, as the quantity is lower than the threshold quantity after selling.

  5. Test case: warn
    Expected: An error is shown, indicating that the format of the command is wrong.

  6. Other incorrect warn commands to try: warn abc, warn x q/abc (where x is larger than the list size)
    Expected: Similar to previous.

F.7. Find transaction

  1. Prerequisites: List transaction command.

  2. Test case: find-t buy
    Expected: All buy transaction will be listed.

  3. Test case: find-t buy n/Alice
    Expected: All buy transaction with Alice being the supplier will be listed.

  4. Test case: find-t buy n/Alice g/apple
    Expected: All buy transaction with Alice being the supplier and the good bought being apple will be listed.

  5. Test case: find-t abc
    Expected: An error is shown, indicating that the format of the command is wrong.

  6. Other incorrect warn commands to try: find-t, find-t x (where x is not "buy" or "sell")
    Expected: Similar to previous.

Appendix G: Effort

  • Difficulty Level: 10

  • Challenges:

    • This is an application with three different databases. In addition to having to define operations for three different objects, many of the commands exhibit much interlinking between the components. There is a need to take extra care to ensure that the interactions are clearly defined and avoid side effects.

    • Many features are dependent on other features being developed compared to AB3, which makes it hard to concurrently arrange the interactions. An API needed to be clearly defined before the start of the sprint in order to allow team members to start working immediately.

    • There are many commands that needs to be tracked, which necessitates consideration whether it would affect the implementation of other commands.

    • There is significant amount of code duplication due to having three database classes. Refactoring is needed to make the code easier to read.

  • Achievements:

    • Refactored some of the Model classes to allow custom extension by future developers and reduce code duplication.

    • Implemented a search feature that navigates between two different databases.

    • 79% code coverage as measured by Coveralls, an increase from the original 75%.