From 922a4dd68d5c65af7faa0571a5d975c2e8aaba56 Mon Sep 17 00:00:00 2001 From: bernarduskrishna <77195969+bernarduskrishna@users.noreply.github.com> Date: Sun, 31 Oct 2021 01:08:52 +0800 Subject: [PATCH 01/42] make storage follow the initial model --- src/main/java/seedu/address/MainApp.java | 35 +++++++++- .../seedu/address/logic/LogicManager.java | 2 + .../java/seedu/address/model/BookKeeping.java | 17 ++++- src/main/java/seedu/address/model/Model.java | 3 +- .../seedu/address/model/ModelManager.java | 51 ++------------ .../address/model/ReadOnlyBookKeeping.java | 18 +++++ .../model/ReadOnlyTransactionList.java | 13 ++++ .../seedu/address/model/TransactionList.java | 23 +++++++ .../address/storage/BookKeepingStorage.java | 66 ++++++++---------- .../storage/JsonBookKeepingStorage.java | 62 +++++++++++++++++ .../storage/JsonSerializableBookKeeping.java | 3 +- .../storage/JsonSerializableTransaction.java | 12 ++-- .../storage/JsonTransactionStorage.java | 66 ++++++++++++++++++ .../java/seedu/address/storage/Storage.java | 2 +- .../seedu/address/storage/StorageManager.java | 63 ++++++++++++++++- .../address/storage/TransactionStorage.java | 69 ++++++++----------- .../seedu/address/logic/LogicManagerTest.java | 21 +++++- .../logic/commands/AddCommandTest.java | 17 +++-- .../logic/commands/ClearCommandTest.java | 8 ++- .../logic/commands/DeleteCommandTest.java | 23 +++++-- .../logic/commands/EditCommandTest.java | 17 +++-- .../EndAndTransactOrderCommandTest.java | 11 +-- .../logic/commands/FindCommandTest.java | 8 ++- .../commands/ListInventoryCommandTest.java | 7 +- .../commands/ListTransactionCommandTest.java | 7 +- .../logic/commands/RemoveCommandTest.java | 26 ++++--- .../commands/RemoveFromOrderCommandTest.java | 8 ++- .../logic/commands/SortCommandTest.java | 11 ++- .../seedu/address/model/ModelManagerTest.java | 19 +++-- .../java/seedu/address/model/ModelStub.java | 3 +- .../address/storage/StorageManagerTest.java | 5 +- 31 files changed, 506 insertions(+), 190 deletions(-) create mode 100644 src/main/java/seedu/address/model/ReadOnlyBookKeeping.java create mode 100644 src/main/java/seedu/address/model/ReadOnlyTransactionList.java create mode 100644 src/main/java/seedu/address/model/TransactionList.java create mode 100644 src/main/java/seedu/address/storage/JsonBookKeepingStorage.java create mode 100644 src/main/java/seedu/address/storage/JsonTransactionStorage.java diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 865bce09efa..7327eb7c736 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Optional; import java.util.logging.Logger; @@ -15,18 +16,27 @@ import seedu.address.commons.util.StringUtil; import seedu.address.logic.Logic; import seedu.address.logic.LogicManager; +import seedu.address.model.BookKeeping; import seedu.address.model.Inventory; import seedu.address.model.Model; import seedu.address.model.ModelManager; +import seedu.address.model.ReadOnlyBookKeeping; import seedu.address.model.ReadOnlyInventory; +import seedu.address.model.ReadOnlyTransactionList; import seedu.address.model.ReadOnlyUserPrefs; +import seedu.address.model.TransactionList; import seedu.address.model.UserPrefs; +import seedu.address.model.order.TransactionRecord; import seedu.address.model.util.SampleDataUtil; +import seedu.address.storage.BookKeepingStorage; import seedu.address.storage.InventoryStorage; +import seedu.address.storage.JsonBookKeepingStorage; import seedu.address.storage.JsonInventoryStorage; +import seedu.address.storage.JsonTransactionStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; +import seedu.address.storage.TransactionStorage; import seedu.address.storage.UserPrefsStorage; import seedu.address.ui.Ui; import seedu.address.ui.UiManager; @@ -57,7 +67,9 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); InventoryStorage inventoryStorage = new JsonInventoryStorage(userPrefs.getInventoryFilePath()); - storage = new StorageManager(inventoryStorage, userPrefsStorage); + TransactionStorage transactionStorage = new JsonTransactionStorage(userPrefs.getTransactionFilePath()); + BookKeepingStorage bookKeepingStorage = new JsonBookKeepingStorage(userPrefs.getBookKeepingFilePath()); + storage = new StorageManager(inventoryStorage, userPrefsStorage, transactionStorage, bookKeepingStorage); initLogging(config); @@ -76,21 +88,40 @@ public void init() throws Exception { private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { Optional addressBookOptional; ReadOnlyInventory initialData; + Optional transactionListOptional; + ReadOnlyTransactionList transactionList; + Optional bookKeepingOptional; + ReadOnlyBookKeeping bookKeeping; try { addressBookOptional = storage.readInventory(); + transactionListOptional = storage.readTransactionList(); + bookKeepingOptional = storage.readBookKeeping(); if (!addressBookOptional.isPresent()) { logger.info("Data file not found. Will be starting with a sample AddressBook"); } + if (!transactionListOptional.isPresent()) { + logger.info("Transaction not found"); + } + if (!bookKeepingOptional.isPresent()) { + logger.info("BookKeeping not found"); + } initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleInventory); + transactionList = transactionListOptional + .orElseGet(() -> new TransactionList(new ArrayList())); + bookKeeping = bookKeepingOptional.orElseGet(() -> new BookKeeping()); } catch (DataConversionException e) { logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); initialData = new Inventory(); + transactionList = new TransactionList(); + bookKeeping = new BookKeeping(); } catch (IOException e) { logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); initialData = new Inventory(); + transactionList = new TransactionList(); + bookKeeping = new BookKeeping(); } - return new ModelManager(initialData, userPrefs); + return new ModelManager(initialData, userPrefs, transactionList, bookKeeping); } private void initLogging(Config config) { diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index be24db919cb..ca1b3000d21 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -47,6 +47,8 @@ public CommandResult execute(String commandText) throws CommandException, ParseE try { storage.saveInventory(model.getInventory()); + storage.saveTransactionList(model.getTransactions()); + storage.saveBookKeeping(model.getBookKeeping()); } catch (IOException ioe) { throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); } diff --git a/src/main/java/seedu/address/model/BookKeeping.java b/src/main/java/seedu/address/model/BookKeeping.java index 6d46f3a859d..64678302bea 100644 --- a/src/main/java/seedu/address/model/BookKeeping.java +++ b/src/main/java/seedu/address/model/BookKeeping.java @@ -1,6 +1,6 @@ package seedu.address.model; -public class BookKeeping { +public class BookKeeping implements ReadOnlyBookKeeping { private Double revenue; private Double cost; private Double profit; @@ -18,6 +18,21 @@ public BookKeeping(Double revenue, Double cost, Double profit) { this.profit = profit; } + /** + * Constructor for BookKeeping. + * + * @param bookKeeping a ReadOnlyBookKeeping version of BookKeeping + */ + public BookKeeping(ReadOnlyBookKeeping bookKeeping) { + this.revenue = bookKeeping.getRevenue(); + this.cost = bookKeeping.getCost(); + this.profit = bookKeeping.getProfit(); + } + + public BookKeeping() { + this(0.0, 0.0, 0.0); + } + public Double getRevenue() { return revenue; } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index ca1782365f7..4fa5dc16c05 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -12,7 +12,6 @@ import seedu.address.model.item.Item; import seedu.address.model.item.ItemDescriptor; import seedu.address.model.order.Order; -import seedu.address.model.order.TransactionRecord; /** * The API of the Model component. @@ -170,7 +169,7 @@ public interface Model { /** * Return a list of {@code TransactionRecord} sorted according to timestamp. */ - List getTransactions(); + ReadOnlyTransactionList getTransactions(); /** * Returns an unmodifiable view of the filtered list to be displayed. diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 87b447df84e..b9f457b20cf 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -6,7 +6,6 @@ import static seedu.address.model.display.DisplayMode.DISPLAY_OPEN_ORDER; import static seedu.address.model.display.DisplayMode.DISPLAY_TRANSACTION_LIST; -import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; @@ -18,7 +17,6 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; import seedu.address.model.display.DisplayList; import seedu.address.model.display.DisplayMode; import seedu.address.model.display.Displayable; @@ -26,8 +24,6 @@ import seedu.address.model.item.ItemDescriptor; import seedu.address.model.order.Order; import seedu.address.model.order.TransactionRecord; -import seedu.address.storage.BookKeepingStorage; -import seedu.address.storage.TransactionStorage; /** * Represents the in-memory model of BogoBogo data. @@ -49,7 +45,8 @@ public class ModelManager implements Model { /** * Initializes a ModelManager with the given inventory and userPrefs. */ - public ModelManager(ReadOnlyInventory inventory, ReadOnlyUserPrefs userPrefs) { + public ModelManager(ReadOnlyInventory inventory, ReadOnlyUserPrefs userPrefs, + ReadOnlyTransactionList transactionList, ReadOnlyBookKeeping bookKeeping) { super(); requireAllNonNull(inventory, userPrefs); @@ -57,25 +54,14 @@ public ModelManager(ReadOnlyInventory inventory, ReadOnlyUserPrefs userPrefs) { this.inventory = new Inventory(inventory); this.userPrefs = new UserPrefs(userPrefs); + this.transactions = transactionList.getTransactionRecordList(); displayList = new DisplayList(this.inventory.getItemList()); optionalOrder = Optional.empty(); - List transactionRecordList = null; - bookKeeping = new BookKeeping(0.0, 0.0, 0.0); - try { - transactionRecordList = new TransactionStorage() - .readTransaction(userPrefs.getTransactionFilePath()).orElse(null); - bookKeeping = new BookKeepingStorage() - .readBookKeeping(userPrefs.getBookKeepingFilePath()) - .orElse(new BookKeeping(0.0, 0.0, 0.0)); - } catch (DataConversionException e) { - System.out.println(e); - } - - transactions = transactionRecordList == null ? new ArrayList<>() : transactionRecordList; + this.bookKeeping = new BookKeeping(bookKeeping); } public ModelManager() { - this(new Inventory(), new UserPrefs()); + this(new Inventory(), new UserPrefs(), new TransactionList(), new BookKeeping()); } //=========== UserPrefs ================================================================================== @@ -366,37 +352,16 @@ public void transactAndClearOrder(Path path) { addRevenueBookKeeping(totalRevenue); - try { - new TransactionStorage().saveTransaction(new ArrayList(transactions), - path); - } catch (IOException e) { - System.out.println(e); - } - - logger.fine(TRANSACTION_LOGGING_MSG + transaction.toString()); } @Override - public List getTransactions() { - return new ArrayList<>(transactions); + public ReadOnlyTransactionList getTransactions() { + return new TransactionList(new ArrayList<>(transactions)); } //=========== BookKeeping ================================================================================ - /** - * Save the BookKeeping to json. - * - * @param bookKeeping current bookKeeping. - */ - public void saveBookKeeping(BookKeeping bookKeeping) { - try { - new BookKeepingStorage().saveBookKeeping(bookKeeping, userPrefs.getBookKeepingFilePath()); - } catch (IOException e) { - System.out.println(e); - } - } - /** * Add cost to bookKeeping. * @@ -404,7 +369,6 @@ public void saveBookKeeping(BookKeeping bookKeeping) { */ public void addCostBookKeeping(Double cost) { bookKeeping.addCost(cost); - saveBookKeeping(bookKeeping); } /** @@ -414,6 +378,5 @@ public void addCostBookKeeping(Double cost) { */ public void addRevenueBookKeeping(Double revenue) { bookKeeping.addRevenue(revenue); - saveBookKeeping(bookKeeping); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyBookKeeping.java b/src/main/java/seedu/address/model/ReadOnlyBookKeeping.java new file mode 100644 index 00000000000..8c29bc9305d --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyBookKeeping.java @@ -0,0 +1,18 @@ +package seedu.address.model; + +public interface ReadOnlyBookKeeping { + /** + * Returns the revenue + */ + Double getRevenue(); + + /** + * Returns the cost + */ + Double getCost(); + + /** + * Returns the profit + */ + Double getProfit(); +} diff --git a/src/main/java/seedu/address/model/ReadOnlyTransactionList.java b/src/main/java/seedu/address/model/ReadOnlyTransactionList.java new file mode 100644 index 00000000000..32b91058232 --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyTransactionList.java @@ -0,0 +1,13 @@ +package seedu.address.model; + +import java.util.ArrayList; + +import seedu.address.model.order.TransactionRecord; + +public interface ReadOnlyTransactionList { + /** + * Returns an unmodifiable view of the items list. + * This list will not contain any duplicate items. + */ + ArrayList getTransactionRecordList(); +} diff --git a/src/main/java/seedu/address/model/TransactionList.java b/src/main/java/seedu/address/model/TransactionList.java new file mode 100644 index 00000000000..effce6dee98 --- /dev/null +++ b/src/main/java/seedu/address/model/TransactionList.java @@ -0,0 +1,23 @@ +package seedu.address.model; + +import java.util.ArrayList; + +import seedu.address.model.order.TransactionRecord; + +public class TransactionList implements ReadOnlyTransactionList { + + private ArrayList transactionRecordList; + + public TransactionList(ArrayList transactionRecordList) { + this.transactionRecordList = transactionRecordList; + } + + public TransactionList() { + this(new ArrayList()); + } + + @Override + public ArrayList getTransactionRecordList() { + return transactionRecordList; + } +} diff --git a/src/main/java/seedu/address/storage/BookKeepingStorage.java b/src/main/java/seedu/address/storage/BookKeepingStorage.java index e911607f10e..4580dc1745c 100644 --- a/src/main/java/seedu/address/storage/BookKeepingStorage.java +++ b/src/main/java/seedu/address/storage/BookKeepingStorage.java @@ -1,52 +1,42 @@ package seedu.address.storage; -import static java.util.Objects.requireNonNull; - import java.io.IOException; import java.nio.file.Path; import java.util.Optional; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.BookKeeping; +import seedu.address.model.ReadOnlyBookKeeping; +import seedu.address.model.ReadOnlyTransactionList; + +public interface BookKeepingStorage { + + /** + * Returns the file path of the BookKeeping file. + */ + Path getBookKeepingPath(); + + /** + * Returns TransactionRecord List data as a {@link ReadOnlyTransactionList}. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readBookKeeping() throws DataConversionException, IOException; + + /** + * @see #getBookKeepingPath() + */ + Optional readBookKeeping(Path filePath) throws DataConversionException, IOException; -public class BookKeepingStorage { /** - * Save current BookKeeping to json. - * - * @param bookKeeping current bookKeeping. - * @param filePath the path of bookKeeping.json. - * @throws IOException if bookKeeping is not serializable. + * Saves the given {@link ReadOnlyBookKeeping} to the storage. + * @param bookKeeping cannot be null. + * @throws IOException if there was any problem writing to the file. */ - public void saveBookKeeping(BookKeeping bookKeeping, Path filePath) throws IOException { - requireNonNull(bookKeeping); - FileUtil.createIfMissing(filePath); - JsonUtil.saveJsonFile(new JsonSerializableBookKeeping(bookKeeping), filePath); - } + void saveBookKeeping(ReadOnlyBookKeeping bookKeeping) throws IOException; /** - * Read BookKeeping from json file. - * - * @param filePath the path of the bookKeeping.json. - * @return the BookKeeping enclosed in an optional. - * @throws DataConversionException if bookKeeping.json cannot be converted to bookKeeping. + * @see #saveBookKeeping(ReadOnlyBookKeeping) */ - public Optional readBookKeeping(Path filePath) throws DataConversionException { - requireNonNull(filePath); - - Optional jsonBookKeeping = JsonUtil.readJsonFile( - filePath, JsonSerializableBookKeeping.class); - - if (!jsonBookKeeping.isPresent()) { - return Optional.empty(); - } - - try { - return Optional.of(jsonBookKeeping.get().toModelType()); - } catch (IllegalValueException e) { - throw new DataConversionException(e); - } - } + void saveBookKeeping(ReadOnlyBookKeeping bookKeeping, Path filePath) throws IOException; } diff --git a/src/main/java/seedu/address/storage/JsonBookKeepingStorage.java b/src/main/java/seedu/address/storage/JsonBookKeepingStorage.java new file mode 100644 index 00000000000..e33cd4d55cc --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonBookKeepingStorage.java @@ -0,0 +1,62 @@ +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.FileUtil; +import seedu.address.commons.util.JsonUtil; +import seedu.address.model.ReadOnlyBookKeeping; + +public class JsonBookKeepingStorage implements BookKeepingStorage { + + private Path filePath; + + public JsonBookKeepingStorage(Path filePath) { + this.filePath = filePath; + } + + @Override + public Path getBookKeepingPath() { + return filePath; + } + + @Override + public Optional readBookKeeping() throws DataConversionException, IOException { + return readBookKeeping(filePath); + } + + @Override + public Optional readBookKeeping(Path filePath) throws DataConversionException, IOException { + requireNonNull(filePath); + + Optional jsonBookKeeping = JsonUtil.readJsonFile( + filePath, JsonSerializableBookKeeping.class); + + if (!jsonBookKeeping.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonBookKeeping.get().toModelType()); + } catch (IllegalValueException e) { + throw new DataConversionException(e); + } + } + + @Override + public void saveBookKeeping(ReadOnlyBookKeeping bookKeeping) throws IOException { + saveBookKeeping(bookKeeping, filePath); + } + + @Override + public void saveBookKeeping(ReadOnlyBookKeeping bookKeeping, Path filePath) throws IOException { + requireNonNull(bookKeeping); + FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializableBookKeeping(bookKeeping), filePath); + } +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableBookKeeping.java b/src/main/java/seedu/address/storage/JsonSerializableBookKeeping.java index 5638fa835de..6bddb763c17 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableBookKeeping.java +++ b/src/main/java/seedu/address/storage/JsonSerializableBookKeeping.java @@ -6,6 +6,7 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.BookKeeping; +import seedu.address.model.ReadOnlyBookKeeping; @JsonRootName(value = "bookKeeping") public class JsonSerializableBookKeeping { @@ -34,7 +35,7 @@ public JsonSerializableBookKeeping(@JsonProperty("revenue") Double revenue, * * @param bookKeeping current bookKeeping. */ - public JsonSerializableBookKeeping(BookKeeping bookKeeping) { + public JsonSerializableBookKeeping(ReadOnlyBookKeeping bookKeeping) { this.revenue = bookKeeping.getRevenue(); this.cost = bookKeeping.getCost(); this.profit = bookKeeping.getProfit(); diff --git a/src/main/java/seedu/address/storage/JsonSerializableTransaction.java b/src/main/java/seedu/address/storage/JsonSerializableTransaction.java index d953e13d20a..2ff53036e5a 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableTransaction.java +++ b/src/main/java/seedu/address/storage/JsonSerializableTransaction.java @@ -1,7 +1,6 @@ package seedu.address.storage; import java.util.ArrayList; -import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -10,15 +9,16 @@ import com.fasterxml.jackson.annotation.JsonRootName; import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.TransactionList; import seedu.address.model.order.TransactionRecord; @JsonRootName(value = "transaction") class JsonSerializableTransaction { - private final List orders = new ArrayList<>(); + private final ArrayList orders = new ArrayList<>(); @JsonCreator - public JsonSerializableTransaction(@JsonProperty("orders") List orders) { + public JsonSerializableTransaction(@JsonProperty("orders") ArrayList orders) { this.orders.addAll(orders); } @@ -26,12 +26,12 @@ public JsonSerializableTransaction(Stream source) { orders.addAll(source.map(JsonAdaptedOrder::new).collect(Collectors.toList())); } - public List toModelType() throws IllegalValueException { - List transactionRecordList = new ArrayList<>(); + public TransactionList toModelType() throws IllegalValueException { + ArrayList transactionRecordList = new ArrayList<>(); for (JsonAdaptedOrder o : orders) { TransactionRecord addition = o.toModelType(); transactionRecordList.add(addition); } - return transactionRecordList; + return new TransactionList(transactionRecordList); } } diff --git a/src/main/java/seedu/address/storage/JsonTransactionStorage.java b/src/main/java/seedu/address/storage/JsonTransactionStorage.java new file mode 100644 index 00000000000..f6526ba2e6b --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonTransactionStorage.java @@ -0,0 +1,66 @@ +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.FileUtil; +import seedu.address.commons.util.JsonUtil; +import seedu.address.model.ReadOnlyTransactionList; + +public class JsonTransactionStorage implements TransactionStorage { + + private Path filePath; + + public JsonTransactionStorage(Path filePath) { + this.filePath = filePath; + } + + @Override + public Path getTransactionFilePath() { + return filePath; + } + + @Override + public Optional readTransactionList() throws DataConversionException, IOException { + return readTransactionList(filePath); + } + + @Override + public Optional readTransactionList(Path filePath) + throws DataConversionException, IOException { + requireNonNull(filePath); + + Optional jsonTransaction = JsonUtil.readJsonFile( + filePath, JsonSerializableTransaction.class); + if (!jsonTransaction.isPresent()) { + System.out.println("yo empty bro"); + return Optional.empty(); + } + + try { + return Optional.of(jsonTransaction.get().toModelType()); + } catch (IllegalValueException e) { + throw new DataConversionException(e); + } + } + + @Override + public void saveTransactionList(ReadOnlyTransactionList transactionList) throws IOException { + saveTransactionList(transactionList, filePath); + } + + @Override + public void saveTransactionList(ReadOnlyTransactionList transactionList, Path filePath) throws IOException { + requireNonNull(transactionList); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializableTransaction(transactionList.getTransactionRecordList().stream()), + filePath); + } +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index 09606c4d2a9..7d91f7ea970 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -12,7 +12,7 @@ /** * API of the Storage component */ -public interface Storage extends InventoryStorage, UserPrefsStorage { +public interface Storage extends InventoryStorage, UserPrefsStorage, TransactionStorage, BookKeepingStorage { @Override Optional readUserPrefs() throws DataConversionException, IOException; diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index b1a5804cb60..c466f8e2c19 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -7,7 +7,9 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.ReadOnlyBookKeeping; import seedu.address.model.ReadOnlyInventory; +import seedu.address.model.ReadOnlyTransactionList; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; @@ -19,14 +21,19 @@ public class StorageManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); private InventoryStorage inventoryStorage; private UserPrefsStorage userPrefsStorage; + private TransactionStorage transactionStorage; + private BookKeepingStorage bookKeepingStorage; /** * Creates a {@code StorageManager} with the given {@code InventoryStorage} and {@code UserPrefStorage}. */ - public StorageManager(InventoryStorage inventoryStorage, UserPrefsStorage userPrefsStorage) { + public StorageManager(InventoryStorage inventoryStorage, UserPrefsStorage userPrefsStorage, + TransactionStorage transactionStorage, BookKeepingStorage bookKeepingStorage) { super(); this.inventoryStorage = inventoryStorage; this.userPrefsStorage = userPrefsStorage; + this.transactionStorage = transactionStorage; + this.bookKeepingStorage = bookKeepingStorage; } // ================ UserPrefs methods ============================== @@ -76,4 +83,58 @@ public void saveInventory(ReadOnlyInventory inventory, Path filePath) throws IOE inventoryStorage.saveInventory(inventory, filePath); } + // ================ Transaction methods ============================== + + @Override + public Path getTransactionFilePath() { + return transactionStorage.getTransactionFilePath(); + } + + @Override + public Optional readTransactionList() throws DataConversionException, IOException { + return readTransactionList(transactionStorage.getTransactionFilePath()); + } + + @Override + public Optional readTransactionList(Path filePath) + throws DataConversionException, IOException { + return transactionStorage.readTransactionList(filePath); + } + + @Override + public void saveTransactionList(ReadOnlyTransactionList transactionList) throws IOException { + saveTransactionList(transactionList, transactionStorage.getTransactionFilePath()); + } + + @Override + public void saveTransactionList(ReadOnlyTransactionList transactionList, Path filePath) throws IOException { + transactionStorage.saveTransactionList(transactionList, filePath); + } + + // ================ BookKeeping methods ============================== + + @Override + public Path getBookKeepingPath() { + return bookKeepingStorage.getBookKeepingPath(); + } + + @Override + public Optional readBookKeeping() throws DataConversionException, IOException { + return readBookKeeping(bookKeepingStorage.getBookKeepingPath()); + } + + @Override + public Optional readBookKeeping(Path filePath) throws DataConversionException, IOException { + return bookKeepingStorage.readBookKeeping(filePath); + } + + @Override + public void saveBookKeeping(ReadOnlyBookKeeping bookKeeping) throws IOException { + saveBookKeeping(bookKeeping, bookKeepingStorage.getBookKeepingPath()); + } + + @Override + public void saveBookKeeping(ReadOnlyBookKeeping bookKeeping, Path filePath) throws IOException { + bookKeepingStorage.saveBookKeeping(bookKeeping, filePath); + } } diff --git a/src/main/java/seedu/address/storage/TransactionStorage.java b/src/main/java/seedu/address/storage/TransactionStorage.java index 375648c19c9..0a7a5739ead 100644 --- a/src/main/java/seedu/address/storage/TransactionStorage.java +++ b/src/main/java/seedu/address/storage/TransactionStorage.java @@ -1,54 +1,45 @@ package seedu.address.storage; -import static java.util.Objects.requireNonNull; - import java.io.IOException; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.order.TransactionRecord; +import seedu.address.model.ReadOnlyTransactionList; + +/** + * Represents a storage for list of {@link seedu.address.model.order.TransactionRecord}. + */ +public interface TransactionStorage { -public class TransactionStorage { /** - * Saves list of TransactionRecord to transaction.json - * - * @param orderList list of TransactionRecord. Cannot be null. - * @param filePath path of Transaction. Cannot be null. - * @throws IOException if transaction cannot be saved. + * Returns the file path of the data file. */ - public void saveTransaction(ArrayList orderList, Path filePath) throws IOException { - requireNonNull(orderList); - requireNonNull(filePath); + Path getTransactionFilePath(); - FileUtil.createIfMissing(filePath); - JsonUtil.saveJsonFile(new JsonSerializableTransaction(orderList.stream()), filePath); - } + /** + * Returns TransactionRecord List data as a {@link ReadOnlyTransactionList}. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readTransactionList() throws DataConversionException, IOException; /** - * Reads list of TransactionRecords from transaction.json - * @param filePath path of Transaction. Cannot be null. - * @return The TransactionRecord List enclosed in an Optional. - * @throws DataConversionException If json file's format is not correct. + * @see #getTransactionFilePath() */ - public Optional> readTransaction(Path filePath) throws DataConversionException { - requireNonNull(filePath); - - Optional jsonTransaction = JsonUtil.readJsonFile( - filePath, JsonSerializableTransaction.class); - if (!jsonTransaction.isPresent()) { - return Optional.empty(); - } - - try { - return Optional.of(jsonTransaction.get().toModelType()); - } catch (IllegalValueException e) { - throw new DataConversionException(e); - } - } + Optional readTransactionList(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyTransactionList} to the storage. + * @param transactionList cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveTransactionList(ReadOnlyTransactionList transactionList) throws IOException; + + /** + * @see #saveTransactionList(ReadOnlyTransactionList) + */ + void saveTransactionList(ReadOnlyTransactionList transactionList, Path filePath) throws IOException; + } diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java index 4f22a34758f..b075783cb6e 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/address/logic/LogicManagerTest.java @@ -23,12 +23,16 @@ import seedu.address.logic.commands.RemoveCommand; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.BookKeeping; import seedu.address.model.Model; import seedu.address.model.ModelManager; import seedu.address.model.ReadOnlyInventory; +import seedu.address.model.TransactionList; import seedu.address.model.UserPrefs; import seedu.address.model.item.Item; +import seedu.address.storage.JsonBookKeepingStorage; import seedu.address.storage.JsonInventoryStorage; +import seedu.address.storage.JsonTransactionStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.StorageManager; import seedu.address.testutil.ItemBuilder; @@ -47,7 +51,12 @@ public void setUp() { JsonInventoryStorage inventoryStorage = new JsonInventoryStorage(temporaryFolder.resolve("inventory.json")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json")); - StorageManager storage = new StorageManager(inventoryStorage, userPrefsStorage); + JsonBookKeepingStorage bookKeepingStorage = new JsonBookKeepingStorage(temporaryFolder + .resolve("bookKeeping.json")); + JsonTransactionStorage transactionStorage = new JsonTransactionStorage(temporaryFolder + .resolve("transactions.json")); + StorageManager storage = new StorageManager(inventoryStorage, userPrefsStorage, + transactionStorage, bookKeepingStorage); logic = new LogicManager(model, storage); } @@ -76,7 +85,12 @@ public void execute_storageThrowsIoException_throwsCommandException() { new JsonInventoryIoExceptionThrowingStub(temporaryFolder.resolve("ioExceptionAddressBook.json")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("ioExceptionUserPrefs.json")); - StorageManager storage = new StorageManager(inventoryStorage, userPrefsStorage); + JsonBookKeepingStorage bookKeepingStorage = new JsonBookKeepingStorage(temporaryFolder + .resolve("bookKeeping.json")); + JsonTransactionStorage transactionStorage = new JsonTransactionStorage(temporaryFolder + .resolve("transactions.json")); + StorageManager storage = new StorageManager(inventoryStorage, userPrefsStorage, + transactionStorage, bookKeepingStorage); logic = new LogicManager(model, storage); // Execute add command @@ -130,7 +144,8 @@ private void assertCommandException(String inputCommand, String expectedMessage) */ private void assertCommandFailure(String inputCommand, Class expectedException, String expectedMessage) { - Model expectedModel = new ModelManager(model.getInventory(), new UserPrefs()); + Model expectedModel = new ModelManager(model.getInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel); } diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index a31b1b250b3..83f44cf7594 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -26,11 +26,13 @@ import org.junit.jupiter.api.Test; import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.BookKeeping; import seedu.address.model.Inventory; import seedu.address.model.Model; import seedu.address.model.ModelManager; import seedu.address.model.ModelStub; import seedu.address.model.ReadOnlyInventory; +import seedu.address.model.TransactionList; import seedu.address.model.UserPrefs; import seedu.address.model.display.DisplayMode; import seedu.address.model.item.Item; @@ -41,7 +43,8 @@ public class AddCommandTest { private ModelStubAcceptingItemAdded modelStub = new ModelStubAcceptingItemAdded(); - private ModelManager model = new ModelManager(getTypicalInventory(), new UserPrefs()); + private ModelManager model = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); @Test public void constructor_nullItem_throwsNullPointerException() { @@ -170,7 +173,8 @@ public void execute_idExistNonexistentName_throwsCommandException() { AddCommand addCommand = new AddCommand(bagelDescriptor); String expectedMessage = AddCommand.MESSAGE_NAME_NOT_FOUND; - Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs()); + Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs(), + model.getTransactions(), model.getBookKeeping()); assertCommandFailure(addCommand, model, expectedModel, expectedMessage); } @@ -184,7 +188,8 @@ public void execute_nameExistNonexistentId_throwsCommandException() { AddCommand addCommand = new AddCommand(bagelDescriptor); String expectedMessage = AddCommand.MESSAGE_ID_NOT_FOUND; - Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs()); + Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs(), + model.getTransactions(), model.getBookKeeping()); assertCommandFailure(addCommand, model, expectedModel, expectedMessage); } @@ -200,7 +205,8 @@ public void execute_nameAlreadyExists_throwsCommandException() { AddCommand addCommand = new AddCommand(bagelDescriptor); String expectedMessage = AddCommand.MESSAGE_NAME_EXISTS; - Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs()); + Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs(), + model.getTransactions(), model.getBookKeeping()); assertCommandFailure(addCommand, model, expectedModel, expectedMessage); } @@ -216,7 +222,8 @@ public void execute_idAlreadyExists_throwsCommandException() { AddCommand addCommand = new AddCommand(bagelDescriptor); String expectedMessage = AddCommand.MESSAGE_ID_EXISTS; - Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs()); + Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs(), + model.getTransactions(), model.getBookKeeping()); assertCommandFailure(addCommand, model, expectedModel, expectedMessage); } diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java index bd70519d10b..63e961f2057 100644 --- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java @@ -5,9 +5,11 @@ import org.junit.jupiter.api.Test; +import seedu.address.model.BookKeeping; import seedu.address.model.Inventory; import seedu.address.model.Model; import seedu.address.model.ModelManager; +import seedu.address.model.TransactionList; import seedu.address.model.UserPrefs; public class ClearCommandTest { @@ -22,8 +24,10 @@ public void execute_emptyAddressBook_success() { @Test public void execute_nonEmptyAddressBook_success() { - Model model = new ModelManager(getTypicalInventory(), new UserPrefs()); - Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs()); + Model model = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); + Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.setInventory(new Inventory()); assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java index 4b502837a1d..05849534d52 100644 --- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java @@ -14,8 +14,10 @@ import org.junit.jupiter.api.Test; +import seedu.address.model.BookKeeping; import seedu.address.model.Model; import seedu.address.model.ModelManager; +import seedu.address.model.TransactionList; import seedu.address.model.UserPrefs; import seedu.address.model.item.ItemDescriptor; import seedu.address.testutil.ItemDescriptorBuilder; @@ -26,7 +28,8 @@ */ public class DeleteCommandTest { - private Model model = new ModelManager(getTypicalInventory(), new UserPrefs()); + private Model model = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); @Test public void execute_deleteExistingItemByName_success() { @@ -35,7 +38,8 @@ public void execute_deleteExistingItemByName_success() { ItemDescriptor bagelDescriptor = new ItemDescriptorBuilder().withName(VALID_NAME_BAGEL).build(); DeleteCommand deleteCommand = new DeleteCommand(bagelDescriptor); - Model expectedModel = new ModelManager(model.getInventory(), new UserPrefs()); + Model expectedModel = new ModelManager(model.getInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.deleteItem(BAGEL); String expectedMessage = String.format(DeleteCommand.MESSAGE_SUCCESS, BAGEL); @@ -50,7 +54,8 @@ public void execute_deleteExistingItemById_success() { ItemDescriptor bagelDescriptor = new ItemDescriptorBuilder().withId(VALID_ID_BAGEL).build(); DeleteCommand deleteCommand = new DeleteCommand(bagelDescriptor); - Model expectedModel = new ModelManager(model.getInventory(), new UserPrefs()); + Model expectedModel = new ModelManager(model.getInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.deleteItem(BAGEL); String expectedMessage = String.format(seedu.address.logic.commands.DeleteCommand.MESSAGE_SUCCESS, BAGEL); @@ -66,7 +71,8 @@ public void execute_deleteExistingItemByNameAndId_success() { .withName(VALID_NAME_BAGEL).withId(VALID_ID_BAGEL).build(); DeleteCommand deleteCommand = new DeleteCommand(bagelDescriptor); - Model expectedModel = new ModelManager(model.getInventory(), new UserPrefs()); + Model expectedModel = new ModelManager(model.getInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.deleteItem(BAGEL); String expectedMessage = String.format(seedu.address.logic.commands.DeleteCommand.MESSAGE_SUCCESS, BAGEL); @@ -94,7 +100,8 @@ public void execute_idExistNonexistentName_throwsCommandException() { DeleteCommand deleteCommand = new DeleteCommand(bagelDescriptor); String expectedMessage = DeleteCommand.MESSAGE_NAME_NOT_FOUND; - Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs()); + Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs(), + new TransactionList(), new BookKeeping()); assertCommandFailure(deleteCommand, model, expectedModel, expectedMessage); } @@ -108,7 +115,8 @@ public void execute_nameExistNonexistentId_throwsCommandException() { DeleteCommand deleteCommand = new DeleteCommand(bagelDescriptor); String expectedMessage = DeleteCommand.MESSAGE_ID_NOT_FOUND; - Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs()); + Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs(), + new TransactionList(), new BookKeeping()); assertCommandFailure(deleteCommand, model, expectedModel, expectedMessage); } @@ -124,7 +132,8 @@ public void execute_multipleMatches_throwsCommandException() { DeleteCommand deleteCommand = new DeleteCommand(descriptor); String expectedMessage = DeleteCommand.MESSAGE_MULTIPLE_MATCHES; - Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs()); + Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.updateFilteredItemList(DISPLAY_INVENTORY, x-> x.equals(BAGEL) || x.equals(DONUT)); assertCommandFailure(deleteCommand, model, expectedModel, expectedMessage); diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java index ab4d611fb48..0d2ed9b7671 100644 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java @@ -19,9 +19,11 @@ import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; +import seedu.address.model.BookKeeping; import seedu.address.model.Inventory; import seedu.address.model.Model; import seedu.address.model.ModelManager; +import seedu.address.model.TransactionList; import seedu.address.model.UserPrefs; import seedu.address.model.item.Item; import seedu.address.model.item.ItemDescriptor; @@ -35,7 +37,8 @@ */ public class EditCommandTest { - private Model model = new ModelManager(getTypicalInventory(), new UserPrefs()); + private Model model = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); @Test public void execute_allFieldsSpecifiedUnfilteredList_success() { @@ -45,7 +48,8 @@ public void execute_allFieldsSpecifiedUnfilteredList_success() { String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_ITEM_SUCCESS, editedItem); - Model expectedModel = new ModelManager(new Inventory(model.getInventory()), new UserPrefs()); + Model expectedModel = new ModelManager(new Inventory(model.getInventory()), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.setItem((Item) model.getFilteredDisplayList().get(0), editedItem); assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); @@ -64,7 +68,8 @@ public void execute_someFieldsSpecifiedUnfilteredList_success() { String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_ITEM_SUCCESS, editedItem); - Model expectedModel = new ModelManager(new Inventory(model.getInventory()), new UserPrefs()); + Model expectedModel = new ModelManager(new Inventory(model.getInventory()), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.setItem(lastItem, editedItem); assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); @@ -77,7 +82,8 @@ public void execute_noFieldSpecifiedUnfilteredList_success() { String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_ITEM_SUCCESS, editedItem); - Model expectedModel = new ModelManager(new Inventory(model.getInventory()), new UserPrefs()); + Model expectedModel = new ModelManager(new Inventory(model.getInventory()), new UserPrefs(), + new TransactionList(), new BookKeeping()); assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); } @@ -93,7 +99,8 @@ public void execute_filteredList_success() { String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_ITEM_SUCCESS, editedItem); - Model expectedModel = new ModelManager(new Inventory(model.getInventory()), new UserPrefs()); + Model expectedModel = new ModelManager(new Inventory(model.getInventory()), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.setItem((Item) model.getFilteredDisplayList().get(0), editedItem); assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); diff --git a/src/test/java/seedu/address/logic/commands/EndAndTransactOrderCommandTest.java b/src/test/java/seedu/address/logic/commands/EndAndTransactOrderCommandTest.java index c9413d8f6d2..0acca200c69 100644 --- a/src/test/java/seedu/address/logic/commands/EndAndTransactOrderCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/EndAndTransactOrderCommandTest.java @@ -11,8 +11,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import seedu.address.model.BookKeeping; import seedu.address.model.Model; import seedu.address.model.ModelManager; +import seedu.address.model.TransactionList; import seedu.address.model.UserPrefs; import seedu.address.model.display.DisplayMode; import seedu.address.model.order.Order; @@ -22,14 +24,15 @@ public class EndAndTransactOrderCommandTest { @TempDir public Path temporaryFolder; - private Model modelWithoutOrder = new ModelManager(getTypicalInventory(), new UserPrefs()); + private Model modelWithoutOrder = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); /** * Returns a model with 5 donuts in its unclosed order */ private Model getModelWithOrderedDonut(Path path) { UserPrefs userPrefs = new UserPrefs(path); - Model model = new ModelManager(getTypicalInventory(), userPrefs); + Model model = new ModelManager(getTypicalInventory(), userPrefs, new TransactionList(), new BookKeeping()); model.addItem(DONUT.updateCount(5)); model.setOrder(new Order()); model.addToOrder(DONUT.updateCount(1)); @@ -53,7 +56,7 @@ public void execute_normalTransaction_itemRemoved() { String expectedMessage = EndAndTransactOrderCommand.MESSAGE_SUCCESS; Model expectedModel = new ModelManager(getTypicalInventory(), - new UserPrefs(temporaryFolder.resolve("transaction.json"))); + new UserPrefs(temporaryFolder.resolve("transaction.json")), new TransactionList(), new BookKeeping()); expectedModel.addItem(DONUT.updateCount(4)); Model modelTemp = getModelWithOrderedDonut(temporaryFolder.resolve("transaction.json")); @@ -75,7 +78,7 @@ public void execute_displayingOrder_itemRemovedAndDisplayInventory() { String expectedMessage = EndAndTransactOrderCommand.MESSAGE_SUCCESS; Model expectedModel = new ModelManager(getTypicalInventory(), - new UserPrefs(temporaryFolder.resolve("transaction.json"))); + new UserPrefs(temporaryFolder.resolve("transaction.json")), new TransactionList(), new BookKeeping()); expectedModel.addItem(DONUT.updateCount(4)); assertCommandSuccess(new EndAndTransactOrderCommand(), modelTemp, expectedMessage, expectedModel); diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java index 4b07953b566..117880b7783 100644 --- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java @@ -20,8 +20,10 @@ import org.junit.jupiter.api.Test; +import seedu.address.model.BookKeeping; import seedu.address.model.Model; import seedu.address.model.ModelManager; +import seedu.address.model.TransactionList; import seedu.address.model.UserPrefs; import seedu.address.model.item.IdContainsNumberPredicate; import seedu.address.model.item.NameContainsKeywordsPredicate; @@ -31,8 +33,10 @@ * Contains integration tests (interaction with the Model) for {@code FindCommand}. */ public class FindCommandTest { - private Model model = new ModelManager(getTypicalInventory(), new UserPrefs()); - private Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs()); + private Model model = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); + private Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); @Test public void equals() { diff --git a/src/test/java/seedu/address/logic/commands/ListInventoryCommandTest.java b/src/test/java/seedu/address/logic/commands/ListInventoryCommandTest.java index ec95c6895e4..44a9c8eef34 100644 --- a/src/test/java/seedu/address/logic/commands/ListInventoryCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ListInventoryCommandTest.java @@ -15,8 +15,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import seedu.address.model.BookKeeping; import seedu.address.model.Model; import seedu.address.model.ModelManager; +import seedu.address.model.TransactionList; import seedu.address.model.UserPrefs; import seedu.address.model.order.Order; import seedu.address.testutil.TypicalOrders; @@ -31,8 +33,9 @@ public class ListInventoryCommandTest { @BeforeEach public void setUp() { - model = new ModelManager(getTypicalInventory(), new UserPrefs()); - expectedModel = new ModelManager(model.getInventory(), new UserPrefs()); + model = new ModelManager(getTypicalInventory(), new UserPrefs(), new TransactionList(), new BookKeeping()); + expectedModel = new ModelManager(model.getInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); } @Test diff --git a/src/test/java/seedu/address/logic/commands/ListTransactionCommandTest.java b/src/test/java/seedu/address/logic/commands/ListTransactionCommandTest.java index eb031980bb7..10f24b76939 100644 --- a/src/test/java/seedu/address/logic/commands/ListTransactionCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ListTransactionCommandTest.java @@ -9,8 +9,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import seedu.address.model.BookKeeping; import seedu.address.model.Model; import seedu.address.model.ModelManager; +import seedu.address.model.TransactionList; import seedu.address.model.UserPrefs; import seedu.address.testutil.TypicalOrders; @@ -21,8 +23,9 @@ public class ListTransactionCommandTest { @BeforeEach public void setUp() { - model = new ModelManager(getTypicalInventory(), new UserPrefs()); - expectedModel = new ModelManager(model.getInventory(), new UserPrefs()); + model = new ModelManager(getTypicalInventory(), new UserPrefs(), new TransactionList(), new BookKeeping()); + expectedModel = new ModelManager(model.getInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); } @Test diff --git a/src/test/java/seedu/address/logic/commands/RemoveCommandTest.java b/src/test/java/seedu/address/logic/commands/RemoveCommandTest.java index ccf8775b814..94762ecd006 100644 --- a/src/test/java/seedu/address/logic/commands/RemoveCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/RemoveCommandTest.java @@ -16,15 +16,18 @@ import org.junit.jupiter.api.Test; +import seedu.address.model.BookKeeping; import seedu.address.model.Model; import seedu.address.model.ModelManager; +import seedu.address.model.TransactionList; import seedu.address.model.UserPrefs; import seedu.address.model.item.ItemDescriptor; import seedu.address.testutil.ItemDescriptorBuilder; public class RemoveCommandTest { - private ModelManager model = new ModelManager(getTypicalInventory(), new UserPrefs()); + private ModelManager model = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); @Test public void constructor_nullItem_throwsNullPointerException() { @@ -39,7 +42,8 @@ public void execute_removeExistingItemByName_success() { .withName(VALID_NAME_BAGEL).withCount(1).build(); RemoveCommand removeCommand = new RemoveCommand(bagelDescriptor); - Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.addItem(BAGEL.updateCount(BAGEL.getCount() - 1)); String expectedMessage = String.format(RemoveCommand.MESSAGE_SUCCESS, 1, BAGEL.getName()); @@ -55,7 +59,8 @@ public void execute_removeExistingItemById_success() { .withId(VALID_ID_BAGEL).withCount(1).build(); RemoveCommand removeCommand = new RemoveCommand(bagelDescriptor); - Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.addItem(BAGEL.updateCount(BAGEL.getCount() - 1)); String expectedMessage = String.format(RemoveCommand.MESSAGE_SUCCESS, 1, BAGEL.getName()); @@ -72,7 +77,8 @@ public void execute_removeExistingItemByNameAndId_success() { .withCount(1).build(); RemoveCommand removeCommand = new RemoveCommand(bagelDescriptor); - Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.addItem(BAGEL.updateCount(BAGEL.getCount() - 1)); String expectedMessage = String.format(RemoveCommand.MESSAGE_SUCCESS, 1, BAGEL.getName()); @@ -89,7 +95,8 @@ public void execute_idExistNonexistentName_throwsCommandException() { RemoveCommand removeCommand = new RemoveCommand(bagelDescriptor); String expectedMessage = RemoveCommand.MESSAGE_NAME_NOT_FOUND; - Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs()); + Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs(), + new TransactionList(), new BookKeeping()); assertCommandFailure(removeCommand, model, expectedModel, expectedMessage); } @@ -103,7 +110,8 @@ public void execute_nameExistNonexistentId_throwsCommandException() { RemoveCommand removeCommand = new RemoveCommand(bagelDescriptor); String expectedMessage = RemoveCommand.MESSAGE_ID_NOT_FOUND; - Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs()); + Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs(), + new TransactionList(), new BookKeeping()); assertCommandFailure(removeCommand, model, expectedModel, expectedMessage); } @@ -117,7 +125,8 @@ public void execute_removeAllOfItem_success() { .withCount(BAGEL.getCount()).build(); RemoveCommand removeCommand = new RemoveCommand(bagelDescriptor); - Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.addItem(BAGEL.updateCount(0)); String expectedMessage = String.format(RemoveCommand.MESSAGE_SUCCESS, BAGEL.getCount(), BAGEL.getName()); @@ -149,7 +158,8 @@ public void execute_multipleMatches_throwsCommandException() { RemoveCommand removeCommand = new RemoveCommand(descriptor); String expectedMessage = RemoveCommand.MESSAGE_MULTIPLE_MATCHES; - Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs()); + Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.updateFilteredItemList(DISPLAY_INVENTORY, x-> x.equals(BAGEL) || x.equals(DONUT)); assertCommandFailure(removeCommand, model, expectedModel, expectedMessage); diff --git a/src/test/java/seedu/address/logic/commands/RemoveFromOrderCommandTest.java b/src/test/java/seedu/address/logic/commands/RemoveFromOrderCommandTest.java index 54164c8b635..46077048c4e 100644 --- a/src/test/java/seedu/address/logic/commands/RemoveFromOrderCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/RemoveFromOrderCommandTest.java @@ -12,8 +12,10 @@ import org.junit.jupiter.api.Test; +import seedu.address.model.BookKeeping; import seedu.address.model.Model; import seedu.address.model.ModelManager; +import seedu.address.model.TransactionList; import seedu.address.model.UserPrefs; import seedu.address.model.item.ItemDescriptor; import seedu.address.model.order.Order; @@ -21,14 +23,16 @@ public class RemoveFromOrderCommandTest { - private Model modelWithoutOrder = new ModelManager(getTypicalInventory(), new UserPrefs()); + private Model modelWithoutOrder = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); private Model modelWithOrder = getModelWithOrderedDonut(); /** * Returns a model with 5 donuts in its unclosed order */ private Model getModelWithOrderedDonut() { - Model model = new ModelManager(getTypicalInventory(), new UserPrefs()); + Model model = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); model.addItem(DONUT.updateCount(5)); model.setOrder(new Order()); model.addToOrder(DONUT.updateCount(5)); diff --git a/src/test/java/seedu/address/logic/commands/SortCommandTest.java b/src/test/java/seedu/address/logic/commands/SortCommandTest.java index 50550a510bc..6763b1591d9 100644 --- a/src/test/java/seedu/address/logic/commands/SortCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/SortCommandTest.java @@ -12,8 +12,10 @@ import org.junit.jupiter.api.Test; import seedu.address.logic.commands.SortCommand.SortOrder; +import seedu.address.model.BookKeeping; import seedu.address.model.Model; import seedu.address.model.ModelManager; +import seedu.address.model.TransactionList; import seedu.address.model.UserPrefs; import seedu.address.model.item.ItemCountComparator; import seedu.address.model.item.ItemNameComparator; @@ -24,7 +26,8 @@ */ public class SortCommandTest { - private Model model = new ModelManager(getTypicalInventory(), new UserPrefs()); + private Model model = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); @Test public void constructor_nullSortOrder_throwsNullPointerException() { @@ -36,7 +39,8 @@ public void execute_sortByName_successful() throws Exception { SortCommand command = new SortCommand(SortCommand.SortOrder.BY_NAME); String expectedMessage = String.format(SortCommand.MESSAGE_SUCCESS, "name"); - Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.sortItems(new ItemNameComparator()); assertCommandSuccess(command, model, expectedMessage, expectedModel); @@ -47,7 +51,8 @@ public void execute_sortByCount_successful() throws Exception { SortCommand command = new SortCommand(SortCommand.SortOrder.BY_COUNT); String expectedMessage = String.format(SortCommand.MESSAGE_SUCCESS, "count"); - Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.sortItems(new ItemCountComparator()); assertCommandSuccess(command, model, expectedMessage, expectedModel); diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 3f4cc7c8abe..ce8e2e68db8 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -251,8 +251,9 @@ public void equals() { UserPrefs userPrefs = new UserPrefs(); // same values -> returns true - modelManager = new ModelManager(inventory, userPrefs); - ModelManager modelManagerCopy = new ModelManager(inventory, userPrefs); + modelManager = new ModelManager(inventory, userPrefs, new TransactionList(), new BookKeeping()); + ModelManager modelManagerCopy = new ModelManager(inventory, userPrefs, + new TransactionList(), new BookKeeping()); assertTrue(modelManager.equals(modelManagerCopy)); // same object -> returns true @@ -265,13 +266,15 @@ public void equals() { assertFalse(modelManager.equals(5)); // different inventory -> returns false - assertFalse(modelManager.equals(new ModelManager(differentInventory, userPrefs))); + assertFalse(modelManager.equals(new ModelManager(differentInventory, userPrefs, + new TransactionList(), new BookKeeping()))); // different filteredList -> returns false String[] keywords = APPLE_PIE.getName().fullName.split("\\s+"); modelManager.updateFilteredItemList(DISPLAY_INVENTORY, new NameContainsKeywordsPredicate(Arrays.asList(keywords))); - assertFalse(modelManager.equals(new ModelManager(inventory, userPrefs))); + assertFalse(modelManager.equals(new ModelManager(inventory, userPrefs, + new TransactionList(), new BookKeeping()))); // resets modelManager to initial state for upcoming tests modelManager.updateFilteredDisplayList(DISPLAY_INVENTORY, PREDICATE_SHOW_ALL_ITEMS); @@ -279,16 +282,18 @@ public void equals() { // different userPrefs -> returns false UserPrefs differentUserPrefs = new UserPrefs(); differentUserPrefs.setInventoryFilePath(Paths.get("differentFilePath")); - assertFalse(modelManager.equals(new ModelManager(inventory, differentUserPrefs))); + assertFalse(modelManager.equals(new ModelManager(inventory, differentUserPrefs, + new TransactionList(), new BookKeeping()))); // different order -> returns false - ModelManager other = new ModelManager(inventory, userPrefs); + ModelManager other = new ModelManager(inventory, userPrefs, + new TransactionList(), new BookKeeping()); other.setOrder(new Order()); assertFalse(modelManager.equals(other)); // different display mode / list -> returns false modelManager.setOrder(new Order()); - other = new ModelManager(inventory, userPrefs); + other = new ModelManager(inventory, userPrefs, new TransactionList(), new BookKeeping()); other.setOrder(new Order()); other.updateFilteredDisplayList(DISPLAY_OPEN_ORDER, PREDICATE_SHOW_ALL_ITEMS); assertFalse(modelManager.equals(other)); diff --git a/src/test/java/seedu/address/model/ModelStub.java b/src/test/java/seedu/address/model/ModelStub.java index 8f68175f9a9..38c84fd0508 100644 --- a/src/test/java/seedu/address/model/ModelStub.java +++ b/src/test/java/seedu/address/model/ModelStub.java @@ -12,7 +12,6 @@ import seedu.address.model.item.Item; import seedu.address.model.item.ItemDescriptor; import seedu.address.model.order.Order; -import seedu.address.model.order.TransactionRecord; /** * A default model stub that have all of its methods failing. @@ -175,7 +174,7 @@ public Order getOrder() { } @Override - public List getTransactions() { + public ReadOnlyTransactionList getTransactions() { throw new AssertionError("This method should not be called."); } diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/address/storage/StorageManagerTest.java index b9155fae96a..24fda93b491 100644 --- a/src/test/java/seedu/address/storage/StorageManagerTest.java +++ b/src/test/java/seedu/address/storage/StorageManagerTest.java @@ -26,7 +26,10 @@ public class StorageManagerTest { public void setUp() { JsonInventoryStorage jsonInventoryStorage = new JsonInventoryStorage(getTempFilePath("ab")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(getTempFilePath("prefs")); - storageManager = new StorageManager(jsonInventoryStorage, userPrefsStorage); + JsonBookKeepingStorage bookKeepingStorage = new JsonBookKeepingStorage(getTempFilePath("bookKeeping")); + JsonTransactionStorage transactionStorage = new JsonTransactionStorage(getTempFilePath("transactions")); + storageManager = new StorageManager(jsonInventoryStorage, userPrefsStorage, + transactionStorage, bookKeepingStorage); } private Path getTempFilePath(String fileName) { From d3faca1b10636f9b0a1fcb1286a892e48b984216 Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Mon, 1 Nov 2021 12:52:25 +0800 Subject: [PATCH 02/42] Implementation for Find --- docs/DeveloperGuide.md | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 1d727058e99..99010ed9c7e 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -293,6 +293,50 @@ _{more aspects and alternatives to be added}_ _{Explain here how the data archiving feature will be implemented}_ +### Find feature + +The find mechanism is facilitated by the built-in `Predicate` class. The FindCommand constructor takes in a predicate +type as a parameter and the list is then filtered with `ModelManager#updateFilteredItemList` method to only contain the +items that match the predicate specified. `Predicate` interface is implemented by 3 different classes: + +* `IdContainsNumberPredicate` — allows finding of items by Id +* `NameContainsKeywordsPredicate` — allows finding of items by Name +* `TagContainsKeywordsPredicate` — allows finding of items by Tag + +Given below is an example usage scenario and how the find mechanism behaves at each step. + +Step 1. The user opens up BogoBogo and executes `find n/Chocolate` to find items with the name chocolate. The +`LogicManager` then calls the `AddressBookParser` to execute `FindCommandParser#parse()`. The `FindCommandParser` +then creates a `FindCommand` with the `NameContainsKeywordsPredicate` as a field in its constructor. Then, the +`LogicManager` calls the `FindCommand#execute()` which will update the filtered list with items that matches the +predicate stated. + +
:information_source: **Note:** If the user input a wrong format of the name, id or tag, a ParseException will be thrown by FindCommandParser and a FindCommand will not be created. + +Step 2. The updated list with items that matches the predicate will then be shown to the user. If none matches, an empty +list will be shown. The same procedure above is executed for finding by Id and Tags as well. + +
:information_source: **Note:** The FindCommand also supports finding by multiple names, ids or tags. +The 3 classes that implement Predicate takes in a list of string which allows storing of multiple predicates. + +
+ +The following sequence diagram shows how the undo operation works: + +![UndoSequenceDiagram](images/UndoSequenceDiagram.png) + +The following activity diagram summarizes what happens when a user executes a Find command: + + + +#### Design considerations: + +**Aspect: How Find executes:** + +* **Alternative 1:** Retrieve current inventory and check each item one by one without using the predicate class + * Pros: Easier to implement + * Cons: May have performance issue as every command will execute several loops. + -------------------------------------------------------------------------------------------------------------------- ## **Documentation, logging, testing, configuration, dev-ops** From c15b36736dd39083236cb00f7194721763d2eaf4 Mon Sep 17 00:00:00 2001 From: bernarduskrishna <77195969+bernarduskrishna@users.noreply.github.com> Date: Mon, 1 Nov 2021 16:13:20 +0800 Subject: [PATCH 03/42] Update usecases --- docs/DeveloperGuide.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 1d727058e99..2978ccfb0f1 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -352,21 +352,23 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli **Extensions** -* 1a. User did not specify the name of item. - * 1a1. BogoBogo notifies user of missing details. +* 1a. User is adding the item for the first time, and did not specify the id, cost price or sell price of the item. + * 1a1. BogoBogo informs user of the missing details. + * 1a2. User reenters with the missing details. - Use case ends. + Use case resumes at step 2. -* 1b. User is adding the item for the first time, and did not specify the id, price or cost of the item. - * 1b1. BogoBogo requests user for the missing details. - * 1b2. User enters the missing details. +* 1b. User is adding item that has been added before, and only specifies either name or id without the other fields. + * 1b1. BogoBogo will replenish the item according to the count indicated (count defaults to 1) - Use case resumes at step 2. + Use case ends. -* 1c. The given id does not match with the given name. - * 1c1. BogoBogo notifies user of the mismatch. +* 1c. User is adding an item that has been added before, but provides an id that corresponds to another item. + * 1c1. BogoBogo notifies user of the mismatch and shows the list of possible matches. + * 1c2. User reenters with the correct details. + * 1c3. BogoBogo will replenish the item according to the count indicated (count defaults to 1) - Use case resumes at step 2. + Use case ends. **UC02 - Deleting an item** From b85b1fa87d073fddb3d745bb674cb9ff47ca1c81 Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Mon, 1 Nov 2021 18:57:21 +0800 Subject: [PATCH 04/42] Implementation for Sort --- docs/DeveloperGuide.md | 46 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 99010ed9c7e..2ba412f6222 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -321,7 +321,7 @@ The 3 classes that implement Predicate takes in a list of string which all
-The following sequence diagram shows how the undo operation works: +The following sequence diagram shows how the find operation works: ![UndoSequenceDiagram](images/UndoSequenceDiagram.png) @@ -337,6 +337,50 @@ The following activity diagram summarizes what happens when a user executes a Fi * Pros: Easier to implement * Cons: May have performance issue as every command will execute several loops. + +### Sort feature + +The sort mechanism is facilitated by the built-in `Comparator` interface. The SortCommand constructor takes in a +predicate enum instruction as a parameter depending on whether the user requested to sort by name or count. The +items' respective fields are then compared with a `Comparator` so that the updated list displayed is sorted. +`Comparator` interface is implemented by e different classes: + +* `ItemNameComparator` — allows sorting of items by name +* `ItemCountComparator` — allows sorting of items by count + +Given below is an example usage scenario and how the sort mechanism behaves at each step. + +Step 1. The user opens up BogoBogo and executes `sort n/` to sort items by name. The `LogicManager` then calls the +`AddressBookParser` to execute `SortCommandParser#parse()`. The `SortCommandParser` then creates a `SortCommand` with +the enum `SortOrder.BY_NAME` as a field in its constructor. Then, the `LogicManager` calls the `SortCommand#execute()` +which will call the `ModelManager#sortItems()` with the `ItemNameComparator` as its parameter. This will then update the +display list with items sorted by name according to the `ItemNameComparator#compare()` method. + +
:information_source: **Note:** If the user input does not input any field to sort by, SortCommandParser will throw a ParseException and a SortCommand will not be created. + +Step 2. The updated list with items sorted by name will then be shown to the user. The same procedure above is executed +for sorting by count as well. + +
:information_source: **Note:** If the user tries to sort when not in inventory mode, a CommandException will be thrown by SortCommand to remind user to list first. + +
+ +The following sequence diagram shows how the sort operation works: + +![UndoSequenceDiagram](images/UndoSequenceDiagram.png) + +The following activity diagram summarizes what happens when a user executes a Sort command: + + + +#### Design considerations: + +**Aspect: How Sort executes:** + +* **Alternative 1:** Retrieve current inventory and check each item one by one without using the comparator class + * Pros: Easier to implement + * Cons: May have performance issue as every command will execute several loops. + -------------------------------------------------------------------------------------------------------------------- ## **Documentation, logging, testing, configuration, dev-ops** From 935bcc889b0eb7378dd89fa04743693669015ef5 Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Mon, 1 Nov 2021 22:14:31 +0800 Subject: [PATCH 05/42] Update find feature UML diagram --- docs/DeveloperGuide.md | 113 ++----------------------- docs/diagrams/FindSequenceDiagram.puml | 60 +++++++++++++ docs/diagrams/SortSequenceDiagram.puml | 60 +++++++++++++ docs/images/FindSequenceDiagram.png | Bin 0 -> 36254 bytes 4 files changed, 125 insertions(+), 108 deletions(-) create mode 100644 docs/diagrams/FindSequenceDiagram.puml create mode 100644 docs/diagrams/SortSequenceDiagram.puml create mode 100644 docs/images/FindSequenceDiagram.png diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 2ba412f6222..74029145148 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -193,106 +193,6 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature - -#### Proposed Implementation - -The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo -history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the -following operations: - -* `VersionedAddressBook#commit()` — Saves the current address book state in its history. -* `VersionedAddressBook#undo()` — Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. - -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` -and `Model#redoAddressBook()` respectively. - -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. - -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the -initial address book state, and the `currentStatePointer` pointing to that single address book state. - -![UndoRedoState0](images/UndoRedoState0.png) - -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command -calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes -to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book -state. - -![UndoRedoState1](images/UndoRedoState1.png) - -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()` -, causing another modified address book state to be saved into the `addressBookStateList`. - -![UndoRedoState2](images/UndoRedoState2.png) - -
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. - -
- -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing -the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` -once to the left, pointing it to the previous address book state, and restores the address book to that state. - -![UndoRedoState3](images/UndoRedoState3.png) - -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. - -
- -The following sequence diagram shows how the undo operation works: - -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) - -
:information_source: **Note:** 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. - -
- -The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once -to the right, pointing to the previously undone state, and restores the address book to that state. - -
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, 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`. Commands that do not modify the address book, such -as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. -Thus, the `addressBookStateList` remains unchanged. - -![UndoRedoState4](images/UndoRedoState4.png) - -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not -pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be -purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern -desktop applications follow. - -![UndoRedoState5](images/UndoRedoState5.png) - -The following activity diagram summarizes what happens when a user executes a new command: - - - -#### Design considerations: - -**Aspect: How undo & redo executes:** - -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. - -* **Alternative 2:** Individual command knows how to undo/redo by itself. - * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). - * Cons: We must ensure that the implementation of each individual command are correct. - -_{more aspects and alternatives to be added}_ - -### \[Proposed\] Data archiving - -_{Explain here how the data archiving feature will be implemented}_ - - ### Find feature The find mechanism is facilitated by the built-in `Predicate` class. The FindCommand constructor takes in a predicate @@ -306,10 +206,10 @@ items that match the predicate specified. `Predicate` interface is impleme Given below is an example usage scenario and how the find mechanism behaves at each step. Step 1. The user opens up BogoBogo and executes `find n/Chocolate` to find items with the name chocolate. The -`LogicManager` then calls the `AddressBookParser` to execute `FindCommandParser#parse()`. The `FindCommandParser` -then creates a `FindCommand` with the `NameContainsKeywordsPredicate` as a field in its constructor. Then, the -`LogicManager` calls the `FindCommand#execute()` which will update the filtered list with items that matches the -predicate stated. +`LogicManager` then calls the `AddressBookParser` which create a `FindCommandParser` object. Then, `FindCommandParser#parse()` +then creates a `FindCommand` object and `NameContainsKeywordsPredicate` object. The `NameContainsKeywordsPredicate` is +passed as a field of the FindCommand constructor. Then, the `LogicManager` calls the `FindCommand#execute()` +which will update the filtered list with items that matches the predicate stated.
:information_source: **Note:** If the user input a wrong format of the name, id or tag, a ParseException will be thrown by FindCommandParser and a FindCommand will not be created. @@ -323,11 +223,8 @@ The 3 classes that implement Predicate takes in a list of string which all The following sequence diagram shows how the find operation works: -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) - -The following activity diagram summarizes what happens when a user executes a Find command: +![FindSequenceDiagram](images/FindSequenceDiagram.png) - #### Design considerations: diff --git a/docs/diagrams/FindSequenceDiagram.puml b/docs/diagrams/FindSequenceDiagram.puml new file mode 100644 index 00000000000..c0daa1a1d8b --- /dev/null +++ b/docs/diagrams/FindSequenceDiagram.puml @@ -0,0 +1,60 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FindCommandParser" as FindCommandParser LOGIC_COLOR +participant ":FindCommand" as FindCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":NameContainsKeywordsPredicate" as NameContainsKeywordsPredicate MODEL_COLOR +end box +[-> LogicManager : execute(sort n/) +activate LogicManager + + +LogicManager -> AddressBookParser : parseCommand(sort n/) +activate AddressBookParser + +create FindCommandParser +AddressBookParser -> FindCommandParser +deactivate FindCommand + +create FindCommand +FindCommandParser -> FindCommand : parse(n/chocolate) +activate FindCommand + +create NameContainsKeywordsPredicate +FindCommandParser -> NameContainsKeywordsPredicate : parse(n/chocolate) +activate NameContainsKeywordsPredicate + +NameContainsKeywordsPredicate --> FindCommand +deactivate NameContainsKeywordsPredicate + +FindCommand --> LogicManager +deactivate FindCommand + +LogicManager -> FindCommand : execute() +activate FindCommand + +FindCommand -> Model : updateFilteredItemList() +activate Model + +Model -> NameContainsKeywordsPredicate : test() +activate NameContainsKeywordsPredicate + +NameContainsKeywordsPredicate --> Model +deactivate NameContainsKeywordsPredicate + +Model --> FindCommand +deactivate Model + +FindCommand --> LogicManager : result +deactivate FindCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/SortSequenceDiagram.puml b/docs/diagrams/SortSequenceDiagram.puml new file mode 100644 index 00000000000..6ff88e39a03 --- /dev/null +++ b/docs/diagrams/SortSequenceDiagram.puml @@ -0,0 +1,60 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":SortCommandParser" as SortCommandParser LOGIC_COLOR +participant ":SortCommand" as SortCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":ItemNameComparator" as ItemNameComparator MODEL_COLOR +end box +[-> LogicManager : execute(find n/chocolate) +activate LogicManager + + +LogicManager -> AddressBookParser : parseCommand(find n/chocolate) +activate AddressBookParser + +create FindCommandParser +AddressBookParser -> FindCommandParser +activate FindCommandParser + +create FindCommand +FindCommandParser -> FindCommand : parse(n/chocolate) +activate FindCommand + +create NameContainsKeywordsPredicate +FindCommandParser -> NameContainsKeywordsPredicate : parse(n/chocolate) +activate NameContainsKeywordsPredicate + +NameContainsKeywordsPredicate --> FindCommand +deactivate NameContainsKeywordsPredicate + +FindCommand --> LogicManager +deactivate FindCommand + +LogicManager -> FindCommand : execute() +activate FindCommand + +FindCommand -> Model : updateFilteredItemList() +activate Model + +Model -> NameContainsKeywordsPredicate : test() +activate NameContainsKeywordsPredicate + +NameContainsKeywordsPredicate --> Model +deactivate NameContainsKeywordsPredicate + +Model --> FindCommand +deactivate Model + +FindCommand --> LogicManager : result +deactivate FindCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/images/FindSequenceDiagram.png b/docs/images/FindSequenceDiagram.png new file mode 100644 index 0000000000000000000000000000000000000000..96d9e806a7e88817e357283bd25eac2285fe6c7e GIT binary patch literal 36254 zcmd4(by(Ef7d{LRDx#urR7w$00cE6=k`$%8Ye)fykdl^05Jiw42I(9SDVd?uAcyYm z?(TXvqUU(x`+MH^xt{mB-uYv^l+EX}_u6}{b+3Ef+jnv@qL(gQzW{+iE{Tf?DL^1+ z>>&`G>T{>SzZCIP$ACYeHo|H)`W7!7OyLGL5K*`}+)CF5{_Lin!%ZU_n-|>7%r8uJ z&24PWOquj8%r4*Ox&eWl4mMU&v-$P!5FGHBmr)BUU(NeC|0uoM%wOLbHa`4#HE%#k zjVL>mTkLt3U-tr~Bb}O0_B*j8WI}6!@R7?~G+YvyPGc4z&$P1K5IGVVWYDM}m|Htq zbIa{qZ34r1V1#moY;gZ{AoDDx$n#rd@9&U?eVuyB5~1l9<3u3dtav|2&Un}>R=-NA z6}OAoJz0(e;j%@sr~A<%f&ftztq^TqV$#Xs6CbD>H~InlSx6r9C|GZ(aJH(&J%Xt9 zy>bmTnQY^bAAv-OzE$USN2*;yKZm%ac!A`lo!PWjUG5HEqW9Nh6mxEWYVT9xw;fWc z8#!G*#lX5x7ZT5HcMdMn5A$HDA8ffrEK2i9wd!rfAtoqI1ZMp?UCP=?B`rvBcAqQ< zCx60%(`3|Mtn}8W`buGc|HvN6k_=_eKT=Drq_e5KM+5D!<{OR#=Xb_Z>xyi)c2mCP z+--I(R@z|f%@iJy7|xFbk7hW3nU^SN1UFgo#sb9(veoxuN!Un@X&#n|(2!35<{PJE7Pk+?pP9sre&RzJT6TE7@7Z08R`%LBLEoBr_1 zsL+VSqhsk>Lp{yEQhsMRwBgcrxEVcM+atOkka|^75dU1T{GF zJsgV^+FHNxD1E=Aco{v_sa7R0X||kouHlMyA>M7Gi!pt@p2dmQu7WFrA+c-Se`kK`z+M-FQ`yfXEl=o~9WeBVIprHt|>dls~! zhuJtOHe>T%*dA8geYu0F>%`frW>P0C?n0(#NRD}QTr1nD<8gU`nJwY6*UH)|&i}-? zuqLumBF|@`)I{EqWueq|j$fLpin?Hon^?lbzSY`k@ZA(e!^ck(}3f4HF~B^vliYK(?3TYG2!v4_5u@DiP-VXOCtz5C(?CERX0WW&)VSJfxh-fOzt zSb}8f*x`K}c8q&Kyv=zh^0GIjRL{uF_qRKrog_p!$5=}6Hlt{879B*goD0YTk8l*} zjdHB;)s`R-cZj&q6Q!5h^8OH`7h>=ReC?1>Wu-$)Jn=9g!KQm#hG~X4?#u$%Q?D*wi)3eO`a}4D ztZSpUyVJCh%c?)m&esuXRqOY_pH?+pvFTJI80fxV=-46SmJ!B0_0f>{Y~Q>WKDPx4 z#0SjOn|kWSmAQ!RG+xCZ3(Y+)g4B+ac_OaocrRk z`AdCoA!mmr1JRFf*ej6We&UKxXK@-q^Ms-i!W)}cQ>J1)~`HTKVS<6|5}at!@}nM%5b-1!U??IL_n47YZbBn$3Kv89Ms9 z><6SPNerw{jyJ|K3~jf7o*b|IxHd(Bv0KdPUG-?K^z{#G)!ekICYWo8 z^j|2Dg0+5As*TBcqLHXf~_!Kxbjv*u<`r^lC<8^7v%88F^l<7ZM1zX0fp6INs(1$_A`Uzf#59+J;zqfql#%*oXp82VVn5ItEV4hS%`leBYWuFxVRx(aM|q- zQSK~=a!uW#;heHkKsqWyhOLheNF!on%pxy?B;tj$>he6-9;%K_G6rp0*ZKMD z(}P{SyadL3y(_)uP1jkF=y2=BikZd+ESUGA)`Uv(J|qw=g$sOcwOR|y?bv?JFKOzG zcF}l^yh<81Jax$?`^OF>FKaMBhP=Q52n1H!2{valHJCIJ)uZ-7UAZ?qb%~<@f*JfdAF0)P$F>%gxUQ~159yXRT!QH4|$hG3MIPW;E zwu5sM9)982mkE6ZIU>hqW`h+|Zq)h=lwfOXrDYm*3G-`02YQR_8ercqIMv_GxIL7< z{{Bf`YJZ86-J&MJ!m09>Q_GBCM*cid-(*II@(7|!n+kw?uBd_nPtFX&o| z$tx&Q%ou@lzq7`**|)~Zy0DtHzt9UWb>@02BTn?ipDJJe1va$uULI`ISs)MEd&jBu zsrlf$i$j!WPNvldt+QOia!*#zhvR1qH9W)2cePo=33F<8_4H)sWk_PJ)>akl-Ss}~fBe!7{NS%>NDo^F<0g>x(2|PDAt`V^2X?1;H0H7YLdYKG6p^ z>I|&+hwnYOO!yq{J_B%t`>fz=Id7palpXSvR}2PuY5=|}eeh=P zoQoluFrO00MP6iVaCsqlH=5n3adWD)$g>dc3o8SNjiHFE#hsCajx&nZ0%9#X+=)vU zzwij+LY$*VF9_~m364eN#tB6`&_3V3&20sy#JoI$5LR_^b!~|wb>uI7G4sBR6|!`Z z7_w|zB?BW;^^oA5_MNT?LPJU*Q2eM8>)!^5!Sp~LSq6#Z#cCspb+-{x!g@TnTkv=1 zq4-~T0?}V;dBPf}c}_1k%HcqC-Xdu4fLAU7Gu~a&{A+3y3dj$@=q0EBAHG1i-Tv!W zXsoNFoK{mTZJb59^zKUa)mg{*mxjl76~wj<9HwFtwD$svw3*C@r&a_-Kh4pCZ5h-Z zM}!8xSXdq*pWQv>?OTjCY+|=9eZ3hP62jwl2y=j!yY78tfkFjKOWeWI)3cXovdW5B z?^e4`2flT!$6{fhhtrE<_$5ix=fp+cn5|7pE|tfA)D|h;-ll)|uB&6CRr@2?UD?6? znUql}y}@huAj5NkXtC!xl`$b0`&A`}iHIlI!{0PWQsB z<9P)VcY87}cuU7@OX4m~R1Zd${tDvMg0DY6*ABA8YG+mH6R+iHvZDiz!&ECwe@Ogr ztGvzBv}C2qd_(j6T?tEr8IPjF{=#~wqQ6C-UAR9hW5aHf+r6-sMlGzK(>2yfBO#&L zjCMc6r_v*N|pRV)T$n_J8Uhv|%N z4#+kEcL^w^4BMC#RUSXSsFjtHY@nWDr>@{C7-!tpJyE9!y{=Ve8qGe(rqwP{9vHvN ze^_wS>=xa0eGewz%4&Y9$ut4oRqqdCzmF@`IhK z84MQ;mNVMRktG=!p_;in(u2>!tWy~7sJk^?%cYWrKu?BdxxT2nalsU(nz^`VSryf) zti}fp>&^E_G~JqzsEz?JdM6s5$`R?(@o7!Jni5r~azS9JWUOQ_$8p1|h?_ z$z!jL8Vky7oaic>cXH2J9{!3q&0_NfQccU!;OEULOAhen4(ds7Wu{hFXI42KDjg-+ zP7oW;1_c;;-CmltY$@PLAAR@6sU z5*f8O>MQ89#l4!bPVmAYb?A(}1*xr(g2Gwbl@AX@r_1*xkX}aAIbIHHQQ@yyTRAaF zK^)K9FLPTVM^L4ojkzcL`|PC${Qj_&)S@gYo=j5>Tl*0S4SNy915VB19dkWwxG~h$ zBq-W_+kxeglT*(O6%X@K_F&nq=77K~eIIQRxQ zT=yS1V1r8zcbU}RGufg1+Ss?gyxKD^a&f9w#ZS_>#H0!=&BJRK=6Qq5O1~>JtySB6 z06Wv7AD~xv1%!E%d7jpE7=ti_&uup8TQm=ud(f~9br_v!hSpq3areiTXnS|oI@ygo zF9w+|kP!vXkabJSz(*IDeHGsMm0tN{xPYSKnsF4&HD9#%E!86GyLL{E!&6gcQl@7O z;ntJg*%y0TCL0ij zRY(HnoAyZj+KkY)MBe^4JgO9gtraXuzi9O;QFbuNTMW#4$$|4Oy)P=gvLhxT_b_4= zKQa_qu+)z<;_yS@-aKcGV%StpTPo*g>Y$;8f39iI&(iPAAG#xs884p-Qc#I~Nx|2G1rm=5hE-S^Tb7u`U^%xnq%ROQ#MT8FDwqoZOYFMrER{D(;?6jYiQ5X74WRA&6T`-J4$ zE|DHrEAK+#8Lrv*4QOW;P4$V@GEY@Ps>FB7(tSJzm#*Jx2zCwI5Uo=+QW253H$kr_ zU4D!2DMeRt5SKe};g}p1} z`)5QYhnQ-;NtgEaS`(Wi6qPRGr)RfNB%#`bPkKl8!wac0W_xJez`ZCz_*+pYs)vho zf-vDy(y)`f?Oka#(O6TUCE{<>pEffz)KV{>7`0`#52CFhGImyaxI}Awt%;E0k+Ub` zmsBZ)OIQHy_si4-)*>Ll>I_~^PBzdk2(+D1PzhtQk4XSKQZaB&w@=U?QF^}+7Qv3NrI~u65<@SuS$qWjC?#w$NU2xRMl1)-Js$IXDas$&_B7?~*Ym<$j zMWwT66t^<;s@p6*sYmATeTM%?V3`Qvu#B9rQ2J;cUi^#iF+KA(>`P&!7EC&MnHu&% z4c8y-%Vnkw!E-Ij2h5w4b(SQcb1AVhpYxn|P-7m-5-?9ZsyJ*`xt-<#CJPtqc6y8j zUZ~+^{c)j}i@>5UkB8qZcsVQbpv3kV*l^pta1J*++{Z+7FFbWz?+ytWn@^u?7)w`i z5)<>uxxYc}gvW{5SF$)YwV*ehaX1!$pEHDHWLyO4C`C1^w4HQqGJ51aTO&hxpXl~v zq*H5~8JktoL6H<}Q3L?_%2ii;D&CZxMHRzeC=s_-p>tDKG`rN)VXpiM8bPdHjH65$ zUi^WPzkD>2tEb1X%OZIOcdMhm7TbHYcGR6de7&ie0g3iVJJQJSi!lYnKlF?VZr7giJ_=b~`<|Zn=G1|U4G;Mu=0}4Nw;`b+)lMIVV zq<-&b+mAYb$@UJ8J=7O-VMH-Z0}&}Dyo=!f6jp4LW|paw^6p!r7bx+ z2|>$O_bQ(EaVXJUBgbn0wDthSlo&GBNxBWIOol7g$9W~pqKp6N8U+cG#i;p}s&a&Z(S{yp1MIQEVE~`hXIMy@ybRc^QIHhS2z}e zPvF(vxibA~meJ8DQKlV$Gul5=xxTRJ!K0c-eSFMp(f`?x!b#~c_i-b`M&$77LV}b) zX;sm7O%OSQSaJG#$R*a^0VJa_wJ3MF&5uyIN}Y63w(j%PGr5U)K`dfo%Jua}MP!|) zeUJ0T?1d9xFBOU2blt-&Ofqt~Ji2rEqFI2WG*fV^gV|2E4UwFAgR?%FRMlm*KH_74 z3C4r{;0=5^tTp*|q-{a??6zBRr%6}7%XaocXS5r$$yXP4<2tS$f46Yf>gVwMamBDs(Q6VE2V@HSnrf zy#P^9rR?R6TUYGyme4yl{#@S$gC{8U_<)XB#o1fJd>yYl&F^o;yzNLkGjX7yp&PK* z;?{xb2Ry+tK}a$~m_gH{QoD>X9lcBE#S5(}Bch^u3bH%rWSHrF1UDK{)|WRZUWS~U z(M@O`A zTt5Hg0q`H#U{`IyJ_|EekSeN}w!@3Qtr4g3xMXA!k(B1oKGYN~>#)Zi)8Ny7dO`;L zAf|8iDs=iC>c|U06)c-dGBevra|H(2Ak9It$qYk!H6l28jyE7*^!gDKxT81DLx{)} zRDxKaCAy#HBY-@8g1vXf(*F$P!bNCW%{8?AmsgZ{kk2>a#2%cYry#g*5X$2hd+3HA z2b_ke1)?uJiK51FKP8C0XJz8lprq>YD!v*A0w{N{!0x1!qAL(*Kw1ORW10{UcJ&wk z!IVQFyT=^L4%*BiU)wFpG0wGCC7x>b~47mvT|8g#GOo?8^{hY|n z#vsV!SI3VP#=`Ki9zDN1fqRz#CSdBn6`vK@D)^U4bPB5&OVJoZ>D(pu0>OhKi}QSvqVPuN|3n z1Y_lXFhp1R;0@td1>0i>torg$UFka&?l@Q5F{_pO*a$9b#UAv;_wR(C3_(x9{RXhp z2N;eba==_wrI5LoR3#;SDh7YLeTLshLimpaXXVmeeMByi??hNNLRi}|)x?)Xt9=8! z>gJEm19AyryEyQ#zf>CfyyFv( z<2vsrSoWyeSd|$T$tasGvAasyu+X(c&Y@NAiisC=(`#$B6MOnJMY~j_Y@>4+k1Aa~ ze{roXIRm~yuR^o4m(Qe{DX`EhoBMpD2!U3~)5OB7HN z7=s3@{1XstjL5}b8v8koVwzb;IPIpP;}{|X5n>>Hwi6C;=1#!2k~z|DMlQxv}P=TU$JPN z4+qE0xHctr(`~Wv4zk2aZ)27{4#t19U%fsv7xQwI3yId#w~*(X$h0Wek^t0VjN>eB zFHeTRjt}x0t3lbQ=C}B=CwNtF{^n@Nas)9cspvKR(TaBu9z4+1)n$tg3=E9iiv9xD z&A;9($Xv245XPj%iI2g`@*k{cE)w)-slj0O%5svBZTZTGjIMgTr1n$ifP;C9}=-*69x9-nDT z;Q~{usj1;1Z=hFK=q#}GH5$0iekh;P*j8WhE}qrUJOPV~pND~SLen(z4^17L=-N~@#u2N!ZuRZ;ACiL$%pMw+avl|r^6{h1_sQdj5F}=$EuqUA!s?JMaUq5xVj=3D6Iys3-zf3LixvtG_ z!LdeB2)#@LS8q{6${b^Xd^%cy%$=Ik&8deCVJpM!D8zYdXW)UtR5Z9(q3JEMd| zt6A;s&K0H~rezsSv5(6}Xy?w-NGo2{$K-rr&+qwr$eCb|VV@&T)Js*(9?m!l1rNX(|}k0|Shq1qBz&N3f|3@Rll*_HDclJiNQD$uTZ@ zD~SP*8e?=h&3YqP;8?Tn7uwvuFJBwp`f%wuUw9yt&%NBmb;lWJhRbtVj#l_Gm#N)e zs(VGqyZl&4r~=8Tu~;}o>7!u!Dgdx&AgUDGEm#%aSEl51Y#$mb7t(eZ4-%y_k*95N z+#Y!=iKH7@8Y**nv{QBUn%JRLX zEVQf3J!!>;(}cWI*2H}d`zwYsw2_p&FC}(Xx6m`$`fe!|%HLLmE4cZb2;flPS*T`5 z>KYqQO_6P^PQeRRn`hWJBa*ZH5U2O7OaoZL(@skXG@gURz7cH3m!L9J9Vl zm7O#uU3aoH>}4Fe_6OuINvQf8?M-yTcslS;gXn(dIQ^x&L({w^bX}WSUJTRNqRQS} zilpS?zMZ4=Sgf{cB%rL!wfS53Mp(P=vBKpz9fVsdAiGQ>3LfRyKqzK|u@r)J-G=?0gZ~$)^6m03SwK|JXva^|0C58l_e-uy_p&-I@PtHN0=jWvB@8i* zZ%&a|qWylthfscp%k)dvJkgP=7xveDw#{A(2#^9S!5e)Emtpi7`A_UYPnFl`O_s&9OcvVE#Oz1Vy8q`CUi_Dte$AS|=D}ZUzWfuktXK1AktO<7>V1E+=1Y3F#w5?YG+CQL z2k3_AzA!DX13kj8ZSp)m0-%w8xIIN|*;`<&s9x?mRI+cCPpbVgTQS9ccl{b*121pr zjc)%5Wh%AL;@v%aCP$GO$?RkH$@VW;b7Dcj%|q^kHsG-dJ#rqft!K@4P!%tgmD|s8 z3RYm|&_%*n^l=Uc;_m4N%nS4a?*4)3 zV+*}B)dx_7@X`3dXaXTs=8amS6p*q?wFSH(eb8+XD|8kJ%^MGrZ z$9pV`*9GYR?=g}$fL(oALk~^EcQ+z0QS4s*5%IpiVZ4whZGS2D0re-P)YdfBSOaDp z$YcCN;2Ou>YHk5+E7eiSXtp1S#8Lp_71=GYyE-r9Gna@38Lv&&DGik$6}-DD=;?VC zs(-wwV8pteWtBT{+|9j z?^PP<=3vu-x0Ih`jD4ibj}FgWu*L1ZlQM$HG75j7KZ$8@d<_oxujrA)zK17$KPp?RV?GkSiYOl6gL`-+7|?bF$T_058>7jqH;Ku*-wkX!OoVPx zxfWC33P+yWb#_V}vJ>zq#S%b0GK*8Dw#dhdjH zUikLejxyNRGHIFb;$9bs_ckf&lKEDe)GEYbKWnoet{Y&MWZIme;l@F6 z)f%Ies<2f0uz$2*F;-+}reaUfS7BeHCI9a2JtcLS)JUfPB zxAn4LeDzpJ*Om|hSS`5AS^>FfW_4Z0+IRrxbQK0h=`arx0_*T`RQ)S*{|J`V99YcP z1mWTtbvFPc=NB&(pJoL~FKpy>yx8L>2>^X8m9W-Z?P&wE*t@(nTj(Y7xpa7#F&=sm z|GthJUs&n>Y+BDcs8aYDd#@m>;W=zV5<=-atq)K0AK2WJ6uNx5+E~Ex_H7iy%tM2P z-i_fD2K&BoHs_8{A#BnQ-Z!k)=!2~C0p~chT)x}qZA}A_dnyLo9g9`v%?m<50YwO- z+UIDrT(v(Rlw3$8CW4nMifO(4zz*v53iw*+J>>|OtFn|dbl>G763z(FtE8^h%w-*Q z1=~M-pCFT=Zkr;bRcfZOyHviUnSJ}+tkcGIaq^@EW*yh{CjKnh$h^sg{I~TVvV)MR z@?%svzQe;p>f1er_#uDNo#qYde_MOle7 z?*6=E-NYWBxX_~i2jdr6*kWF-F=r_k9E<4tJi@CnKpk+LRnDFyyFU~1amrcg7QvpC zeblpjxR-`tfUTD89X2i&$*HYwSqMAC=~b3*Ebstw*on$%zP7|GcA?V@qVeF~>?n0W zdvY$_6}IQ==yfSxto$YNyf|B96_f;_^VR^1G6HA(Wj1uxMvKVg5`2GlNubw;OPCK^ zG;)5xIbX_`!@AF-%iTHSC%|%mk@$JeKe;`}UA%aMQ(P-GOZR)S=z1ivdXceMYL(@rmX9cL^g_*P9p&&KF8y3{9_y~&5{Jg#i!yEo9jcL zB{Ci!t*1CZnwt!QdzkCNia!hx`ZC@0^w&EO>=)LENXhs@8h=)grr$Tcy z*JzcwGmnfYi^IHR@E~%-ZukEcmh^L4Oc#Y1b}hoccz^loGX#%s!+{WOQ@#Hk@%@A{ zY{a4!_XcQ^o>A{K(Bs;0L)=dtdCDI{xqrZ#$!uE@XQN8wEI^3UHV_EzAztF|i1R0= zlD^H&Li_w>LES%w{f>C_b1oA#&p;p-_6bG)zHtD}fro2Vn2YtTn(%&j0=PB6rFf1U zFY>otdfy@<_c&$FgQ-kV>8PEIthMvn$}A4#?A31vPl-OiK1Lg^M6s{LVBEv8swacj zjQ73BC1FPyT-he2J5QBdp34An-1*~0{CywK;Mx?4+9*?O=7*`~Qq?-BQ%k~ZZY@rc zUJ?;L@I}C{{F*dyfM0M6AXd7#w%l$OcJB3<#H+4WF_80OPzw&@%}tbmttITo&q`EaRfgx zw8{8;wXJa%{m(Wb6HP7!aK|V%QfS@9Kykwr=YAfSCpgiWxLRs&k!CVW zZ*0~ivL9Qb0FQ@|<>&kO(X^9c)9<03{bI@YA%2lkpKVPJi8H^J`5UAJpgYc8W{YlV zsP1`b!V`<*le_?>zcr#RBa^8Km@_3RQhRt)jLGc84jOGm!oW#iI^Uw-^y>7CT`^|Q zlw&Vd@nMI_d6_}FnS}&w$X$!W^|tdHtJDHIDfELKev~a#WYcrYNp*7kCq8bv$u&#>$(zEBEN12 ziGImy3pL2v~n!+qlnjI-g%A~8i4wqqVcQs z!VlqFgs+a%JfG<=H2gT(@Nmnt|K-f2G`ZOkP~1p3T_ofeW$(%|WL8Ho9JouD7Tx%5 zWj|}zU*+O)fLT@qWrJv9A~VJqbO}#gX$Ve+7Rf=*U6Ow1QzS&OvRM4i^jIB3B!Fb# z+NvadySZ2@gHgK~k89+(Yh&mmmq={JvG+-N3g8rpLa*CBJWDh+_SK^jn*GGR1dNZr z8u#XeJT&i4noqIJ27;ItUAN7iCl{XPNTtZ|jHgYd$-CO8q9=Gjb>vA@ zEdr2>t7tl`{5!QMrNM>|LinB0@RtL&fwBWT_4fm>jXSUc#?Kbi4P%Vk>8hUF4sKop zZR){J|BqW@y*ckv7_xzqX%pPa!AGS_M@BIl+Y^Nhy7mQWKiYr6Y-R`qbUBue;&n+$ z&n8VC)XrrsCBW(W#L}knty1ejV){{|Ob_|{iEn_D#lkX1tesa$e=(^Lx$1YP# z_2u==3_RVcs0pG*5N8!>Xc7DsOn#9Ozp88yiJ4YI(ZM0CdmDY#a)-KLIkeJyd!mY> z^$`=>4_fz@c+AzL!>u%XBi64b146YWeW^@RVcuVND0lC0kB9yaEnF;s3bC{ZPE2!f ztedNM>HGJ*Zimh)Wh5rx>?Y_Ri#*oh>IVDUJo)cd`{>F`@_kvlSH>Rd>oK*l;NUc7 zZ5sFBuN@0EaJYFLt-C!%cGoRS$Vr(EVr6s#q)pz-s*{+s(-{D< zj%ubFDU(#p{Smdy$o<4@aBkf6)t% zfiggz9M1!PDd|tiz=g<;`0sX+4LcLu>hnOPFuxzxWUj2u}+S>3! zG@kH9vd;nEz61GVq6o!ID}1W_sMMstAaQStC)SLN!X;cKikB-DNE|mSyyFE4KiN+) zJu=pu=RXpU+XY@HAEK{MAzO3ql5JIr2RjyyZ7m=zFGxH^_-DWsc)vh(z*jPp4 zgAyPi93(WYr($LXUABN?N5Z}}5bgfIU`H%^vWY)3Jp6m|%H3ZtA(dKM0 z;iGOz`B%@)f3~kDxXVj)ZbWn2hHOyKW+o-Q2a-cml}L`22M@whgC#`XNFVah?JN$y z)PKl$-}D2kyZm)R~jya-f7q?botPT%e@G%PO5NO|)=vlobe$TGSFn;EnxUw;!y zC4*t9B%~RQ66W)56`pG3=@z`{BQw{zl0#iy5-3=#c(hjboRS=s?84j-QJDw&C0bNJ zo7H!pzkL0_U-Oa`{=^c1ji0W8@cNDR`WdbO2>UhuX)di)~2M5eaN{E?QI1)t$zJPfbMC^vE)A?{yK;NpKIa=eB6>R)KK z1+@UG$6on6SqX5{??3)0=>LEB$G^zopVTZ!#LWbWxKbYc?EvTg#$KMl=_fqzN$#IN zlq3C}Jv@OXe@*#c2#F%+yAkyEPYu6z+zM6bB%$qp066ewly`Fnf2q?ju7+Vn+K^lB zAB*5!ug`^`kEzhdyx*XTyz1LH@aG-Vg|6q+l z835*uy!}mqgcgUt+=%Y>`sQu8!>gY(`(K%j+vshJf>0nDsP^l4{>MW+fD}LsHhAmr zto5IhNn&7rj9n%8ja>U>oq7&+z&(q!Zxjh)S5E=ULi|qHw_gLK*wsBeubw{D`-?>` z+!grmk@c+Vd1jJXu(fwMr|;B96{ec@td5M}b<_a@z78MMWDvF!TJr>;Qsl5M8@= z0EzgM?*FmSclXs#o#t^_{u&m|Q?8NPdZ3*4h}&x1BE3vDwxx-;?W0th_UDKh3y##V*uy>?DY`s>#wTwH}#(vejPE}H$Jd`_9EVN-P{*9q3qq5*t(1WA1h zjRd(p^C>a|N?Ej=!ih9st6u#pz{N$MOPpA#j)f|JQtHYqtqLG?9q}TCvX~7zhtKY9 zQ9ZQOGkWnt?_jrKe_uP8gb9WQiI5Mf)1tREq%)D*e?YMT zAa@M^QM#kUBZr#~a~XUpX?)b*Ie<)FCfZJJbay7@>PcC0LqTQbzk-Ai86!~?x6R8) zX=d%HK+?BJG3$pGWvh$DvB2uI#v?#^VamEC(4Zet=^!cL0$kQp#y6S`u=vUPX6iN+(BHj*+xTs-pm977oaF!^VE0P<#?o zLlb#kdezKt?l@z_q6yTl*yB4Zc!btoMYbgaG2~Qk!lm##H*dbl3yFv@&h4Ffy1O-Ww?VI%96)a% zRyT9loNs&e%5tt#tG?#*La*&YM*ilwNp?$OKt+Y7jBI{3wbDGTj-SG@yZ^hIX_k52 za+^@bmt-NClg+f-#%{~F!W*W<1(;nRxD6%^2Zt_GE`F3}=_01TER0>RLTZNvFtw?{ zB>K#%8QkK`(16;o7E*pVxJ<#uy~`457lx+0*}`BPkx;iW*ApKPm6s3uXfo*XFsMO2 zk0Df`CYW}rY*2=;8+;n6|+)?iMz@;?REZ2XN#HnceC3(|Is{-qp|g1q_}u(eV1Nvm(GxI-TP}}yh*3nBtT-} z`H_Sk|L7R~3awXlbL3t)uGYQ*p#k-*Hh61b%YO6yg$t5^qNg)bPT$TibE!r$yDjMl z@P+3uV}M5HDZPlWmh*T}Zw`rYvWTBHGUi%=^G?gLil~S!4fMv!_ROy-`%_U#6|0p$ zVrODXd*b2S(OoN?@-fZB*-fO&?B$$AAWGO^*cW6ry$LLHF=N00yyX2%w;R9 z3`B%(fc_S3Y_35A%twPQ+gP!;$}iKi;msvd&Bg>EZc$u~0AeQ~w=x;8iMu1+OK*o@ zwt-O{b9#A{WwxC%8=7}?pptbD&uX2$LV^12(t1zyH8K<+-xX)CtxH%HI7WX1-HD8I&S&L{SEIOMMxE^s#}+%bWdug|`h#Wg`33+miOekTk)?Z_Ct zZs9VDb{3@=ce`XHX=v;t*xe3$>g#WCNQ;jF&F?IH8)`BsWq5OE*s~N=Y@Z4(zjN5l z%U6cY?XK_4wWmg=KZIJ+(!yWAjn&5h8YAFcp;V|63ybOM(34vGtt#?j<5#-hqi{^Y zU8mGkrZTk}nCZqWK1XEqYcG`P!0h(jk~`8?`|9am{DN=79q|6eYI@pHisBPJ2K@Kj zqX_cRNAMZn+Y(_+Jl)?9G@o~1S3#r$)o(i{;$gsCzXP7x6Fy1NW09}_{_0`8Zf|s* zo2S}W{lt9$_=JCGGZp^>UcMRA?kr_~gD9cM#IoxspvHCsCLKz`a6t+5pK%{R*e{Dh z=`0Nb*fSz$2s#go8JcnbM+7Bf#2tHCb<-;?*e@wk)d%*wPKJVTO!_~} zLB_a4Oc8#ComUe`L5_j&e?Y-wBrXQi{tx${5Rtt6@ zqgTeiqt}xm!vGr#U;4qqR~p__KoS5g#ZN47{P=f%MN+Cqql#}ya5wHH#OJFY9Hy9gM1D$Wk6ZS?0`PaE2V83xA+D1D zgO>+IMdf%sD`cCI_`hiQAGHBV2x}8$IA7^^waniNqTikXE;|2goBsmt_tk$*@!!?s z?{EJXJAPa;{eSkW-XUn`^%>HKyKbTV3s)iE6XP=}kASTGFN*)zWA7No;C!)21s$Mc zYmVbf7}xt$CrfROSFfpCY*J@)v_#t zy(jZ$-{JRl{AgwbJL^JyGle`Qc+Wi*V=DT2%;Zu!EDxYlJ0EYIzu|uA= z)MmKO+QfEGF}eS$Ebl|J-s{FATZ80-c?;yk_mnjA=4%j8`s-}x7!;FDL+=H=_F6Ek zOINc@w;baDNGL^()`jb{N=DJauFLmi!+P&~%AeL}J5oCEzJLAeEcsR;La&?EmIYl_ zFMg$jnVjgF=i<7>9;?Wlo;b0gq1hiEXp)avY^-f8bKL&m_+TJfD<5jB5mls(@0ev| zJ2R2$&Hhl4ncFW*-o&p8MKepon zKZMe4KO7Vhwg5uxZw~`mR?zJ>0NGN@+>R`--hBh4Se;oz^-W8l@(HGuw43h^StVw) zJ33I-cFNcSTFDe?4RG5vh{W5kAzfq3!R@GXZ4y>wOD9mn3 zqF`G6NMN67Be?fq$NcE^3jfDB;?4I5=1b$L;EYa}N(_GXlGZru7w27&IYvHP>2s`7 z&k*CcHR!`TO^C->UxE;EDa~t`o2|?VS<3!mkyW**_@DNmg4F_kT+eTBfQ;<;EZ8nwkH|dUq(O=RX>#Fy35o!f5!gAD3v^Dgl z?H+$EIt-BH_vUy?YoGtPE>58|HyA!=rLOH@e~?P-*36_*-Ys01^;8^QL2<<51+N>^9(Gbn#*2Qsvj835yyGj^vv2~{$H zbYvAl%V=6#rv${os53xyWW3P(?9;V}=9f#8lxN!=BEmTz*i6<-D-}NdYL`b+iLsiv zcaiMAwk6GpV;=LE;$<%sRQ8*L!t7|##zm9KGg0&NbK-Zw)on@}d+hF7DUM;M5L~O& zZf?JFAn~rG1}Cr6*K9A{$ieMp?4e+ISl0Zty@7q7fIGO%xyeJE`RoS5-@~FvzfPo4 zMLoF7x?t5AnzrW}_0`omcRIeP(9X%KU1j6d1M51KzP*K}hcdA!8o}y2sNwD}4=0Mz6XJ~|gdY4yGM2;AWlzZTx$ou*!LX zo%teZHhRVpW~?et8JS6`-GR*e^Xg)xSfuP5#fzA!@rrc{iBNj-{P+ob0;;q}_b0(E za>ZF57Uq|IZ>bb1wkE+>Gt@L{NQVUeX#2zGoad7GIa@r)#SnNQL>os{87wD7PNWN*IWMGz<(aFw!913?hn>A}}c3HFOF?C<@4obV&>#-Q6|c88EQ!{r&TO zYkh0oASztm7$F^N!qWJ0h&&H7%YU2$t=1iUC#) zDra!rlx=>Ixma}EOm836V|wZ%@W#t7BebtdtOf#%+LVfW-{_H@`RxB7T&>h$N`Umh zpz|Wj=72$nvf3n5k>G=6vAZgLrIxBO2HH(jG>vnrxF)4i=jy%`=|&ZBG_r$^BK0iQAOhP;M)wk~|eRql ze#3dbmMZ6sCu>@6rf`p%x$1|>PuWZ3y{uPVh65cKmzOpR&MTrDEe68UCf5v$`+MsW z>dhNjA!5Yx`eN;8O$yqTxpcbn0@a@NSPP8amZ?UY?-feCExLMPA!{vnCSN16p2`$u z?{$&!Hk(7z)Vnj5pWo@j54g=u?cpV-=DeBWj}u3$voKy%=Y6>HDTGnQ95m@*FPKP| z-4GC`x{u81w6d656X|xIvPlC~dY^V*QoO3kTnI8d<+HM013FGe$E;c}4Qh$#BbPMp z2+Rk0_{AdK_fLu0^<-hX15Eak85(q;c;lLdu1vtiNizOMeDEZeL-4n4@o&svXcCs! z#RVkT%b2|WVLO^=yY+UV$6{WtwabxOuz-H8wL$_exk{ffU9ZLstx!j+G!?P5G;zf` zl4|D{V?D;+j};3q=DylqM@|214+Tr}st8aB|Cy74qMD=W8a1z;k-N&kO1_51N#&Z= z;vN`V>c)pfovO69reFbzk7L-pzz+-Sfe%Eurf(bEC^E8ItZx7jVM#jF*>I2&t?p}^ zv3}r8^>99X$y&yxo3N_f-rsthe>0AeOwMXG8S=!`>(y*@iHo8;VOBOB_q6XDD zvHVQN;|qs*he9+h;%#*!)5^Dlie&0%wznT_gX^13+k5p=MS5VC%H<)sdVsS4$X zQpQ)W?SbP?pJ4I@usde?HN$b?Jv zo&=g!bcm>_!nyAQO|U?xv0{KtBgwHPs<=-Q1_~#a{GLkSIL;R>M|trtWftl|GU|#f zu7TKo+=Z?E>WP@5C534ve=nP(G!h(b)+17vh5P+cSe0t@0hd)1=V59Pp`u#M!prW; zGTa3%1Li`6B~fpjxz}3)P>7cxh8bWMBJPr+V`H_=uF#V>6i!#2;gp0*sEG9Qzl}5Q zxI=7)n;18tIdV_PoLR|%{K+~*UkrLHCeJNg|9+_F-2;QR1O<)Q8x}qTb|Hh(STWopzgf@=!sVlW-nTm|aNE64^=F zZ-+Ay6y@;Sqz(xPmmG1}RQN!d;x~yq(<1*limaU(6G0re=7V$bD~TKLr%J2?_)a^) zr)ex#*tdll`{EFKeX=HxP7EXoIx{AOtU#w8E+g|6hPu?}G2$UQl4bYLY{cgxoXS`! zCMmD^mmG0U6-3bmhAn&Bv58LE7}2w6@;>|=5pe68!Si)S>P(paefyJ)L=Afw5V!Z4 zs}_|ug%;YvZGGKdvt6C;QJrE9TEo>j#ekq@v9OH&{OS=TS-&@{qvYygLhG7(!4vj}AYh%*ste3+QIB?c>2XI#%X(4ZGD zIz~44(SUzKBfy4Tl@jZCY%spQg9LQ70tZC@OoyOjW$aBtmFV7V*)Rd-PZ=voF=Syb z{O|NIUZje|~_uLS0r*J@dtd=cr(AzIj@FFz07qe0tZ=nTQv z`@8lU%SHBb zz61*?^c3UC_CK%^Uzx1G(j2=epou1ql|NG^-}tgPR&(EfG?llj>(!s{Iemh}EH1V0 z!%sNz`&q=ZZ`BltY=R@COvulS-SQ$k3MPCO{#Dq{6cESm``N#b{h8$x=30MOeXtUzd2il8H%3+o5iBzIj@OSyox9^z z{aTM;MwMCX`0!yxz?(MA;8HJHxVr&uydRukOz~}7G+q_~Zf64o&(z^ehIV#Vx}FXQ z{cV)~AFn2}BQQRDuCTle{<){qIQ)1<1k3qt239S)Z2UNf5k2i55{I4+z{hH;HCv!7!gb`F)s8f&^T?zG^BcM^FN) zP?+0VhPYne>hV8(a-N4^s;ii};Td#mVqK6Ti#0-pS?e7c^D{r3-jmC$r$qEXcINHR z=5hTffqkj=P0apuDjv8=#%oSVT)fZ92hL5~o{{#6vS!ZN#9MReGABb9;tnlg81Lz* zA5qXhyYPrubjd>z++9t?BQ zPOX9P!_&;PpcUlLbbH~=qpqO%Hs+}ZC_qWld;WBrFQP~V0)T@@Z;9ysot7qsIFTIT zJfTJ^(6PXx3^4?jCL*)nfj^ zXzwZ?c+ynq6HBMGvb-EUEcz(!ZTQiP?mvzi$dG(1Gif&QR(EF4-uzfU$97Yk>o5zB zmKMQ*HcNc``iFx90FJzL|Nd{X$pMot9zF|$$thsS@`H^kIF&hJ)!VEz@g+@!Ke&BR z2Lo`-cJmMXQr@(j{%(%Xlm%;a5|K)TP360M+;(m6K9A0buZw@va&Xo25F6DS)837| zk_U~Po55Ue20bB1tH-OHWA)@In&4zj7wq?TVvAmJbz0r+7Ik+B%QVP$MOj}q@iq|j z8q4jAz4-=|URW5W+3ZQl?3OKZ z>KG9d^T8k$Cib>c!$pluWv{-M?wN0Zi6gA}9FJoEI8zO+=}ZY2m% z>y=wR9S|Gho_BJ|SoUhut^LG1A2wX|dX>+E@oXCH>+!2E0%5LO#WB=rsXTI*r=QQ+ z1P5}JW^!6bsmO;2W~R={Ou?Qgm5VJ;?J;U#d-iS5%Lc_nsnaG&PZZbcID%IoN2m19lJ10R&!dqtAjG@6;2a!y2Y;X z{)HkUM14Wg+den4==Ubl)u7^Gw2tLal`_;^cQ|IQM3vN$#!Wdd%u4x*=_C4uH1PwG?W_%?gnqISWTypBp(kbAzECn**`7>uo&=`0> z_Rf{BOGyhOy!33xRD3R%)6WIqQv}oK0N!{Xb%#af)7(>rZQ^YwQ=D4T)fs~Fn&*fp z)cABiR&)>hv(91n@ZdLaAdVHB^xJp=>83qBrH;FVI|FD<*0VS*L`xU@=rC7p+dSx5 zG91nqZr>!i%tz(4jPkRd?p`h&-pc6k31U<2KF2lTIaH&Xn5Z{2Fu*sOBprE!n0%wY zU2AJ&eSYp~(v#`{iX;=jiNtE+o;^?hq9uZ#j`A`FvXyJnQJ>5Jwf6@26T{%L^% zW6@+jj<3CPeaX3@#4$2KQZ-2dA~%OzpX!u>t}5k~Dk5zvDkQ@7K!vw?C|4qqXwg_5 z6#;}2uS99my7DJC4I^bt0s;fAma(wfQfAF$8pTG-bA`IHljP09xrwShV|yf|^8N8= zv@_zRfA*)QNTry|ci!B#Zltl-^+R9E(d)@`SezQfL_#n|ZPeFg7RgG>%gt*-3;58X zRC8JQ?kjI#{`niHi^j#s4EU^gVu94bmyAU`+yjl?5)}vx3oEvt=MV7-4_fXit6u!% zA_vKnego{saIoL6tj{=hPUFFY)-Ya-dL0K;0Y!A}*w@2l`Hp$`?s?$A&ipV21qN(k zXs%t>dZe!cgc?&v(^}%#lbU*mLi4&}&g@tVM{j1T#Om!6ZU_s?db}kY*2zD$GFs=dREC?Sq0)4k zl{V}zE`C?yKqAw5FN{Z8;1(+k)c7b?`C@G*Rz`HClUk8MZ5r$6^wtm1bRi-l3U#VH zx*t=p;D^`y$3uQRV!?+B9vaO3Vbww_;9~nSw&4CCIy^bqfO(aN>fWn|kI}q-0!tQo zRLG#g!Lrfz`Qo?~P^}-1RxylsVB}xt%!9gYWyZwtW(5Kxkud2TN*dNIUilIr@MUgL z8tJVhJL|Ez?zcRt%46Oq+5H~DT8`Du0X}zHO+f={{$c$^9{#|-(GnSX*eQSI$LaVe zy_T*nnr|vFpstx_$lYDKnTZyeH3A|=FC(8@u*90s9&;Dq(AV98^>dWu+Ko0wf{YNK z3r6OdQ-QM4GCCL&as+$DVRXtvdgJBkKe_KV(6L~;bP(YY0pB_~eXG2I?RdUXJEAer z?d8DMxMh0Mt7Z4Wg8aXPgcBx;H;{0#G1obp9@d(ow?nF`YY+-$3psMly?bea>>MBM zz~(;@^MG4P3~aLK(=W=m!u(X)v=^#PO=deeZU=#EKM;^NpWe333f}2G1gtOlgg2qB zx}S-KoT@ipcr53hkfZO&iGIs`{70NfuM?I-5bk#3{>vL!viM{oDP3j^Us-DVv$>|K zswr4|6TzDQVOHMuJGK78JSrkT7I??^H;{WN!`SWwbDhqH4NRuPgJIHpM?{%32h=jr zyeAHdBJbY$Io#2gOWjUQ>A8FwK98=5*kA9bfRFIMTl~(X6(%)SEe!gjyF-=OmpENr z;`VqEL)?!_vivxV!~sWfBZ#{lsKuFDk-?VkVvDRq!UZ}qWwlINt2dKOzdT8f`8p+_ zdwRSR$-l=s<=UMA{UJ(MSnPbggcQ2ZNK1Ghs4n>G<}QuS_?c#f36Mn5B!f_#4MMRi zJwxY5uqsvGLBwxgUHu2Q_VYLV!5?ie!S{*rQltW;Em&=!V)w%t z@4Q3Jl_V1!X=#DQBHI!uIFIc`i}jCg)1{ej&icWf`hB>&j3fhRiCcwg)MM=0_9mP* zQ~4FIR9Fyly)`M=RFacx1l_YrNLRV_YUjiPOnq11k#Zq}xUp8#CCllyTq7Ne6EQLE z2Tu6pRxZxGCq>F`%tzuBFf5Q%Miv%JGub8!cI99SUposm|8c|7?hCPKj@Kh4o{e^! zAyjv4rnnd~w6IK52Uq^ITj0-q0!iXEkPi-SB1QND)Vf3p0J2x6W%=~aqB=ECPcGk1p z&TEs*I8KAcV#&~^V0PyZ&@vDV01UWtHHXhu&}gI{qs=s4G>!;&wkca!_+*N85LoHu z%6l+3Kr~hb8Y&G846v)_Cm8m8MKm{1HsMo+iCy;PhVlpp>qYlk3s#tVSTp<8t2tr; z7qKW?O_WU%EO(mD3R<^(l&|zP|X{xm*J;#V8Ae zV!oJ|Sm8orAAlu-%U$zLO;dg%aO-ic`Fc+SMV8X_3#>pj_sBGw_xzb4NTc zY2)uR+J!1yt{3r`rW)!pmfY3?+r-*t$PewT z??kEFW4R#r?};!4bv4g*ApjxsPQ??a)81#UN`V%C zb%N{n*l+_3Lb9FLBdoe-Z!?X5-hfR~a`P5QI+7*zaX-UV_WH&cjI|%t5a3_Zk6rNQ zKR{wBkhq_TSkQb2MEq<#QDV@nupF}<3MM8dJg{qQz+=W#KWS(|*IZObfruB^IleGi z6LisdIzvn4SOdGd10Qg60uNuB9a$5D($mwA9YR@+S}c_zi;FifDFc@8Nx1d%&9xhi zGD?Pw`x#)0>(@11HuKU^Q$?lD>wFjhnqJif#ur-+<^gMOS<*pS!EtMeCxZZcvEI+FbeZ^x@|7uy+hQ0s5E0%e6Kq7w}?Y??|R{!30(ZjK-L82vuqe z@~KkPCn#>xOsF?56#(vwo9onVU4^z)iK(ASE)Q$8_EjLYIVO2p0<9NYl=yF1PI07Z zlz@K4TPkzOHldoEV@igDYm4LIG&Bw)(E;(P@(yW~iK+dH??#i&Pg2I~v3{bNNR?$W zk^>F%<~l35k$Un}JVIMr9l!}OR2ISci}wfD&OG0~*=w-XMIEI3?)3%+Nv2wi-U1_G zkh%_HQ=dF!xK>YKYlWq=fQFji#^S*P#C+L))6RUINWsUDjP4SvQE6~f;AI+*a74G1 zg9xOVH)g-6FD^bdUk8ujq83<7@jZu{{5;$6D1}NJi(0O(u6FKyq_;e=s#emMAZbR! zQxSO0er}+vs|z%WR3Mo&gRh5*Ujx1mvC@TsFl;uO8ZoIpmj<{yco3K(y^4+IpP*9uHT$#T-q`?^{n}Gv@DGp+-SX0Qg5l& zmOU|jtRYc#9zg@yyvi0YJ)Yll=3#VB@#GxS>tqA?E9=l@+6i4i2sqKg!~lTGKv0%eKP7U&T~jI?|+cBu4LO?2muAICwn%> zuh`E$4`pVCCGQK06jEU0Hf&y48=p3=kGh{IrI+FZ3NVsQI>VZqIfuF0Dt*doFjO&I z@^$TTV)+iEdl@95!%2_L*^pTZ5C}M@Ol)j5t4fMxxXo#NC&uAJIXOApX5FcfrLh*H zsjk8-+o%o`(6}=>HT7idNJrjy`4a}0*X{w?MOLF%bSjVNRmnTE1!d38&4s~01%oGY zsy(e4ZK;y?T0JORFr*~OGG}5SPM0j~sr4A;dd(wL25YTB$8|P(Sj#iB6q-m%?r3XB ztZm|_b@+R|BgVm&{e^prm9&H>{j>4Zh7#r~WBD^YT=p6?>>+Sm-inXuBTWygJr{>Y z>x6Qb$wOD$Ry>v_R&y*hF)-k_6}#k!!Az>bRUs^h`Eg;y4FQG8sYASUismDrU}=55 z2S-iS5Bv@(jo?PhD03jVmkxEmmu`nA}kseev#Nmiq8R8hi ze%W{v*X!1&F}!&R{-Kbm-O<5Y==%MLWqVhZz8wa{5WZhz;6FfyeLJ9qfBQWCyx64!G3ipIsZDPjnG9dTuL0&;W|tcQZ&87S_!K#1va22ZTOP zsMDO^yi0_b4J4cQ+ISu>wwcd~(h~J(7<~PBY4MG>s;cn7uc+`Vc1=MAT5t5+kSO_3 znOd%XuFBZJC3>j!Pj?Zo!4q5rtXc7^L}PV}uOD|)!5=TnQcM$_CHNwK7g|}^Ew3c< zjraNneH9b$4?SiXN|#`H7N5}A?B+vq0B6{%JFJ1&DoJVDdn*a=c-wl6z_sL%5K9T_ z{KQV^{tC%k)L*SKYtt`tNHTp6y4t!poxL!vh&^|@+_(D$-~~0x>%^zo5AyH$kDQ$g zfVI`T6j}%r{rnjjxNL~vG+G)SUDz{1f~UCXH&Pd-+%k2xL)lk!u1Ep4@)TA%;&Q#? z&hr(IE>QmcdDw>A;>afvv!ju+lQk?9n~t!Jx5v&bJjJtcCtM&7wl_Xs-hDYZJmaQ_ z(;%|bI_BHsask{+irpiFy48=_Y*T%#dOJ6#Jx8vtEi=E}yLX%K`{R2qeH!UO5|ADd z_5oWdu>u{%65X%DbBW(>DJ*X<#lkc8T)#wc4R{ZZFM9E|6z=DC{KwAqCzas6_g2Z_ znYY__$A2Bj^>M|Kb?B^ioLjYn`yw)Xd>>)G29j{Vm@Bjvdxxf!osOB1{Ez|;m4>05d) zZFSOn^*z6}$qPgRt^9-+Jd{0L;d8Y79YaG9Kzd#|=;ZOIJeek;oH8zp)mJgJKs#Qg zaL_!SB>gwb`!-~$5?<$S-n0&HK|0$W#j8DKQ;4!!)mD^sXEd++lBP9wqI9#9(=auE z^-pkdZYs&;p13%3$%^=}fnuqgQ2H6m!ez2YYk<=JS0fu!*`UB_6wRpw`A#vP-Rdjw z1fKi`TN$5yYt|~J|3Y3LHOOt^6sK=RF%2QnhgIp)ebRE9r(TBt#sr1xLbO0V#U zXQ(7k&a}FdGZDciZ5gPxkk+p_%EFC`chENuo|@K#d)Zd0pG-iCYDQR<%*P&n05urJ z5R2MrvOBCyQvBU!b`DZzQ9^lR9JO;848S7v@nAb{`Q0D~J3HAI+Tv@tcetuY6D7Qv zbR~4zK8B&ZdMbu9>agZz;=P!${gL7+|@$QG+?b>htYB5}M)H z$89UPaRyT-f=~*+YrSi#o!q8;Iku&?q9T(k;e-@?m`?V{m#;&XJfH>kU^cW}RL4PI zgg@^Ghw|dIqU!-jn~f$ktSQ%CMI4=SyhlN(^qS7riza>s08|<1^|(K(z^fH$YBZV) zq4{gFM2Yvqiz;0#ql~H&D<$>ATSjmXur|h9+2u#WO|RR!F6YrQ{V!8p6dsHz@G{#1 zrj>P#rVi?)KUC0Pu=;R`Hczgs|D_wTr(^i?$E4Xmk7D-%gP6*F3{1OwP`pmO-=lS)YDY0M@vQoC5z+V+CLr30CFjRjTMAX z&usgKXu;&DJa!Y;kSP$@jt)5^_ZQ@!Kl~rA`j&wF4WnC+Z;ZNNWQIQlNfiJSXElU#9J-=m?enbHlg`G)lm2kUALB-(=S4<3S-X&DtiZHpn&^9b> zJq2rEzR(xKijTUuJ$AoGtiwAID@jl4gGw+YGq0k4&im22gPM(9dGQK~e$X+{8!dQ- z!}a(0k|DEgYERb`$g&QL2dr206k9|q-UQuAG|I~eeh+)aH(Ry?$(YYP4l@U6y#MLp zs)pBI-Dd@3I{-nLEB*}7auO>PZ-0+KN=Z8ri1Yv9Lth>_3mR32zSjem{BivlZ2}_A zZO2t=68r{M2sNU|dRS^6mGBx@ru*t}b24s3jog`zeHKAkaCnRcZ-H>a;$LAAk=>IN zcn0{@=}~XhWh+!1Yo4%xLWJOpXD~l`6S93DuR-Hj_nz1yn)wy$koA(KY?XKlWpaDe z*d??-p%>e1L7SvKH8K9E&Wc(wBimQ|AD@-;s{}iGy~q5>am>v_sud_=xBoJvWYM;G zQpQ!=Qj1gxHanekk-@ESA`jXqDj)%|EfB6EzjqRiK2l!NeN!}Jb?|NW=RONqp6!-oDO+4lPXZQaV z^PN9i@;m%g!t75<<;k{`UF}ZuC?OS#1$6c<#QF!AjWpjoQEZJ(n12v-jVjA=5YsJ6 z)CjIHM0W3hQ3CXNP}J=Av4k7$oxsjaA&AKl4~{U4O(`L)U6g5C0Sis0OsJ7LwvBAd z{{-WN#&1z% z9sP$AOz#JfF5J=x$UbuSCoFx4Dk}Hgs?-}#rW!^k(viS(c z=}V=lqce@4E-Fi!EwHo=cu%Lc@|853^`4rhHZwmyFDoBHj)E&noUlWYKzG*gkBpnr@P@Qti<0|00MwPQ-7K9boz;BQ%l9G}8c9w4L z)zgtfAdLZw_bso=3T&qZ zQ!LtsYNm~QT;5oZF9oY}HMR3_m^}nt>YKSvGSteqr*`0fw^#8TzfEUWKi%~B)8p80 zbk1*?%-`iu3kN|4FbtpBr{Ol;Fwy8f78LR_ARv^px^%jfgx}VstJGOMSIJyZ&Euiq zZ^7sS)Q3T!!)B5%+j=~D~M(%~q=wzA5b|2%Ok~ zAN#*sG)1;JqQspccOZ!-Xbz(DTsa!?u(1U7R^|J5Z2L)5NAaDEx~w09>vW$x@>TYy z^5G%D{1Uq$6$m_q;Xbb#hF)=gr82w4(6c2neeh*UrC@t0YiGWi+~V;slPC4s+k17_ zv(Ng#O7=!HiQf&^;%|QP z7nPghpM?&)UXPwupsLFUYvepStlKEhWceG?XaA12` zww*8t$^1_O7aP1XCaG=bpCaf49cG88s*WgfvU4m9|4{R39l}c$@w$Z*+b`dop5Cz2 zLHg5IZ=$J=CZnD$!W91Y2`i@YSPIQf>#fE3(Y+Ec*Uj`VJL#x*S6DDp22Vx z>_A1^Ti1h07hx5q8bJvpGNdeZxim_o$Hy^AY8oToA{SmC_!MxNDL^j=f?QN(7t{Dm zKH8eZgXatL1NGM4p04E&R2T>nz^0D$DMYxu@7mR34810Nx%jA^l*mEUiOGt60ls!W zc9dTG`<8xMJhPJF2sLwB_V-Ej;hR-|m!goFT%$h)Yf=8^01?Gz#XbSoT_3gB`DCuA zZcALMvhzkGM+6Pu`t4S6CgmPueSv*xcO%Sbc^Th~a9kWA`~bw2-_()cB^9sPVZ3Ch zJjIt?X6MRuW}10>7;fi|a$KZs|D1HWi0E=dgeiUtb}20ArJz}BEAg7eR=&MvqU4ZdgZv@1e;q3Y;JcMk7v|`>A3*QAV*Ea@Z+1XpX zI{>yk5hxD{Cl97L$ONY7;{&G18Vw|vBIrF@$C!vO3Bxbn(4nLLNhX z`CTq9L+`IT)bBDzWUYvWOubg6xw!u6P?2?^_x{<2_jcrjkZ`YosHk_|{ektDxV)Ao z0=EA}m}lG3q*(f&J;nLTJnRHz<%5O@XE}&Z^^UCPB0CKHn2i3z$RSwp@(N)TSD(=R zJyKBrRBl9?O-2l@`X?R{SwW2T(FDds9zPjDueuk<$e6rh7(HXglHa^aU`*i>K_8a& z&jx>i|BrcJ1w8rCkN@|KxkrwFJVnC`cS20|l!&Z9ha9 Date: Mon, 1 Nov 2021 22:31:58 +0800 Subject: [PATCH 06/42] Update sort feature UML diagram --- docs/DeveloperGuide.md | 16 ++++----- docs/diagrams/SortSequenceDiagram.puml | 44 +++++++++++-------------- docs/images/SortSequenceDiagram.png | Bin 0 -> 32624 bytes 3 files changed, 25 insertions(+), 35 deletions(-) create mode 100644 docs/images/SortSequenceDiagram.png diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 74029145148..be9d0f8ed16 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -240,7 +240,7 @@ The following sequence diagram shows how the find operation works: The sort mechanism is facilitated by the built-in `Comparator` interface. The SortCommand constructor takes in a predicate enum instruction as a parameter depending on whether the user requested to sort by name or count. The items' respective fields are then compared with a `Comparator` so that the updated list displayed is sorted. -`Comparator` interface is implemented by e different classes: +`Comparator` interface is implemented by different classes below: * `ItemNameComparator` — allows sorting of items by name * `ItemCountComparator` — allows sorting of items by count @@ -248,10 +248,10 @@ items' respective fields are then compared with a `Comparator` so that the updat Given below is an example usage scenario and how the sort mechanism behaves at each step. Step 1. The user opens up BogoBogo and executes `sort n/` to sort items by name. The `LogicManager` then calls the -`AddressBookParser` to execute `SortCommandParser#parse()`. The `SortCommandParser` then creates a `SortCommand` with -the enum `SortOrder.BY_NAME` as a field in its constructor. Then, the `LogicManager` calls the `SortCommand#execute()` -which will call the `ModelManager#sortItems()` with the `ItemNameComparator` as its parameter. This will then update the -display list with items sorted by name according to the `ItemNameComparator#compare()` method. +`AddressBookParser` which create a `SortCommandParser` object. Then, `SortCommandParser#parse()` creates a `SortCommand` +object. Then the `LogicManager` calls the `SortCommand#execute()` which calls the `Model#SortItems()` and creates an +`ItemNameComparator` object which is passed as a parameter inside `Model#SortItems()`. The `Model#SortItems()` then +update the display list with items sorted by name according to the `ItemNameComparator#compare()` method.
:information_source: **Note:** If the user input does not input any field to sort by, SortCommandParser will throw a ParseException and a SortCommand will not be created. @@ -264,11 +264,7 @@ for sorting by count as well. The following sequence diagram shows how the sort operation works: -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) - -The following activity diagram summarizes what happens when a user executes a Sort command: - - +![SortSequenceDiagram](images/SortSequenceDiagram.png) #### Design considerations: diff --git a/docs/diagrams/SortSequenceDiagram.puml b/docs/diagrams/SortSequenceDiagram.puml index 6ff88e39a03..6468ec2f5e0 100644 --- a/docs/diagrams/SortSequenceDiagram.puml +++ b/docs/diagrams/SortSequenceDiagram.puml @@ -19,41 +19,35 @@ activate LogicManager LogicManager -> AddressBookParser : parseCommand(find n/chocolate) activate AddressBookParser -create FindCommandParser -AddressBookParser -> FindCommandParser -activate FindCommandParser +create SortCommandParser +AddressBookParser -> SortCommandParser +activate SortCommandParser -create FindCommand -FindCommandParser -> FindCommand : parse(n/chocolate) -activate FindCommand +create SortCommand +SortCommandParser -> SortCommand : parse(n/) +activate SortCommand -create NameContainsKeywordsPredicate -FindCommandParser -> NameContainsKeywordsPredicate : parse(n/chocolate) -activate NameContainsKeywordsPredicate +SortCommand --> LogicManager +deactivate SortCommand -NameContainsKeywordsPredicate --> FindCommand -deactivate NameContainsKeywordsPredicate +LogicManager -> SortCommand : execute() +activate SortCommand -FindCommand --> LogicManager -deactivate FindCommand +create ItemNameComparator +LogicManager -> ItemNameComparator : execute() +activate ItemNameComparator -LogicManager -> FindCommand : execute() -activate FindCommand - -FindCommand -> Model : updateFilteredItemList() +SortCommand -> Model : SortItems() activate Model -Model -> NameContainsKeywordsPredicate : test() -activate NameContainsKeywordsPredicate - -NameContainsKeywordsPredicate --> Model -deactivate NameContainsKeywordsPredicate +ItemNameComparator --> Model +deactivate ItemNameComparator -Model --> FindCommand +Model --> SortCommand deactivate Model -FindCommand --> LogicManager : result -deactivate FindCommand +SortCommand --> LogicManager : result +deactivate SortCommand [<--LogicManager deactivate LogicManager diff --git a/docs/images/SortSequenceDiagram.png b/docs/images/SortSequenceDiagram.png new file mode 100644 index 0000000000000000000000000000000000000000..8d69bc9ddf49a4216fc7ebd045770125e6cd6f86 GIT binary patch literal 32624 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 f8C9dU&HWuyVzE>9?>B(za?ec}jcRl|PdLth2 literal 0 HcmV?d00001 From 44abee8e636027c17d992e265c1e1d75f55cee40 Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Mon, 1 Nov 2021 22:45:57 +0800 Subject: [PATCH 07/42] Update design considerations --- docs/DeveloperGuide.md | 45 +++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index be9d0f8ed16..599a56bbf52 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -214,10 +214,7 @@ which will update the filtered list with items that matches the predicate stated
:information_source: **Note:** If the user input a wrong format of the name, id or tag, a ParseException will be thrown by FindCommandParser and a FindCommand will not be created. Step 2. The updated list with items that matches the predicate will then be shown to the user. If none matches, an empty -list will be shown. The same procedure above is executed for finding by Id and Tags as well. - -
:information_source: **Note:** The FindCommand also supports finding by multiple names, ids or tags. -The 3 classes that implement Predicate takes in a list of string which allows storing of multiple predicates. +list will be shown. The same procedure above is executed for finding by Id and Tags as well.
@@ -230,10 +227,10 @@ The following sequence diagram shows how the find operation works: **Aspect: How Find executes:** -* **Alternative 1:** Retrieve current inventory and check each item one by one without using the predicate class - * Pros: Easier to implement - * Cons: May have performance issue as every command will execute several loops. - +* **Finding Multiple Names, Ids or Tags:** The FindCommand supports finding by multiple names, ids or tags. +`IdContainsNumberPredicate`, `NameContainsKeywordsPredicate` and `TagContainsKeywordsPredicate` takes in a list of +strings which allows storing of multiple predicates. The items in the list are then matched with each predicate to +update the filtered list. Thus, the displayed list contains items that matches multiple predicates given. ### Sort feature @@ -266,6 +263,37 @@ The following sequence diagram shows how the sort operation works: ![SortSequenceDiagram](images/SortSequenceDiagram.png) +### Mutating Inventory + +The sort mechanism is facilitated by the built-in `Comparator` interface. The SortCommand constructor takes in a +predicate enum instruction as a parameter depending on whether the user requested to sort by name or count. The +items' respective fields are then compared with a `Comparator` so that the updated list displayed is sorted. +`Comparator` interface is implemented by different classes below: + +* `ItemNameComparator` — allows sorting of items by name +* `ItemCountComparator` — allows sorting of items by count + +Given below is an example usage scenario and how the sort mechanism behaves at each step. + +Step 1. The user opens up BogoBogo and executes `sort n/` to sort items by name. The `LogicManager` then calls the +`AddressBookParser` which create a `SortCommandParser` object. Then, `SortCommandParser#parse()` creates a `SortCommand` +object. Then the `LogicManager` calls the `SortCommand#execute()` which calls the `Model#SortItems()` and creates an +`ItemNameComparator` object which is passed as a parameter inside `Model#SortItems()`. The `Model#SortItems()` then +update the display list with items sorted by name according to the `ItemNameComparator#compare()` method. + +
:information_source: **Note:** If the user input does not input any field to sort by, SortCommandParser will throw a ParseException and a SortCommand will not be created. + +Step 2. The updated list with items sorted by name will then be shown to the user. The same procedure above is executed +for sorting by count as well. + +
:information_source: **Note:** If the user tries to sort when not in inventory mode, a CommandException will be thrown by SortCommand to remind user to list first. + +
+ +The following sequence diagram shows how the sort operation works: + +![SortSequenceDiagram](images/SortSequenceDiagram.png) + #### Design considerations: **Aspect: How Sort executes:** @@ -273,7 +301,6 @@ The following sequence diagram shows how the sort operation works: * **Alternative 1:** Retrieve current inventory and check each item one by one without using the comparator class * Pros: Easier to implement * Cons: May have performance issue as every command will execute several loops. - -------------------------------------------------------------------------------------------------------------------- ## **Documentation, logging, testing, configuration, dev-ops** From ba0afc4f75e85ec86f29c0c9bc906c5ef2516172 Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Tue, 2 Nov 2021 00:34:24 +0800 Subject: [PATCH 08/42] Mutating Inventory --- docs/DeveloperGuide.md | 57 +++++++++++-------- .../address/model/item/UniqueItemList.java | 1 - 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 599a56bbf52..877384879dc 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -265,42 +265,51 @@ The following sequence diagram shows how the sort operation works: ### Mutating Inventory -The sort mechanism is facilitated by the built-in `Comparator` interface. The SortCommand constructor takes in a -predicate enum instruction as a parameter depending on whether the user requested to sort by name or count. The -items' respective fields are then compared with a `Comparator` so that the updated list displayed is sorted. -`Comparator` interface is implemented by different classes below: +This section explains how various commands update the list of items and display the result. -* `ItemNameComparator` — allows sorting of items by name -* `ItemCountComparator` — allows sorting of items by count +As a background context, all the item objects are contained in a `UniqueItemList` object which enforces uniqueness between +items and prevent duplicates. The `Inventory` manipulates the '`UniqueItemList` to update its content which then update the +`ObservableList`. The `ObservableList` is bounded to the UI so that the UI automatically updates when the +data in the list change. -Given below is an example usage scenario and how the sort mechanism behaves at each step. +`UniqueItemList` is involved when the items are manipulated to ensure the uniqueness of the items. This, the design needs to +ensure that every command mutates the `UniqueItemList` through `Inventory`. -Step 1. The user opens up BogoBogo and executes `sort n/` to sort items by name. The `LogicManager` then calls the -`AddressBookParser` which create a `SortCommandParser` object. Then, `SortCommandParser#parse()` creates a `SortCommand` -object. Then the `LogicManager` calls the `SortCommand#execute()` which calls the `Model#SortItems()` and creates an -`ItemNameComparator` object which is passed as a parameter inside `Model#SortItems()`. The `Model#SortItems()` then -update the display list with items sorted by name according to the `ItemNameComparator#compare()` method. +The general flow of inventory manipulation through AddCommand is as below: +1. The `AddCommand` object in `Logic` component interacts with `Model` component by calling the `Model#addItem()` if a +new item is added and `Model#restockItem()` if an existing item is restocked. +2. The `Model#addItem()` and `Model#restockItem()` methods then call methods with the same method signature in `Inventory`, `Inventory#addItem()` and `Inventory#restockItem()`. +3. The `Inventory` then manipulates the `UniqueItemList` by calling the methods with the same method signature, `UniqueItemList#addItem()` and `UniqueItemList#restockItem()`. +4. UniqueItemList then updates the `ObservableList#add` and `ObservableList#set` methods which updates the list to be returned to the user. +The returned list has added a new item or incremented the count of the existing item. -
:information_source: **Note:** If the user input does not input any field to sort by, SortCommandParser will throw a ParseException and a SortCommand will not be created. -Step 2. The updated list with items sorted by name will then be shown to the user. The same procedure above is executed -for sorting by count as well. +Flow:`AddCommand` -> `Model` -> `Inventory` -> `UniqueItemList` -> `ObservableList` -
:information_source: **Note:** If the user tries to sort when not in inventory mode, a CommandException will be thrown by SortCommand to remind user to list first. +The above flow applies for all the other similar commands that manipulates the inventory. +The detailed flow for each command is found below: -
+**`AddCommand:`** +AddCommand#execute() -> Model#addItem() or Model#restockItem() -> Inventory#addItem() or Inventory#restockItem() +-> UniqueItemList#addItem() or UniqueItemList#setItem() -> ObservableList#add() or ObservableList#set() -The following sequence diagram shows how the sort operation works: +**`RemoveCommand:`** +RemoveCommand#execute() -> Model#removeItem() -> Inventory#removeItem() -> UniqueItemList#setItem() -> ObservableList#set() -![SortSequenceDiagram](images/SortSequenceDiagram.png) +**`EditCommand:`** +EditCommand#execute() -> Model#setItem() -> Inventory#setItem() -> UniqueItemList#setItem() -> ObservableList#set() + +**`ClearCommand:`** +ClearCommand#execute() -> Model#setItem() -> Inventory#resetData() -> Inventory#setItems() -> UniqueItemList#setItem() -> ObservableList#set() + +**`DeleteCommand:`** +DeleteCommand#execute() -> Model#deleteItem() -> Inventory#deleteItems() -> UniqueItemList#removeItem() -> ObservableList#remove() + +**`SortCommand:`** +SortCommand#execute() -> Model#sortItem() -> Inventory#sortItems() -> UniqueItemList#sortItem() -> ObservableList#sort() -#### Design considerations: -**Aspect: How Sort executes:** -* **Alternative 1:** Retrieve current inventory and check each item one by one without using the comparator class - * Pros: Easier to implement - * Cons: May have performance issue as every command will execute several loops. -------------------------------------------------------------------------------------------------------------------- ## **Documentation, logging, testing, configuration, dev-ops** diff --git a/src/main/java/seedu/address/model/item/UniqueItemList.java b/src/main/java/seedu/address/model/item/UniqueItemList.java index 695986a5fc6..f1a0ff362f8 100644 --- a/src/main/java/seedu/address/model/item/UniqueItemList.java +++ b/src/main/java/seedu/address/model/item/UniqueItemList.java @@ -113,7 +113,6 @@ public void setItem(Item target, Item editedItem) { /** * Removes the specified count of the equivalent item from the list. - * If item is * The item must exist in the list. */ public void remove(Item toRemove) { From 39b91e74d280bc6b132e63463bae6abb67333a6b Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Tue, 2 Nov 2021 00:40:23 +0800 Subject: [PATCH 09/42] Fix Errors --- docs/DeveloperGuide.md | 130 ++++++++++++++++++++--------------------- 1 file changed, 64 insertions(+), 66 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 877384879dc..45e73ab3311 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -193,50 +193,56 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. -### Find feature +### Mutating Inventory -The find mechanism is facilitated by the built-in `Predicate` class. The FindCommand constructor takes in a predicate -type as a parameter and the list is then filtered with `ModelManager#updateFilteredItemList` method to only contain the -items that match the predicate specified. `Predicate` interface is implemented by 3 different classes: +This section explains how various commands update the list of items and display the result. -* `IdContainsNumberPredicate` — allows finding of items by Id -* `NameContainsKeywordsPredicate` — allows finding of items by Name -* `TagContainsKeywordsPredicate` — allows finding of items by Tag +As a background context, all the item objects are contained in a `UniqueItemList` object which enforces uniqueness between +items and prevent duplicates. The `Inventory` manipulates the '`UniqueItemList` to update its content which then update the +`ObservableList`. The `ObservableList` is bounded to the UI so that the UI automatically updates when the +data in the list change. -Given below is an example usage scenario and how the find mechanism behaves at each step. +`UniqueItemList` is involved when the items are manipulated to ensure the uniqueness of the items. This, the design needs to +ensure that every command mutates the `UniqueItemList` through `Inventory`. -Step 1. The user opens up BogoBogo and executes `find n/Chocolate` to find items with the name chocolate. The -`LogicManager` then calls the `AddressBookParser` which create a `FindCommandParser` object. Then, `FindCommandParser#parse()` -then creates a `FindCommand` object and `NameContainsKeywordsPredicate` object. The `NameContainsKeywordsPredicate` is -passed as a field of the FindCommand constructor. Then, the `LogicManager` calls the `FindCommand#execute()` -which will update the filtered list with items that matches the predicate stated. +The general flow of inventory manipulation through AddCommand is as below: +1. The `AddCommand` object in `Logic` component interacts with `Model` component by calling the `Model#addItem()` if a + new item is added and `Model#restockItem()` if an existing item is restocked. +2. The `Model#addItem()` and `Model#restockItem()` methods then call methods with the same method signature in `Inventory`, `Inventory#addItem()` and `Inventory#restockItem()`. +3. The `Inventory` then manipulates the `UniqueItemList` by calling the methods with the same method signature, `UniqueItemList#addItem()` and `UniqueItemList#restockItem()`. +4. UniqueItemList then updates the `ObservableList#add` and `ObservableList#set` methods which updates the list to be returned to the user. + The returned list has added a new item or incremented the count of the existing item. -
:information_source: **Note:** If the user input a wrong format of the name, id or tag, a ParseException will be thrown by FindCommandParser and a FindCommand will not be created. -Step 2. The updated list with items that matches the predicate will then be shown to the user. If none matches, an empty -list will be shown. The same procedure above is executed for finding by Id and Tags as well. +Flow:`AddCommand` -> `Model` -> `Inventory` -> `UniqueItemList` -> `ObservableList` -
+The above flow applies for all the other similar commands that manipulates the inventory. +The detailed flow for each command is found below: -The following sequence diagram shows how the find operation works: +**`AddCommand:`** +AddCommand#execute() -> Model#addItem() or Model#restockItem() -> Inventory#addItem() or Inventory#restockItem() +-> UniqueItemList#addItem() or UniqueItemList#setItem() -> ObservableList#add() or ObservableList#set() -![FindSequenceDiagram](images/FindSequenceDiagram.png) +**`RemoveCommand:`** +RemoveCommand#execute() -> Model#removeItem() -> Inventory#removeItem() -> UniqueItemList#setItem() -> ObservableList#set() +**`EditCommand:`** +EditCommand#execute() -> Model#setItem() -> Inventory#setItem() -> UniqueItemList#setItem() -> ObservableList#set() -#### Design considerations: +**`ClearCommand:`** +ClearCommand#execute() -> Model#setItem() -> Inventory#resetData() -> Inventory#setItems() -> UniqueItemList#setItem() -> ObservableList#set() -**Aspect: How Find executes:** +**`DeleteCommand:`** +DeleteCommand#execute() -> Model#deleteItem() -> Inventory#deleteItems() -> UniqueItemList#removeItem() -> ObservableList#remove() -* **Finding Multiple Names, Ids or Tags:** The FindCommand supports finding by multiple names, ids or tags. -`IdContainsNumberPredicate`, `NameContainsKeywordsPredicate` and `TagContainsKeywordsPredicate` takes in a list of -strings which allows storing of multiple predicates. The items in the list are then matched with each predicate to -update the filtered list. Thus, the displayed list contains items that matches multiple predicates given. +**`SortCommand:`** +SortCommand#execute() -> Model#sortItem() -> Inventory#sortItems() -> UniqueItemList#sortItem() -> ObservableList#sort() ### Sort feature -The sort mechanism is facilitated by the built-in `Comparator` interface. The SortCommand constructor takes in a -predicate enum instruction as a parameter depending on whether the user requested to sort by name or count. The -items' respective fields are then compared with a `Comparator` so that the updated list displayed is sorted. +The sort mechanism is facilitated by the built-in `Comparator` interface. The SortCommand constructor takes in a +predicate enum instruction as a parameter depending on whether the user requested to sort by name or count. The +items' respective fields are then compared with a `Comparator` so that the updated list displayed is sorted. `Comparator` interface is implemented by different classes below: * `ItemNameComparator` — allows sorting of items by name @@ -244,15 +250,15 @@ items' respective fields are then compared with a `Comparator` so that the updat Given below is an example usage scenario and how the sort mechanism behaves at each step. -Step 1. The user opens up BogoBogo and executes `sort n/` to sort items by name. The `LogicManager` then calls the -`AddressBookParser` which create a `SortCommandParser` object. Then, `SortCommandParser#parse()` creates a `SortCommand` -object. Then the `LogicManager` calls the `SortCommand#execute()` which calls the `Model#SortItems()` and creates an -`ItemNameComparator` object which is passed as a parameter inside `Model#SortItems()`. The `Model#SortItems()` then +Step 1. The user opens up BogoBogo and executes `sort n/` to sort items by name. The `LogicManager` then calls the +`AddressBookParser` which create a `SortCommandParser` object. Then, `SortCommandParser#parse()` creates a `SortCommand` +object. Then the `LogicManager` calls the `SortCommand#execute()` which calls the `Model#SortItems()` and creates an +`ItemNameComparator` object which is passed as a parameter inside `Model#SortItems()`. The `Model#SortItems()` then update the display list with items sorted by name according to the `ItemNameComparator#compare()` method.
:information_source: **Note:** If the user input does not input any field to sort by, SortCommandParser will throw a ParseException and a SortCommand will not be created. -Step 2. The updated list with items sorted by name will then be shown to the user. The same procedure above is executed +Step 2. The updated list with items sorted by name will then be shown to the user. The same procedure above is executed for sorting by count as well.
:information_source: **Note:** If the user tries to sort when not in inventory mode, a CommandException will be thrown by SortCommand to remind user to list first. @@ -263,52 +269,44 @@ The following sequence diagram shows how the sort operation works: ![SortSequenceDiagram](images/SortSequenceDiagram.png) -### Mutating Inventory - -This section explains how various commands update the list of items and display the result. - -As a background context, all the item objects are contained in a `UniqueItemList` object which enforces uniqueness between -items and prevent duplicates. The `Inventory` manipulates the '`UniqueItemList` to update its content which then update the -`ObservableList`. The `ObservableList` is bounded to the UI so that the UI automatically updates when the -data in the list change. +### Find feature -`UniqueItemList` is involved when the items are manipulated to ensure the uniqueness of the items. This, the design needs to -ensure that every command mutates the `UniqueItemList` through `Inventory`. +The find mechanism is facilitated by the built-in `Predicate` class. The FindCommand constructor takes in a predicate +type as a parameter and the list is then filtered with `ModelManager#updateFilteredItemList` method to only contain the +items that match the predicate specified. `Predicate` interface is implemented by 3 different classes: -The general flow of inventory manipulation through AddCommand is as below: -1. The `AddCommand` object in `Logic` component interacts with `Model` component by calling the `Model#addItem()` if a -new item is added and `Model#restockItem()` if an existing item is restocked. -2. The `Model#addItem()` and `Model#restockItem()` methods then call methods with the same method signature in `Inventory`, `Inventory#addItem()` and `Inventory#restockItem()`. -3. The `Inventory` then manipulates the `UniqueItemList` by calling the methods with the same method signature, `UniqueItemList#addItem()` and `UniqueItemList#restockItem()`. -4. UniqueItemList then updates the `ObservableList#add` and `ObservableList#set` methods which updates the list to be returned to the user. -The returned list has added a new item or incremented the count of the existing item. +* `IdContainsNumberPredicate` — allows finding of items by Id +* `NameContainsKeywordsPredicate` — allows finding of items by Name +* `TagContainsKeywordsPredicate` — allows finding of items by Tag +Given below is an example usage scenario and how the find mechanism behaves at each step. -Flow:`AddCommand` -> `Model` -> `Inventory` -> `UniqueItemList` -> `ObservableList` +Step 1. The user opens up BogoBogo and executes `find n/Chocolate` to find items with the name chocolate. The +`LogicManager` then calls the `AddressBookParser` which create a `FindCommandParser` object. Then, `FindCommandParser#parse()` +then creates a `FindCommand` object and `NameContainsKeywordsPredicate` object. The `NameContainsKeywordsPredicate` is +passed as a field of the FindCommand constructor. Then, the `LogicManager` calls the `FindCommand#execute()` +which will update the filtered list with items that matches the predicate stated. -The above flow applies for all the other similar commands that manipulates the inventory. -The detailed flow for each command is found below: +
:information_source: **Note:** If the user input a wrong format of the name, id or tag, a ParseException will be thrown by FindCommandParser and a FindCommand will not be created. -**`AddCommand:`** -AddCommand#execute() -> Model#addItem() or Model#restockItem() -> Inventory#addItem() or Inventory#restockItem() --> UniqueItemList#addItem() or UniqueItemList#setItem() -> ObservableList#add() or ObservableList#set() +Step 2. The updated list with items that matches the predicate will then be shown to the user. If none matches, an empty +list will be shown. The same procedure above is executed for finding by Id and Tags as well. -**`RemoveCommand:`** -RemoveCommand#execute() -> Model#removeItem() -> Inventory#removeItem() -> UniqueItemList#setItem() -> ObservableList#set() +
-**`EditCommand:`** -EditCommand#execute() -> Model#setItem() -> Inventory#setItem() -> UniqueItemList#setItem() -> ObservableList#set() +The following sequence diagram shows how the find operation works: -**`ClearCommand:`** -ClearCommand#execute() -> Model#setItem() -> Inventory#resetData() -> Inventory#setItems() -> UniqueItemList#setItem() -> ObservableList#set() +![FindSequenceDiagram](images/FindSequenceDiagram.png) -**`DeleteCommand:`** -DeleteCommand#execute() -> Model#deleteItem() -> Inventory#deleteItems() -> UniqueItemList#removeItem() -> ObservableList#remove() -**`SortCommand:`** -SortCommand#execute() -> Model#sortItem() -> Inventory#sortItems() -> UniqueItemList#sortItem() -> ObservableList#sort() +#### Design considerations: +**Aspect:** +* **Finding Multiple Names, Ids or Tags:** The FindCommand supports finding by multiple names, ids or tags. +`IdContainsNumberPredicate`, `NameContainsKeywordsPredicate` and `TagContainsKeywordsPredicate` takes in a list of +strings which allows storing of multiple predicates. The items in the list are then matched with each predicate to +update the filtered list. Thus, the displayed list contains items that matches multiple predicates given. -------------------------------------------------------------------------------------------------------------------- From 8b51398be60d8c24f75fba76ffe186f4a4849f5c Mon Sep 17 00:00:00 2001 From: bernarduskrishna <77195969+bernarduskrishna@users.noreply.github.com> Date: Tue, 2 Nov 2021 04:33:04 +0800 Subject: [PATCH 10/42] Update DG - Order related stuff --- docs/DeveloperGuide.md | 35 ++++++++++++++++ docs/diagrams/OrderFinalState.puml | 32 +++++++++++++++ docs/diagrams/OrderInitialState.puml | 25 ++++++++++++ docs/diagrams/OrderItem1State.puml | 28 +++++++++++++ docs/diagrams/OrderItem2State.puml | 32 +++++++++++++++ docs/diagrams/OrderSorderState.puml | 28 +++++++++++++ .../TransactOrderSequenceDiagram.puml | 38 ++++++++++++++++++ docs/images/OrderFinalState.png | Bin 0 -> 17503 bytes docs/images/OrderInitialState.png | Bin 0 -> 11896 bytes docs/images/OrderItem1State.png | Bin 0 -> 13446 bytes docs/images/OrderItem2State.png | Bin 0 -> 15493 bytes docs/images/OrderSorderState.png | Bin 0 -> 12657 bytes docs/images/TransactOrderSequenceDiagram.png | Bin 0 -> 14713 bytes 13 files changed, 218 insertions(+) create mode 100644 docs/diagrams/OrderFinalState.puml create mode 100644 docs/diagrams/OrderInitialState.puml create mode 100644 docs/diagrams/OrderItem1State.puml create mode 100644 docs/diagrams/OrderItem2State.puml create mode 100644 docs/diagrams/OrderSorderState.puml create mode 100644 docs/diagrams/TransactOrderSequenceDiagram.puml create mode 100644 docs/images/OrderFinalState.png create mode 100644 docs/images/OrderInitialState.png create mode 100644 docs/images/OrderItem1State.png create mode 100644 docs/images/OrderItem2State.png create mode 100644 docs/images/OrderSorderState.png create mode 100644 docs/images/TransactOrderSequenceDiagram.png diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 2978ccfb0f1..508ed24bae1 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -193,6 +193,41 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. +### Ordering + +### Implementation + +When ModelManager is initialised, optionalOrder is set to Optional.empty(). +At this point, the user has 1 order record with 2 items in his transaction list. + +![Initial_State](images/OrderInitialState.png) + +Step 1. The user enters ordering mode via the `sorder` command. + +Upon entering the ordering mode, optionalOrder now has a new Order() which is empty + +![Sorder_State](images/OrderSorderState.png) + +Step 2. The user adds an item to the order via the `iorder` command. + +Upon entering `iorder Banana c/1`, the order now contains 1 banana item. + +![Iorder_State](images/OrderItem1State.png) + +Next, upon entering `iorder Strawberry c/1`, the order now contains 1 strawberry item. + +![Iorder_State](images/OrderItem2State.png) + +Step 3. The user transacts the order via the `eorder` command. + +After the transaction is done, optionalOrder is reinitialised to Optional.empty() + +![Initial_State](images/OrderFinalState.png) + +Step 4. The new transactions are saved to json file. + +![Transact_Order_Sequence_Diagram](images/TransactOrderSequenceDiagram.png) + ### \[Proposed\] Undo/redo feature #### Proposed Implementation diff --git a/docs/diagrams/OrderFinalState.puml b/docs/diagrams/OrderFinalState.puml new file mode 100644 index 00000000000..01e5ebe9411 --- /dev/null +++ b/docs/diagrams/OrderFinalState.puml @@ -0,0 +1,32 @@ +@startuml +!include style.puml +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title iorder state 2 + +Class modelManager as "__modelManager:ModelManager__" + +Class optionalOrder as "__optionalOrder:OptionalOrder__" + +Class trans2 as "__transactionRecord2:TransactionRecord__" +Class item1 as "__banana:Item__" +Class item2 as "__strawberry:Item__" +item2 -up-> trans2 +item1 -up-> trans2 + +Class transactions as "__transactions:List__" + +Class trans1 as "__transactionRecord1:TransactionRecord__" +Class item1Trans1 as "__pencil:Item__" +Class item2Trans2 as "__pen:Item__" +item2Trans2 -up-> trans1 +item1Trans1 -up-> trans1 + + +transactions -up-> modelManager +trans1 -up-> transactions +trans2 -up-> transactions +optionalOrder -up-> modelManager + +@end diff --git a/docs/diagrams/OrderInitialState.puml b/docs/diagrams/OrderInitialState.puml new file mode 100644 index 00000000000..34fb549103b --- /dev/null +++ b/docs/diagrams/OrderInitialState.puml @@ -0,0 +1,25 @@ +@startuml +!include style.puml +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title Initial state + +Class modelManager as "__modelManager:ModelManager__" + +Class optionalOrder as "__optionalOrder:OptionalOrder__" + +Class transactions as "__transactions:List__" + +Class trans1 as "__transactionRecord1:TransactionRecord__" +Class item1Trans1 as "__pencil:Item__" +Class item2Trans2 as "__pen:Item__" +item2Trans2 -up-> trans1 +item1Trans1 -up-> trans1 + + +transactions -up-> modelManager +optionalOrder -up-> modelManager +trans1 -up-> transactions + +@end diff --git a/docs/diagrams/OrderItem1State.puml b/docs/diagrams/OrderItem1State.puml new file mode 100644 index 00000000000..b81f7aa244f --- /dev/null +++ b/docs/diagrams/OrderItem1State.puml @@ -0,0 +1,28 @@ +@startuml +!include style.puml +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title iorder state 1 + +Class modelManager as "__modelManager:ModelManager__" + +Class optionalOrder as "__optionalOrder:OptionalOrder__" +Class ord as "__order:Order__" +Class item1 as "__banana:Item__" + +Class transactions as "__transactions:List__" + +Class trans1 as "__transactionRecord1:TransactionRecord__" +Class item1Trans1 as "__pencil:Item__" +Class item2Trans2 as "__pen:Item__" +item2Trans2 -up-> trans1 +item1Trans1 -up-> trans1 + +transactions -up-> modelManager +trans1 -up-> transactions +item1 -up-> ord +ord -up-> optionalOrder +optionalOrder -up-> modelManager + +@end diff --git a/docs/diagrams/OrderItem2State.puml b/docs/diagrams/OrderItem2State.puml new file mode 100644 index 00000000000..b9e9ef92a2a --- /dev/null +++ b/docs/diagrams/OrderItem2State.puml @@ -0,0 +1,32 @@ +@startuml +!include style.puml +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title iorder state 2 + +Class modelManager as "__modelManager:ModelManager__" + +Class optionalOrder as "__optionalOrder:OptionalOrder__" + +Class ord as "__order:Order__" +Class item1 as "__banana:Item__" +Class item2 as "__strawberry:Item__" +item2 -up-> ord +item1 -up-> ord + +Class transactions as "__transactions:List__" + +Class trans1 as "__transactionRecord1:TransactionRecord__" +Class item1Trans1 as "__pencil:Item__" +Class item2Trans2 as "__pen:Item__" +item2Trans2 -up-> trans1 +item1Trans1 -up-> trans1 + + +transactions -up-> modelManager +trans1 -up-> transactions +ord -up-> optionalOrder +optionalOrder -up-> modelManager + +@end diff --git a/docs/diagrams/OrderSorderState.puml b/docs/diagrams/OrderSorderState.puml new file mode 100644 index 00000000000..a7db11b1bb0 --- /dev/null +++ b/docs/diagrams/OrderSorderState.puml @@ -0,0 +1,28 @@ +@startuml +!include style.puml +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title sorder state + +Class modelManager as "__modelManager:ModelManager__" + +Class optionalOrder as "__optionalOrder:OptionalOrder__" + +Class ord as "__order:Order__" + +Class transactions as "__transactions:List__" + +Class trans1 as "__transactionRecord1:TransactionRecord__" +Class item1Trans1 as "__pencil:Item__" +Class item2Trans2 as "__pen:Item__" +item2Trans2 -up-> trans1 +item1Trans1 -up-> trans1 + + +transactions -up-> modelManager +trans1 -up-> transactions +ord -up-> optionalOrder +optionalOrder -up-> modelManager + +@end diff --git a/docs/diagrams/TransactOrderSequenceDiagram.puml b/docs/diagrams/TransactOrderSequenceDiagram.puml new file mode 100644 index 00000000000..8510ba9d711 --- /dev/null +++ b/docs/diagrams/TransactOrderSequenceDiagram.puml @@ -0,0 +1,38 @@ +@startuml +!include style.puml + + +Participant ":Logic" as logic LOGIC_COLOR +Participant ":Storage" as storage STORAGE_COLOR +Participant ":InventoryStorage" as inventoryStorage STORAGE_COLOR_T2 +Participant ":TransactionStorage" as transactionStorage STORAGE_COLOR_T2 +Participant ":BookKeepingStorage" as bookKeepingStorage STORAGE_COLOR_T2 + +activate logic LOGIC_COLOR + + +logic -[LOGIC_COLOR]> storage : saveInventory() +activate storage STORAGE_COLOR + +storage -[STORAGE_COLOR]> inventoryStorage : Save to inventory.json +activate inventoryStorage STORAGE_COLOR_T2 +inventoryStorage --[STORAGE_COLOR]> storage +deactivate inventoryStorage + +storage -[STORAGE_COLOR]> transactionStorage : Save to transactions.json +activate transactionStorage STORAGE_COLOR_T2 +transactionStorage --[STORAGE_COLOR]> storage +deactivate transactionStorage + +storage -[STORAGE_COLOR]> bookKeepingStorage : Save to bookKeeping.json +activate bookKeepingStorage STORAGE_COLOR_T2 +bookKeepingStorage --[STORAGE_COLOR]> storage +deactivate bookKeepingStorage + +storage --[STORAGE_COLOR]> logic +deactivate storage + + +deactivate logic + +@enduml diff --git a/docs/images/OrderFinalState.png b/docs/images/OrderFinalState.png new file mode 100644 index 0000000000000000000000000000000000000000..9cfd5dddab0805103ef74ef917ab8241450f6804 GIT binary patch literal 17503 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 G@c&=?4t0tE literal 0 HcmV?d00001 diff --git a/docs/images/OrderInitialState.png b/docs/images/OrderInitialState.png new file mode 100644 index 0000000000000000000000000000000000000000..21875b2d7a8c7b540d1bd94a958364acb5fafb3f GIT binary patch literal 11896 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 literal 0 HcmV?d00001 diff --git a/docs/images/OrderItem1State.png b/docs/images/OrderItem1State.png new file mode 100644 index 0000000000000000000000000000000000000000..617214f940add733a6cc484702a6e17cfb116c8d GIT binary patch literal 13446 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 literal 0 HcmV?d00001 diff --git a/docs/images/OrderItem2State.png b/docs/images/OrderItem2State.png new file mode 100644 index 0000000000000000000000000000000000000000..46d08fc40ca9cce3933effab92e10eb9f0a402fa GIT binary patch literal 15493 zcmeIZWo#W?vnIOj#CBqK%uF#;VvN}_CT3=cnR%O;nK@>PnVFfHnVBhOn)dshGv_-a zjjl9T^W#eQhrOll)vIe&Jyok#J=Hsp$ld|}4} zfgqQR<(0wzc^w1+Oyihxq97)<#E0CfHs~=w35Un0S!7jCE%*WI`%l!USc2>k=O2p2 z4lCDKKV)8A`WVb8ybAX!dG3=fSDBh9s9*zBid|M=^m9f@eo>V&<9&uoT&0QmK%v7r zP9iZ@(&05TTM^-I&5D&bE3`iQjT;Hwrnr{^Kqyo>%D!Fi7c>nhi@eQ71& z#yw}e#0M!%-NLfvjmg|x<4a?mq>n8h=d_bSL-i*+)t$=Xwv7bLOk+Pbw4|(1y<+kS zY@nMwljZ>F-?*O2tXBTQvgJm-{oJ`QEI0#BPskhkO)I2F7^hOsnJKpqtFPJ3lO=Zr zzagv2B-7@3uc4@NxR=(qU(H%-pU}1QTOvVp6_<$BvPJv+sYADFMd^(514WS%d+I3KV)hzfb5gkp90)KZvG#E+AB|7w|$u zU;?-7VGE5rfxR3gzJ-=no-Z!$=TsmmAZ9dM;285n_6w|*@dZu;{z`TN&f1V}^hE9& z#@f56@c>_Z`kh>tEPKJmvx<|4;!A*`ezCDo8&cWAyZYEzEZz@Ko`hDdoGAUmpOOg6 zJp(#q`q`7!#Z~CnuIw3l=V)t^vX5x92++j@p>LXLE{NtO45WLID^cPO4v$R{${BXQ zv#Zh8IM{KTeGo~%kwTxuKcGaj=lomf;>nydVi_5U@QDBqdX>f8S=h-JK9ntE4$=58 zCG#0lAiG$(GJT^N2*m#7r{H&aN6nMekB(>xH+8rRA1nJ?%SbamKvHKM2hzd@We`1y zpvaGVc6xpYheI5_`-lYvfv%4wGr_0)iNrS**`8k+t*4yS6jG1aH?rhja__LFUP0qP zvXG;$(BsTDjjQgI-GTFV`k+wKAz=#x@FRAsKy=_I_=BDv2owqR-~az&E7feFR&3~_ z(FF}dV`JC%LrM+KG3s-G_s_Q?c!M!O zjzI^8AIj-_ccpb*Yy*_C9rbCgp6)I@JwdbYvXv`zXp~CPPC)1Wkp85gRoTS9d+eHF zn{gHG<=S2OCro2pwilZ{zP`T10{-LLMT#YYoS^1R&y5dH5X9Q}+|JBjzMPM8d3$?H zrhFq-?(Obgwwu#fX>u@(v$nR@XtK|R8?abu)Tp=mzJD^D#3aT04DJa=FR1JAfr)`o zGQRoqnTE!+)AwT-E*rf@BY5hMM4m))d`yh((U^ZHA9zJHb)1h(EWTK)?ZvdLB|biW zcU0L>5z_8xHrGEkWIoPh$!IF4Qi&>yK1PvB6}bihH65Yr zdBXxY`|Qa({jpNoK1vTi_3fqfwe=o=uPA zAtol)ds=ll3xWkTL;SPs@|T;sHulTapT7boCMLl1IX_WzcTQKD7B)(F<%r27?CY(! zg!L`yRI4)>$xQ8kU`HZyg7@1IXIef^muf6UB~%Is2vk*8#!~N4rp(XJGqgQ1e*0GM zbhR7a?9ox-+@b8D$kE-?1NHv>co7nvJc9**Pz z@F(l`g{N}Z6#_q^$?NOCL{?wNCDVD_?k{&P3olf3nbi1_T8K9Y?zHY$sc`4)pg2hJ>KLbWpx6mxz%uy82|XCYwEw0|Xna zx%`e-B&qq$S87ZY6uUC?`glu=s`Wm9{w!annTiVo1w~~(-4aKw5d4ZOZCo&rFOwZ> z9>lih8dFdxU%(&mHvzvDa42swfhhnPkSmRLa;i3O!2tr|{W3{T;@#cd%kyIbgH|hG zc`5E|G!5FOD!r%6AqH-j8~6_&_9t`U9_vddclRf<-@zajOJ{bbI;<`<*zVD()mp_F zSc*o+#86Sqm8c~!8US)1IH!2@Yp*stO>2+*W?9fSF*yuGA*CFfOgZ34r7wz0h3l;Y z8EtsgK0&^!@#c5RZDc zkx|5wdU{8mz@7enWD3SgyLO%hJUzWYRO|(gU+HFujEvnup=S8yQmio8*qegFwTSZ+ z$v&a*)+D5AFvl??Us*|yZhLD=RG-aPo7tSW;|he$&s?a6yJ=hO_4JC~CVp`0R+~5r zGc6dew@x*ktCdT7nJv?jYs(zV`GPuC7wdZRAHu{Qcn-p0wgP4(i#|V>%F5cEDO9M@ z8~8+{==;ToMyZzTLU0cj9rEG!%;wsJxky}mc{#h!NB(|B>Fjbcz*z;#>+gP^>ng@?P6mls+(P% z#Of@Nrt@X=PnW6gB@Gs<*ICVfyL|iFRGK;$7zL5@9#Gd^!I0$8UOFM)-pOPx(>@pg zII~7Ko!Sk`4y3^sgW+``PRo$(1g@`l6kqybdhZS=@f-4>J6dk_B5;C@4#IGoFd1Hc zP3CMgduZMrS9wUL+|dHk<)usR7Gluh`1y_G_`_j{=VC%z@Y)|oR#)Q>4j8G1JOa*imSlcpOjU%%q@R2K`PG>A?b=%CU@V~5*YE5I zvvi3wn;}>R@t%vl`CGC_2cSfn7=|8|uB?-iyl0G;|hAQRHohlc|D*2K9az624_p$ z9*DXqO2@O%87tC4I5A?kD(ml^{`)(O(#}r@!RGxhWJ1`P!eP|K(exwxk+6oNg4SY% zIYVr#KI2F<@L1z^-GLBp4_w9ac8B2C>4I#fvNVC)Mei2r*=k_BC2x}CvNNd(h>=k; zk^DD}O>k|j#EFFA)LU)17#nl)^OGYIuBIzM!yu*cxT%W{)+%W_{ZW}hzQ29A^Su^J^8^DbH+oVFL zM>+RJFE)$mt4rC6SEv~Z;6@@>z79%~0CLN;@;p5&TU;D+7>mRu!!u1=ezO!wcW%Zj zm70BoKUQpYZKyR*Cgf=r-w!~yKY8$notS`&Ei5lj0-{C^4X3E<+uNc`m7DC%0hh^* z;B{}|_X8;w@M7z$80y$-aF(HvzWebJuvNa4B<_cmys@kKm}LDXxo4>%0JjLcJmMb)$lr48I456OzDo+HqjQ z=+9Pb8UD$s&C~UXc6J6&f#G4gaAt*(AdV7qC9RGgh^m7J0(+n(DXIFbf1*L#MeBj6 zaHc#rmoO;**-@`6V<*m;p6_HxFN z;9X&+9H(G9h%b=9^Q5ttvhE!2CKHGAn6FhYnR^LwXBdZ8J3Bk*jfAhR4Myga)4XBP z3EoRn|M~LoB+v#xoXjDoxqcWmTvc_u%_wI~dl=n zM$AG1?Rtj7Z|@wgYPp*|=`AN>=e32{w6v&I^xYHIg$R@LWsfT}lZ1(7PvIGn1H;LI z;W^z0T#Q>w=Z7t0C82+^eagl(Wsft%QZ+w~i2L+rp%|-Ezl~-l{2gZmR zV3x*P(^%9gkG5_TRtFdTpA|O)Fq@Wa%qTb^!a3Fp#3R1D0##WJD;6I-qku;m>g(O5 z1XKGw+zneVpEnh*AI}~hn_y|Q_yAQuMgB8(0v=rvg!Pe;F%rtqel&ByR%zxay_`%^ zB|SN*rkVftZ`H>^QNp(em>Vmj|IzKFS===U3lez5`zVjz%!rp{H|Qf90|i`RYq()knGaBND@%m0E z&Hw{3+doMmupn3nMn+0en@Ogd*1Pl>2i~~qS2mej3Ih+23SsJm@hG$HZ{8CAX$Wbe z$f)m8QLg**zg_%~30}&Mt8ueNNydkbU#ekML-OCXy8r_wtjsjAR<6@u=tbp~kyW8e z{1iV^gU&zc>8+bG#*~`F{jM%uu{G`+4dxi)H_b$!Xf(3RLIg#87l!89xvcdmIz;;R zMLYi4y(U0W@F;6$0tZOb(!4_9U45A-ZqM5=1g8%jv_}wS3`vdbs)bbxM%&&)Sivwd zomC0+18K0?)%zyka&0sz%i+MMHQ0c<BRhTnbsORGqZ9O4AS4F>8&YfTfP#T`w=(gufp*S3l5NyW_+yGI75hS$@R<||F-YJ15<`T9&@ zp!pNUu3fj^Wn19#^S#_O#4B*HLi1(fEq34zFs{9^P4ujbq!>uwR*->s0-jyRUzoEz z#=d_!G?1bo48*j{K#t@cDB4!LEYE-E{thsR_qLD7Ts2nn-o0@@j={Qfjf*>uQ{oft zp{EDnj$AwYDY>P4U+6Sxs68_y0|v&n&4HOgdU}z(NlJ2cP_3;m9eY#ZOMC3%i~{cD zmN8=kS6PVjIKTj;Ahp}FLf-ZDqpFizJn|&se{ODj$iN)KF)~_(9I3&}wT0%doNX>@ zWFg+YzvQ}4w})73y+8#_!Ftf9wK(l04=$^~#w-iuSQqGjt%j?{tR@9!N7U2H=x8`E z*d?x0aW*k8(&kKef@>?Jr>JJA=ByT}mILMng5x<11%_aKT30m@zdUH5U2*{n zP^2@*-YBtUiTSfffrw-f2N7AdT(wrU>C2P6ikf%m@tOKU>h0{`d&>X;JG5GRW?` z@C~VY8|6*A)l*ht*!`B$0|8dJ)uT4~5(vvEyQh(0Acl z_TvL{7p*%7NoW83ceVcZDL431g4G!P{r1pO-=G~$>TCFfYblbHWOW{-)L+K~k0{1M zIMxlN;iW%Ib4kkwau8wn_p^T17F#ZKenx8j+Lx3{dg!UeAH{dUC-c36_|Q|Fh;#{d z&Ngv1^~@P@s&NIdGB+A}si9yCC*c}=GzK70+uv-TZiJs`^K~!2U$lY>`>Zym)HX_QC{BtV{_IRVWe5;rCh3 z`SWJhLZ$&3CO+@CYU$*AKG?0VJKOId2klQ5Py3_LO_;JjuVHg6!!)b;acwtoNl+=% z;BVl+{GFNv(l*}!51W(4o0+0L-~h3+-a%&KGE$osQkRYP>x0|Hj#@Kq&RXJrVyuwK<^bwS$`vHO3a5+L2^O5a9WCuHGcvnD% zd+86w8|aT9qYKYri}Ah;1Z@Ark!0sErS#uTuUF{znM~I0g->HK{ZtZ}LV(AW$Z*!M zm-a(4{QhohP9z+DJC46YW2r(nASd@kx%QZJkzeuWt%$ZZljZV!%dd{uu8G$S(`j-oAEQlHoU>r6&<-RyV)6GM)mRJWPM8$2?5 zWJQ?K+ey*eryw^T52>l~*Q~|6QJUW*6m516yGltx5Mxe@j?=hF1 z9#cDvKi5F5G%QSm%Ve5Q)Ud3D75w@_$n3HnfN_~jv&{P<* z?P#+HdHy4nMx(r#}9QItt=CVP)F-r$KTvnN_9E)MbbUucb!wkPV7@zOMibMl&e zkKSK(s3fFmH5czNI+Va*nVF0Ni{Gd$eJhtOjNT}@T_TyTnJpgD&|qDxo+W6F)1Q>k z8Eb0=B}9-b8YlnnX2??K!ByfWbF-uz0#Di>OvcX&IV?ZH{-|Pi2yaByJa=tl?o&uM zRy3*IQ7v>j$n}a%?2RC_U+2p=KzbVIC(wmr)U4EV9*UQ^UqUQK?MpvLR){#Ypnblh zAdLQJ<9E>{ohm|gk@L5YVuaEn5W|*NDO=TI&;k&qw#v@n@LvbYI;KN+Xr$BY`#sAG z#dLR$B$MfunF6^~=q$(;V9E;mn{kT2EF6{Anh#IZm3{cndD z);7vye~%O?wS};YtbAl}Y}FF?wO&awR)jT&GB-J7UA(9@e|65*GS{VxprY9uP5p#6 zol5uymA|filc64$#7>i=XQUB0ELIzxx093hzP{o)xDc()Uv;77utrkuBb2n<{1qmT z=6gbL6BXM^CMAlcD|DQkzh?tUEa|(MWYIDgaKI;&uQX)(YL0gl)lSydYeg<_hy+}2 z58o=CcH;7!p7$qw@|}cxEfy(W)-?5L=pVA@%~}96o6vTSo8+-yUHMnKj&X+x2;@zr z!afGXNnU;Tah4?s4A*R5OOQu8xpy}XFJhf4o;Dkz$R_2lo4ri@a-YUcnDw!|(jc%< zWXyqfXd^-BHtleOxpYZx>5)Uthq~2&0DDL>y6( z%zrE;rI4@H{9X_2dz-;CtzJ3F=K&ts?R6GUe7e|*qEltoIny`&pC)qH{Fe`}4_r>i zv0^-yylpT&?xjA;GXL5PFS$c5S;~{Y096e8(~1E=$m#ApBg+h(**TiNp82l=$6 z3I%=DJ`{C%8yeEq6OFwYaA92kukqiWf?&fx!Qq)7l;xLmZk8tde4{@9)D|Yyg(7ez zwM@+yWp$^eMc0u1?in2&^aHdxWfmDlMsjPg8{9;IV8jRMR2Hjm)zN1=Bl2(y_C!IsY;|5}^q*Y%mH+0%b=G9ADlXr4d#H65MdxjsPa=~4Z7zLw9=ex8g* zqA&~24a{qk|ASMADFJcL9w?RW6-o=`BHY|o?Mt&%9ZI1r1B3Q1ez$Dk1IyAU9_cs~;pPYAj0f}Gy^9=l=x zC%28LQ-0{38Z{k=2l}7UxdMTXpd82xW9t6?Nv}W{MxQ%obUnQTzV3HP-(7#hGFF0# zHob<4cqE{(RXC6@_zaNK|9FSe;s-hsF$GG=eHV^L6y%D;?#?S9A{B!nA?SzSoRW`> z%6*4|qzC=~Py7ENCA73}pi*x3P){K%8Oe3)`|z(1-Tj5>Rz?r^Lfq!bvu!gM#7D62 zF~EhX2+JS)eu1_Bs|r=^|NbSR`TF7E78A=m;b1Exmk=TRA|99?-3$RQCYuf%VWE+x zv(`WQgRwv@yyuU^)eN+^BWExN&T;U15|A?&v~1vR2)Hz<6n~^3$}pQNJqgEC$z){` zjb&=K(HBeQm|-!W&L43l!mukAutN;);|qW^V~v4iemcm@WieI!i1fO>SaVXOG*%#! zJ=ozRHfRY?SQ`|SoF|SwQK3sylO)p_@OV-eXTHchm)_7i z3nQEO5iL$vMI}^W18YA6Os2u|-;4gYXVu0XXsx5oB_|VG-x=@Tve&MEWv}_l6|U0K zx6{++hlf!ht62yzgd>B$Iq5@7nd05QP0X}=L&^C~TygvA#z~HYX{%6l6}~}pP-?LM ziy)ZToq&dcLE3#uWjh8)J$PqeaC!-0FWBc0*pjzS8+7#G&QHo z8bDS2W-aNXW=WnJ;L`2A%g=X`_&u$FGy?m!hEf~!|CXDc*8h{62225Igzv;m*3B2Z zZjReJnhS@1*x!ZIR08Zs7mYW1Ay->zOna*W$)T&@rK_O|^IwA9;L<&0>U^WdW+ z%YPOq+zi9sLq}JB44~hHeAkr9j@l#to3oUFd#!@GD=3)?2}%aHt75QXnSC~s;|t5Yw+w9ga(NsD`Fv${&1uv#gi|uLn~aWDWn;V^A%yM^2YQ7vOPB`#xQh14 zleEfO!PK){Ubtq9GvZ3F3C@Sq@zmo4t=29ra+YMJGIz`+d^?KfNK2xEWI&%~$p7Su zfa%_HMR2y!;PTfJo_BS+K*L8`7x3> z0_ZsqKL4paKWs39Gg$+lD&MSWYifV0KHYe|0wxqy@l0?0$IlPw6X}2Ne~OsN4;W>O zWTp_Xd2-YX!3djIEB!Z-cV{yH7&2q-2ZY<@kG{osqT`eqq5tOqpV2&0!F-z4QA(Kr z?+IVe`3B*4x7pnPXWNDJ9V#b{-Cmej-Lt!2>u15eD;G;Y6d>Tnby}vRhfr82x?2og zuUS4?eEqB?lm4dJ4jJh|j$j9S`PAcjm&|MpD3OmcC;psxJuiDkbMq)f)S&|EnGz@o z2+dw3k(-fOGV;rk*do@jXC2{`Arw!IGm(iQ}`PvU>34-__9xt7Uf6X*qpbV78$|qCfl@6U;UqlThOY@-#JVVX6 zXi+RrIO&Vc63&0SQKAHJ5Rj-~jI&(N$z|2JMBm`6s2)b4D-eb>8h*H@Dunog9n2BT z9n2Rj5G*WHLgIe&3FBnw?6|g2^A)bhArOOzH!>>hGv`U~*x4u&&0LbV&CiSQOowX2 zhIuK(UWZ63TohGmzqe$2VrK1n}xCrft z=!!h^PC$aDG90t>w@`jeP^ubL6pn6rynjkvQB*vs$r0=u*EwOk#fx!g{mu-fN<^C6 ze_%Q1J9j(;87o_rKN?YA!K7h+w%=YFo1nCBzny(1ND317BnjK3V?I0l#?0_Ui6cZx zchka{3gs$z(kDe5cz2(AzXT(pIi=HId8y&y{0P^4AJ5R=Z*Za3zu6D{PBtRNhHNUB z8%WvF=q`1aJcFj~pWk(5t0HQsoK%pJ5hBvvj4#AZWZ{GS`M(NB310wVyFvuzhczJo zqOrC9+?beH!)kqodUL_h{&KOIqLg23dN`0hvsc+#SVCC5!(Ko`Z2f9D!YweLiX*fa zEXr+bX%H-YUjgtV)2u-4GwE!u=Wn{B(^5yt^GiiBzk}3EA0%H##(ox7_K$4G5T_@? zJj}A(4>{j*jL;-%taf+()(q2mbaR*?guRpbSsA#Bf z?&!8%6jynSE(^*$nl+F-gHoQfwr?obe(3{P>|MIHV1GWriH`~b%D(|~$I0bRYbWIG zbp8GQ-<2?MIaq zW**Sk4dv>q?q8aP3Ee#m(Tw}XU|E>ouhxTFy+|bRCl3?Beb-Z%e!rKCk0ms;UJgpe z{G5qAL)9rqg}Qx}&Q4AeE@1tSP%+V6^m226_Ahup9dXB@PGk#B$#63?FuA<=U%row zHXZC0L?l`EyknL-ut+PnsW0|n%5a{U(xEarK{djWms1yUGtLw}&*aqY=|rTMkylmGZg}$*Pu#qr~m2=;0ZMS-dmFIRB-YV&-15ehR zL9Z(BrZV;!A4e**R$tHj!x_-l)rwz$ldFR?)`_FZU_3#GP#lJCTB6nNqn!>N2zujT z@C6mMTUFPZ!vX#!Pf5!1oG>z~#giTnAXc6BBI|6JssvNZzFl8OboNDqz96r;ax(Ar zj@X%}Khx<|)VTqSM$B0*MfTywSDV`N1{QV8CZB!=IrF%j%rw+s> zdHLs-!0^pQ%REwCsON_b5*ZmZLEf9gg`MOcRS@jRf_^epW4G#5ee;&IV;B$ECJ2@n z8k^mt^q2=vv#JcqVux${?kTRxzlC%bQrd{JG~A|5Y`846$8WC=tIIUxo$-DL|LUY|{BnD47;1*ZSV;iZ5LfC0 zhtX08{|4+Fe<%bw?|Buq#A=64H~Tx{2&Je+Yl-(oS7sxDh_q>E4hd6;6+ZS#qBJ|5 zeu3%ItpdZ7|IICWUsU7w^vJZ-R*ny1UNy+5pOEzQ5KNhGcYI(RgRbX{-`9xo?tybQV z6^!L%yrl`e4AR>AIqe!L4B6CesK>4eppL|NNnb2dw7f!HaTn$$>;8UNy_iP3Z`*Xz z>#ckAHVmu*p#=iS=VE-ut(e{G_PFd?e0)6}BdikaC;J2YIUuY5dYx?T#VnrlCNxnvL9i}GQf7mYi1f9iCcEu*{%%0)<%(B^_$!#j z>vn~2qoMg^eR1}cVip6XAxQ<7(COgZMJovQ6@k&W`Zp^r(M=X<5=TPjN2le}V0f^P) zKVaC|+tM+QigqP(*RNR}fQbl(ZQ44+zMp0@RbPWxxUR}C|4@|7eKHh~2zs<;xlsxx zsZfM;M_LlZ#KZphGplp<&Q6$zaZ7FSa+)+x(9iDM-(9Xx6&Rq1D0W}>w1=HuNrw9w zBgL%pl%6Cz(Wiy5TevWh#8A$c2Mx<8rSsf)kVgwW$E@#L)hL8CT9=Jc_Z$X zlZasRLFAq}=tTyR*7suw#hcYu}Z zdBmMx*8r#CYQK)UF~_+3!6`9$y>xS7DCl=`o*kNEe_VXTN%X2oNllB`(m0MTxM)q) zY6V5>Thn^ur1QP|_pZ;$g=9A3OLSCO@5|u>VxWY_>DYDld!|-&Q;2at9e)y$;<&`; zE&kdYY{Rp)+S`y&SL+pvIR`$)oZ7`a{r3VT8&lMJ-cIY01kHkrRQl@i*47 zeh~wA%!OZkhh>0CPUf$kssZ;xF@>=5Ir_>xCWUHR!gm-Y2T;KC*EmD1*b4_-U$hnF zhXBiv1ygfwI{#3eO91q-T;8xWr1(UK+3Ux`Npi?#F_g^jVz3Q_aE-3jg=weQS1!A! z2$+wL3lShs?+xb^pqv-~^5eyl(57!*A{aJAw<|Oy)txW;^!1!CwB}A5057Y)713j6Z!)t5pUriv zg5;%B=GW%}4sbjK`Y_#{UfZ%d2j{6t^HRUv>P~sNKmPSf`5U?eY$4?LL%1v%zuwr; z`7ITDAkY^BZ2Iz`k&L#=aZFUq%`1k?o;5F^W*(!;FkYu zGDf|vUh9`xbQFVmaIQwhl$d_Ux2vuPAPE2>0AONk3HqF38Wk+7as|_f`HtM^>Gg2 zi!ak!S@t}#>4{M1u3KzuSvnU3HLdqGqm992O;|*8^<%>=?QN9;&(?I-%o5Dv-j+l= zWi9W$KdI^6=|Af7b?DA@B|R{Ac+(ZeMg~`> z_5Y5Y4P&BD>L!J{>fdevXN8joOPwxjx6RIQLK{dZ|ILkQHh1s?Q1cBo!@6^sCzl~+ zTf|-<#?Hle7`CpqILW7^hV`m9utgTRM%Tyh+2dS{wOJ;n4#t`I?jE|2!<6|W!!aWt zU?z#^hw-{)xHG)6@pi%{dAQ)Zf4W52V@}shHE2-}w6>~gsQmic?T{wzn>IW!pIu6; zdXcn5=M|ybmE(B6kcKg=(YuS__O%;*Q3K8F0^^f_>)yxO+Z?4$raCo9mu~;D8+msJ z>`mAc`fV*&;vVERC+`#PgSfllllXRmij6AnxHS#`dj2N3drYk#Z!h}gBIQiRW7|Yl zE@F0PCbi>`KwLV0%Ky@XDGPs*N-@70qZu@|Z1M3J1(#<@?tVodD0VePdR+LS{qJ9` zr|D|2t7(^8rxSR_)qyI7Q%OSjV`J~&O-3o5F0+d9wBIN6Kh-l={A?}^I6DP4ri>=C zqEwGcur@iR&bwWNOMYk=2V(6zr8lg6O03ftcYHncDz48vRKOysYyhNBVyYy*dJ}(r zz>K?MKneapP!9fY^P8ZsXHrN)+>z+F)#q|EXbbmTm6Qp+^M4C0#X z-KHvZi-1;1pu79|t!tO@UtPPR@w62>LV+^zgWg+G`6tV^}hs z`kf|F68q8F#Tyj-Tcm|bWW!+d_sI8ypV2!*0!EB)MbLkh_s|suJ@Wp=ipve*N+i#6 zx^qRtKHj)V#dA8)XI%oX&rhpSu#DlvypVBhP^t1SzRDIMo$rB6XHUmX&ug-{x^C`L zvZ#B%+_lV}nK?C3l*(Dx05&Eq0`HkzAD|Kvx}UX36e<+qVY4SEA7B>OaR?lM~v1R zhHJk+E?cS9eCP8s#^3hjY7B{pmtKRz=hFz#`&y>4REp=99AwH0lS}c_22HNZlHov}LQF}ZbY=6~NPtIzPI92Z!+1O!Q z#o{rY{al#D{TTKU^<|W{p+(wi&b&44_6_LBxMnahF}gf2ch>;9c&$;w<3<@I{Y(0f z(PR!i8!GHzg|By$*Y<*(?n>^l2xa|IH`^Or8yuK!|4wWM*Vf68`Y#W|`F( zbocdkr!Z&&5Uq{n-0HZRo$kMDpQSl4g&i2e|hP+yneGSlB3~mxkfCeiGc>td< z@s-UKmpCVLS)8G5e~ zzE}DuLDnSSm>fQ*;L?@IPDMSD9qWkRF6{FgFtGl(J;sBHYi)M=P$_tZs1G2+=r5w9 zYs2xd^EIO@)qjZ9V7CxxHrIQ>0-vC8SP{k2@AJGqm4xG~9sH$@UC4Y3>b)&{X}y30 z9w?+fheRk`qEm&jw0s3X+{m8F=>Qvd0NbeEz)0x+ExhbUi-cx_(M;h2bIcgeBSjgC z8SL3VQ7m8AW;n5h^a-Ygxl(jYgWE`23Krg3;pWgy5ALL(;#79-$6Ms~)i!bm zK4@~i9B6yF!opy!0z$)Ioll<_qi{_nrzyoWu^6|%4hoJo-$nqH>^KIkQuC!i$%Lj^n auShzB3e9HMUw;7uKtF{f1xxvVdH)a7-$h*j literal 0 HcmV?d00001 diff --git a/docs/images/OrderSorderState.png b/docs/images/OrderSorderState.png new file mode 100644 index 0000000000000000000000000000000000000000..1c75552e4047f1b6f80d8484243e9822521961a5 GIT binary patch literal 12657 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 Date: Tue, 2 Nov 2021 13:44:41 +0800 Subject: [PATCH 11/42] Fix naming issues --- src/main/java/seedu/address/MainApp.java | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 7327eb7c736..8df5a114299 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -86,36 +86,37 @@ public void init() throws Exception { * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - Optional addressBookOptional; + Optional inventoryOptional; ReadOnlyInventory initialData; Optional transactionListOptional; ReadOnlyTransactionList transactionList; Optional bookKeepingOptional; ReadOnlyBookKeeping bookKeeping; try { - addressBookOptional = storage.readInventory(); + inventoryOptional = storage.readInventory(); transactionListOptional = storage.readTransactionList(); bookKeepingOptional = storage.readBookKeeping(); - if (!addressBookOptional.isPresent()) { + if (inventoryOptional.isEmpty()) { logger.info("Data file not found. Will be starting with a sample AddressBook"); } - if (!transactionListOptional.isPresent()) { + if (transactionListOptional.isEmpty()) { logger.info("Transaction not found"); } - if (!bookKeepingOptional.isPresent()) { + if (bookKeepingOptional.isEmpty()) { logger.info("BookKeeping not found"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleInventory); + initialData = inventoryOptional.orElseGet(SampleDataUtil::getSampleInventory); transactionList = transactionListOptional .orElseGet(() -> new TransactionList(new ArrayList())); - bookKeeping = bookKeepingOptional.orElseGet(() -> new BookKeeping()); + bookKeeping = bookKeepingOptional.orElseGet(BookKeeping::new); + } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); + logger.warning("Data file not in the correct format. Will be starting with an empty inventory"); initialData = new Inventory(); transactionList = new TransactionList(); bookKeeping = new BookKeeping(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty inventory"); initialData = new Inventory(); transactionList = new TransactionList(); bookKeeping = new BookKeeping(); @@ -182,7 +183,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty inventory"); initializedPrefs = new UserPrefs(); } From c102554b9180ae9abcb40ccd6c0ec5e319a3df10 Mon Sep 17 00:00:00 2001 From: bryanwee023 Date: Tue, 2 Nov 2021 13:44:56 +0800 Subject: [PATCH 12/42] Fix parsing of prices --- .../seedu/address/commons/core/Messages.java | 7 ++-- .../logic/parser/AddCommandParser.java | 4 +-- .../logic/parser/EditCommandParser.java | 6 ++-- .../address/logic/parser/ParserUtil.java | 33 +++++-------------- .../java/seedu/address/model/item/Item.java | 6 ++-- .../address/model/util/SampleDataUtil.java | 2 +- src/main/java/seedu/address/ui/ItemCard.java | 4 +-- 7 files changed, 23 insertions(+), 39 deletions(-) diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 3891368a09a..c0111bdcb54 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -11,8 +11,11 @@ public class Messages { public static final String MESSAGE_ITEMS_LISTED_OVERVIEW = "%1$d items listed!"; public static final String MESSAGE_INVALID_COUNT_INTEGER = "The count provided must be positive!"; public static final String MESSAGE_INVALID_COUNT_FORMAT = "The count provided must be integer!"; - public static final String MESSAGE_INVALID_SALESPRICE_FORMAT = "The sales price provided must be positive integer!"; - public static final String MESSAGE_INVALID_COSTPRICE_FORMAT = "The cost price provided must be positive integer!"; + + public static final String MESSAGE_INVALID_PRICE_FORMAT = "Prices provided must be numerical values!"; + public static final String MESSAGE_INVALID_PRICE_RANGE = + "Prices provided must be at least $0 and less than $10,000,000!"; + public static final String MESSAGE_INVALID_ID_FORMAT = "The id provided must be integer!"; public static final String MESSAGE_INVALID_ID_LENGTH_AND_SIGN = "The id provided must be positive" + " and at most 6 digits!"; diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index db2e30c4149..20fdfafb221 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -54,11 +54,11 @@ public AddCommand parse(String args) throws ParseException { } // Parse salesPrice if (argMultimap.getValue(PREFIX_SALESPRICE).isPresent()) { - toAddDescriptor.setSalesPrice(ParserUtil.parseSalesPrice(argMultimap.getValue(PREFIX_SALESPRICE).get())); + toAddDescriptor.setSalesPrice(ParserUtil.parsePrice(argMultimap.getValue(PREFIX_SALESPRICE).get())); } // Parse costPrice if (argMultimap.getValue(PREFIX_COSTPRICE).isPresent()) { - toAddDescriptor.setCostPrice(ParserUtil.parseCostPrice(argMultimap.getValue(PREFIX_COSTPRICE).get())); + toAddDescriptor.setCostPrice(ParserUtil.parsePrice(argMultimap.getValue(PREFIX_COSTPRICE).get())); } // Parse tags if (argMultimap.getValue(PREFIX_TAG).isPresent()) { diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 0b2521fb7b8..1e4aa19581d 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -37,12 +37,10 @@ public EditCommand parse(String args) throws ParseException { PREFIX_COSTPRICE, PREFIX_SALESPRICE); Index index; - boolean editId = true; ItemDescriptor itemDescriptor = new ItemDescriptor(); if (argMultimap.getValue(PREFIX_NAME).isPresent()) { itemDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - editId = false; } if (argMultimap.getValue(PREFIX_ID).isPresent()) { itemDescriptor.setId(ParserUtil.parseId(argMultimap.getValue(PREFIX_ID).get())); @@ -51,10 +49,10 @@ public EditCommand parse(String args) throws ParseException { itemDescriptor.setCount(ParserUtil.parseCount(argMultimap.getValue(PREFIX_COUNT).get())); } if (argMultimap.getValue(PREFIX_COSTPRICE).isPresent()) { - itemDescriptor.setCostPrice(ParserUtil.parseCostPrice(argMultimap.getValue(PREFIX_COSTPRICE).get())); + itemDescriptor.setCostPrice(ParserUtil.parsePrice(argMultimap.getValue(PREFIX_COSTPRICE).get())); } if (argMultimap.getValue(PREFIX_SALESPRICE).isPresent()) { - itemDescriptor.setSalesPrice(ParserUtil.parseSalesPrice(argMultimap.getValue(PREFIX_SALESPRICE).get())); + itemDescriptor.setSalesPrice(ParserUtil.parsePrice(argMultimap.getValue(PREFIX_SALESPRICE).get())); } parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(itemDescriptor::setTags); diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 792ce99d3bf..24a58fc795d 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -76,37 +76,22 @@ public static Set parseTags(Collection tags) throws ParseException } /** - * Parses {@code String costPrice} into a {@code Double}. + * Parses {@code String price} into a {@code Double}. */ - public static Double parseCostPrice(String costPrice) throws ParseException { + public static Double parsePrice(String price) throws ParseException { + double priceValue; try { - Double.parseDouble(costPrice); + priceValue = Double.parseDouble(price); } catch (NumberFormatException e) { - throw new ParseException(Messages.MESSAGE_INVALID_COSTPRICE_FORMAT); + throw new ParseException(Messages.MESSAGE_INVALID_PRICE_FORMAT); } - if (Double.parseDouble(costPrice) > 0) { - return Double.parseDouble(costPrice); - } else { - throw new ParseException(Messages.MESSAGE_INVALID_COSTPRICE_FORMAT); - } - } - - /** - * Parses {@code String salesPrice} into a {@code Double}. - */ - public static Double parseSalesPrice(String salesPrice) throws ParseException { - try { - Double.parseDouble(salesPrice); - } catch (NumberFormatException e) { - throw new ParseException(Messages.MESSAGE_INVALID_SALESPRICE_FORMAT); + if (priceValue < 0 || priceValue >= 10000000) { + throw new ParseException(Messages.MESSAGE_INVALID_PRICE_RANGE); } - if (Double.parseDouble(salesPrice) > 0) { - return Double.parseDouble(salesPrice); - } else { - throw new ParseException(Messages.MESSAGE_INVALID_SALESPRICE_FORMAT); - } + // Round off to 2 decimal places + return (double) Math.round(priceValue * 100) / 100; } /** diff --git a/src/main/java/seedu/address/model/item/Item.java b/src/main/java/seedu/address/model/item/Item.java index 1c601599bc8..f792d433b8c 100644 --- a/src/main/java/seedu/address/model/item/Item.java +++ b/src/main/java/seedu/address/model/item/Item.java @@ -178,10 +178,8 @@ public String toString() { .append(getId()) .append("; count: ") .append(getCount()) - .append("; costPrice: ") - .append(getCostPrice()) - .append("; salesPrice: ") - .append(getSalesPrice()); + .append(String.format("; costPrice: $%.2f", getCostPrice())) + .append(String.format("; salesPrice: $%.2f", getSalesPrice())); Set tags = getTags(); if (!tags.isEmpty()) { diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 6a2aa2a7da6..1e7bf9bca07 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -24,7 +24,7 @@ public static Item[] getSampleItems() { getTagSet("baked"), 3.0, 5.0), new Item(new Name("Oreo Cheesecake"), 109128, 1, getTagSet("desert"), 4.0, 5.0), - new Item(new Name("Strawberry Shortcake"), 1991287, 2, + new Item(new Name("Strawberry Shortcake"), 199127, 2, getTagSet("desert"), 2.1, 3.2), new Item(new Name("Cold Brew Coffee"), 121858, 5, getTagSet("beverage"), 3.2, 4.4), diff --git a/src/main/java/seedu/address/ui/ItemCard.java b/src/main/java/seedu/address/ui/ItemCard.java index c06a1412f8d..a921c771e9f 100644 --- a/src/main/java/seedu/address/ui/ItemCard.java +++ b/src/main/java/seedu/address/ui/ItemCard.java @@ -53,8 +53,8 @@ public ItemCard(Item item, int displayedIndex) { name.setText(item.getName().fullName); id.setText(String.format("#%06d", item.getId())); count.setText(String.format("Quantity: %d", item.getCount())); - costPrice.setText(String.format("Cost Price: $ %s", item.getCostPrice())); - salesPrice.setText(String.format("Sales Price: $ %s", item.getSalesPrice())); + costPrice.setText(String.format("Cost Price: $ %.2f", item.getCostPrice())); + salesPrice.setText(String.format("Sales Price: $ %.2f", item.getSalesPrice())); item.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); From bd20062d632dd62f9ab76a7a26912809df3df84c Mon Sep 17 00:00:00 2001 From: bryanwee023 Date: Tue, 2 Nov 2021 13:45:18 +0800 Subject: [PATCH 13/42] Add tests for parsing prices --- .../address/logic/parser/ParserUtilTest.java | 63 ++++++++++++++----- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index f5e1cc47285..eff6453a623 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -21,19 +21,23 @@ public class ParserUtilTest { private static final String INVALID_NAME = "Pudding^"; private static final String INVALID_TAG = "#nice"; private static final String INVALID_COUNT_ZERO = "0"; - private static final String INVALID_COUNT_1 = "sweet"; - private static final String INVALID_COUNT_2 = "-1"; - private static final String INVALID_Id = "abc"; - private static final String INVALID_Id_2 = "-1"; - private static final String INVALID_Id_3 = "123"; + private static final String INVALID_COUNT_FORMAT = "sweet"; + private static final String INVALID_COUNT_NEGATIVE = "-1"; + private static final String INVALID_ID_FORMAT = "abc"; + private static final String INVALID_ID_NEGATIVE = "-1"; + private static final String INVALID_PRICE_FORMAT = "abc"; + private static final String INVALID_PRICE_NEGATIVE = "-1"; + private static final String INVALID_PRICE_OVERFLOW = "999999999.1"; private static final String VALID_NAME = "Pudding"; private static final String VALID_TAG_1 = "nice"; private static final String VALID_TAG_2 = "sweet"; private static final String VALID_COUNT_1 = "2"; private static final String VALID_COUNT_2 = "12"; - private static final String VALID_Id_1 = "223"; - private static final String VALID_Id_2 = "122489"; + private static final String VALID_ID_1 = "223"; + private static final String VALID_ID_2 = "122489"; + private static final String VALID_PRICE_1 = "1.21"; + private static final String VALID_PRICE_2 = "12.2121"; // Should be rounded to 2 decimal places private static final String WHITESPACE = " \t\r\n"; @@ -133,7 +137,7 @@ public void parseCount_null_throwsParseException() { @Test public void parseCount_invalidValue_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseCount(INVALID_COUNT_1)); + assertThrows(ParseException.class, () -> ParserUtil.parseCount(INVALID_COUNT_FORMAT)); } @Test @@ -143,7 +147,7 @@ public void parseCount_zeroNumber_throwsParseException() { @Test public void parseCount_negativeNumber_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseCount(INVALID_COUNT_2)); + assertThrows(ParseException.class, () -> ParserUtil.parseCount(INVALID_COUNT_NEGATIVE)); } @Test @@ -167,25 +171,54 @@ public void parseId_null_throwsParseException() { @Test public void parseId_invalidValue_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseId(INVALID_Id)); + assertThrows(ParseException.class, () -> ParserUtil.parseId(INVALID_ID_FORMAT)); } @Test public void parseId_negativeValue_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseId(INVALID_Id_2)); + assertThrows(ParseException.class, () -> ParserUtil.parseId(INVALID_ID_NEGATIVE)); } @Test public void parseId_validId_returnsId() throws Exception { - Integer expectedId = Integer.parseInt(VALID_Id_1); - Integer actualId = ParserUtil.parseId(VALID_Id_1); + Integer expectedId = Integer.parseInt(VALID_ID_1); + Integer actualId = ParserUtil.parseId(VALID_ID_1); assertEquals(expectedId, actualId); } @Test public void parseId_validId2_returnsId() throws Exception { - Integer expectedId = Integer.parseInt(VALID_Id_2); - Integer actualId = ParserUtil.parseId(VALID_Id_2); + Integer expectedId = Integer.parseInt(VALID_ID_2); + Integer actualId = ParserUtil.parseId(VALID_ID_2); assertEquals(expectedId, actualId); } + + @Test + public void parsePrice_validPrices_returnsPrice() throws Exception { + // 2 decimal places + double expectedPrice = Double.parseDouble(VALID_PRICE_1); + double actualPrice = ParserUtil.parseId(VALID_PRICE_1); + assertEquals(expectedPrice, actualPrice); + + // Extra decimal places + expectedPrice = Math.round(Double.parseDouble(VALID_PRICE_1) / 100) * 100; + actualPrice = ParserUtil.parseId(VALID_PRICE_1); + assertEquals(expectedPrice, actualPrice); + } + + @Test + public void parsePrice_negativePrice_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseId(INVALID_PRICE_NEGATIVE)); + } + + @Test + public void parsePrice_largePrice_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseId(INVALID_PRICE_OVERFLOW)); + } + + + @Test + public void parsePrice_notNumbers_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseId(INVALID_PRICE_FORMAT)); + } } From 6716faff4ce70bc8dd2adf87ab9bcd8d0a733d6d Mon Sep 17 00:00:00 2001 From: bryanwee023 Date: Tue, 2 Nov 2021 15:55:16 +0800 Subject: [PATCH 14/42] Fix parsing issues in find command --- .../address/commons/util/StringUtil.java | 34 ++++---- .../address/logic/commands/FindCommand.java | 77 +++++-------------- .../logic/parser/FindCommandParser.java | 43 ++++++----- .../address/logic/parser/ParserUtil.java | 24 ++++-- .../model/item/IdContainsNumberPredicate.java | 8 +- .../java/seedu/address/model/item/Item.java | 3 +- .../item/NameContainsKeywordsPredicate.java | 5 +- .../item/TagContainsKeywordsPredicate.java | 16 ++-- .../java/seedu/address/model/tag/Tag.java | 2 +- 9 files changed, 100 insertions(+), 112 deletions(-) diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index b9ae234d35e..87ee68f8e04 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -7,6 +7,8 @@ import java.io.StringWriter; import java.util.Arrays; import java.util.Random; +import java.util.stream.IntStream; +import java.util.stream.Stream; /** * Helper functions for handling strings. @@ -15,29 +17,33 @@ public class StringUtil { private static final long DEFAULT_RANDOM_SEED = 10L; /** - * Returns true if the {@code sentence} contains the {@code word}. - * Ignores case, but a full word match is required. + * Returns true if any of the phrases in {@code sentence} starts with the {@code query}. + * Ignores case. *
examples:
      *       containsWordIgnoreCase("ABc def", "abc") == true
-     *       containsWordIgnoreCase("ABc def", "DEF") == true
-     *       containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
+     *       containsWordIgnoreCase("ABc def", "DE") == true
+     *       containsWordIgnoreCase("ABc def", "def ghi") == false
      *       
* @param sentence cannot be null - * @param word cannot be null, cannot be empty, must be a single word + * @param query cannot be null */ - public static boolean containsWordIgnoreCase(String sentence, String word) { + public static boolean phrasesStartsWithQuery(String sentence, String query) { requireNonNull(sentence); - requireNonNull(word); + requireNonNull(query); - String preppedWord = word.trim(); - checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); - //checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word"); + String preppedQuery = query.trim().toLowerCase(); + checkArgument(!preppedQuery.isEmpty(), "query parameter cannot be empty"); - String preppedSentence = sentence; - String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); + String preppedSentence = sentence.toLowerCase(); + // Indexes of start of each word in the sentence + IntStream phrasesIndexes = IntStream.range(0, sentence.length() - 1).filter( + x -> sentence.charAt(x) != ' ' + && (x == 0 || sentence.charAt(x - 1) == ' ') + ); - return Arrays.stream(wordsInPreppedSentence) - .anyMatch(preppedWord::equalsIgnoreCase); + // True if any phrase starts with the query + return phrasesIndexes + .anyMatch(i -> preppedSentence.startsWith(preppedQuery, i)); } /** diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 0302c5d6766..fc2ca9a477d 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -3,15 +3,20 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ID; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.model.display.DisplayMode.DISPLAY_INVENTORY; import seedu.address.commons.core.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; import seedu.address.model.item.IdContainsNumberPredicate; +import seedu.address.model.item.Item; import seedu.address.model.item.NameContainsKeywordsPredicate; import seedu.address.model.item.TagContainsKeywordsPredicate; +import java.util.List; +import java.util.function.Predicate; + /** * Finds and lists all items in inventory whose name contains any of the argument keywords. * Keyword matching is case insensitive. @@ -23,52 +28,28 @@ public class FindCommand extends Command { public static final String MESSAGE_INVENTORY_NOT_DISPLAYED = "Can't find outside inventory mode. Please use `list` first"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all items whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Able to find multiple names and ids with one command by putting multiple flags" + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all items whose descrption matches any of " + + "the specified names, ids, or tags, and displays them as a list with index numbers.\n" + "\n Parameters: " + PREFIX_NAME + "NAME " + PREFIX_ID + "ID " + + PREFIX_TAG + "TAG " + "\n Example: " + COMMAND_WORD + " " + PREFIX_ID + "019381 or " + COMMAND_WORD + " " + PREFIX_NAME + "Banana " + PREFIX_NAME + "bread."; - private final NameContainsKeywordsPredicate namePredicate; - private final IdContainsNumberPredicate idPredicate; - private final TagContainsKeywordsPredicate tagPredicate; + private final List> predicates; /** * Creates FindCommand in the case of query by name * - * @param namePredicate name of the item that the user is finding + * @param predicates list of predicates that describe the item(s) that the user is finding. + * (list cannot be empty). */ - public FindCommand(NameContainsKeywordsPredicate namePredicate) { - this.namePredicate = namePredicate; - this.idPredicate = null; - this.tagPredicate = null; - } - - /** - * Creates FindCommand in the case of query by id - * - * @param idPredicate id of the item that the user is finding - */ - public FindCommand(IdContainsNumberPredicate idPredicate) { - this.idPredicate = idPredicate; - this.namePredicate = null; - this.tagPredicate = null; - } - - /** - * Creates FindCommand in the case of query by tag - * - * @param tagPredicate tag of the item that the user is finding - */ - public FindCommand(TagContainsKeywordsPredicate tagPredicate) { - this.idPredicate = null; - this.namePredicate = null; - this.tagPredicate = tagPredicate; + public FindCommand(List> predicates) { + assert predicates.size() > 0; + this.predicates = predicates; } @Override @@ -79,15 +60,10 @@ public CommandResult execute(Model model) throws CommandException { throw new CommandException(MESSAGE_INVENTORY_NOT_DISPLAYED); } - if (namePredicate != null) { - model.updateFilteredItemList(DISPLAY_INVENTORY, namePredicate); - } - if (idPredicate != null) { - model.updateFilteredItemList(DISPLAY_INVENTORY, idPredicate); - } - if (tagPredicate != null) { - model.updateFilteredItemList(DISPLAY_INVENTORY, tagPredicate); - } + Predicate combinedPredicate = item -> + predicates.stream().anyMatch(predicate -> predicate.test(item)); + + model.updateFilteredItemList(DISPLAY_INVENTORY, combinedPredicate); return new CommandResult( String.format(Messages.MESSAGE_ITEMS_LISTED_OVERVIEW, model.getFilteredDisplayList().size())); @@ -95,20 +71,9 @@ public CommandResult execute(Model model) throws CommandException { @Override public boolean equals(Object other) { - if (idPredicate != null) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && idPredicate.equals(((FindCommand) other).idPredicate)); // state check - } - if (namePredicate != null) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && namePredicate.equals(((FindCommand) other).namePredicate)); // state check - } else { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && tagPredicate.equals(((FindCommand) other).tagPredicate)); - } + return other == this // short circuit if same object + || (other instanceof FindCommand // instanceof handles nulls + && predicates.equals(((FindCommand) other).predicates)); // state check } } diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index ccca2626bcc..5ebe0a68521 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -5,13 +5,18 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.function.Predicate; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.item.IdContainsNumberPredicate; +import seedu.address.model.item.Item; import seedu.address.model.item.NameContainsKeywordsPredicate; import seedu.address.model.item.TagContainsKeywordsPredicate; +import seedu.address.model.tag.Tag; /** * Parses input arguments and creates a new FindCommand object @@ -25,10 +30,7 @@ public class FindCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public FindCommand parse(String args) throws ParseException { - boolean areIds = false; - List id; - List names; - List tags; + String trimmedArgs = args.trim(); if (trimmedArgs.isEmpty()) { throw new ParseException( @@ -43,27 +45,28 @@ public FindCommand parse(String args) throws ParseException { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } - // Parse id - if (!argMultimap.getValue(PREFIX_ID).isEmpty()) { - areIds = true; + List> predicates = new ArrayList<>(); + + // Add name predicate if name(s) specified + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + predicates.add( + new NameContainsKeywordsPredicate(argMultimap.getAllValues(PREFIX_NAME)) + ); } - // Parse tag - if (!argMultimap.getValue(PREFIX_TAG).isEmpty()) { - tags = argMultimap.getAllValues(PREFIX_TAG); - return new FindCommand((new TagContainsKeywordsPredicate(tags))); + // Add id predicate if id(s) specified + if (argMultimap.getValue(PREFIX_ID).isPresent()) { + Collection queryIds = ParserUtil.parseIds(argMultimap.getAllValues(PREFIX_ID)); + predicates.add(new IdContainsNumberPredicate(queryIds)); } - // Initialise the list of names or id - if (areIds) { - id = argMultimap.getAllValues(PREFIX_ID); - for (String element : id) { - ParserUtil.parseId(element); - } - return new FindCommand((new IdContainsNumberPredicate(id))); + // Add tag predicate if tag(s) specified + if (argMultimap.getValue(PREFIX_TAG).isPresent()) { + Collection queryTags = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + predicates.add(new TagContainsKeywordsPredicate(queryTags)); } - names = argMultimap.getAllValues(PREFIX_NAME); - return new FindCommand((new NameContainsKeywordsPredicate(names))); + + return new FindCommand(predicates); } } diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 24a58fc795d..f8aa3ed202b 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -95,19 +95,33 @@ public static Double parsePrice(String price) throws ParseException { } /** - * Parses {@code String count} into a {@code id}. + * Parses {@code String id} into an {@code Integer}. */ public static Integer parseId(String id) throws ParseException { + Integer idValue; try { - Integer.parseInt(id); + idValue = Integer.parseInt(id); } catch (NumberFormatException e) { throw new ParseException(Messages.MESSAGE_INVALID_ID_FORMAT); } - if (id.length() <= 6 && Integer.parseInt(id) > 0) { - return Integer.parseInt(id); - } else { + + if (id.length() != 6 || idValue < 0) { throw new ParseException(Messages.MESSAGE_INVALID_ID_LENGTH_AND_SIGN); } + + return idValue; + } + + /** + * Parses {@code Collection idStrings} into a {@code Set}. + */ + public static Set parseIds(Collection idStrings) throws ParseException { + requireNonNull(idStrings); + final Set idSet = new HashSet<>(); + for (String idString : idStrings) { + idSet.add(parseId(idString)); + } + return idSet; } /** diff --git a/src/main/java/seedu/address/model/item/IdContainsNumberPredicate.java b/src/main/java/seedu/address/model/item/IdContainsNumberPredicate.java index cc95adde069..6c99a87de82 100644 --- a/src/main/java/seedu/address/model/item/IdContainsNumberPredicate.java +++ b/src/main/java/seedu/address/model/item/IdContainsNumberPredicate.java @@ -1,6 +1,8 @@ package seedu.address.model.item; +import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.function.Predicate; import seedu.address.commons.util.StringUtil; @@ -10,16 +12,16 @@ * Only output items with id that matches exactly with the query */ public class IdContainsNumberPredicate implements Predicate { - private final List keynumbers; + private final Collection keynumbers; - public IdContainsNumberPredicate(List keynumbers) { + public IdContainsNumberPredicate(Collection keynumbers) { this.keynumbers = keynumbers; } @Override public boolean test(Item item) { return keynumbers.stream() - .anyMatch(keynumbers -> StringUtil.containsWordIgnoreCase(item.getId().toString(), keynumbers)); + .anyMatch(keynumber -> keynumber.equals(item.getId())); } @Override diff --git a/src/main/java/seedu/address/model/item/Item.java b/src/main/java/seedu/address/model/item/Item.java index f792d433b8c..3bc6ac9a9c8 100644 --- a/src/main/java/seedu/address/model/item/Item.java +++ b/src/main/java/seedu/address/model/item/Item.java @@ -174,8 +174,7 @@ public int hashCode() { public String toString() { final StringBuilder builder = new StringBuilder(); builder.append(getName()) - .append("; id: ") - .append(getId()) + .append(String.format("; count: %06d", getId())) .append("; count: ") .append(getCount()) .append(String.format("; costPrice: $%.2f", getCostPrice())) diff --git a/src/main/java/seedu/address/model/item/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/item/NameContainsKeywordsPredicate.java index 03daf3a5735..ad94b3b61db 100644 --- a/src/main/java/seedu/address/model/item/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/item/NameContainsKeywordsPredicate.java @@ -17,9 +17,8 @@ public NameContainsKeywordsPredicate(List keywords) { @Override public boolean test(Item item) { - boolean multipleWord = keywords.stream() - .anyMatch(keyword -> StringUtil.containsMultipleWord(item.getName().fullName, keyword)); - return multipleWord; + return keywords.stream() + .anyMatch(keyword -> StringUtil.phrasesStartsWithQuery(item.getName().fullName, keyword)); } @Override diff --git a/src/main/java/seedu/address/model/item/TagContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/item/TagContainsKeywordsPredicate.java index 0ca32d76cd1..5da35782012 100644 --- a/src/main/java/seedu/address/model/item/TagContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/item/TagContainsKeywordsPredicate.java @@ -1,18 +1,20 @@ package seedu.address.model.item; +import java.util.Collection; import java.util.List; import java.util.function.Predicate; import seedu.address.commons.util.StringUtil; +import seedu.address.model.tag.Tag; /** * Tests that a {@code Item}'s {@code tag} matches any of the keywords given. */ public class TagContainsKeywordsPredicate implements Predicate { - private final List keywords; + private final Collection keytags; - public TagContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; + public TagContainsKeywordsPredicate(Collection keytags) { + this.keytags = keytags; } @Override @@ -21,17 +23,15 @@ public boolean test(Item item) { if (item.getTags().isEmpty()) { return false; } - String itemTag = item.getTags().toString().substring(2, lengthOfItemTag - 2); - boolean multipleWord = keywords.stream() - .anyMatch(keyword -> StringUtil.containsMultipleWord(itemTag, keyword)); - return multipleWord; + + return item.getTags().stream().anyMatch(keytags::contains); } @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof TagContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((TagContainsKeywordsPredicate) other).keywords)); // state check + && keytags.equals(((TagContainsKeywordsPredicate) other).keytags)); // state check } } diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index 1406a5bd91a..7eaec4a085f 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -22,7 +22,7 @@ public class Tag { public Tag(String tagName) { requireNonNull(tagName); checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); - this.tagName = tagName; + this.tagName = tagName.toLowerCase(); } /** From 3f02d7fa4d4f629a52d115adb66e2dd01265716a Mon Sep 17 00:00:00 2001 From: bryanwee023 Date: Tue, 2 Nov 2021 16:03:16 +0800 Subject: [PATCH 15/42] Fix styling --- .../address/commons/util/StringUtil.java | 69 +------------------ .../address/logic/commands/FindCommand.java | 9 +-- .../model/item/IdContainsNumberPredicate.java | 4 -- .../item/TagContainsKeywordsPredicate.java | 2 - 4 files changed, 5 insertions(+), 79 deletions(-) diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 87ee68f8e04..cf593a8a2e1 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -5,16 +5,13 @@ import java.io.PrintWriter; import java.io.StringWriter; -import java.util.Arrays; import java.util.Random; import java.util.stream.IntStream; -import java.util.stream.Stream; /** * Helper functions for handling strings. */ public class StringUtil { - private static final long DEFAULT_RANDOM_SEED = 10L; /** * Returns true if any of the phrases in {@code sentence} starts with the {@code query}. @@ -37,8 +34,8 @@ public static boolean phrasesStartsWithQuery(String sentence, String query) { String preppedSentence = sentence.toLowerCase(); // Indexes of start of each word in the sentence IntStream phrasesIndexes = IntStream.range(0, sentence.length() - 1).filter( - x -> sentence.charAt(x) != ' ' - && (x == 0 || sentence.charAt(x - 1) == ' ') + x -> sentence.charAt(x) != ' ' + && (x == 0 || sentence.charAt(x - 1) == ' ') ); // True if any phrase starts with the query @@ -46,68 +43,6 @@ public static boolean phrasesStartsWithQuery(String sentence, String query) { .anyMatch(i -> preppedSentence.startsWith(preppedQuery, i)); } - /** - * Returns true if the {@code sentence} contains the {@code word}. - * Ignores case, but a full word match is required. - *
examples:
-     *       containsWordIgnoreCase("ABc def", "abc def") == true
-     *       containsWordIgnoreCase("ABc def", "abc DEF") == true
-     *       containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
-     *       
- * @param sentence cannot be null - * @param word cannot be null, cannot be empty, must be a single word - */ - public static boolean containsMultipleWord(String sentence, String word) { - requireNonNull(sentence); - requireNonNull(word); - if (sentence.length() == 0 || word.length() == 0) { - return false; - } - - String preppedWord = word.trim(); - checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); - String[] wordsInPreppedWord = preppedWord.split("\\s+"); - String preppedSentence = sentence; - String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); - - return StringUtil.equalArray(wordsInPreppedSentence, wordsInPreppedWord); - } - /** - * Checks whether first array string contains the second array string in exact order - * example: [aaa, bbb, ddd] contains [aaa, Bbb] but does not contain [aaa, ddd] - */ - public static boolean equalArray (String [] first, String [] second) { - int firstLength = first.length; - int secondLength = second.length; - if (firstLength == 0 || secondLength == 0) { - return false; - } - boolean doesMatch = false; - for (int i = 0; i < firstLength - secondLength + 1; i = i + 1) { - doesMatch = doesMatch || StringUtil.equalArrayElements(first, second, i); - } - return doesMatch; - } - /** - * Helper Function for equalArray - * Checks whether first string array from firstStringIndex is the same as - * second array string with all elements in same order - */ - public static boolean equalArrayElements (String [] first, String [] second, int firstStringIndex) { - if (firstStringIndex >= first.length) { - return false; - } - int indexOfFirst = firstStringIndex; - boolean doesMatch = true; - for (int j = 0; j < second.length; j = j + 1) { - if (!first[indexOfFirst].equalsIgnoreCase(second[j])) { - doesMatch = doesMatch && false; - break; - } - indexOfFirst = indexOfFirst + 1; - } - return doesMatch; - } /** * Returns a detailed message of the t, including the stack trace. */ diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index fc2ca9a477d..a600cba6dfb 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -6,16 +6,13 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.model.display.DisplayMode.DISPLAY_INVENTORY; +import java.util.List; +import java.util.function.Predicate; + import seedu.address.commons.core.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.item.IdContainsNumberPredicate; import seedu.address.model.item.Item; -import seedu.address.model.item.NameContainsKeywordsPredicate; -import seedu.address.model.item.TagContainsKeywordsPredicate; - -import java.util.List; -import java.util.function.Predicate; /** * Finds and lists all items in inventory whose name contains any of the argument keywords. diff --git a/src/main/java/seedu/address/model/item/IdContainsNumberPredicate.java b/src/main/java/seedu/address/model/item/IdContainsNumberPredicate.java index 6c99a87de82..cb844055262 100644 --- a/src/main/java/seedu/address/model/item/IdContainsNumberPredicate.java +++ b/src/main/java/seedu/address/model/item/IdContainsNumberPredicate.java @@ -1,12 +1,8 @@ package seedu.address.model.item; import java.util.Collection; -import java.util.List; -import java.util.Set; import java.util.function.Predicate; -import seedu.address.commons.util.StringUtil; - /** * Tests that a {@code Item}'s {@code Id} matches any of the keywords given. * Only output items with id that matches exactly with the query diff --git a/src/main/java/seedu/address/model/item/TagContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/item/TagContainsKeywordsPredicate.java index 5da35782012..ebeb7f33e53 100644 --- a/src/main/java/seedu/address/model/item/TagContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/item/TagContainsKeywordsPredicate.java @@ -1,10 +1,8 @@ package seedu.address.model.item; import java.util.Collection; -import java.util.List; import java.util.function.Predicate; -import seedu.address.commons.util.StringUtil; import seedu.address.model.tag.Tag; /** From 274a0ea1a03198c0654b9cf52095abf14467f4a9 Mon Sep 17 00:00:00 2001 From: bryanwee023 Date: Tue, 2 Nov 2021 18:27:17 +0800 Subject: [PATCH 16/42] Update tests --- .../address/commons/util/StringUtilTest.java | 163 +++++------------- .../logic/commands/CommandTestUtil.java | 2 +- .../logic/commands/FindCommandTest.java | 162 +++++++---------- .../logic/parser/AddressBookParserTest.java | 2 +- .../logic/parser/FindCommandParserTest.java | 107 ++++++++---- .../address/logic/parser/ParserUtilTest.java | 26 ++- .../item/IdContainsNumberPredicateTest.java | 14 +- .../java/seedu/address/testutil/ItemUtil.java | 4 +- 8 files changed, 201 insertions(+), 279 deletions(-) diff --git a/src/test/java/seedu/address/commons/util/StringUtilTest.java b/src/test/java/seedu/address/commons/util/StringUtilTest.java index 78f01622672..3657d61a0c9 100644 --- a/src/test/java/seedu/address/commons/util/StringUtilTest.java +++ b/src/test/java/seedu/address/commons/util/StringUtilTest.java @@ -48,42 +48,7 @@ public void isNonZeroUnsignedInteger() { } - //---------------- Tests for containsWordIgnoreCase -------------------------------------- - - /* - * Invalid equivalence partitions for word: null, empty, multiple words - * Invalid equivalence partitions for sentence: null - * The four test cases below test one invalid input at a time. - */ - - @Test - public void containsWordIgnoreCase_nullWord_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> StringUtil.containsWordIgnoreCase("typical sentence", null)); - } - @Test - public void containsMultipleWord_nullWord_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> StringUtil.containsMultipleWord("typical sentence", null)); - } - - @Test - public void containsWordIgnoreCase_emptyWord_throwsIllegalArgumentException() { - assertThrows(IllegalArgumentException.class, "Word parameter cannot be empty", () - -> StringUtil.containsWordIgnoreCase("typical sentence", " ")); - } - @Test - public void containsMultipleWord_emptyWord_throwsIllegalArgumentException() { - assertThrows(IllegalArgumentException.class, "Word parameter cannot be empty", () - -> StringUtil.containsMultipleWord("typical sentence", " ")); - } - - @Test - public void containsWordIgnoreCase_nullSentence_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> StringUtil.containsWordIgnoreCase(null, "abc")); - } - @Test - public void containsMultipleWord_nullSentence_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> StringUtil.containsMultipleWord(null, "abc")); - } + //---------------- Tests for phrasesStartWithQuery -------------------------------------- /* * Valid equivalence partitions for word: @@ -100,108 +65,64 @@ public void containsMultipleWord_nullSentence_throwsNullPointerException() { * Possible scenarios returning true: * - matches first word in sentence * - last word in sentence - * - middle word in sentence - * - matches multiple words + * - matches middle phrases + * - partial matches + * - matches phrases even if case different * * Possible scenarios returning false: - * - query word matches part of a sentence word - * - sentence word matches part of the query word + * - query is a substring but not a prefix of the phrase (e.g. query: "bc" and sentence: "abc") + * - only query's first word matches phrase (e.g. query: "aaa bbb" and sentence: "bbb ccc") * * The test method below tries to verify all above with a reasonably low number of test cases. */ @Test - public void equalArray_validInputs_correctResult() { + public void phrasesStartWithQuery_validInputs_returnsTrue() { //one word matches - String [] first = {"aaa", "bbb", "Ccc"}; - String [] second = {"aaa"}; - assertTrue(StringUtil.equalArray(first, second)); - //complete match - String [] first2 = {"aaa", "bbb", "Ccc"}; - String [] second2 = {"aaa", "bbb", "Ccc"}; - assertTrue(StringUtil.equalArray(first2, second2)); + String sentence = "aaa bbb ccc"; + String query = "aaa"; + assertTrue(StringUtil.phrasesStartsWithQuery(sentence, query)); + //two word match + String sentence2 = "aaa bbb ccc"; + String query2 = "bbb ccc"; + assertTrue(StringUtil.phrasesStartsWithQuery(sentence2, query2)); + //middle phrase match + String sentence3 = "aaa bbb ccc ddd"; + String query3 = "bbb ccc"; + assertTrue(StringUtil.phrasesStartsWithQuery(sentence3, query3)); + //partial word match + String sentence4 = "aaa bbb ccc ddd"; + String query4 = "bbb c"; + assertTrue(StringUtil.phrasesStartsWithQuery(sentence4, query4)); //different case - String [] first3 = {"aaa", "bbb", "Ccc"}; - String [] second3 = {"ccc"}; - assertTrue(StringUtil.equalArray(first3, second3)); - //empty string - String [] first4 = {"aaa", "bbb", "Ccc"}; - String [] second4 = {}; - assertFalse(StringUtil.equalArray(first4, second4)); - //partial string - String [] first5 = {"aaa", "bbb", "Ccc"}; - String [] second5 = {"aa"}; - assertFalse(StringUtil.equalArray(first5, second5)); + String sentence5 = "aaA bbb ccc"; + String query5 = "aaa bbB"; + assertTrue(StringUtil.phrasesStartsWithQuery(sentence5, query5)); } @Test - public void equalArrayElements_validInputs_correctResult() { - //wrong index - String [] first = {"aaa", "bbb", "Ccc"}; - String [] second = {"Ccc"}; - assertFalse(StringUtil.equalArrayElements(first, second, 1)); - //correct index - String [] first2 = {"aaa", "bbb", "Ccc"}; - String [] second2 = {"Ccc"}; - assertTrue(StringUtil.equalArrayElements(first2, second2, 2)); - //different case - String [] first3 = {"aaa", "bbb", "Ccc"}; - String [] second3 = {"ccc"}; - assertTrue(StringUtil.equalArrayElements(first3, second3, 2)); - //index out of bounds - String [] first4 = {"aaa", "bbb", "Ccc"}; - String [] second4 = {}; - assertFalse(StringUtil.equalArrayElements(first4, second4, 3)); - //partial string - String [] first5 = {"aaa", "bbb", "Ccc"}; - String [] second5 = {"aa"}; - assertFalse(StringUtil.equalArrayElements(first5, second5, 1)); + public void phrasesStartWithQuery_validInputs_returnsFalse() { + // substring match but not a prefix + String sentence = "abc def geh"; + String query = "ef"; + assertFalse(StringUtil.phrasesStartsWithQuery(sentence, query)); + // only first word of query matches phrase + String sentence2 = "aaa bbb ccc"; + String query2 = "bbb ddd"; + assertFalse(StringUtil.phrasesStartsWithQuery(sentence2, query2)); } @Test - public void containsWordIgnoreCase_validInputs_correctResult() { - - // Empty sentence - assertFalse(StringUtil.containsWordIgnoreCase("", "abc")); // Boundary case - assertFalse(StringUtil.containsWordIgnoreCase(" ", "123")); - - // Matches a partial word only - assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bb")); // Sentence word bigger than query word - assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bbbb")); // Query word bigger than sentence word - - // Matches word in the sentence, different upper/lower case letters - assertTrue(StringUtil.containsWordIgnoreCase("aaa bBb ccc", "Bbb")); // First word (boundary case) - assertTrue(StringUtil.containsWordIgnoreCase("aaa bBb ccc@1", "CCc@1")); // Last word (boundary case) - assertTrue(StringUtil.containsWordIgnoreCase(" AAA bBb ccc ", "aaa")); // Sentence has extra spaces - assertTrue(StringUtil.containsWordIgnoreCase("Aaa", "aaa")); // Only one word in sentence (boundary case) - assertTrue(StringUtil.containsWordIgnoreCase("aaa bbb ccc", " ccc ")); // Leading/trailing spaces - - // Matches multiple words in sentence - assertTrue(StringUtil.containsWordIgnoreCase("AAA bBb ccc bbb", "bbB")); + public void phrasesStartWithQuery_emptySentence_returnsFalse() { + String sentence = ""; + String query = "aaa"; + assertFalse(StringUtil.phrasesStartsWithQuery(sentence, query)); } - @Test - public void containsMultipleWord_validInputs_correctResult() { - - // Empty sentence - assertFalse(StringUtil.containsMultipleWord("", "abc")); // Boundary case - assertFalse(StringUtil.containsMultipleWord(" ", "123")); - - // Matches a partial word only - assertFalse(StringUtil.containsMultipleWord("aaa bbb ccc", "bb")); // Sentence word bigger than query word - assertFalse(StringUtil.containsMultipleWord("aaa bbb ccc", "bbbb")); // Query word bigger than sentence word - - // Matches word in the sentence, different upper/lower case letters - assertTrue(StringUtil.containsMultipleWord("aaa bBb ccc", "Bbb")); // First word (boundary case) - assertTrue(StringUtil.containsMultipleWord("aaa bBb ccc@1", "CCc@1")); // Last word (boundary case) - assertTrue(StringUtil.containsMultipleWord(" AAA bBb ccc ", "aaa")); // Sentence has extra spaces - assertTrue(StringUtil.containsMultipleWord("Aaa", "aaa")); // Only one word in sentence (boundary case) - assertTrue(StringUtil.containsMultipleWord("aaa bbb ccc", " ccc ")); // Leading/trailing spaces - - // Matches multiple words in sentence - assertTrue(StringUtil.containsMultipleWord("AAA bBb ccc bbb", "bbB")); - assertTrue(StringUtil.containsMultipleWord("AAA bBb ccc bbb", "bbB ccc")); - assertTrue(StringUtil.containsMultipleWord("AAA bBb ccc bbb", "bbB ccc bbb")); + public void phrasesStartWithQuery_emptyQuery_throwsIllegalArgument() { + String sentence = "aaa bbb ccc"; + String query = ""; + assertThrows(IllegalArgumentException.class, () -> StringUtil.phrasesStartsWithQuery(sentence, query)); } //---------------- Tests for getDetails -------------------------------------- diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index ca7d78153aa..40b7a1bccde 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -34,7 +34,7 @@ public class CommandTestUtil { public static final String VALID_NAME_DONUT = "Donut"; public static final String VALID_NAME_100PLUS = "100Plus"; public static final String VALID_NAME_H20 = "H20"; - public static final String VALID_ID_BAGEL = "123"; + public static final String VALID_ID_BAGEL = "094021"; public static final String VALID_ID_DONUT = "789013"; public static final String VALID_COUNT_BAGEL = "5"; public static final String VALID_COUNT_DONUT = "6"; diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java index 117880b7783..a197d4efe02 100644 --- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java @@ -2,21 +2,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static seedu.address.commons.core.Messages.MESSAGE_ITEMS_LISTED_OVERVIEW; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_ITEMS; import static seedu.address.model.display.DisplayMode.DISPLAY_INVENTORY; -import static seedu.address.model.display.DisplayMode.DISPLAY_OPEN_ORDER; -import static seedu.address.testutil.TypicalItems.CHOCOCHIP; -import static seedu.address.testutil.TypicalItems.DALGONA_COFFEE; -import static seedu.address.testutil.TypicalItems.EGGNOG; -import static seedu.address.testutil.TypicalItems.FOREST_CAKE; +import static seedu.address.testutil.TypicalItems.APPLE_PIE; +import static seedu.address.testutil.TypicalItems.BANANA_MUFFIN; import static seedu.address.testutil.TypicalItems.getTypicalInventory; -import java.util.Arrays; -import java.util.Collections; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; import org.junit.jupiter.api.Test; @@ -26,8 +23,9 @@ import seedu.address.model.TransactionList; import seedu.address.model.UserPrefs; import seedu.address.model.item.IdContainsNumberPredicate; +import seedu.address.model.item.Item; import seedu.address.model.item.NameContainsKeywordsPredicate; -import seedu.address.model.order.Order; +import seedu.address.model.item.TagContainsKeywordsPredicate; /** * Contains integration tests (interaction with the Model) for {@code FindCommand}. @@ -38,121 +36,95 @@ public class FindCommandTest { private Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs(), new TransactionList(), new BookKeeping()); - @Test - public void equals() { - NameContainsKeywordsPredicate firstNamePredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("first")); - NameContainsKeywordsPredicate secondNamePredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("second")); - IdContainsNumberPredicate firstIdPredicate = - new IdContainsNumberPredicate(Collections.singletonList("#140272")); - IdContainsNumberPredicate secondIdPredicate = - new IdContainsNumberPredicate(Collections.singletonList("#475272")); - - - FindCommand findNameFirstCommand = new FindCommand(firstNamePredicate); - FindCommand findNameSecondCommand = new FindCommand(secondNamePredicate); - FindCommand findIdFirstCommand = new FindCommand(firstIdPredicate); - FindCommand findIdSecondCommand = new FindCommand(secondIdPredicate); - - - // same Name type-> returns true - assertTrue(findNameFirstCommand.equals(findNameFirstCommand)); + private NameContainsKeywordsPredicate pieNamePredicate = + new NameContainsKeywordsPredicate(List.of(APPLE_PIE.getName().fullName)); + private IdContainsNumberPredicate pieIdPredicate = + new IdContainsNumberPredicate(List.of(APPLE_PIE.getId())); + private TagContainsKeywordsPredicate pieTagPredicate = + new TagContainsKeywordsPredicate(APPLE_PIE.getTags()); + private NameContainsKeywordsPredicate muffinNamePredicate = + new NameContainsKeywordsPredicate(List.of(BANANA_MUFFIN.getName().fullName)); + + // Returns a predicate that returns true if any of the given predicates will return true + private static Predicate combinePredicate(Predicate... predicates) { + return item -> { + for (int i = 0; i < predicates.length; i++) { + if (predicates[i].test(item)) { + return true; + } + } + return false; + }; + } - // same Id type-> returns true - assertTrue(findIdFirstCommand.equals(findIdFirstCommand)); + @Test + public void constructor_emptyList_throwsAssertionError() { + assertThrows(AssertionError.class, () -> new FindCommand(new ArrayList<>())); + } - // same Name values -> returns true - FindCommand findNameFirstCommandCopy = new FindCommand(firstNamePredicate); - assertTrue(findNameFirstCommand.equals(findNameFirstCommandCopy)); + @Test + public void equals() { - // same Id values -> returns true - FindCommand findIdFirstCommandCopy = new FindCommand(firstIdPredicate); - assertTrue(findIdFirstCommand.equals(findIdFirstCommandCopy)); + FindCommand findNameCommand = new FindCommand(List.of(pieNamePredicate)); + FindCommand findIdCommand = new FindCommand(List.of(pieTagPredicate)); - // different Name types -> returns false - assertFalse(findNameFirstCommand.equals(1)); + // same object + assertEquals(findNameCommand, findNameCommand); - // different Id types -> returns false - assertFalse(findIdFirstCommand.equals(1)); + // same predicate + assertEquals(findNameCommand, new FindCommand(List.of(pieNamePredicate))); - // null -> returns false - assertFalse(findNameFirstCommand.equals(null)); + // different types -> returns false + assertFalse(findNameCommand.equals(1)); // null -> returns false - assertFalse(findIdFirstCommand.equals(null)); - - // different Name -> returns false - assertFalse(findNameFirstCommand.equals(findNameSecondCommand)); + assertFalse(findNameCommand.equals(null)); - // different Id -> returns false - assertFalse(findIdFirstCommand.equals(findIdSecondCommand)); + // different predicates -> returns false + assertNotEquals(findNameCommand, findIdCommand); } @Test - public void execute_zeroNameKeywords_noItemFound() { - String expectedMessage = String.format(MESSAGE_ITEMS_LISTED_OVERVIEW, 0); - NameContainsKeywordsPredicate predicate = preparePredicateName(" "); - FindCommand command = new FindCommand(predicate); - expectedModel.updateFilteredItemList(DISPLAY_INVENTORY, predicate); + public void execute_existentName_itemFound() { + String expectedMessage = String.format(MESSAGE_ITEMS_LISTED_OVERVIEW, 1); + FindCommand command = new FindCommand(List.of(pieNamePredicate)); + expectedModel.updateFilteredItemList(DISPLAY_INVENTORY, pieNamePredicate); assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Collections.emptyList(), model.getFilteredDisplayList()); } @Test - public void execute_zeroIdKeywords_noItemFound() { - String expectedMessage = String.format(MESSAGE_ITEMS_LISTED_OVERVIEW, 0); - IdContainsNumberPredicate predicate = preparePredicateId(" "); - FindCommand command = new FindCommand(predicate); - expectedModel.updateFilteredItemList(DISPLAY_INVENTORY, predicate); + public void execute_existentId_itemFound() { + String expectedMessage = String.format(MESSAGE_ITEMS_LISTED_OVERVIEW, 1); + FindCommand command = new FindCommand(List.of(pieIdPredicate)); + expectedModel.updateFilteredItemList(DISPLAY_INVENTORY, pieIdPredicate); assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Collections.emptyList(), model.getFilteredDisplayList()); } @Test - public void execute_multipleNameKeywords_multipleItemsFound() { + public void execute_existentTag_itemFound() { String expectedMessage = String.format(MESSAGE_ITEMS_LISTED_OVERVIEW, 3); - NameContainsKeywordsPredicate predicate = preparePredicateName("Chocolate Egg Forest"); - FindCommand command = new FindCommand(predicate); - expectedModel.updateFilteredItemList(DISPLAY_INVENTORY, predicate); + FindCommand command = new FindCommand(List.of(pieTagPredicate)); + expectedModel.updateFilteredItemList(DISPLAY_INVENTORY, pieTagPredicate); assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Arrays.asList(CHOCOCHIP, EGGNOG, FOREST_CAKE), model.getFilteredDisplayList()); } @Test - public void execute_multipleIdKeywords_multipleItemsFound() { + public void execute_multiplePredicates_itemsFound() { String expectedMessage = String.format(MESSAGE_ITEMS_LISTED_OVERVIEW, 2); - IdContainsNumberPredicate predicate = preparePredicateId("444444 555555"); - FindCommand command = new FindCommand(predicate); - expectedModel.updateFilteredItemList(DISPLAY_INVENTORY, predicate); + FindCommand command = new FindCommand(List.of(pieIdPredicate, muffinNamePredicate)); + + Predicate combinedPredicate = combinePredicate(pieIdPredicate, muffinNamePredicate);; + expectedModel.updateFilteredItemList(DISPLAY_INVENTORY, combinedPredicate); assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Arrays.asList(CHOCOCHIP, DALGONA_COFFEE), model.getFilteredDisplayList()); } @Test - public void execute_displayNotInInventoryMode_failure() { - IdContainsNumberPredicate predicate = preparePredicateId("444444 555555"); - FindCommand command = new FindCommand(predicate); - - model.setOrder(new Order()); - model.updateFilteredDisplayList(DISPLAY_OPEN_ORDER, PREDICATE_SHOW_ALL_ITEMS); - String expectedMessage = FindCommand.MESSAGE_INVENTORY_NOT_DISPLAYED; + public void execute_multiplePredicatesSameItem_itemFound() { + String expectedMessage = String.format(MESSAGE_ITEMS_LISTED_OVERVIEW, 1); + FindCommand command = new FindCommand(List.of(pieIdPredicate, pieNamePredicate)); - assertCommandFailure(command, model, expectedMessage); - } - - /** - * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. - */ - private NameContainsKeywordsPredicate preparePredicateName(String userInput) { - return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); - } - - /** - * Parses {@code userInput} into a {@code IdContainsKeywordsPredicate}. - */ - private IdContainsNumberPredicate preparePredicateId(String userInput) { - return new IdContainsNumberPredicate(Arrays.asList(userInput.split("\\s+"))); + expectedModel.updateFilteredItemList(DISPLAY_INVENTORY, pieIdPredicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); } diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index 4445c7f2aa3..8f211431b46 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -103,7 +103,7 @@ public void parseCommand_find() throws Exception { List keywords = Arrays.asList("foo"); FindCommand command = (FindCommand) parser.parseCommand( FindCommand.COMMAND_WORD + " " + PREFIX_NAME + "foo"); - assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command); + assertEquals(new FindCommand(List.of(new NameContainsKeywordsPredicate(keywords))), command); } @Test diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java index 98d94b9d2e9..e2db97870ee 100644 --- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java @@ -1,22 +1,33 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_ID_FORMAT; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_ID_LENGTH_AND_SIGN; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ID_BAGEL; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_100PLUS; +import static seedu.address.logic.commands.CommandTestUtil.ID_DESC_BAGEL; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_ID_BAGEL; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_ID_BAGEL_2; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; +import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BAGEL; +import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_DONUT; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_BAKED; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BAGEL; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_DONUT; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_H20; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_BAKED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ID; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalItems.BAGEL; -import java.util.Arrays; +import java.util.HashSet; +import java.util.List; import org.junit.jupiter.api.Test; import seedu.address.logic.commands.FindCommand; import seedu.address.model.item.IdContainsNumberPredicate; import seedu.address.model.item.NameContainsKeywordsPredicate; +import seedu.address.model.item.TagContainsKeywordsPredicate; +import seedu.address.model.tag.Tag; public class FindCommandParserTest { @@ -24,54 +35,78 @@ public class FindCommandParserTest { @Test public void parse_emptyArg_throwsParseException() { - assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } @Test - public void parse_validIdArgs_returnsFindCommand() { - // no leading and trailing whitespaces + public void parse_singleArgs_returnsFindCommand() { + // name prefix + List nameList = List.of(VALID_NAME_BAGEL); FindCommand expectedFindCommand = - new FindCommand(new IdContainsNumberPredicate(Arrays.asList("140262"))); - assertParseSuccess(parser, " id/140262", expectedFindCommand); - } + new FindCommand(List.of(new NameContainsKeywordsPredicate(nameList))); + assertParseSuccess(parser, NAME_DESC_BAGEL, expectedFindCommand); - @Test - public void parse_negativeIdArgs_throwsParseException() { - assertParseFailure(parser, " id/-123123", String.format( - MESSAGE_INVALID_ID_LENGTH_AND_SIGN, FindCommand.MESSAGE_USAGE)); + // name prefix with spacing + nameList = List.of(VALID_NAME_BAGEL + " " + VALID_NAME_DONUT); + expectedFindCommand = + new FindCommand(List.of(new NameContainsKeywordsPredicate(nameList))); + assertParseSuccess(parser, + NAME_DESC_BAGEL + " " + VALID_NAME_DONUT, expectedFindCommand); + + // id prefix + HashSet idSet = new HashSet<>(); + idSet.add(BAGEL.getId()); + expectedFindCommand = + new FindCommand(List.of(new IdContainsNumberPredicate(idSet))); + assertParseSuccess(parser, ID_DESC_BAGEL, expectedFindCommand); + + // tag prefix + HashSet tagSet = new HashSet<>(); + tagSet.add(new Tag(VALID_TAG_BAKED)); + expectedFindCommand = + new FindCommand(List.of(new TagContainsKeywordsPredicate(tagSet))); + assertParseSuccess(parser, TAG_DESC_BAKED, expectedFindCommand); } @Test - public void parse_notSixDigitsIdArgs_returnsFindCommand() { + public void parse_multipleValidArgs_returnsFindCommand() { + // Repeated prefixes + List nameList = List.of(VALID_NAME_BAGEL, VALID_NAME_DONUT); FindCommand expectedFindCommand = - new FindCommand(new IdContainsNumberPredicate(Arrays.asList(VALID_ID_BAGEL))); - assertParseSuccess(parser, " id/123", expectedFindCommand); + new FindCommand(List.of(new NameContainsKeywordsPredicate(nameList))); + assertParseSuccess(parser, + NAME_DESC_BAGEL + " " + NAME_DESC_DONUT, expectedFindCommand); + + // different prefixes + nameList = List.of(VALID_NAME_BAGEL); + HashSet idSet = new HashSet<>(); + idSet.add(BAGEL.getId()); + + expectedFindCommand = + new FindCommand(List.of( + new NameContainsKeywordsPredicate(nameList), + new IdContainsNumberPredicate(idSet) + )); + + assertParseSuccess(parser, NAME_DESC_BAGEL + " " + ID_DESC_BAGEL, expectedFindCommand); } @Test - public void parse_validNameArgs_returnsFindCommand() { - // no leading and trailing whitespaces - FindCommand expectedFindCommand = - new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(VALID_NAME_BAGEL, VALID_NAME_DONUT))); - FindCommand expectedFindCommand2 = - new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList("Bagel donut"))); - assertParseSuccess(parser, " n/Bagel n/Donut", expectedFindCommand); - - // multiple whitespaces between keywords - assertParseSuccess(parser, " n/Bagel n/Donut", expectedFindCommand); - // names with spaces - assertParseSuccess(parser, " n/Bagel donut", expectedFindCommand2); + public void parse_negativeIdArgs_throwsParseException() { + assertParseFailure(parser, PREFIX_ID + INVALID_ID_BAGEL_2, String.format( + MESSAGE_INVALID_ID_LENGTH_AND_SIGN, FindCommand.MESSAGE_USAGE)); } @Test - public void parse_validNameWithNumbersArgs_returnsFindCommand() { - // no leading and trailing whitespaces - FindCommand expectedFindCommand = - new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(VALID_NAME_100PLUS, VALID_NAME_H20))); - assertParseSuccess(parser, " n/100Plus n/H20", expectedFindCommand); + public void parse_notDigitsInArg_throwsParseException() { + assertParseFailure(parser, PREFIX_ID + INVALID_ID_BAGEL, String.format( + MESSAGE_INVALID_ID_FORMAT, FindCommand.MESSAGE_USAGE)); + } - // multiple whitespaces between keywords - assertParseSuccess(parser, " n/100Plus n/H20", expectedFindCommand); + @Test + public void parse_invalidTag_throwsParseException() { + assertParseFailure(parser, INVALID_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); } } diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index eff6453a623..39659f1db70 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -25,6 +25,7 @@ public class ParserUtilTest { private static final String INVALID_COUNT_NEGATIVE = "-1"; private static final String INVALID_ID_FORMAT = "abc"; private static final String INVALID_ID_NEGATIVE = "-1"; + private static final String INVALID_ID_SHORTER = "123"; private static final String INVALID_PRICE_FORMAT = "abc"; private static final String INVALID_PRICE_NEGATIVE = "-1"; private static final String INVALID_PRICE_OVERFLOW = "999999999.1"; @@ -34,10 +35,9 @@ public class ParserUtilTest { private static final String VALID_TAG_2 = "sweet"; private static final String VALID_COUNT_1 = "2"; private static final String VALID_COUNT_2 = "12"; - private static final String VALID_ID_1 = "223"; - private static final String VALID_ID_2 = "122489"; + private static final String VALID_ID = "123456"; private static final String VALID_PRICE_1 = "1.21"; - private static final String VALID_PRICE_2 = "12.2121"; // Should be rounded to 2 decimal places + private static final String VALID_PRICE_2 = "12.2121"; // Should be rounded to 2 decimal places private static final String WHITESPACE = " \t\r\n"; @@ -180,16 +180,14 @@ public void parseId_negativeValue_throwsParseException() { } @Test - public void parseId_validId_returnsId() throws Exception { - Integer expectedId = Integer.parseInt(VALID_ID_1); - Integer actualId = ParserUtil.parseId(VALID_ID_1); - assertEquals(expectedId, actualId); + public void parseId_lessThan6Digits_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseId(INVALID_ID_SHORTER)); } @Test - public void parseId_validId2_returnsId() throws Exception { - Integer expectedId = Integer.parseInt(VALID_ID_2); - Integer actualId = ParserUtil.parseId(VALID_ID_2); + public void parseId_validId_returnsId() throws Exception { + Integer expectedId = Integer.parseInt(VALID_ID); + Integer actualId = ParserUtil.parseId(VALID_ID); assertEquals(expectedId, actualId); } @@ -197,12 +195,12 @@ public void parseId_validId2_returnsId() throws Exception { public void parsePrice_validPrices_returnsPrice() throws Exception { // 2 decimal places double expectedPrice = Double.parseDouble(VALID_PRICE_1); - double actualPrice = ParserUtil.parseId(VALID_PRICE_1); + double actualPrice = ParserUtil.parsePrice(VALID_PRICE_1); assertEquals(expectedPrice, actualPrice); // Extra decimal places - expectedPrice = Math.round(Double.parseDouble(VALID_PRICE_1) / 100) * 100; - actualPrice = ParserUtil.parseId(VALID_PRICE_1); + expectedPrice = Math.round(Double.parseDouble(VALID_PRICE_2) * 100) / 100.0; + actualPrice = ParserUtil.parsePrice(VALID_PRICE_2); assertEquals(expectedPrice, actualPrice); } @@ -212,7 +210,7 @@ public void parsePrice_negativePrice_throwsParseException() { } @Test - public void parsePrice_largePrice_throwsParseException() { + public void parsePrice_largePrice_throwsParseException() { assertThrows(ParseException.class, () -> ParserUtil.parseId(INVALID_PRICE_OVERFLOW)); } diff --git a/src/test/java/seedu/address/model/item/IdContainsNumberPredicateTest.java b/src/test/java/seedu/address/model/item/IdContainsNumberPredicateTest.java index 69b63df6a51..f9664bd94c9 100644 --- a/src/test/java/seedu/address/model/item/IdContainsNumberPredicateTest.java +++ b/src/test/java/seedu/address/model/item/IdContainsNumberPredicateTest.java @@ -15,8 +15,8 @@ public class IdContainsNumberPredicateTest { @Test public void equals() { - List firstPredicateKeywordList = Collections.singletonList("140121"); - List secondPredicateKeywordList = Arrays.asList("140252", "124535"); + List firstPredicateKeywordList = Collections.singletonList(140121); + List secondPredicateKeywordList = Arrays.asList(140121, 124535); IdContainsNumberPredicate firstPredicate = new IdContainsNumberPredicate(firstPredicateKeywordList); IdContainsNumberPredicate secondPredicate = new IdContainsNumberPredicate(secondPredicateKeywordList); @@ -41,7 +41,7 @@ public void equals() { @Test public void test_idContainsNumber_returnsTrue() { // exact - IdContainsNumberPredicate predicate = new IdContainsNumberPredicate(Collections.singletonList("140121")); + IdContainsNumberPredicate predicate = new IdContainsNumberPredicate(Collections.singletonList(140121)); assertTrue(predicate.test(new ItemBuilder().withId("140121").build())); } @@ -52,16 +52,12 @@ public void test_idDoesNotContainNumber_returnsFalse() { assertFalse(predicate.test(new ItemBuilder().withId("147564").build())); // partial match - predicate = new IdContainsNumberPredicate(Arrays.asList("140342", "140812")); + predicate = new IdContainsNumberPredicate(Arrays.asList(140342, 140812)); assertFalse(predicate.test(new ItemBuilder().withId("140").build())); // completely doesn't match - predicate = new IdContainsNumberPredicate(Arrays.asList("140242", "243812")); + predicate = new IdContainsNumberPredicate(Arrays.asList(140242, 243812)); assertFalse(predicate.test(new ItemBuilder().withId("203523").build())); - - // Keywords match name and tag, but does not match id - predicate = new IdContainsNumberPredicate(Arrays.asList("12345", "baked")); - assertFalse(predicate.test(new ItemBuilder().withName("Apple Pie").withId("12346").withTags("baked").build())); } } diff --git a/src/test/java/seedu/address/testutil/ItemUtil.java b/src/test/java/seedu/address/testutil/ItemUtil.java index e0771d1ee63..f766d47e0c3 100644 --- a/src/test/java/seedu/address/testutil/ItemUtil.java +++ b/src/test/java/seedu/address/testutil/ItemUtil.java @@ -36,7 +36,7 @@ public static String getAddCommand(ItemDescriptor itemDescriptor) { public static String getDeleteCommand(Item item) { return DeleteCommand.COMMAND_WORD + " " + item.getName() - + " " + PREFIX_ID + item.getId(); + + " " + PREFIX_ID + String.format("%06d", item.getId()); } /** @@ -45,7 +45,7 @@ public static String getDeleteCommand(Item item) { public static String getRemoveCommand(Item item) { return RemoveCommand.COMMAND_WORD + " " + item.getName() - + " " + PREFIX_ID + item.getId() + + " " + PREFIX_ID + String.format("%06d", item.getId()) + " " + PREFIX_COUNT + item.getCount(); } From ca1e7a32971dc0357012ce8a74b32a996d68fa0a Mon Sep 17 00:00:00 2001 From: bryanwee023 Date: Tue, 2 Nov 2021 18:47:27 +0800 Subject: [PATCH 17/42] Fix spelling --- src/main/java/seedu/address/logic/commands/FindCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index a600cba6dfb..e5c1aaf9331 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -25,7 +25,7 @@ public class FindCommand extends Command { public static final String MESSAGE_INVENTORY_NOT_DISPLAYED = "Can't find outside inventory mode. Please use `list` first"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all items whose descrption matches any of " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all items whose description matches any of " + "the specified names, ids, or tags, and displays them as a list with index numbers.\n" + "\n Parameters: " + PREFIX_NAME + "NAME " From cbd78b666d38698c2f250a602c4f5893ac0f507d Mon Sep 17 00:00:00 2001 From: bryanwee023 Date: Tue, 2 Nov 2021 19:01:56 +0800 Subject: [PATCH 18/42] Update help links --- .../java/seedu/address/logic/commands/HelpCommand.java | 4 +--- .../java/seedu/address/logic/parser/HelpCommandParser.java | 2 +- src/main/java/seedu/address/ui/HelpWindow.java | 2 +- .../java/seedu/address/logic/commands/HelpCommandTest.java | 7 ++++--- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index be134041c3e..7fcee3e79af 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -9,9 +9,7 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; - public static final String SHOWING_HELP_MESSAGE = "Opened help window."; - - private String messageUsage = ""; + private final String messageUsage; /** * Creates a HelpCommand with specific help messages */ diff --git a/src/main/java/seedu/address/logic/parser/HelpCommandParser.java b/src/main/java/seedu/address/logic/parser/HelpCommandParser.java index c1f7ad6da55..1667f62cfaa 100644 --- a/src/main/java/seedu/address/logic/parser/HelpCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/HelpCommandParser.java @@ -30,7 +30,7 @@ public class HelpCommandParser implements Parser { */ public HelpCommand parse(String args) throws ParseException { String trimmedArgs = args.trim(); - final String userGuide = "https://github.com/AY2122S1-CS2103-F10-2/tp/blob/master/docs/UserGuide.md"; + final String userGuide = "https://ay2122s1-cs2103-f10-2.github.io/tp/UserGuide.html"; final String helpMessage = "\nRefer to the user guide: " + userGuide; switch (trimmedArgs) { diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 9a665915949..6bed5c3fe5a 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,7 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2122s1-cs2103-f10-2.github.io/tp/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java index 13abfaa2823..d93ad5eb5c2 100644 --- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java @@ -1,7 +1,6 @@ package seedu.address.logic.commands; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.HelpCommand.SHOWING_HELP_MESSAGE; import org.junit.jupiter.api.Test; @@ -12,9 +11,11 @@ public class HelpCommandTest { private Model model = new ModelManager(); private Model expectedModel = new ModelManager(); + private static final String DUMMY_MESSAGE = "Dummy help message"; + @Test public void execute_help_success() { - CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, false); - assertCommandSuccess(new HelpCommand(SHOWING_HELP_MESSAGE), model, expectedCommandResult, expectedModel); + CommandResult expectedCommandResult = new CommandResult(DUMMY_MESSAGE, false); + assertCommandSuccess(new HelpCommand(DUMMY_MESSAGE), model, expectedCommandResult, expectedModel); } } From c3270266e44a22910545c535e06ab27ca01116d1 Mon Sep 17 00:00:00 2001 From: bryanwee023 Date: Tue, 2 Nov 2021 19:08:45 +0800 Subject: [PATCH 19/42] Improve how-to messages --- src/main/java/seedu/address/logic/commands/RemoveCommand.java | 1 - src/main/java/seedu/address/logic/commands/SortCommand.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/seedu/address/logic/commands/RemoveCommand.java b/src/main/java/seedu/address/logic/commands/RemoveCommand.java index e8f6be3344d..c526a927f01 100644 --- a/src/main/java/seedu/address/logic/commands/RemoveCommand.java +++ b/src/main/java/seedu/address/logic/commands/RemoveCommand.java @@ -22,7 +22,6 @@ public class RemoveCommand extends Command { public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes the given item(s) from the inventory.\n" - + "Bogo Bogo still remembers memory about an item including cp and sp \n" + "Parameters: NAME " + "Or " + PREFIX_ID + "ID" + " [" + PREFIX_COUNT + "COUNT" + "]\n" diff --git a/src/main/java/seedu/address/logic/commands/SortCommand.java b/src/main/java/seedu/address/logic/commands/SortCommand.java index d556efafd3a..921c5776a63 100644 --- a/src/main/java/seedu/address/logic/commands/SortCommand.java +++ b/src/main/java/seedu/address/logic/commands/SortCommand.java @@ -20,7 +20,7 @@ public enum SortOrder { BY_NAME, BY_COUNT }; public static final String COMMAND_WORD = "sort"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Sorts the currently displayed items by name or count\n" + + ": Sorts the items in the inventory by name or count\n" + "Parameters: " + "NAME " + PREFIX_NAME + " or COUNT" + PREFIX_COUNT From aba4aeaddfcafb87768654376272c660622341d8 Mon Sep 17 00:00:00 2001 From: bryanwee023 Date: Tue, 2 Nov 2021 19:10:14 +0800 Subject: [PATCH 20/42] Have sorting by name ignore case --- .../java/seedu/address/model/item/ItemNameComparator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/seedu/address/model/item/ItemNameComparator.java b/src/main/java/seedu/address/model/item/ItemNameComparator.java index c92809c8592..cb13c967cdd 100644 --- a/src/main/java/seedu/address/model/item/ItemNameComparator.java +++ b/src/main/java/seedu/address/model/item/ItemNameComparator.java @@ -3,13 +3,13 @@ import java.util.Comparator; /** - * Comparators that compares items by their names. + * Comparators that compares items by their names. Ignores case */ public class ItemNameComparator implements Comparator { @Override public int compare(Item item1, Item item2) { - String name1 = item1.getName().fullName; - String name2 = item2.getName().fullName; + String name1 = item1.getName().fullName.toLowerCase(); + String name2 = item2.getName().fullName.toLowerCase(); return name1.compareTo(name2); } From 4a09f976740974908396f87cd43f29a772971bf6 Mon Sep 17 00:00:00 2001 From: bryanwee023 Date: Tue, 2 Nov 2021 19:46:30 +0800 Subject: [PATCH 21/42] Fix window name --- src/main/resources/images/shopping_cart.png | Bin 0 -> 16271 bytes src/main/resources/view/MainWindow.fxml | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/images/shopping_cart.png diff --git a/src/main/resources/images/shopping_cart.png b/src/main/resources/images/shopping_cart.png new file mode 100644 index 0000000000000000000000000000000000000000..d9af3b5d6452f8586d88a7e863b19ac70cd154af GIT binary patch literal 16271 zcmb8WbySq!7e4yV3`57z4MV4NNe+legGfp$pdek+Fmx*+2qGbZARsMBgGe{h-QC^u z8$aLsTle34-?e7VtXXHBv(G-~?DIT(KW~(_<`V*38e9MX2vn36bpQZ_{s{uGG0|Tp zt_80EKu%vpQC`<;de0y0Gx^xmg^>WxX2d~$cY1@M`AfCRA){9a0l2l%q;P!;-&k1Q zbF*@QyFjotQ8CVRx8RgJnx?%ZdlE0@52x5DC$930Yj5Aloo0I4%&YbNfiT|#*};Pg ziF&V%{fDmyt_J@yD#ZVPe{d5;rj<@s zSPeyS_L-HxaU_1q%E}L+pcRZ9>a0XrmY~x@cSiNeK=t;89aHFPZo=*Y1AAlaTWqkh zaxLqeMC@iq{%S7q?$F~%O^u1Ag=P|Dhdf+Ugoeq#keZV>SqB#kkm$VAMeJ&Q7PpXE4kNEwGAP{!{PD+E|D>JG@?<}=JS{GNTlgk6Y`Vm zndLSG1zd9!C1&%j#4R5z+hxR8;u(aYEh04ZgA|TM>5o7=Z!qwZj@2}8&Nde(+^w4DcGYi8-qAQ9y!?lXSjvRI6NSh9D0KxkMrcW-E!*Oe~ zJqMI7UAa(93K~;(HlPXwXIjI?p5EPPzDUNF(u|Z=PXuBmhzQ1G4TgZo&<}2+EjDfK z_7v`IiJ|V@&z1X{YjOxaO1fKyj2^6{&AlJ}06%&#Y>VZGP*!dSgcQKP3|Q}!FD%!+ zfOw@pq^ozFIB+I%0f3Gbv`F$|@#}DGITjs|OO{O?-oPjWQdWlI0}g8*yb}i=Qdmp` zfPh7|U;-&(Pz`{wvgU&IK_Xu{hVg~vN41E+LR^va%&b@+fcSXaj~{rn!~+!EEyag< zgzLeg>bMFD+-AneR}CC)_cA6yx~x#!QhE@}46*)8-W0g;5_H z-F`8IKgM^n-RoRkMpn<}08h)e+O8Hzh=9ayr8eE8N1PP~eIhq^bT4;bwj zD&PaIc2AqXzgpsqiu_5tKNTJ_RJRMzW-gUuq9Iq2PXo1lhZPT>{qj;)er$;+Fim30 z4&4e$46cU7-FKxhQ^mtsSn?|}-TURZ5a{igK=vm((2D|V$zYsovzho|Lj0%iK08;5 zYDu5Ta=p=R#!-=9iPC(}N+XlGd3w>+)kSSsS=y|^S)5J@x2OjbB#^DLfy%6yXfbR) z+f+quiUh~T?zTSveMXyeG*8#bPPI&wjho#Q(w&7wI#pj7!q8>r6OP5GMmQ+?M1!v+>zm-h}h4x-yDI-1jYp9vZRvI^%nww`eD8fl3pKWAE3ydV4oNqpO9c|g*9Xq zr!n`7oskvLV?6BC%!hAN;VuiZom#h!>w0G3O36OGalmRlcxW%(f^-RZ%rH5?loW|lkC!d-RKac`Fug(cUb z@XB7-y7k#QSiq!bsGD;`$A|~E`*#_vk_Jz``Mnx%PyUD)M`L`fi=w0S zn7+cb?-%AIaSWgNgAgMyWfY$~Da{#qnkepp;}!mKTDbWoC7gqB)ncu2IHJYrjb`tb zq2{Lmruc!7FL78@N2m#J0hB#o)ITHesIER`NW_=E;M*DkSt3uV)^TFh$5=ZrBL0@v zi8@_OpPSZynd@fpZ&lZwTt5=?97g1 z4U5XU0$;yu^g~8mU{yy@q^pF_ACbvt3xLbt)r$zx6dPW`1RF9{jzRE9ULPY+_u*); z!uL<(Qe4=7jv3PuTDBW^ftf#2dSlu~6Ju>u=0Cx(>D*&uZW|ghjFaCI>OFB%E~1nJ z>rT2GA0k__d|q*HbieDDtHBQHVwXDIzU_HRG11zT5u6(oz2)TQWc-I?P>kC*J9bi( zF?G1i=p$&0N)gN%VX34Z(xJ?H`cObfgVaYelb#Bgd_5&oONE!dA_xMIrAD1Wegtku zCe(ZL2K+$w69J&0{uVR9T-tH}qH*=!i1;RB&tFOXCv)c+=}5FEbA}KwzD^1h99wio zikK4_WRIe{qaYTe?FKWn(k@@rC(eK}Y3a6CuW$ejjhBS4Qdi1YfxaFv@Jaoh%ZlUygIiz{uU6e{VvWu{Lq#b(_8zYJ!A-%FKpp;i*3ZNymg4fV zhZVJ$6(DDv#pn+n1a`djv^i6t7IJYs0NaH(s3U#L+nMem8e#u=vG112o^dwk^g zeMACT?rTk4o$>89toI?Nz85N7h)9KX4qEv6>;hBumVRWTSMU7>rDM=0UJcOT$Ah0C z0#*L_W$4?-thGk)I|ebDbD2QW)b#a}n>HG?+s8-#GEc_9TVj1~ytgP1+73C|F>&-+H4uBv-$u)*_VJ4!!PN6(Q5+ZE!x6v9tJAolC89R^8Y?BhLM-1kRbs^* zIpG-5)p1w$e)8V`UNRDO6II18aew}izd6wVJWM<04j=hkmIlZOVQhE?Tgg-)_uBZX z1?BF{S-u_&L|C;7MDkKY652&?vM9$8e{*QcQpQ_`hV>A2PXhtYJm=Fz)k!xefPVv)Fpo^)^ z#jNHp2qN@vH#^1v7$Arj3UkiLwXfMOFT5F- zHMVSl(Ge=(%#Zs=sWhn0r5yiRtPeH#YJE$e&@O9^m4bzx_h7v`P~QD9y`q7pB%IEO ziArB9_>1Ke@RmL?0-WvaZbZIo^c?F*Z~4Bg<9CS$54*hW7&Zt9AORrALgHJ73(rtN zivR2EK%ig|9gK8-*cn<18fXOX#q4Cabtkt1*=kVrR1-+qTN06yf)M3_ixI0xURt8{ zaRUe7Pz*S~5Co2S5p1B(pB4iK)0JCz6N9H!7j{L}##ObD08r#YsW_n?(N*z`sqW=+ zz(&_G&gU0nia3CX1i+gH93?MH{ieaI7kiKe%Tswc zexHYtqHR*50JI65QHnMu6!V5c@5@slK|goYychd*$r1OO5NhDBBJk#F)YkYrP&fSO z919r>WDkE>#Ahcoa{ZF?-V~gWXBozd1dxDsBv9rqtvHc^r(ev~4om$B(*Ddc_Tok@ zht}H$aNma9r*4jlb16e11d=y8oyyE{%;g$2ICmeTd>;mAV*}X0k{i7u#ryEdN`o6( zK#sr$(7g|oVI)A}AhgpykbP(+U5w7Qi^hgzlT*Gy+}=gNE1YwO^0dJSfD;ZZF7%q2 z;5|{e?au?@BWZ)F0S_tYWnpM0o%i@p#XL;8mPf(f-U9D)vqx}kJY7Zo%J-yR03Tvr z1{{UMz6@caCs0Px!YNQAO*sT?l7cq(e|%SiWIL|10i2H(7aX(@$lUDL3tkiNhL-WF zHfSSDKJnsn{WBYi32fK*AISFD09E#o#kG4(L}yCJOFK#S(WS0RWyBcQCO%42{vG|| zenh6SGxYfpry^q}G$C&V2EYJZA|MrO=U%uade;pfY3mB6NlyhiM{8kNRDcA`S%5k6 zV2>2C4<^0R$~xM8W}hd+cWSA~O^s^I34;EO{(u7YF*azD^kifm^oszA>YE0XPi6rs zC7t-ukIPP_((rHu27rO2pam%P9_=RA|BXg{$84zpFcF`ufW9Ypa!*NY26<8zXY=|BQS7hXhz=lwO}6)PD|v7lBuufK?TQD!9uMV!@3q&4EQfxQcVA zjyrwz9hNUZ2!OHy2VgyL-qsrJ16aW>fTzgu*Fr@;T~8-I5hI5H=D*0xpcVITw@A1E zKVhaa){Nnc>VHUEt%0Rlh(HVL43?-GU# z%c6=j0;F*aFL;RfPA_O^n3*eb4LVeu7_6)8eET!3n#novJNQ4(2m|AXby#W9vsTuC z`vkyC2eH6bk?$crTuOSY38x%Q;ss&zJK z+RQzdJ9jJuy~C);EqGJ2niB;vBzPd%`Zky?t7ae?|ISh|#B?s)8e1Cp`9GJLK^BY1 zh!8sh0J|FKQ~9_i?lCrwrV4HaUf3s?X&{bb%5L*CdTjL4m4aZld6+F$kFsBxtayQM zlY%q7{e{ip>^O=>mU!VQ{CyGEH-%pUu2>7q#h5M{bOn5~*DNalWUF48+b~AJ;iYG& zNJ_Xhsl~zw&d^sO37~9nKj|X;;r{~BL^jYe9{(fn`Gi2n@AVGCL-C6-%u<}mJn$P1 zJxFmTw(rMviv`}&kn6CoUyinl;50QR{c_7<;yvu}-b}A~IjF=JoJbLXLJ^3U;!+mZ zBuXJ;RaNE>*qs{v)MK0lHZ&qAa};U`@u=OvVwKayN?v(#{4&u20<@Gy=z@yVu*)>( z8OIG*e@+}O0xhyTP2yGMWwk%jx;D14fI=9DF*>fF*M*CwL~Q>ebGD%%X{oXu15l}C z^AfWSj?t6(j3s9eNC&P#6?9bfEM-|BZDGv^B0xNOaGZRJvy%Cv#mI+&VZICj(e)Um z#^-=Z&Gyq@=i}*%pH7OFKH)uS6Q$ghYuf)k>^!TDiYp0DBRnv`3Cxh8DIxIw9Qbj< zoZz_hPZ2j+F<2N-4mM;>AHQb)G zjnz&HQ@#tbdqogo=TwJTVtkf5+1$&hzG(MR23x!|AXJ>|CmS->(jeT8Livdld%-vq zhM$WYLifl$jV#x<3eO;B$e+_`(cL|uXzE2EhpYs|h zeWtw_;RTn7c@$yUS{^F$Du#A|6dzgT@PgiNOwdr=N++JUw-@p*_H*FwJAlsI5EFp~ zeBeOS#^Hi|3}kHV=g;82hSyLE&}QU)A!2hAp7D>ABzMNDLg077p0j+)I^0O5`rfvK zG}3>~t$=zPM`z5gXeB8Pk^i-4dUUzV*%-l5FhWNT1l4 zs=8K)&HMf>LDFv?q65EX^J0*D7(GU=Nzmyjyb*lG?7@IfInYcrkFw%vD7(r!9H~bI zf1JO8nfaVNnDjcan)I48obQd7_y4bCxU2S~v?*BpS{QUB4?B$|XcGTx5Q|>EXUbovW&d!V|E4b&I;~nw$ z5gVm=ZoDfND{(&$M|jc~^Db&4@2>Z;#GHIK%n#SG7*7<%v$mUYP4sXM!S4Z9oQz=1 z+D~+sZ*gBv>DX?4R|Dz4gZv6i>}u#`EByhH+@U=*cC z7kQ<$PxWwRA(pm2w4NeH`YasQIl|gpDiY%Z>{b#S&kIcaqj-Yp!Ek#tZWRl?^?S*~ z{<{V*-GB%=#)fUp+q;wPGHeqm?Mv|I!T9X>s`cL7EE&083E$^t?Q!5#4a7$QvB*&? zGmX!Mk8u^u{Bg^;2O5;Jc}cGC+a5YmhLZ=CF>99z{)$F6)$&>rYoGbH7sE9WBsxef z-eNL~K-)#5{Ack?Q(T*6@+*rsG8NC_>yhcW$S5_8M2@^PgkBs z%1Rr%n|D7=(EfM1+xq}Y6PY;rR$0$5M)SabmR;_9k2%B)bJfz=7Vh@OWyazQQQ*G9wL%DZzI7q{paQ!^y%71zzIqJE z(v*KfkcAsp_zY&Qy@fM*l?H2e9iOKL0U-DFQGmQI|Seo8^7VrU&gHzC5V>x7DE?14RtX@4eCw(a?C z4+9-L|J{wJ;(+T>b0jnHOa?UNiMwqC8f>_KH*~WC21buNGVk#eQ(_0AX#N3XSQU=V z>2PiJW>z;Ku`wRz@&cgSCjj1Jz#rsbx1fQ}iip$$2_*euiO2gHrq)F^@;B2V{evtGo@>pQtBvCAZ7&EbjOwa zw5jw=XDK9BMP8KBG}!`DMi4Y}&&B}n0HOCMCvD4!i-n`VXa-h`7|?Xly`kaYqt~&P zE*A+pWXvsyVuRYc7b!|Du{|3wAFgkk9#`hV{*= zMPN?IV&d7LSiEM)a>LaRPar-P&_x^44W#0N2YYPty}pfFq>)+GCI<8VwYIj#V=zWN zL32vlIwlx;L0eFJatt{;0}vhx6+6JH)lOYI!{e`vuLHzlUB^okBU=b~Q-uvekYf$w zqe-^+Tigot9d>pek!2{v)#X|QD*w2@7)14g7EFxBx4~9GN9K+1^NXytTavxN_4Wl} zmFhTb;Egpg^7~sLjZ85kH@j~vh!<_p5AXHIYEgPP;Vpsd-^74_hrgqF2knrbym27d zEN06!I%PGZp&C){folWg?!_3M=G!I)3qg?TxD_SO{hHa5f=K|x7-nP_)>kn@Lwiw5 zK!HJx3V=~m){0^Q#d=HczJ6x|$~r*E;_{>CUlVK@Ejb)g zz_Uq^(eproF49o}b%E|2JwegC(vzv+=Nbu|AHgrnss8XHH%m&tylA(wF?^0k(5u+} zz3i7+%IBU!*2zWg%YgF9&u2{ULLBt;J||W!!rmpG5?z zakvmIWu0uw>%W^Bkd1M?fxVPgV(TCsXElyXz{Wz>Gx{v^xZZNbIW&TiB=|@n=r7K* zz#6l!!);MY&6F9z1qK4~?O;yK{!11tAnzrc?sGpMzLrl%0FgS_E1QqbjK|xQUiaN# z4(Db(_fz|0dGA0&%Y+FtBQmIUmd66^v>^i-RCLNDgaT@9*M;X&LJ-J{Z%Ej$08W+s zL9>`3X{KE@6?k+148$NK^=wb;ryMQn;?s;A69+z!i4{K#bj7T4<5-v+g#r1{;OJ__ ze01`@LuMu{nXr0DFdAiVyETWK?%ebbVogzxy4z)BUd^Evg`#a-by3QN?9 zgEEzv?_N4dW*tuyMh}1CF!-I8Ki2Z_7@Y>lDUp-POGMExR4mGHp>|_)53ntH?H%pk z*_Q-FXeO(@MyCK=l_o78R$^*qnL*?>_ZBD-L8mT{iY(Sq<1Mj&)1^|LqQ^^cKCew$ zlWmysSd%lhH_!BJn;Xs|O23r(i}IIzQCkmWrN)at`;ZF`MwbBoJPMYXW1++7a=9~- zhr%ZA;lCK;HP;Yz+VBh>*S0((_ly@?oehHN;#kayM;7b94yet4deXR zP=Y8>0b@}==O^ZW9FwIzOF|XY^+OwM!^YL}9m|{?tCk0$0~2yBk4E(XmlNY(Q2Ff8 zeixIVQ7OaUF8PK{~=;N4K^aT&F?L{o6A{u8*wKB=)S22hLqYF?|H^O0W!63iIHiE z!Dtx2O3BC76Kb@F(suZH^GLMJ&evsawnU4(N-9dBUKX=Tii^&1;Yr8%YhtV42Lvwo zjsxtR-3)4zvm7_&Wuo>A9G;flN?@Cr+42J4YuzBoLA9rf9yaQi)>`W}QjAxu3PT}z^ ztr$P~-RY?n`u?EMLC~qTlA@ZgAnab0CDH&3!qsm;XE8bK-XjtRwr9^kL?J zy9AN7!%A`xN1N#sI%As^DnTg5@xMG)GaXJcK^K}qve9bUSfYEAZLX&f8wYReZlZ|<=#Pt%}f9-Xq~2TfNLmkhV-QahEUHEH$Rv`g=>CQ8{m zFHqGES;VsZqV;&rJi-8b5%|-Qm;t1rCqxj~)F(tINv*$+Z&iYur|`>OO1ErFn)xx4 zH3c&3#*%Ke$X9^ga}_ObRYqR#BFc0QWR1{#f$l7)LwlVz6Gh2fyLG@3Y(17(3 zJ=Th7j+i_ev2K6UJ&BdoSA*AkYk>{LXckW>8-#$1zNV|p8I^o_K{LpZ6hvDLB=|fH zG}Mi)s~0>m$<%7b{A>{S^63Kccs^>1y(gr7rTCb;l%fwewEe<9cRS1}orQ`)tfwjpbp13k(swime?<4l;n=DRUp1^6m{(z_I_a-MHT0)Db?i z#gfCG#c<_?UA$lsE76pwbJ6=bilzm1VM15b6gd*KxWZEOZC1WYwVH4K#;6wXNCOws z{=f%zHRaJEtXJL`0F9N`$G(u^JVv7^WrzQ;;K9)@__oE3|E^sO@Tmn& z^=e+C7;W1z5i_LDWWzRJvkpGg0xF$B0n#I;6*SJ18G3GWpLs-V)u*tVC^q76{{uA zj$Ra~P{BQ}_jOTt+%Y6!KG3a0CWaqa=SXxf}=E&@-&n7_T~bTP<>GeMPAc+dOHnJTQ@ zV@830SRMniI8qMtKV>#P)p`xgmirFoq04H`-r5UqN&_fWFcH&D9?e${T4-1?QlOQe ztqM1dFf7UpVA_Avl4^tP=bvDSZ7+}VME3=B0)~)s%8h(cKox>mMdQn?$C3osO>qW9 zh%ewjRv3#cxnoIxxeC~o^WpnA5+zw@6JbRw`d58M@Zsqmoc9LMBYQQab@(-CsqX#XZ=igEyEjE3g^h$Zvf`HKFhup*j&u z9eQ%Y>`%5-qEoX-A+WZ%sy4ZWBP^q7&mC9Q3Bk2xZJ2wbeF^;$BPRBk*OTYf6MkW) z^UFM#kx=ziIO2aElOW7_Z_@Ip|1T9#J6Y?{dGX8}!(t&JZO1rTW-k}8Q}UI*+^4e1 z=*fk@|92cWBmAGFX8AY9$q_&E0=N9Y{A1P>g5@Y8&Pw%XJldVUqEs>R+|1)aE@%h=v3R2s?~z`osJ5d*R@ zkiZZ#xy96Xi-3zS4@}vc?~H(u5u18CxB2pQK+Oo*;wg}w82l{;ZPnvZf}#hH zrbw|;YfAlj3thqXMX_82LFSEPITmmXlpO;J z0pGg`ke>uGA0g4a2I40LMZ@q{3x50%fQKTcUK(_~w0no%<=?O&g#Q+R0MAkIfXd=i+BsqV;51Yn;5L{_@cDI#4R?J=l< z(l4Br3^J=ujH+TZo<{6IfScDImj>z>C+UIgzwS-=snsaHO+R^Ue$TLFf;sz$?T;Ui z8>#mFd0>)zzSrSjKwb*1(&^RjrcMg>`ldI*XHEc;$m??-f6{=%oT5jOe|dhpYJfTdNS%-#}gWjm$4UC^>hGU zVVj5=se)P`G7Gm@=$W1aWgqAhC2MQ9pZ9J1e$p3#03UdG{-E!K@`ta8fB|&&lfq4p z-riiK{4vmymuS6mWfsLtPgG7Ij{JF09~-gYt^&FnY@kxkmp|UiV#+WTCSim0OQ_HQ zGmAl<3c-5$*;1y%x|u5SY;`Z1j#^WcJax2H9gtxxSC6cj>D4y7qCdt$?XD!KBwu+g z7PdSgDhZW|=}_=X`z5#2oc4llIM4EnZMPxkk75#F(SSU40;72OLh)m`)c;K)>jh2G zRQ$wupmUY!vtF^dX|xQPz}QuN@jI6oDC&@Bp~WMjC<@uEb7ML`&ZzFna=s+>NA2zn z1~#m|W9y@Ga7h-a#ISg{EZ0jcVAj^QzlD=c*`xpBUPa!tXPGB!c5;=Xn*={d!?)%3 zHu;sy?a9Z7Z3d;SWx}t)e~+fmOZxDE+aFS7@4kNq2#n1zE|>KJ0^Lvn$A-l}N)fk} zAHQsT&xdS19}r@eAv9$};otDqM=a1T8&3Y0&z<_LCXGxeHQEsR2MosQ!o^bf&BG4l zCIR|ugBVQuxpW4%;pKo^ z&!VjaWFPMh{t4V)`t)w88LeWI{E@=;TZ12n&je1QeGNqa@m9Uh?NP%v47I%t`04&^ zMUSx^9>+hy1PU-l9I)wxk40>%M#gxZrVK~*(p^Di+BW9ErgUncnE%~r%KNbY6b-e1 zEO{Ls(?4kn@7|#qH+=hq_g~rG;wN(hC>z(1L1iwv2Af>)>Sndovw1IR{`#|HpoI*U zEW68x$bqIs>dN1L1HD6cI26SRTiY0vbYP_QnpA+t$AuyI%b-3HrqwebAO=cx9mY zD>JAG)hC9umxM+gTbTA_58Ap-+FFjux4y%|4^qz``YLL_Zk>Vw^g;k$96&#$>uH&~ z@Mq#~CC=L$v`NCk{}y0UOcYJ@`jhJ;s_(+@~r;b=|Oc9)2(1oi=vzkg%>| z1GV__;sbomK?|Lu9U=?eV=8$%+|!rmH$#>q>fcV>lJozRntt}b+?z#-N6M0!1~%zF z9}c%UeeeadEH@j!1YyQt&v{$bP~F5uBgzz^E4bro_D&j!VL>nKDjB2RH)UAGRT<|+|G z-ofBr{3rf6+5|`d^GeMS zVh`olLE5lFB0|BZ z&oCG5gt~=2cUcA=e(V7Vl;`sHY;|d>1o!38=^$3hZEho>DLF3XujA+%%D$}6WtAx! zy5nTLr;+tAxJfGdI?}#6(|DPkRM=;(r#a=XJ2o2M$=sy%Hj_Yv;-8IQeyEI%n*QU- zK^-GW;D5ll)=Rdz_;W*gh<0|lZVS_$(&N>DsRzxkFNn!KZU|{b_|92U24)&((fW`L z_!~9C);gm4Hvg|9+bBAQ-CdA{fY8Ph@qq46+gA z+CCpJ|EGqpwVV!FPQ?`acsiMGYXtYOBO-J#ChO}+z|V}$Q;E@=N>}K#7?LH5PYSv< ziW~d8tLrA6luh?+9rezp%1r@DtD}z?dbf#2?&5}j;=(^WeU3kSjT^$Kg>@fk)8TY< zSYtx()icJ0El%g!RAxL;v-_FcD9|IR7qV*_Y?Avee{4I+-#2`F$g=*W|Q zvWNW!3yYvjkiJo`ZZRCtO`!ZBszSLIkGDnwsG)v#DJUrj!1@;83IV+vlL+0 z5ZQVh;68^Ik~UyuQ+vEk((doYwD^--C}2Dd6Ck2R3pPQBDd74QXZ;6!aK%n^xuz;% z0T|t$cgh14mj%n%PjqZLZI}NU>bEzi__Y}ULmNaRkWFhZ3b#zA$iqDB;T3*Lfd12u z3*iHf_1kVJH69AX4IMj7Fl8DlF24R zODu;*(_dRP^33lm&DPT=ZL_MUM`~()fFr$U(331gsd>UP>>@Bue#hEcfV}6waNPnN8XS@feCHm?^yOIKw6$khJJ8CnH)8tAp6M%F5m)B=(7SU`vKVHVTY= z4x95zwK?_6p|(c5dF4xIaRGJ_Z!T?D7n9(g#Q{$oyK1(wQ4mrG+M#B^C^h;`96Of^ zOJn}-u?og)Sr;UZS#jg> ze%lDZhVVH85miBn7kbPr8!39}f-gSS5fi!h#)gRZN&+qhWZ7#h1gt6;IvUzykouO$ zpjbMRXB&k_{@itV7vEpq9FTC0y(CWOB@BpC^Q$wfyXKu*9KH+hbgb#R8uy;8NSgT5 ze856;M?Q`591Nz(Xp8f0{PjZM)2JX-5b$ttlJNEWgn4O0hq~F$P3o=@2JM%SpjT9i z!fz$sf@(yN2;^r^`y(>XZ&HY{Pha77Y09N{nl3!8^S5#DoU+SYesZDJZ!^G%~g!YZt&#+K^btqRA@&hqB;Q zG5Lh`&`Y5GO_xK<=o*ENGhh~SJz2PZYe6$(DZ*`OG~X#6Sn?7Y zYK4x69aK4gBfPVTutE}4A1kY}ICd;e6zp86a&GD2ur(H8Toj&O+7D@1np^h;H&j!+ zUAGf6c6BS2@|A5IJyG#dnv!Gmw4~`hxsnj%cp9*_w0k?>r1R#nxW#}ygizo%aZ`zY zcoUpMn;xZkQK&I|+s02QjjHv17|$1ibY}o^PY`fZOvu*L0Td%jT@8VF{NUKm&XTsQ zf1S>cuxFS0q8J$Yp)Q@Sc}(p=kDu$FdTSz_mGDN3a`j3e!@S{2j``Ct5fSR%g}Pd! z`_xhlGA9wdsYH}Ry^+twO=4lG2ig+Uwf|<8>`awG*r|R+N*6dj<~)nKCwNy_PbGd@l&Sl>uTUQVC!-JM27@paNUuSc7`eFf?L!i8iJO}%i5 z0w>Yis%!hpe%p_2IKn023t2~$m+ik!zRV$A5OqYv$n1R_T(G8759-h?rTEK_b(+9s z6ADN!G)=?~<$s-$aQp@N+S{Xk%y!-2 zZkguwwB5+EGgSJw|Zicm?vs>fK z^|Tv(jDbK8#=hB8nFWiDrZUKYYUeb9Bs;(Rjdl?5iE-ZX(IQqL6%$1>x1-=FYj5zX#Y z3sh~6ri0?-GtwK5vyP0H?tNmkH?ck^pWUaJnOqCwl5In5tJe&Z#nY{dM1J(9oP-yH zh%ZFOrz4v2@9ida3w#&-e{Jp+cwz2RTYJeaQ#))7MuA$VXq&>!wbD0`NJ`*W%;=j2 znlmR-GXZ690x;^a$aOL{)3R#zF^of+&7V(ByIt?0~j(Bm_8iNEgNu=LhNy8gM}UmJSS#hQA)+3CDQ8|OUG zaj<)K;iFV-Rpugv<-z`dPHIi-9v%;jn5bTnUZ3K%YZTD3M3yb42kf&o)z;w$HyQ9Q zVNE(&dOHf;f3po<8LW$*A@-XWN>Lk(g=O4p1{@-au(J=jf z=XbKz?3Ks@nZ28p?`4ua(i?wLX;0=r_6hExm?IZpsCz2;Eot%0nCT2d`G}7mQ*K)h z^IIwELr9UaXz%&eBdIDHUfDci#)}47W&4 z*-v{=#)1R`0r$qKwYCQOPRV_=>h>kwL@3(nibdGE(P2Ovr&Eh@ZdKQrT0L%+pw7B$ zYG$1rX`>VYlJu2HTb!4AsMD6Pjt+1#|6%S@;3J^cuqV2xx?d|OA1B{a)Xx?ZL-SmL zDt}{L5-OSLnK5f3^}MQ9CpLtCFPNpHZ<$1?gMj_Fx(2oLvCMEBDp3dF5}jJCTkoZ` zf!9Vulyz1;=>8z&IGr)9R!3oFr~doeV5AD^GgNsP7BLjCC11XQF;v|K)>8>#OrFl0 zDAeo*o^dMsQU75i-&LhMci-JR;!scERIn@vez7{Dk6gZ>n_{yslxa{WG==41_1s~jZcvCc8 zd;bPV8d3ap+>CyA10XUEfV70R07I6X;(*zv4(NA0mV7}H}Vds~+Io&exKSofyKc`>`iMvMcTKFaClRLX7m%ddyuvI$2Q z1(H<4XHT)kvBL_%0)UqkZXV^_QX(Mzqpzh`*|zH0Z+Wiq*SToCoSvf8$cDsqRL;2W zdNKkj6V@e1ng+_g`c{*{P7KVmJ%{=L$Jcu~!$LEV#q!!qLCm+aYRpKt7ONXbT>l!15G>$a!G$n7xxW9=O`>)1fUWt7oi z009Qx^K0_@$&ZGF`$PE4E*WRphw|7lmp&wF$c1o}S=r)x`PIBRKj_ z#i(D^$?ORHU5)Er;42+#8&2ZGp|I{SdBJR7dl{mx>lfm9tA0L#r;l6g`;cgEZT{0M zB|AwU6yg3;i>2v3tan&C+r%XfPq?^gk%G*vi?JU{!x55NG-)rX#Z!G2O>n6|q!Ue@ zMEkWy!N?moUd;>;PUD@*lJPiCh)99}n_*m{Q)l%LjNGiGw0M^VFnq3OF`wl%E8Vhv zR>BOTH}mPli4&nkmWuM^^dk`;j6&e5@w={w#X+v*o-B^M^kI6VFqHM#UU zoLfKR+Z`eed-dvED-t{_FS3&V*a1*K8}HM21Ku&k%RWJv6@<-zO&-ey%d!w>9np(l zx+2eoZrLcI#6Tkhk>QK2bf@kQi@#oDG5u^sN9eB{sDx(IRMzZ~VE?NydJ_+eZpD0= z`?Lbdzdw1AYgy0VGRJFvWCILcAV?j^JG{@ZRn^&T87C%lyH_?EVz}%xSR}YLhdu;? z1x*J~o8Ixyl=weR-wly50LjIoK(eck zp@JT~bQ)Q~43qn1igmJCnY8|3m8rn~qzn?!xhF=`&EANyzLT=g?gzHe4k=-1YgY44mSV5n%E z1jtrjgi{;U6RvN~&he!ZJ#zB?V1WNJ>d}gRQ}?3=Bfbt|%hyql7#rSHt|_8iZu%w^ z^W_pO+iL>lpV~r;Mz5#LT<0O291HvNL1-rCE;($~0?SD>kFdGmDIEEgS)S4HY_P{` zQWJ)|ON~7m$Yf#cG_$~%6mGG=L^DIy}_op$a0%Abxx&epaA2v|}kbhCqoNl>q6ovGTlzyM;~oUf~jHqx3|#|9PAw;)XOM0KN-OK6YS%=-f{0<(<*aq*=P`|9!Q{tT;uW z__gzkEHzcZOkL)%kz&4Y9%$m}oX95vDnlH-oX4T&eI;l7es`X*ehCbsz@onod8SV? zoj#Q(L4rt=DKzjf&uK&Ja>fg!Pb=E>oV*V`R)l9FVwtVa!;L$=eDQ~4*=_-zD5SWb z3+V0fNx9pDQIuuYN)}}~e0MR1vyU>$@wD^x=%;p#JkK|$gNpf#G2%5CA&8v3(Su~x z)>DK2RC$rFAv;cAXur3QAcr$3Nyjz`OY{W=4(&FVN>E){2|ZHT?@8nI*`*mpda9?z z<9w3@qmU$V^9t_Qgu}7^ieKY|0$SWv03B5UnkY?A;*2JB_mwv})Er48U)U~{aHig_ zU~0$R6Af=#<RgzHH;<^zF_eG2O-jz) z+-NLR^~wl{Gn02;dB{M@_bILdlOYSu$@+X*vLEGj2u8eDvvc4 JOB5`E{|`N4$}s={ literal 0 HcmV?d00001 diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 077165fdf6b..f9b32333e35 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -12,9 +12,9 @@ + title="BogoBogo" minWidth="450" minHeight="600" onCloseRequest="#handleExit"> - + From c59bb4cd7a4bef2e3f3c62324f49dddde9b3eeae Mon Sep 17 00:00:00 2001 From: bryanwee023 Date: Tue, 2 Nov 2021 20:22:05 +0800 Subject: [PATCH 22/42] Standardise formatting of help messages --- .../address/logic/commands/AddCommand.java | 18 ++++++++---------- .../logic/commands/AddToOrderCommand.java | 12 ++++++------ .../address/logic/commands/DeleteCommand.java | 8 ++++---- .../address/logic/commands/EditCommand.java | 17 ++++++++--------- .../commands/EndAndTransactOrderCommand.java | 3 +-- .../address/logic/commands/FindCommand.java | 2 ++ .../address/logic/commands/HelpCommand.java | 9 ++++++++- .../logic/commands/ListTransactionCommand.java | 2 +- .../address/logic/commands/RemoveCommand.java | 10 +++++----- .../logic/commands/RemoveFromOrderCommand.java | 10 +++++----- .../address/logic/commands/SortCommand.java | 9 +++------ .../logic/commands/StartOrderCommand.java | 3 +-- .../logic/parser/HelpCommandParser.java | 4 +--- .../logic/commands/HelpCommandTest.java | 4 ++-- .../logic/parser/HelpCommandParserTest.java | 4 +--- 15 files changed, 56 insertions(+), 59 deletions(-) diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 42fd2de443c..f32a78cd319 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -24,16 +24,14 @@ public class AddCommand extends Command { public static final String COMMAND_WORD = "add"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds item(s) to the inventory. " - + "\nEnter all fields if item added for the first time" - + "\nIf replenish an existing item, only name or/and id, and count are needed" - + "\nParameters: " - + "NAME " - + PREFIX_ID + "ID " - + PREFIX_COUNT + "COUNT " - + "[" + PREFIX_COSTPRICE + "COSTPRICE]" - + " [" + PREFIX_SALESPRICE + "SALESPRICE] " - + " [" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " + + "\n Parameters: " + + "[ NAME |" + + PREFIX_ID + "ID ]" + + " (" + PREFIX_COUNT + "COUNT)" + + " (" + PREFIX_COSTPRICE + "COSTPRICE) " + + " (" + PREFIX_SALESPRICE + "SALESPRICE) " + + " (" + PREFIX_TAG + "TAG)...\n" + + " Example: " + COMMAND_WORD + " " + "Banana Bread " + PREFIX_ID + "019381 " + PREFIX_COUNT + "10 " diff --git a/src/main/java/seedu/address/logic/commands/AddToOrderCommand.java b/src/main/java/seedu/address/logic/commands/AddToOrderCommand.java index 57d8d808f81..7dca5cefe97 100644 --- a/src/main/java/seedu/address/logic/commands/AddToOrderCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddToOrderCommand.java @@ -19,13 +19,13 @@ public class AddToOrderCommand extends Command { public static final String COMMAND_WORD = "iorder"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an item to current order list. \n" - + "Parameters: " - + "NAME " - + "Or " + PREFIX_ID + "ID " - + PREFIX_COUNT + "COUNT \n" - + "Example: " + COMMAND_WORD + " " + + " Parameters: " + + "[ NAME | " + + PREFIX_ID + "ID ]" + + " (" + PREFIX_COUNT + "COUNT)\n" + + " Example: " + COMMAND_WORD + " " + "Milk " - + PREFIX_COUNT + "10 "; + + PREFIX_COUNT + "10"; public static final String MESSAGE_SUCCESS = "Items added to order: %d x %s"; diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index bc869ef8dbe..496edbf7e0b 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -21,10 +21,10 @@ public class DeleteCommand extends Command { public static final String MESSAGE_USAGE = COMMAND_WORD + ": Erases the item from the inventory entirely.\n" - + "Removes all memory about an item including cp and sp \n" - + "Parameters: " + "NAME" - + " Or " + PREFIX_ID + "ID \n" - + "Example: " + COMMAND_WORD + " Apple Pie"; + + "Removes all memory about an item like cost and sales price \n" + + " Parameters: " + "[ NAME | " + + PREFIX_ID + "ID ]\n" + + " Example: " + COMMAND_WORD + " Apple Pie"; public static final String MESSAGE_SUCCESS = "Item deleted: %1$s"; public static final String MESSAGE_ITEM_NOT_FOUND = "No such item in the inventory"; diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 78cef301d0a..9740caed351 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -31,15 +31,14 @@ public class EditCommand extends Command { public static final String COMMAND_WORD = "edit"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the item identified " - + "by the index number used in the displayed item list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_ID + "ID] " - + "[" + PREFIX_COSTPRICE + "COSTPRICE] " - + "[" + PREFIX_SALESPRICE + "SALESPRICE] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " + + "by the index number used in the displayed item list.\n" + + " Parameters: INDEX" + + " (" + PREFIX_NAME + "NAME)" + + " (" + PREFIX_ID + "ID) " + + " (" + PREFIX_COSTPRICE + "COSTPRICE)" + + " (" + PREFIX_SALESPRICE + "SALESPRICE)" + + " (" + PREFIX_TAG + "TAG)...\n" + + " Example: " + COMMAND_WORD + " 1 " + PREFIX_ID + "192028 "; public static final String MESSAGE_EDIT_ITEM_SUCCESS = "Edited Item: %1$s"; diff --git a/src/main/java/seedu/address/logic/commands/EndAndTransactOrderCommand.java b/src/main/java/seedu/address/logic/commands/EndAndTransactOrderCommand.java index 0ad71931d2b..27d3db172f0 100644 --- a/src/main/java/seedu/address/logic/commands/EndAndTransactOrderCommand.java +++ b/src/main/java/seedu/address/logic/commands/EndAndTransactOrderCommand.java @@ -11,8 +11,7 @@ public class EndAndTransactOrderCommand extends Command { public static final String COMMAND_WORD = "eorder"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": exit ordering mode and make transactions." - + "\nExample: " + COMMAND_WORD; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": exit ordering mode and make transactions."; public static final String MESSAGE_SUCCESS = "Order is placed."; diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 0302c5d6766..af52046b856 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ID; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.model.display.DisplayMode.DISPLAY_INVENTORY; import seedu.address.commons.core.Messages; @@ -29,6 +30,7 @@ public class FindCommand extends Command { + "\n Parameters: " + PREFIX_NAME + "NAME " + PREFIX_ID + "ID " + + PREFIX_TAG + "TAG " + "\n Example: " + COMMAND_WORD + " " + PREFIX_ID + "019381 or " + COMMAND_WORD + " " + PREFIX_NAME + "Banana " diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index 7fcee3e79af..3710914aa44 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -8,13 +8,20 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; + public static final String defaultMessage = "\nRefer to the user guide: " + + "https://ay2122s1-cs2103-f10-2.github.io/tp/UserGuide.html"; private final String messageUsage; + /** * Creates a HelpCommand with specific help messages */ public HelpCommand(String message) { - this.messageUsage = message; + if (message.isEmpty()) { + this.messageUsage = defaultMessage; + } else { + this.messageUsage = message; + } } public String getMessageUsage() { diff --git a/src/main/java/seedu/address/logic/commands/ListTransactionCommand.java b/src/main/java/seedu/address/logic/commands/ListTransactionCommand.java index c74effd9ab8..7ff22a17a2a 100644 --- a/src/main/java/seedu/address/logic/commands/ListTransactionCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListTransactionCommand.java @@ -20,7 +20,7 @@ public class ListTransactionCommand extends ListCommand { public static final String MESSAGE_USAGE = ListInventoryCommand.COMMAND_WORD + " " + TRANSACTIONS_KEYWORD + ": lists all past transactions.\n" + ListInventoryCommand.COMMAND_WORD + " " + TRANSACTIONS_KEYWORD - + " ID: list items in the specified transaction.\n"; + + " ID: list items in the specified transaction."; private final Optional transactionId; diff --git a/src/main/java/seedu/address/logic/commands/RemoveCommand.java b/src/main/java/seedu/address/logic/commands/RemoveCommand.java index c526a927f01..10e52b0b0e3 100644 --- a/src/main/java/seedu/address/logic/commands/RemoveCommand.java +++ b/src/main/java/seedu/address/logic/commands/RemoveCommand.java @@ -22,11 +22,11 @@ public class RemoveCommand extends Command { public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes the given item(s) from the inventory.\n" - + "Parameters: NAME " - + "Or " + PREFIX_ID + "ID" - + " [" + PREFIX_COUNT + "COUNT" + "]\n" - + "Example: " + COMMAND_WORD + " Apple Pie " - + PREFIX_COUNT + " 1"; + + " Parameters: [ NAME | " + + PREFIX_ID + "ID ]" + + " (" + PREFIX_COUNT + "COUNT" + ")\n" + + " Example: " + COMMAND_WORD + " Apple Pie " + + PREFIX_COUNT + "1"; public static final String MESSAGE_SUCCESS = "Item removed: %d x %s"; public static final String MESSAGE_ITEM_NOT_FOUND = "No such item in the inventory"; diff --git a/src/main/java/seedu/address/logic/commands/RemoveFromOrderCommand.java b/src/main/java/seedu/address/logic/commands/RemoveFromOrderCommand.java index b55e25627ab..89c33e65b72 100644 --- a/src/main/java/seedu/address/logic/commands/RemoveFromOrderCommand.java +++ b/src/main/java/seedu/address/logic/commands/RemoveFromOrderCommand.java @@ -18,11 +18,11 @@ public class RemoveFromOrderCommand extends Command { public static final String COMMAND_WORD = "corder"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes an item from current order list . " - + "\nParameters: " - + "NAME " - + "Or " + PREFIX_ID + "ID" - + PREFIX_COUNT + "COUNT " - + "\nExample: " + COMMAND_WORD + " " + + "\n Parameters: " + + "[ NAME | " + + PREFIX_ID + "ID ]" + + " (" + PREFIX_COUNT + "COUNT)" + + "\n Example: " + COMMAND_WORD + " " + "Milk " + PREFIX_COUNT + "10 "; diff --git a/src/main/java/seedu/address/logic/commands/SortCommand.java b/src/main/java/seedu/address/logic/commands/SortCommand.java index 921c5776a63..bf980d712ae 100644 --- a/src/main/java/seedu/address/logic/commands/SortCommand.java +++ b/src/main/java/seedu/address/logic/commands/SortCommand.java @@ -21,12 +21,9 @@ public enum SortOrder { BY_NAME, BY_COUNT }; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sorts the items in the inventory by name or count\n" - + "Parameters: " - + "NAME " + PREFIX_NAME - + " or COUNT" + PREFIX_COUNT - + "\nExample: " + COMMAND_WORD + " " - + PREFIX_COUNT + " or " - + COMMAND_WORD + " " + PREFIX_NAME; + + " Example: " + + COMMAND_WORD + " " + PREFIX_COUNT + + " or " + COMMAND_WORD + " " + PREFIX_NAME; public static final String MESSAGE_SUCCESS = "Listed items sorted by %s"; public static final String MESSAGE_INVENTORY_NOT_DISPLAYED = diff --git a/src/main/java/seedu/address/logic/commands/StartOrderCommand.java b/src/main/java/seedu/address/logic/commands/StartOrderCommand.java index ad86020c01d..1395b8b9157 100644 --- a/src/main/java/seedu/address/logic/commands/StartOrderCommand.java +++ b/src/main/java/seedu/address/logic/commands/StartOrderCommand.java @@ -12,8 +12,7 @@ public class StartOrderCommand extends Command { public static final String COMMAND_WORD = "sorder"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Creates an order and enter ordering mode. " - + "\nExample: " + COMMAND_WORD; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Creates an order and enter ordering mode."; public static final String MESSAGE_SUCCESS = "Ordering mode: Please enter item name and quantity."; diff --git a/src/main/java/seedu/address/logic/parser/HelpCommandParser.java b/src/main/java/seedu/address/logic/parser/HelpCommandParser.java index 1667f62cfaa..3c6621f5f9d 100644 --- a/src/main/java/seedu/address/logic/parser/HelpCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/HelpCommandParser.java @@ -30,8 +30,6 @@ public class HelpCommandParser implements Parser { */ public HelpCommand parse(String args) throws ParseException { String trimmedArgs = args.trim(); - final String userGuide = "https://ay2122s1-cs2103-f10-2.github.io/tp/UserGuide.html"; - final String helpMessage = "\nRefer to the user guide: " + userGuide; switch (trimmedArgs) { case AddCommand.COMMAND_WORD: @@ -76,7 +74,7 @@ public HelpCommand parse(String args) throws ParseException { return new HelpCommand(EndAndTransactOrderCommand.MESSAGE_USAGE); default: - return new HelpCommand(helpMessage); + return new HelpCommand(""); } } } diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java index d93ad5eb5c2..23a977f6269 100644 --- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java @@ -8,11 +8,11 @@ import seedu.address.model.ModelManager; public class HelpCommandTest { + private static final String DUMMY_MESSAGE = "Dummy help message"; + private Model model = new ModelManager(); private Model expectedModel = new ModelManager(); - private static final String DUMMY_MESSAGE = "Dummy help message"; - @Test public void execute_help_success() { CommandResult expectedCommandResult = new CommandResult(DUMMY_MESSAGE, false); diff --git a/src/test/java/seedu/address/logic/parser/HelpCommandParserTest.java b/src/test/java/seedu/address/logic/parser/HelpCommandParserTest.java index e704504c6f3..aa27436b4eb 100644 --- a/src/test/java/seedu/address/logic/parser/HelpCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/HelpCommandParserTest.java @@ -123,9 +123,7 @@ public void parse_validListsArgs_returnsHelpCommand() { @Test public void parse_validEmptyArgs_success() { // asking help for empty command - final String userGuide = "https://github.com/AY2122S1-CS2103-F10-2/tp/blob/master/docs/UserGuide.md"; - final String message = "\nRefer to the user guide: " + userGuide; - HelpCommand expectedHelpCommand = new HelpCommand(message); + HelpCommand expectedHelpCommand = new HelpCommand(HelpCommand.defaultMessage); assertParseSuccess(parser, "", expectedHelpCommand); } From ee77a354dbf1fba7ff09b486f79e497a9efdefb7 Mon Sep 17 00:00:00 2001 From: bryanwee023 Date: Tue, 2 Nov 2021 20:54:05 +0800 Subject: [PATCH 23/42] Fix documentation bugs --- docs/UserGuide.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 0424492435b..5f58a681368 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -254,9 +254,9 @@ Format: Find items in the inventory. Note that display must be in inventory mode (see `list`). -Format: `find [ n/{name}... | id/{id}... | t/{tag} ]` +Format: `find [ n/{name}... | id/{id}... | t/{tag}... ]` -Searching by 1 or more names, or 1 or more ids, or 1 or more tags is supported. However, searching by multiple tags simultaneously is not supported. +BogoBogo searches and lists items in the inventory that has matches any of the specified name, id, or tag. Examples: @@ -303,6 +303,10 @@ Format: `sorder` Add an item into the current order. BogoBogo will let you know if there isn't enough items in the inventory to fulfill the order request. +
+:bulb: Once having added items, you can view your current order with list order. +
+ Format: `iorder [ {name} | id/{id} ] (c/{count})` @@ -324,7 +328,7 @@ Examples: Cancels the specified order from the current order. Format: -`corder [ {name} | id/{serial number} ]` +`corder [ {name} | id/{serial number} ] (c/COUNT)` Flag | Argument | Description | Remarks | --------|----------------|---------------------------------------------|-------------------------------------------------| From a970d7305a664fcf30eaf2017e51f1e5c33941b6 Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Wed, 3 Nov 2021 12:15:42 +0800 Subject: [PATCH 24/42] Error for editing count directly --- src/main/java/seedu/address/logic/commands/EditCommand.java | 6 ++++++ src/main/java/seedu/address/model/item/ItemDescriptor.java | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 78cef301d0a..a4c3d0a4ac2 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -45,6 +45,8 @@ public class EditCommand extends Command { public static final String MESSAGE_EDIT_ITEM_SUCCESS = "Edited Item: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; public static final String MESSAGE_DUPLICATE_ITEM = "This item already exists in the inventory."; + public static final String MESSAGE_COUNT_CNT_BE_EDITED = "Count cannot be directly edited. Please remove/delete " + + "the item and add it back into the inventory."; public static final String MESSAGE_DUPLICATE_ID = "This id clashes with another item in the inventory."; public static final String MESSAGE_DUPLICATE_NAME = "This name clashes with another item in the inventory."; public static final String MESSAGE_INVENTORY_NOT_DISPLAYED = @@ -78,6 +80,10 @@ public CommandResult execute(Model model) throws CommandException { if (index.getZeroBased() >= lastShownList.size()) { throw new CommandException(Messages.MESSAGE_INVALID_ITEM_DISPLAYED_INDEX); } + if (!toEditDescriptor.getCount().equals(Optional.empty())) { + throw new CommandException(MESSAGE_COUNT_CNT_BE_EDITED); + } + Item itemToEdit = (Item) lastShownList.get(index.getZeroBased()); Item editedItem = createEditedItem(itemToEdit, toEditDescriptor); diff --git a/src/main/java/seedu/address/model/item/ItemDescriptor.java b/src/main/java/seedu/address/model/item/ItemDescriptor.java index e4f14003932..de036ebfa84 100644 --- a/src/main/java/seedu/address/model/item/ItemDescriptor.java +++ b/src/main/java/seedu/address/model/item/ItemDescriptor.java @@ -53,7 +53,7 @@ public ItemDescriptor(ItemDescriptor toCopy) { * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, id, tags, costPrice, salesPrice); + return CollectionUtil.isAnyNonNull(name, id, count, tags, costPrice, salesPrice); } public void setName(Name name) { From c2da7c73bcbb89b13076eb833e988a5f59aa30e3 Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Wed, 3 Nov 2021 12:30:46 +0800 Subject: [PATCH 25/42] DeleteCommand detects extra flags --- .../java/seedu/address/commons/core/Messages.java | 6 ++---- .../address/logic/parser/DeleteCommandParser.java | 14 +++++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index c0111bdcb54..20f14ea9f4c 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -11,14 +11,12 @@ public class Messages { public static final String MESSAGE_ITEMS_LISTED_OVERVIEW = "%1$d items listed!"; public static final String MESSAGE_INVALID_COUNT_INTEGER = "The count provided must be positive!"; public static final String MESSAGE_INVALID_COUNT_FORMAT = "The count provided must be integer!"; - + public static final String MESSAGE_INVALID_COUNT_INDEX = "The index provided must be a number and cannot be 0 " + + "or negative!"; public static final String MESSAGE_INVALID_PRICE_FORMAT = "Prices provided must be numerical values!"; public static final String MESSAGE_INVALID_PRICE_RANGE = "Prices provided must be at least $0 and less than $10,000,000!"; - public static final String MESSAGE_INVALID_ID_FORMAT = "The id provided must be integer!"; public static final String MESSAGE_INVALID_ID_LENGTH_AND_SIGN = "The id provided must be positive" + " and at most 6 digits!"; - public static final String MESSAGE_INVALID_COUNT_INDEX = "The index provided must be a number and cannot be 0 " - + "or negative!"; } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index 14067595e3a..a83cd57140f 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -1,8 +1,7 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_COUNT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ID; +import static seedu.address.logic.parser.CliSyntax.*; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.parser.exceptions.ParseException; @@ -13,6 +12,8 @@ */ public class DeleteCommandParser implements Parser { + public static final String EXTRA_FLAGS_PRESENT = "Invalid Command Format! Delete Command does not require" + + " costprice, salesprice, count or tag fields."; /** * Parses the given {@code String} of arguments in the context of the DeleteCommand * and returns a DeleteCommand object for execution. @@ -21,13 +22,20 @@ public class DeleteCommandParser implements Parser { public DeleteCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_ID, PREFIX_COUNT); + ArgumentTokenizer.tokenize(args, PREFIX_ID, PREFIX_COUNT, PREFIX_COSTPRICE, PREFIX_SALESPRICE, + PREFIX_TAG); // Check that either name or id specified if (argMultimap.getPreamble().isEmpty() && argMultimap.getValue(PREFIX_ID).isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); } ItemDescriptor toDeleteDescriptor = new ItemDescriptor(); + // If any other flags present + if (!argMultimap.getValue(PREFIX_COUNT).isEmpty() || !argMultimap.getValue(PREFIX_COSTPRICE).isEmpty() + || !argMultimap.getValue(PREFIX_SALESPRICE).isEmpty() + || !argMultimap.getValue(PREFIX_TAG).isEmpty()) { + throw new ParseException(EXTRA_FLAGS_PRESENT); + } // Parse name if (!argMultimap.getPreamble().isEmpty()) { From 161f4eba9f7af161409c327e08cef271165b5fa5 Mon Sep 17 00:00:00 2001 From: bryanwee023 Date: Wed, 3 Nov 2021 14:50:49 +0800 Subject: [PATCH 26/42] Fix list transaction bugs --- .../commands/EndAndTransactOrderCommand.java | 15 ++++++------ .../address/logic/commands/HelpCommand.java | 4 ++-- src/main/java/seedu/address/model/Model.java | 8 ++++++- .../seedu/address/model/ModelManager.java | 24 +++++++++++-------- .../{ui => model/display}/ItemCard.java | 3 ++- .../display}/TransactionCard.java | 5 ++-- .../java/seedu/address/model/item/Item.java | 2 +- .../address/model/item/UniqueItemList.java | 6 +++++ .../java/seedu/address/model/order/Order.java | 7 ++++++ .../model/order/TransactionRecord.java | 2 +- 10 files changed, 50 insertions(+), 26 deletions(-) rename src/main/java/seedu/address/{ui => model/display}/ItemCard.java (97%) rename src/main/java/seedu/address/{ui => model/display}/TransactionCard.java (94%) diff --git a/src/main/java/seedu/address/logic/commands/EndAndTransactOrderCommand.java b/src/main/java/seedu/address/logic/commands/EndAndTransactOrderCommand.java index 27d3db172f0..8b7330767be 100644 --- a/src/main/java/seedu/address/logic/commands/EndAndTransactOrderCommand.java +++ b/src/main/java/seedu/address/logic/commands/EndAndTransactOrderCommand.java @@ -16,8 +16,7 @@ public class EndAndTransactOrderCommand extends Command { public static final String MESSAGE_SUCCESS = "Order is placed."; - public static final String MESSAGE_EMPTY_ORDER = "Order was empty. To start a new order, please restart the " - + "order process with 'sorder'"; + public static final String MESSAGE_EMPTY_ORDER = "Current order is empty. Order is closed."; public static final String MESSAGE_NO_UNCLOSED_ORDER = "Please use `sorder` to enter ordering mode first."; @@ -38,20 +37,20 @@ public CommandResult execute(Model model) throws CommandException { throw new CommandException(MESSAGE_NO_UNCLOSED_ORDER); } - boolean emptyOrder = model.getOrder().getOrderItems().isEmpty(); - - // Transact order - model.transactAndClearOrder(); - // If current displaying order, return to displaying inventory if (model.getDisplayMode() == DISPLAY_OPEN_ORDER) { model.updateFilteredDisplayList(DISPLAY_INVENTORY, PREDICATE_SHOW_ALL_ITEMS); } - if (emptyOrder) { + // Check if order is empty + if (model.getOrder().isEmpty()) { + model.closeOrder(); return new CommandResult(MESSAGE_EMPTY_ORDER); } + // Transact order + model.transactAndCloseOrder(); + return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index 3710914aa44..c851f35fce7 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -8,7 +8,7 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; - public static final String defaultMessage = "\nRefer to the user guide: " + public static final String DEFAULT_MESSAGE = "\nRefer to the user guide: " + "https://ay2122s1-cs2103-f10-2.github.io/tp/UserGuide.html"; private final String messageUsage; @@ -18,7 +18,7 @@ public class HelpCommand extends Command { */ public HelpCommand(String message) { if (message.isEmpty()) { - this.messageUsage = defaultMessage; + this.messageUsage = DEFAULT_MESSAGE; } else { this.messageUsage = message; } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index 4fa5dc16c05..519c96fd25f 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -148,6 +148,12 @@ public interface Model { */ Order getOrder(); + /** + * Close the current order. Does not save the order beforehand. + * Model must have an unclosed order. + */ + void closeOrder(); + /** * Decrements the count of the given {@code target} in the order by {@code amount}. * {@code target} must exist in the order. @@ -164,7 +170,7 @@ public interface Model { /** * Destroys the current order when ordering finish. */ - void transactAndClearOrder(); + void transactAndCloseOrder(); /** * Return a list of {@code TransactionRecord} sorted according to timestamp. diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index b9f457b20cf..b58d5275c67 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,6 +4,7 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import static seedu.address.model.display.DisplayMode.DISPLAY_INVENTORY; import static seedu.address.model.display.DisplayMode.DISPLAY_OPEN_ORDER; +import static seedu.address.model.display.DisplayMode.DISPLAY_TRANSACTION; import static seedu.address.model.display.DisplayMode.DISPLAY_TRANSACTION_LIST; import java.nio.file.Path; @@ -244,7 +245,7 @@ public Double openTransaction(String id) { .reduce((a, b) -> a + b).get(); // Display transaction - currentDisplay = DISPLAY_TRANSACTION_LIST; + currentDisplay = DISPLAY_TRANSACTION; displayList.setItems(transactionOptional.get().getOrderItems()); return totalCost; } @@ -295,6 +296,12 @@ public Order getOrder() { return optionalOrder.get(); } + @Override + public void closeOrder() { + assert hasUnclosedOrder(); + optionalOrder = Optional.empty(); + } + @Override public boolean hasUnclosedOrder() { return optionalOrder.isPresent(); @@ -325,33 +332,30 @@ public void removeFromOrder(Item item, int amount) { } @Override - public void transactAndClearOrder() { + public void transactAndCloseOrder() { transactAndClearOrder(userPrefs.getTransactionFilePath()); } /** * Helper TransactAndClearOrder with given path (for testing purposes) - * + * Model must have an unclosed order. The order must have at least 1 item. * @param path the path of the file */ public void transactAndClearOrder(Path path) { assert hasUnclosedOrder(); + assert !optionalOrder.get().isEmpty(); TransactionRecord transaction = inventory.transactOrder(optionalOrder.get()); - // Reset to no order status - optionalOrder = Optional.empty(); - transactions.add(transaction); - - if (transaction.getOrderItems().size() == 0) { - return; - } + transactions.add(transaction); Double totalRevenue = transaction.getOrderItems().stream() .map(i -> i.getCount() * i.getSalesPrice()) .reduce(0.0, (subTotal, next) -> subTotal + next); addRevenueBookKeeping(totalRevenue); + closeOrder(); + logger.fine(TRANSACTION_LOGGING_MSG + transaction.toString()); } diff --git a/src/main/java/seedu/address/ui/ItemCard.java b/src/main/java/seedu/address/model/display/ItemCard.java similarity index 97% rename from src/main/java/seedu/address/ui/ItemCard.java rename to src/main/java/seedu/address/model/display/ItemCard.java index a921c771e9f..4413ce01b85 100644 --- a/src/main/java/seedu/address/ui/ItemCard.java +++ b/src/main/java/seedu/address/model/display/ItemCard.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.address.model.display; import java.util.Comparator; @@ -8,6 +8,7 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Region; import seedu.address.model.item.Item; +import seedu.address.ui.UiPart; /** * An UI component that displays information of an {@code Item}. diff --git a/src/main/java/seedu/address/ui/TransactionCard.java b/src/main/java/seedu/address/model/display/TransactionCard.java similarity index 94% rename from src/main/java/seedu/address/ui/TransactionCard.java rename to src/main/java/seedu/address/model/display/TransactionCard.java index c696cdc1c60..c5acf30f9c4 100644 --- a/src/main/java/seedu/address/ui/TransactionCard.java +++ b/src/main/java/seedu/address/model/display/TransactionCard.java @@ -1,10 +1,11 @@ -package seedu.address.ui; +package seedu.address.model.display; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; import seedu.address.model.order.TransactionRecord; +import seedu.address.ui.UiPart; /** * An UI component that displays information of an {@code transaction}. @@ -48,7 +49,7 @@ public TransactionCard(TransactionRecord transaction, int displayedIndex) { Double sp = transaction.getOrderItems().stream() .map(item -> item.getCount() * item.getSalesPrice()).reduce((a, b) -> a + b).get(); - totalPrice.setText("Total price: " + sp.toString()); + totalPrice.setText(String.format("Total price: $.2f", sp)); totalItems.setText(String.format("Total items: %d", transaction.getOrderItems().size())); } diff --git a/src/main/java/seedu/address/model/item/Item.java b/src/main/java/seedu/address/model/item/Item.java index 3bc6ac9a9c8..9da11fa732d 100644 --- a/src/main/java/seedu/address/model/item/Item.java +++ b/src/main/java/seedu/address/model/item/Item.java @@ -9,8 +9,8 @@ import javafx.scene.layout.Region; import seedu.address.model.display.Displayable; +import seedu.address.model.display.ItemCard; import seedu.address.model.tag.Tag; -import seedu.address.ui.ItemCard; import seedu.address.ui.UiPart; /** diff --git a/src/main/java/seedu/address/model/item/UniqueItemList.java b/src/main/java/seedu/address/model/item/UniqueItemList.java index f1a0ff362f8..b039352ebc8 100644 --- a/src/main/java/seedu/address/model/item/UniqueItemList.java +++ b/src/main/java/seedu/address/model/item/UniqueItemList.java @@ -56,6 +56,12 @@ public boolean containsName(Item toCheck) { return internalList.stream().anyMatch(x -> toCheck.isSameName(x) && x.getCount() > 0); } + /** + * Returns true if item list is empty. + */ + public boolean isEmpty() { + return internalList.isEmpty(); + } /** * Returns true if the list contains an item that matches the given {@code ItemDescriptor} diff --git a/src/main/java/seedu/address/model/order/Order.java b/src/main/java/seedu/address/model/order/Order.java index b9daac38d78..78dc8abe83f 100644 --- a/src/main/java/seedu/address/model/order/Order.java +++ b/src/main/java/seedu/address/model/order/Order.java @@ -88,6 +88,13 @@ public ObservableList getOrderItems() { return items.asUnmodifiableObservableList(); } + /** + * Returns true if order is empty. + */ + public boolean isEmpty() { + return items.isEmpty(); + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/model/order/TransactionRecord.java b/src/main/java/seedu/address/model/order/TransactionRecord.java index 3822bb3c7fd..ce2c5da17de 100644 --- a/src/main/java/seedu/address/model/order/TransactionRecord.java +++ b/src/main/java/seedu/address/model/order/TransactionRecord.java @@ -8,8 +8,8 @@ import javafx.scene.layout.Region; import seedu.address.commons.util.StringUtil; import seedu.address.model.display.Displayable; +import seedu.address.model.display.TransactionCard; import seedu.address.model.item.Item; -import seedu.address.ui.TransactionCard; import seedu.address.ui.UiPart; /** From 14b92156edf3585d140cad7f011162af58b14230 Mon Sep 17 00:00:00 2001 From: bryanwee023 Date: Wed, 3 Nov 2021 15:02:52 +0800 Subject: [PATCH 27/42] Update tests --- .../EndAndTransactOrderCommandTest.java | 19 ++++++++++++++----- .../logic/parser/HelpCommandParserTest.java | 2 +- .../seedu/address/model/ModelManagerTest.java | 2 +- .../java/seedu/address/model/ModelStub.java | 7 ++++++- .../seedu/address/model/order/OrderTest.java | 13 +++++++++++++ 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/test/java/seedu/address/logic/commands/EndAndTransactOrderCommandTest.java b/src/test/java/seedu/address/logic/commands/EndAndTransactOrderCommandTest.java index 0acca200c69..3a55f6a4765 100644 --- a/src/test/java/seedu/address/logic/commands/EndAndTransactOrderCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/EndAndTransactOrderCommandTest.java @@ -51,6 +51,20 @@ public void execute_noUnclosedOrder_failure() { assertCommandFailure(command, modelWithoutOrder, EndAndTransactOrderCommand.MESSAGE_NO_UNCLOSED_ORDER); } + @Test + public void execute_emptyOrder_success() { + EndAndTransactOrderCommand command = new EndAndTransactOrderCommand(); + + Model modelWithEmptyOrder = new ModelManager(getTypicalInventory(), + new UserPrefs(), new TransactionList(), new BookKeeping()); + modelWithEmptyOrder.setOrder(new Order()); + + Model modelWithoutOrder = new ModelManager(getTypicalInventory(), + new UserPrefs(), new TransactionList(), new BookKeeping()); + CommandResult expectedResult = new CommandResult(EndAndTransactOrderCommand.MESSAGE_EMPTY_ORDER); + assertCommandSuccess(command, modelWithEmptyOrder, expectedResult, modelWithoutOrder); + } + @Test public void execute_normalTransaction_itemRemoved() { String expectedMessage = EndAndTransactOrderCommand.MESSAGE_SUCCESS; @@ -64,11 +78,6 @@ public void execute_normalTransaction_itemRemoved() { assertCommandSuccess(new EndAndTransactOrderCommand(), modelTemp, expectedMessage, expectedModel); } - @Test - public void execute_orderIsEmpty_failure() { - // TODO: Behaviour not supported yet. Change and update accordingly - } - @Test public void execute_displayingOrder_itemRemovedAndDisplayInventory() { Model modelTemp = getModelWithOrderedDonut(temporaryFolder.resolve("transaction.json")); diff --git a/src/test/java/seedu/address/logic/parser/HelpCommandParserTest.java b/src/test/java/seedu/address/logic/parser/HelpCommandParserTest.java index aa27436b4eb..29a7f46b0ea 100644 --- a/src/test/java/seedu/address/logic/parser/HelpCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/HelpCommandParserTest.java @@ -123,7 +123,7 @@ public void parse_validListsArgs_returnsHelpCommand() { @Test public void parse_validEmptyArgs_success() { // asking help for empty command - HelpCommand expectedHelpCommand = new HelpCommand(HelpCommand.defaultMessage); + HelpCommand expectedHelpCommand = new HelpCommand(HelpCommand.DEFAULT_MESSAGE); assertParseSuccess(parser, "", expectedHelpCommand); } diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index ce8e2e68db8..221aa1cbefc 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -186,7 +186,7 @@ public void setOrder_typicalOrder_orderIsSet() { @Test public void transactAndClearOrder_noOrderIsSetYet_throwAssertionError() { ModelManager model = new ModelManager(); - assertThrows(AssertionError.class, model::transactAndClearOrder); + assertThrows(AssertionError.class, model::transactAndCloseOrder); } @Test diff --git a/src/test/java/seedu/address/model/ModelStub.java b/src/test/java/seedu/address/model/ModelStub.java index 38c84fd0508..da5709d63a5 100644 --- a/src/test/java/seedu/address/model/ModelStub.java +++ b/src/test/java/seedu/address/model/ModelStub.java @@ -143,6 +143,11 @@ public boolean hasUnclosedOrder() { throw new AssertionError("This method should not be called."); } + @Override + public void closeOrder() { + throw new AssertionError("This method should not be called."); + } + @Override public void addToOrder(Item item) { throw new AssertionError("This method should not be called."); @@ -159,7 +164,7 @@ public void removeFromOrder(Item item, int amount) { } @Override - public void transactAndClearOrder() { + public void transactAndCloseOrder() { throw new AssertionError("This method should not be called."); } diff --git a/src/test/java/seedu/address/model/order/OrderTest.java b/src/test/java/seedu/address/model/order/OrderTest.java index 4b2572a8bfe..b3dcd4fcafc 100644 --- a/src/test/java/seedu/address/model/order/OrderTest.java +++ b/src/test/java/seedu/address/model/order/OrderTest.java @@ -1,7 +1,9 @@ package seedu.address.model.order; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.logic.commands.CommandTestUtil.VALID_ID_BAGEL; import static seedu.address.logic.commands.CommandTestUtil.VALID_ID_DONUT; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BAGEL; @@ -150,4 +152,15 @@ public void getItem_multipleMatches_returnMultiple() { assertEquals(order.getItems(descriptor), List.of(DONUT, BAGEL)); } + @Test + public void isEmpty() { + // Return true if order empty + assertTrue(order.isEmpty()); + + // Return false if order has items + order.addItem(DONUT); + order.addItem(BAGEL); + assertFalse(order.isEmpty()); + } + } From 2cd3d81924b5979f7eea8058a058b7f231d8a23c Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Wed, 3 Nov 2021 20:24:43 +0800 Subject: [PATCH 28/42] Fix AddCommand bugs --- .../address/logic/commands/AddCommand.java | 75 ++++++++++++------- .../address/logic/commands/EditCommand.java | 16 ++-- .../logic/parser/DeleteCommandParser.java | 6 +- .../address/logic/parser/ParserUtil.java | 2 +- .../logic/commands/AddCommandTest.java | 71 +++++++++--------- .../logic/commands/EditCommandTest.java | 6 +- .../address/logic/parser/ParserUtilTest.java | 20 +++-- 7 files changed, 114 insertions(+), 82 deletions(-) diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 42fd2de443c..1cf34b26754 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -15,6 +15,7 @@ import seedu.address.model.Model; import seedu.address.model.item.Item; import seedu.address.model.item.ItemDescriptor; +import seedu.address.model.item.Name; /** * Adds an item to the inventory. @@ -44,12 +45,14 @@ public class AddCommand extends Command { public static final String MESSAGE_SUCCESS_NEW = "New item added: %1$s"; public static final String MESSAGE_SUCCESS_REPLENISH = "Item replenished: %d x %s"; + public static final String MESSAGE_EXTRA_PRICE_FLAGS = "There seems to be extra price fields. If you wish to " + + "edit prices, please use 'edit' instead."; + public static final String MESSAGE_EXTRA_TAG_FLAGS = "There seems to be extra tag field. If you wish to " + + "edit tags, please use 'edit' instead."; public static final String MESSAGE_INCOMPLETE_INFO = "Item has not been added before," + " please provide name, id, cost price, and sales price"; public static final String MESSAGE_ID_NOT_FOUND = "Name provided exists but id provided is nonexistent"; public static final String MESSAGE_NAME_NOT_FOUND = "Id provided exists but name provided is nonexistent"; - public static final String MESSAGE_ID_EXISTS = "Id provided already used for another item"; - public static final String MESSAGE_NAME_EXISTS = "Name provided already used for another item"; public static final String MESSAGE_MULTIPLE_MATCHES = "Multiple candidates found, which one did you mean to add?"; private final ItemDescriptor toAddDescriptor; @@ -68,7 +71,10 @@ public CommandResult execute(Model model) throws CommandException { assert(toAddDescriptor.getCount().isPresent()); List matchingItems = model.getItems(toAddDescriptor); - + boolean extraCostFlags = false; + boolean extraTagFlag = false; + boolean nameEmpty = true; + boolean idEmpty = true; // Check if item exists in inventory if (matchingItems.size() == 0) { // Check name and id are specified @@ -85,48 +91,61 @@ public CommandResult execute(Model model) throws CommandException { return new CommandResult(String.format(MESSAGE_SUCCESS_NEW, newItem)); } - // Check that id and name of the replenished item does not exist - if (!toAddDescriptor.getName().equals(Optional.empty()) - && !toAddDescriptor.getId().equals(Optional.empty()) - && toAddDescriptor.getCostPrice().equals(Optional.empty()) - && toAddDescriptor.getSalesPrice().equals(Optional.empty())) { + if (toAddDescriptor.getCostPrice().equals(Optional.empty())) { toAddDescriptor.setCostPrice(1.0); + } else { + extraCostFlags = true; + } + if (toAddDescriptor.getSalesPrice().equals(Optional.empty())) { toAddDescriptor.setSalesPrice(1.0); - //check that id does not exist + } else { + extraCostFlags = true; + } + if (!toAddDescriptor.getTags().equals(Optional.empty())) { + extraTagFlag = true; + } + // Check that only 1 item fit the description + if (matchingItems.size() > 1) { + model.updateFilteredItemList(DISPLAY_INVENTORY, toAddDescriptor::isMatch); + throw new CommandException(MESSAGE_MULTIPLE_MATCHES); + } + if (!toAddDescriptor.getId().equals(Optional.empty()) + && !toAddDescriptor.getName().equals(Optional.empty())) { if (!model.hasId(toAddDescriptor.buildItem())) { throw new CommandException(MESSAGE_ID_NOT_FOUND); } - //check that name does not exist if (!model.hasName(toAddDescriptor.buildItem())) { throw new CommandException(MESSAGE_NAME_NOT_FOUND); } } - // Check that only 1 item fit the description - if (matchingItems.size() > 1) { - model.updateFilteredItemList(DISPLAY_INVENTORY, toAddDescriptor::isMatch); - throw new CommandException(MESSAGE_MULTIPLE_MATCHES); - } - // Check that id and name of new item does not exist - if (!toAddDescriptor.getName().equals(Optional.empty()) - && !toAddDescriptor.getId().equals(Optional.empty()) - && !toAddDescriptor.getCostPrice().equals(Optional.empty()) - && !toAddDescriptor.getSalesPrice().equals(Optional.empty())) { - toAddDescriptor.setCostPrice(1.0); - toAddDescriptor.setSalesPrice(1.0); - //check that id exists - if (model.hasId(toAddDescriptor.buildItem())) { - throw new CommandException(MESSAGE_ID_EXISTS); + if (toAddDescriptor.getName().equals(Optional.empty())) { + toAddDescriptor.setName(new Name("sample")); + nameEmpty = true; + if (!model.hasId(toAddDescriptor.buildItem())) { + throw new CommandException(MESSAGE_ID_NOT_FOUND); } - //check that name exists - if (model.hasName(toAddDescriptor.buildItem())) { - throw new CommandException(MESSAGE_NAME_EXISTS); + toAddDescriptor.setName(null); + } + if (toAddDescriptor.getId().equals(Optional.empty())) { + toAddDescriptor.setId(1); + idEmpty = true; + if (!model.hasName(toAddDescriptor.buildItem())) { + throw new CommandException(MESSAGE_NAME_NOT_FOUND); } + toAddDescriptor.setId(null); } Item target = matchingItems.get(0); int amount = toAddDescriptor.getCount().get(); model.restockItem(target, amount); model.addCostBookKeeping(amount * target.getCostPrice()); + String replenishedMessage = String.format(MESSAGE_SUCCESS_REPLENISH, amount, target.getName()); + if (extraCostFlags) { + return new CommandResult(replenishedMessage + "\n" + MESSAGE_EXTRA_PRICE_FLAGS); + } + if (extraTagFlag) { + return new CommandResult(replenishedMessage + "\n" + MESSAGE_EXTRA_TAG_FLAGS); + } return new CommandResult(String.format(MESSAGE_SUCCESS_REPLENISH, amount, target.getName())); } diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index a4c3d0a4ac2..7b850cdad2b 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -45,8 +45,8 @@ public class EditCommand extends Command { public static final String MESSAGE_EDIT_ITEM_SUCCESS = "Edited Item: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; public static final String MESSAGE_DUPLICATE_ITEM = "This item already exists in the inventory."; - public static final String MESSAGE_COUNT_CNT_BE_EDITED = "Count cannot be directly edited. Please remove/delete " + - "the item and add it back into the inventory."; + public static final String MESSAGE_COUNT_CNT_BE_EDITED = "Count cannot be directly edited. Please remove/delete " + + "the item and add it back into the inventory."; public static final String MESSAGE_DUPLICATE_ID = "This id clashes with another item in the inventory."; public static final String MESSAGE_DUPLICATE_NAME = "This name clashes with another item in the inventory."; public static final String MESSAGE_INVENTORY_NOT_DISPLAYED = @@ -80,9 +80,6 @@ public CommandResult execute(Model model) throws CommandException { if (index.getZeroBased() >= lastShownList.size()) { throw new CommandException(Messages.MESSAGE_INVALID_ITEM_DISPLAYED_INDEX); } - if (!toEditDescriptor.getCount().equals(Optional.empty())) { - throw new CommandException(MESSAGE_COUNT_CNT_BE_EDITED); - } Item itemToEdit = (Item) lastShownList.get(index.getZeroBased()); Item editedItem = createEditedItem(itemToEdit, toEditDescriptor); @@ -96,10 +93,15 @@ public CommandResult execute(Model model) throws CommandException { if (model.hasName(editedItem) && !toEditDescriptor.getName().equals(Optional.empty())) { throw new CommandException(MESSAGE_DUPLICATE_NAME); } - - + if ((!toEditDescriptor.getCount().equals(Optional.empty())) + && !(toEditDescriptor.getCount().get().equals(itemToEdit.getCount()))) { + throw new CommandException(MESSAGE_COUNT_CNT_BE_EDITED); + } model.setItem(itemToEdit, editedItem); model.updateFilteredDisplayList(DISPLAY_INVENTORY, PREDICATE_SHOW_ALL_ITEMS); + if ((!toEditDescriptor.getCount().equals(Optional.empty()))) { + return new CommandResult(String.format(MESSAGE_COUNT_CNT_BE_EDITED, editedItem)); + } return new CommandResult(String.format(MESSAGE_EDIT_ITEM_SUCCESS, editedItem)); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index a83cd57140f..d7d5e858ecb 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -1,7 +1,11 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.*; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COSTPRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COUNT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALESPRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.parser.exceptions.ParseException; diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index f8aa3ed202b..e15a105620e 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -105,7 +105,7 @@ public static Integer parseId(String id) throws ParseException { throw new ParseException(Messages.MESSAGE_INVALID_ID_FORMAT); } - if (id.length() != 6 || idValue < 0) { + if (id.length() > 6 || idValue < 0) { throw new ParseException(Messages.MESSAGE_INVALID_ID_LENGTH_AND_SIGN); } diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index 83f44cf7594..2b0d0d7205f 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -128,40 +128,36 @@ public void execute_newItemNoSalesPrice_incompleteInfofailure() { @Test public void execute_existingItemNameDescription_restockSuccessful() { - modelStub.addItem(BAGEL.updateCount(5)); - - ItemDescriptor validDescriptor = new ItemDescriptorBuilder() - .withName(VALID_NAME_BAGEL) - .withCount(5) - .build(); - - AddCommand addCommand = new AddCommand(validDescriptor); + model.addItem(BAGEL); + ItemDescriptor bagelDescriptor = new ItemDescriptorBuilder().withName(VALID_NAME_BAGEL) + .withCount(VALID_COUNT_BAGEL).build(); + AddCommand addCommand = new AddCommand(bagelDescriptor); String expectedMessage = String.format(AddCommand.MESSAGE_SUCCESS_REPLENISH, 5, VALID_NAME_BAGEL); - ModelStubAcceptingItemAdded expectedModel = new ModelStubAcceptingItemAdded(); + + Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.addItem(BAGEL); expectedModel.restockItem(BAGEL, 5); - assertCommandSuccess(addCommand, modelStub, expectedMessage, expectedModel); + assertCommandSuccess(addCommand, model, expectedMessage, expectedModel); } @Test public void execute_existingItemIdDescription_restockSuccessful() { - modelStub.addItem(BAGEL); - - ItemDescriptor validDescriptor = new ItemDescriptorBuilder() - .withId(VALID_ID_BAGEL) - .withCount(5) - .build(); - - AddCommand addCommand = new AddCommand(validDescriptor); + model.addItem(BAGEL); + ItemDescriptor bagelDescriptor = new ItemDescriptorBuilder().withId(VALID_ID_BAGEL) + .withCount(VALID_COUNT_BAGEL).build(); + AddCommand addCommand = new AddCommand(bagelDescriptor); String expectedMessage = String.format(AddCommand.MESSAGE_SUCCESS_REPLENISH, 5, VALID_NAME_BAGEL); - ModelStubAcceptingItemAdded expectedModel = new ModelStubAcceptingItemAdded(); + + Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); expectedModel.addItem(BAGEL); expectedModel.restockItem(BAGEL, 5); - assertCommandSuccess(addCommand, modelStub, expectedMessage, expectedModel); + assertCommandSuccess(addCommand, model, expectedMessage, expectedModel); } @Test @@ -193,39 +189,40 @@ public void execute_nameExistNonexistentId_throwsCommandException() { assertCommandFailure(addCommand, model, expectedModel, expectedMessage); } - @Test - public void execute_nameAlreadyExists_throwsCommandException() { + public void execute_extraPriceFlags_restockSuccessful() { model.addItem(BAGEL); ItemDescriptor bagelDescriptor = new ItemDescriptorBuilder() - .withName(VALID_NAME_BAGEL).withId("173927") - .withCount(VALID_COUNT_BAGEL).withCostPrice(VALID_COSTPRICE_BAGEL) - .withSalesPrice(VALID_SALESPRICE_BAGEL).build(); + .withName(VALID_NAME_BAGEL).withCount(VALID_COUNT_BAGEL).withSalesPrice(VALID_SALESPRICE_BAGEL).build(); AddCommand addCommand = new AddCommand(bagelDescriptor); - String expectedMessage = AddCommand.MESSAGE_NAME_EXISTS; + String replenishMessage = String.format(AddCommand.MESSAGE_SUCCESS_REPLENISH, 5, VALID_NAME_BAGEL); + String expectedMessage = replenishMessage + "\n" + AddCommand.MESSAGE_EXTRA_PRICE_FLAGS; - Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs(), - model.getTransactions(), model.getBookKeeping()); + Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); + expectedModel.addItem(BAGEL); + expectedModel.restockItem(BAGEL, 5); - assertCommandFailure(addCommand, model, expectedModel, expectedMessage); + assertCommandSuccess(addCommand, model, expectedMessage, expectedModel); } @Test - public void execute_idAlreadyExists_throwsCommandException() { + public void execute_extraTagFlags_restockSuccessful() { model.addItem(BAGEL); ItemDescriptor bagelDescriptor = new ItemDescriptorBuilder() - .withName("boo").withId(VALID_ID_BAGEL).withCount(VALID_COUNT_BAGEL) - .withCostPrice(VALID_COSTPRICE_BAGEL) - .withSalesPrice(VALID_SALESPRICE_BAGEL).build(); + .withName(VALID_NAME_BAGEL).withCount(VALID_COUNT_BAGEL).withTags(VALID_TAG_BAKED).build(); AddCommand addCommand = new AddCommand(bagelDescriptor); - String expectedMessage = AddCommand.MESSAGE_ID_EXISTS; + String replenishMessage = String.format(AddCommand.MESSAGE_SUCCESS_REPLENISH, 5, VALID_NAME_BAGEL); + String expectedMessage = replenishMessage + "\n" + AddCommand.MESSAGE_EXTRA_TAG_FLAGS; - Model expectedModel = new ModelManager(model.getInventory(), model.getUserPrefs(), - model.getTransactions(), model.getBookKeeping()); + Model expectedModel = new ModelManager(getTypicalInventory(), new UserPrefs(), + new TransactionList(), new BookKeeping()); + expectedModel.addItem(BAGEL); + expectedModel.restockItem(BAGEL, 5); - assertCommandFailure(addCommand, model, expectedModel, expectedMessage); + assertCommandSuccess(addCommand, model, expectedMessage, expectedModel); } @Test diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java index 0d2ed9b7671..d18ecf6a9ae 100644 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java @@ -14,6 +14,7 @@ import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_ITEM; import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_ITEM; import static seedu.address.testutil.TypicalItems.getTypicalInventory; +import static seedu.address.testutil.TypicalItems.getTypicalItems; import org.junit.jupiter.api.Test; @@ -42,11 +43,11 @@ public class EditCommandTest { @Test public void execute_allFieldsSpecifiedUnfilteredList_success() { - Item editedItem = new ItemBuilder().build(); + Item editedItem = new ItemBuilder().withCount(getTypicalItems().get(0).getCount().toString()).build(); ItemDescriptor descriptor = new ItemDescriptorBuilder(editedItem).build(); EditCommand editCommand = new EditCommand(INDEX_FIRST_ITEM, descriptor); - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_ITEM_SUCCESS, editedItem); + String expectedMessage = String.format(EditCommand.MESSAGE_COUNT_CNT_BE_EDITED, editedItem); Model expectedModel = new ModelManager(new Inventory(model.getInventory()), new UserPrefs(), new TransactionList(), new BookKeeping()); @@ -121,6 +122,7 @@ public void execute_duplicateItemFilteredList_failure() { // edit item in filtered list into a duplicate in inventory Item itemInList = model.getInventory().getItemList().get(INDEX_SECOND_ITEM.getZeroBased()); + EditCommand editCommand = new EditCommand(INDEX_FIRST_ITEM, new ItemDescriptorBuilder(itemInList).build()); diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index 39659f1db70..a34882fa0a9 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -25,7 +25,7 @@ public class ParserUtilTest { private static final String INVALID_COUNT_NEGATIVE = "-1"; private static final String INVALID_ID_FORMAT = "abc"; private static final String INVALID_ID_NEGATIVE = "-1"; - private static final String INVALID_ID_SHORTER = "123"; + private static final String INVALID_ID_LONGER = "1232343"; private static final String INVALID_PRICE_FORMAT = "abc"; private static final String INVALID_PRICE_NEGATIVE = "-1"; private static final String INVALID_PRICE_OVERFLOW = "999999999.1"; @@ -35,7 +35,8 @@ public class ParserUtilTest { private static final String VALID_TAG_2 = "sweet"; private static final String VALID_COUNT_1 = "2"; private static final String VALID_COUNT_2 = "12"; - private static final String VALID_ID = "123456"; + private static final String VALID_ID_1 = "123456"; + private static final String VALID_ID_2 = "123"; private static final String VALID_PRICE_1 = "1.21"; private static final String VALID_PRICE_2 = "12.2121"; // Should be rounded to 2 decimal places @@ -180,14 +181,21 @@ public void parseId_negativeValue_throwsParseException() { } @Test - public void parseId_lessThan6Digits_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseId(INVALID_ID_SHORTER)); + public void parseId_moreThan6Digits_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseId(INVALID_ID_LONGER)); } @Test public void parseId_validId_returnsId() throws Exception { - Integer expectedId = Integer.parseInt(VALID_ID); - Integer actualId = ParserUtil.parseId(VALID_ID); + Integer expectedId = Integer.parseInt(VALID_ID_1); + Integer actualId = ParserUtil.parseId(VALID_ID_1); + assertEquals(expectedId, actualId); + } + + @Test + public void parseId_validIdLessThan6digits_returnsId() throws Exception { + Integer expectedId = Integer.parseInt(VALID_ID_2); + Integer actualId = ParserUtil.parseId(VALID_ID_2); assertEquals(expectedId, actualId); } From 75cf22c52401d4853b5215ab89cbeb2139121749 Mon Sep 17 00:00:00 2001 From: bernarduskrishna <77195969+bernarduskrishna@users.noreply.github.com> Date: Wed, 3 Nov 2021 21:04:11 +0800 Subject: [PATCH 29/42] Fix sale price not showing properly in card --- src/main/java/seedu/address/model/display/TransactionCard.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/seedu/address/model/display/TransactionCard.java b/src/main/java/seedu/address/model/display/TransactionCard.java index c5acf30f9c4..ccc58a93fa7 100644 --- a/src/main/java/seedu/address/model/display/TransactionCard.java +++ b/src/main/java/seedu/address/model/display/TransactionCard.java @@ -49,7 +49,7 @@ public TransactionCard(TransactionRecord transaction, int displayedIndex) { Double sp = transaction.getOrderItems().stream() .map(item -> item.getCount() * item.getSalesPrice()).reduce((a, b) -> a + b).get(); - totalPrice.setText(String.format("Total price: $.2f", sp)); + totalPrice.setText(String.format("Total price: $%.2f", sp)); totalItems.setText(String.format("Total items: %d", transaction.getOrderItems().size())); } From 971c9a0c38f0aa3e947e5ad813f5d6716df57146 Mon Sep 17 00:00:00 2001 From: bernarduskrishna <77195969+bernarduskrishna@users.noreply.github.com> Date: Wed, 3 Nov 2021 21:20:16 +0800 Subject: [PATCH 30/42] Fix discrepancy between sampleBookKeeping and sampleInventory --- src/main/java/seedu/address/MainApp.java | 2 +- src/main/java/seedu/address/model/ModelManager.java | 5 +++-- .../java/seedu/address/model/util/SampleDataUtil.java | 8 ++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 8df5a114299..8a4a599e071 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -108,7 +108,7 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { initialData = inventoryOptional.orElseGet(SampleDataUtil::getSampleInventory); transactionList = transactionListOptional .orElseGet(() -> new TransactionList(new ArrayList())); - bookKeeping = bookKeepingOptional.orElseGet(BookKeeping::new); + bookKeeping = bookKeepingOptional.orElseGet(SampleDataUtil::getSampleBookKeeping); } catch (DataConversionException e) { logger.warning("Data file not in the correct format. Will be starting with an empty inventory"); diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index b58d5275c67..7cf10148271 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -49,9 +49,10 @@ public class ModelManager implements Model { public ModelManager(ReadOnlyInventory inventory, ReadOnlyUserPrefs userPrefs, ReadOnlyTransactionList transactionList, ReadOnlyBookKeeping bookKeeping) { super(); - requireAllNonNull(inventory, userPrefs); + requireAllNonNull(inventory, userPrefs, transactionList, bookKeeping); - logger.fine("Initializing with inventory: " + inventory + " and user prefs " + userPrefs); + logger.fine("Initializing with inventory: " + inventory + ", user prefs " + userPrefs + + ", transaction list: " + transactionList + ", bookkeeping: " + bookKeeping); this.inventory = new Inventory(inventory); this.userPrefs = new UserPrefs(userPrefs); diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1e7bf9bca07..cd8218c1124 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -4,7 +4,9 @@ import java.util.Set; import java.util.stream.Collectors; +import seedu.address.model.BookKeeping; import seedu.address.model.Inventory; +import seedu.address.model.ReadOnlyBookKeeping; import seedu.address.model.ReadOnlyInventory; import seedu.address.model.item.Item; import seedu.address.model.item.Name; @@ -48,4 +50,10 @@ public static Set getTagSet(String... strings) { .collect(Collectors.toSet()); } + public static ReadOnlyBookKeeping getSampleBookKeeping() { + double cost = Arrays.stream(getSampleItems()) + .map(item -> item.getCostPrice() * item.getCount()).reduce((a, b) -> a + b).get(); + return new BookKeeping(0.0, cost, -cost); + } + } From 21d849cd5a638010653b6ae8412bafcfb3c2c572 Mon Sep 17 00:00:00 2001 From: bernarduskrishna <77195969+bernarduskrishna@users.noreply.github.com> Date: Wed, 3 Nov 2021 21:28:34 +0800 Subject: [PATCH 31/42] Clear should clear not only inventory but also bookkeeping and transactions. --- .../seedu/address/logic/commands/ClearCommand.java | 2 ++ src/main/java/seedu/address/model/BookKeeping.java | 9 +++++++++ src/main/java/seedu/address/model/Model.java | 4 ++++ src/main/java/seedu/address/model/ModelManager.java | 13 +++++++++++++ 4 files changed, 28 insertions(+) diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 1339fdd71f7..bf34167f26c 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -19,6 +19,8 @@ public class ClearCommand extends Command { public CommandResult execute(Model model) { requireNonNull(model); model.setInventory(new Inventory()); + model.initialiseBookKeeping(); + model.initialiseTransactions(); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/model/BookKeeping.java b/src/main/java/seedu/address/model/BookKeeping.java index 64678302bea..060728ff127 100644 --- a/src/main/java/seedu/address/model/BookKeeping.java +++ b/src/main/java/seedu/address/model/BookKeeping.java @@ -64,4 +64,13 @@ public void addRevenue(Double revenue) { this.revenue += revenue; this.profit += revenue; } + + /** + * Reinitialise bookKeeping. + */ + public void initialise() { + this.revenue = 0.0; + this.cost = 0.0; + this.profit = 0.0; + } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index 519c96fd25f..c36ecd48c99 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -213,9 +213,13 @@ public interface Model { */ DisplayMode getDisplayMode(); + void initialiseTransactions(); + void addCostBookKeeping(Double cost); void addRevenueBookKeeping(Double revenue); BookKeeping getBookKeeping(); + + void initialiseBookKeeping(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 7cf10148271..a757f37b340 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -365,6 +365,11 @@ public ReadOnlyTransactionList getTransactions() { return new TransactionList(new ArrayList<>(transactions)); } + @Override + public void initialiseTransactions() { + transactions = new TransactionList().getTransactionRecordList(); + } + //=========== BookKeeping ================================================================================ /** @@ -384,4 +389,12 @@ public void addCostBookKeeping(Double cost) { public void addRevenueBookKeeping(Double revenue) { bookKeeping.addRevenue(revenue); } + + @Override + /** + * reinitialise bookKeeping. + */ + public void initialiseBookKeeping() { + bookKeeping.initialise(); + } } From d4085fdd39955c88158a2069344e3e729a49377d Mon Sep 17 00:00:00 2001 From: bernarduskrishna <77195969+bernarduskrishna@users.noreply.github.com> Date: Wed, 3 Nov 2021 21:41:57 +0800 Subject: [PATCH 32/42] Fix error message for edit --- src/main/java/seedu/address/commons/core/Messages.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 20f14ea9f4c..624415216a6 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -11,8 +11,8 @@ public class Messages { public static final String MESSAGE_ITEMS_LISTED_OVERVIEW = "%1$d items listed!"; public static final String MESSAGE_INVALID_COUNT_INTEGER = "The count provided must be positive!"; public static final String MESSAGE_INVALID_COUNT_FORMAT = "The count provided must be integer!"; - public static final String MESSAGE_INVALID_COUNT_INDEX = "The index provided must be a number and cannot be 0 " - + "or negative!"; + public static final String MESSAGE_INVALID_COUNT_INDEX = "The index provided must be a number (can't be >1 number)" + + " and cannot be 0 or negative!"; public static final String MESSAGE_INVALID_PRICE_FORMAT = "Prices provided must be numerical values!"; public static final String MESSAGE_INVALID_PRICE_RANGE = "Prices provided must be at least $0 and less than $10,000,000!"; From 267d968882a6c5d5162fc697c8f969da077c53d0 Mon Sep 17 00:00:00 2001 From: bernarduskrishna <77195969+bernarduskrishna@users.noreply.github.com> Date: Wed, 3 Nov 2021 21:43:36 +0800 Subject: [PATCH 33/42] Fix test --- src/main/java/seedu/address/model/ModelManager.java | 2 +- src/test/java/seedu/address/model/ModelStub.java | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index a757f37b340..aa64fca57dc 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -390,10 +390,10 @@ public void addRevenueBookKeeping(Double revenue) { bookKeeping.addRevenue(revenue); } - @Override /** * reinitialise bookKeeping. */ + @Override public void initialiseBookKeeping() { bookKeeping.initialise(); } diff --git a/src/test/java/seedu/address/model/ModelStub.java b/src/test/java/seedu/address/model/ModelStub.java index da5709d63a5..e53f1d35729 100644 --- a/src/test/java/seedu/address/model/ModelStub.java +++ b/src/test/java/seedu/address/model/ModelStub.java @@ -133,6 +133,11 @@ public DisplayMode getDisplayMode() { throw new AssertionError("This method should not be called."); } + @Override + public void initialiseTransactions() { + throw new AssertionError("This method should not be called."); + } + @Override public void setOrder(Order order) { throw new AssertionError("This method should not be called."); @@ -192,4 +197,9 @@ public void addRevenueBookKeeping(Double revenue) { public BookKeeping getBookKeeping() { throw new AssertionError("This method should not be called."); } + + @Override + public void initialiseBookKeeping() { + throw new AssertionError("This method should not be called."); + } } From 7d2c0832eaf38299d9a03df61cd31acb6a664d5b Mon Sep 17 00:00:00 2001 From: bernarduskrishna <77195969+bernarduskrishna@users.noreply.github.com> Date: Wed, 3 Nov 2021 21:45:27 +0800 Subject: [PATCH 34/42] Add some javadocs --- src/main/java/seedu/address/model/Model.java | 18 ++++++++++++++++++ .../java/seedu/address/model/ModelManager.java | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index c36ecd48c99..a397dc4ea41 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -213,13 +213,31 @@ public interface Model { */ DisplayMode getDisplayMode(); + /** + * Initialise Transactions. + */ void initialiseTransactions(); + /** + * Add a specified cost to bookKeeping. + * @param cost the specified cost + */ void addCostBookKeeping(Double cost); + /** + * Add a specified revenue to bookKeeping. + * @param revenue the specified revenue + */ void addRevenueBookKeeping(Double revenue); + /** + * Return the current BookKeeping. + * @return the current BookKeeping. + */ BookKeeping getBookKeeping(); + /** + * Initialise BookKeeping. + */ void initialiseBookKeeping(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index aa64fca57dc..49fecfaad60 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -391,7 +391,7 @@ public void addRevenueBookKeeping(Double revenue) { } /** - * reinitialise bookKeeping. + * Reinitialise bookKeeping. */ @Override public void initialiseBookKeeping() { From 56e6e4283ae0baf75dfd798406d29e4d16beb3db Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Wed, 3 Nov 2021 22:01:01 +0800 Subject: [PATCH 35/42] Fix bugs --- .../logic/commands/AddToOrderCommand.java | 16 ++++++++++++ .../logic/parser/AddToOrderCommandParser.java | 26 ++++++++++++++----- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/main/java/seedu/address/logic/commands/AddToOrderCommand.java b/src/main/java/seedu/address/logic/commands/AddToOrderCommand.java index 7dca5cefe97..1a1a3860e97 100644 --- a/src/main/java/seedu/address/logic/commands/AddToOrderCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddToOrderCommand.java @@ -6,6 +6,7 @@ import static seedu.address.model.display.DisplayMode.DISPLAY_INVENTORY; import java.util.List; +import java.util.Optional; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; @@ -33,6 +34,10 @@ public class AddToOrderCommand extends Command { public static final String MESSAGE_NO_UNCLOSED_ORDER = "Please use `sorder` to enter ordering mode first."; public static final String MESSAGE_MULTIPLE_MATCHES = "Multiple candidates found, which one do you mean to add?"; + public static final String MESSAGE_EXTRA_PRICE_FLAG = + "Extra price flags are ignored."; + public static final String MESSAGE_EXTRA_TAG_FLAG = + "Extra tag flags are ignored."; private final ItemDescriptor toAddDescriptor; @@ -105,6 +110,17 @@ public CommandResult execute(Model model) throws CommandException { Item toAddItem = matchingItems.get(0).updateCount(toAddDescriptor.getCount().get()); model.addToOrder(toAddItem); + if (!toAddDescriptor.getSalesPrice().equals(Optional.empty()) + || !toAddDescriptor.getCostPrice().equals(Optional.empty())) { + String addMessage = String.format(MESSAGE_SUCCESS, toAddItem.getCount(), toAddItem.getName()); + return new CommandResult( + addMessage + "\n" + MESSAGE_EXTRA_PRICE_FLAG); + } + if (!toAddDescriptor.getTags().equals(Optional.empty())) { + String addMessage = String.format(MESSAGE_SUCCESS, toAddItem.getCount(), toAddItem.getName()); + return new CommandResult( + addMessage + "\n" + MESSAGE_EXTRA_TAG_FLAG); + } return new CommandResult( String.format(MESSAGE_SUCCESS, toAddItem.getCount(), toAddItem.getName())); } diff --git a/src/main/java/seedu/address/logic/parser/AddToOrderCommandParser.java b/src/main/java/seedu/address/logic/parser/AddToOrderCommandParser.java index c8c8472ad3a..e9c5b179ea4 100644 --- a/src/main/java/seedu/address/logic/parser/AddToOrderCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddToOrderCommandParser.java @@ -1,9 +1,7 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_COUNT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ID; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.*; import seedu.address.logic.commands.AddToOrderCommand; import seedu.address.logic.parser.exceptions.ParseException; @@ -20,15 +18,20 @@ public class AddToOrderCommandParser implements Parser { @Override public AddToOrderCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_ID, PREFIX_COUNT, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_ID, PREFIX_COUNT, PREFIX_TAG, + PREFIX_COSTPRICE, PREFIX_SALESPRICE); if (argMultimap.getValue(PREFIX_ID).isEmpty() && argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddToOrderCommand.MESSAGE_USAGE)); } ItemDescriptor toAddDescriptor = new ItemDescriptor(); + // if both name tag and prefix are present + if (!argMultimap.getPreamble().isEmpty() && argMultimap.getValue(PREFIX_NAME).isPresent()) { + throw new ParseException("Name field is specified 2 times." + "\n" + AddToOrderCommand.MESSAGE_USAGE); + } - // Parse name + // Parse preamble if (!argMultimap.getPreamble().isEmpty()) { toAddDescriptor.setName(ParserUtil.parseName(argMultimap.getPreamble())); } @@ -42,7 +45,18 @@ public AddToOrderCommand parse(String args) throws ParseException { } else { toAddDescriptor.setCount(1); } - + // Parse costprice + if (argMultimap.getValue(PREFIX_COSTPRICE).isPresent()) { + toAddDescriptor.setCostPrice(ParserUtil.parsePrice(argMultimap.getValue(PREFIX_COSTPRICE).get())); + } + // Parse salesprice + if (argMultimap.getValue(PREFIX_SALESPRICE).isPresent()) { + toAddDescriptor.setSalesPrice(ParserUtil.parsePrice(argMultimap.getValue(PREFIX_SALESPRICE).get())); + } + // Parse tag + if (argMultimap.getValue(PREFIX_TAG).isPresent()) { + toAddDescriptor.setTags(ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_SALESPRICE))); + } return new AddToOrderCommand(toAddDescriptor); } } From 2429ce5b0cc62dcc19bd9fd8e851d52a3adbab82 Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Wed, 3 Nov 2021 22:20:24 +0800 Subject: [PATCH 36/42] fix checkstyle --- .../java/seedu/address/commons/core/Messages.java | 1 + .../logic/parser/AddToOrderCommandParser.java | 12 +++++++++--- .../logic/parser/AddToOrderCommandParserTest.java | 11 +++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 20f14ea9f4c..309981b0bda 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -19,4 +19,5 @@ public class Messages { public static final String MESSAGE_INVALID_ID_FORMAT = "The id provided must be integer!"; public static final String MESSAGE_INVALID_ID_LENGTH_AND_SIGN = "The id provided must be positive" + " and at most 6 digits!"; + public static final String MESSAGE_NAME_SPECIFIED_TWICE = "Name field is specified twice!"; } diff --git a/src/main/java/seedu/address/logic/parser/AddToOrderCommandParser.java b/src/main/java/seedu/address/logic/parser/AddToOrderCommandParser.java index e9c5b179ea4..8410b933f18 100644 --- a/src/main/java/seedu/address/logic/parser/AddToOrderCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddToOrderCommandParser.java @@ -1,7 +1,13 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.*; +import static seedu.address.commons.core.Messages.MESSAGE_NAME_SPECIFIED_TWICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COSTPRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COUNT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALESPRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import seedu.address.logic.commands.AddToOrderCommand; import seedu.address.logic.parser.exceptions.ParseException; @@ -26,9 +32,9 @@ public AddToOrderCommand parse(String args) throws ParseException { } ItemDescriptor toAddDescriptor = new ItemDescriptor(); - // if both name tag and prefix are present + // if both name flag and prefix are present if (!argMultimap.getPreamble().isEmpty() && argMultimap.getValue(PREFIX_NAME).isPresent()) { - throw new ParseException("Name field is specified 2 times." + "\n" + AddToOrderCommand.MESSAGE_USAGE); + throw new ParseException(MESSAGE_NAME_SPECIFIED_TWICE + "\n" + AddToOrderCommand.MESSAGE_USAGE); } // Parse preamble diff --git a/src/test/java/seedu/address/logic/parser/AddToOrderCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddToOrderCommandParserTest.java index f33174b9ed3..230bc9d7967 100644 --- a/src/test/java/seedu/address/logic/parser/AddToOrderCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddToOrderCommandParserTest.java @@ -1,6 +1,7 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_NAME_SPECIFIED_TWICE; import static seedu.address.logic.commands.CommandTestUtil.COUNT_DESC_BAGEL; import static seedu.address.logic.commands.CommandTestUtil.COUNT_DESC_DONUT; import static seedu.address.logic.commands.CommandTestUtil.COUNT_DESC_ZERO; @@ -16,6 +17,8 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_COUNT_BAGEL; import static seedu.address.logic.commands.CommandTestUtil.VALID_ID_BAGEL; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BAGEL; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_DONUT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; @@ -97,6 +100,14 @@ public void parse_noNameOrId_failure() { assertParseFailure(parser, COUNT_DESC_BAGEL, expectedMessage); } + @Test + public void parse_twoNameFields_failure() { + String expectedMessage = String.format(MESSAGE_NAME_SPECIFIED_TWICE + "\n" + AddToOrderCommand.MESSAGE_USAGE); + + // both name and id prefix missing + assertParseFailure(parser, VALID_NAME_BAGEL + " " + PREFIX_NAME + VALID_NAME_DONUT , expectedMessage); + } + @Test public void parse_countZero_failure() { assertParseFailure(parser, VALID_NAME_BAGEL + ID_DESC_BAGEL + COUNT_DESC_ZERO, From b38dbf99edec9fda8eb69858d3222ffd05f6ed8c Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Wed, 3 Nov 2021 22:34:16 +0800 Subject: [PATCH 37/42] HelpCommand notify user of invalid commands --- src/main/java/seedu/address/logic/commands/HelpCommand.java | 5 ++++- .../java/seedu/address/logic/parser/HelpCommandParser.java | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index c851f35fce7..b9138fbe1d6 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -10,8 +10,9 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; public static final String DEFAULT_MESSAGE = "\nRefer to the user guide: " + "https://ay2122s1-cs2103-f10-2.github.io/tp/UserGuide.html"; + public static final String INVALID_WORD = "Command given does not exist."; - private final String messageUsage; + private String messageUsage; /** * Creates a HelpCommand with specific help messages @@ -19,6 +20,8 @@ public class HelpCommand extends Command { public HelpCommand(String message) { if (message.isEmpty()) { this.messageUsage = DEFAULT_MESSAGE; + } else if (message.equals("invalid")) { + this.messageUsage = INVALID_WORD + DEFAULT_MESSAGE; } else { this.messageUsage = message; } diff --git a/src/main/java/seedu/address/logic/parser/HelpCommandParser.java b/src/main/java/seedu/address/logic/parser/HelpCommandParser.java index 3c6621f5f9d..850211794a0 100644 --- a/src/main/java/seedu/address/logic/parser/HelpCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/HelpCommandParser.java @@ -72,9 +72,10 @@ public HelpCommand parse(String args) throws ParseException { case EndAndTransactOrderCommand.COMMAND_WORD: return new HelpCommand(EndAndTransactOrderCommand.MESSAGE_USAGE); - - default: + } + if (trimmedArgs == "") { return new HelpCommand(""); } + return new HelpCommand("invalid"); } } From baf85ea54f01f3a3b76ffc088d86bce232bbb469 Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Wed, 3 Nov 2021 22:39:46 +0800 Subject: [PATCH 38/42] Add HelpCommand tests --- .../logic/parser/HelpCommandParserTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/java/seedu/address/logic/parser/HelpCommandParserTest.java b/src/test/java/seedu/address/logic/parser/HelpCommandParserTest.java index 29a7f46b0ea..3f01120f7c3 100644 --- a/src/test/java/seedu/address/logic/parser/HelpCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/HelpCommandParserTest.java @@ -47,6 +47,22 @@ public void parse_validDeleteArgs_returnsHelpCommand() { assertParseSuccess(parser, "delete", expectedHelpCommand); } + @Test + public void parse_validNonExistentCommand_returnsHelpCommand() { + // asking help for delete command + final String message = "invalid"; + HelpCommand expectedHelpCommand = new HelpCommand(message); + assertParseSuccess(parser, "fneoubv", expectedHelpCommand); + } + + @Test + public void parse_generalHelpMessage_returnsHelpCommand() { + // asking help for delete command + final String message = ""; + HelpCommand expectedHelpCommand = new HelpCommand(message); + assertParseSuccess(parser, "", expectedHelpCommand); + } + @Test public void parse_validClearArgs_returnsHelpCommand() { // asking help for clear command From dd30a813968c2761548bd1e8b111e68c021202c3 Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Wed, 3 Nov 2021 22:49:24 +0800 Subject: [PATCH 39/42] Delete Find and sort in DG --- docs/DeveloperGuide.md | 61 ------------------------------------------ 1 file changed, 61 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 63c80cdef7e..c1593c9fb2c 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -273,67 +273,6 @@ DeleteCommand#execute() -> Model#deleteItem() -> Inventory#deleteItems() -> Uniq **`SortCommand:`** SortCommand#execute() -> Model#sortItem() -> Inventory#sortItems() -> UniqueItemList#sortItem() -> ObservableList#sort() -### Sort feature - -The sort mechanism is facilitated by the built-in `Comparator` interface. The SortCommand constructor takes in a -predicate enum instruction as a parameter depending on whether the user requested to sort by name or count. The -items' respective fields are then compared with a `Comparator` so that the updated list displayed is sorted. -`Comparator` interface is implemented by different classes below: - -* `ItemNameComparator` — allows sorting of items by name -* `ItemCountComparator` — allows sorting of items by count - -Given below is an example usage scenario and how the sort mechanism behaves at each step. - -Step 1. The user opens up BogoBogo and executes `sort n/` to sort items by name. The `LogicManager` then calls the -`AddressBookParser` which create a `SortCommandParser` object. Then, `SortCommandParser#parse()` creates a `SortCommand` -object. Then the `LogicManager` calls the `SortCommand#execute()` which calls the `Model#SortItems()` and creates an -`ItemNameComparator` object which is passed as a parameter inside `Model#SortItems()`. The `Model#SortItems()` then -update the display list with items sorted by name according to the `ItemNameComparator#compare()` method. - -
:information_source: **Note:** If the user input does not input any field to sort by, SortCommandParser will throw a ParseException and a SortCommand will not be created. - -Step 2. The updated list with items sorted by name will then be shown to the user. The same procedure above is executed -for sorting by count as well. - -
:information_source: **Note:** If the user tries to sort when not in inventory mode, a CommandException will be thrown by SortCommand to remind user to list first. - -
- -The following sequence diagram shows how the sort operation works: - -![SortSequenceDiagram](images/SortSequenceDiagram.png) - -### Find feature - -The find mechanism is facilitated by the built-in `Predicate` class. The FindCommand constructor takes in a predicate -type as a parameter and the list is then filtered with `ModelManager#updateFilteredItemList` method to only contain the -items that match the predicate specified. `Predicate` interface is implemented by 3 different classes: - -* `IdContainsNumberPredicate` — allows finding of items by Id -* `NameContainsKeywordsPredicate` — allows finding of items by Name -* `TagContainsKeywordsPredicate` — allows finding of items by Tag - -Given below is an example usage scenario and how the find mechanism behaves at each step. - -Step 1. The user opens up BogoBogo and executes `find n/Chocolate` to find items with the name chocolate. The -`LogicManager` then calls the `AddressBookParser` which create a `FindCommandParser` object. Then, `FindCommandParser#parse()` -then creates a `FindCommand` object and `NameContainsKeywordsPredicate` object. The `NameContainsKeywordsPredicate` is -passed as a field of the FindCommand constructor. Then, the `LogicManager` calls the `FindCommand#execute()` -which will update the filtered list with items that matches the predicate stated. - -
:information_source: **Note:** If the user input a wrong format of the name, id or tag, a ParseException will be thrown by FindCommandParser and a FindCommand will not be created. - -Step 2. The updated list with items that matches the predicate will then be shown to the user. If none matches, an empty -list will be shown. The same procedure above is executed for finding by Id and Tags as well. - -
- -The following sequence diagram shows how the find operation works: - -![FindSequenceDiagram](images/FindSequenceDiagram.png) - - #### Design considerations: **Aspect:** From 60503bf8afca5e26d6fab5a1729b937da2f4749e Mon Sep 17 00:00:00 2001 From: GraceWang2322 <> Date: Wed, 3 Nov 2021 23:02:10 +0800 Subject: [PATCH 40/42] Fix bugs --- .../logic/parser/HelpCommandParser.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/main/java/seedu/address/logic/parser/HelpCommandParser.java b/src/main/java/seedu/address/logic/parser/HelpCommandParser.java index 850211794a0..0eb473ccb47 100644 --- a/src/main/java/seedu/address/logic/parser/HelpCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/HelpCommandParser.java @@ -34,48 +34,36 @@ public HelpCommand parse(String args) throws ParseException { switch (trimmedArgs) { case AddCommand.COMMAND_WORD: return new HelpCommand(AddCommand.MESSAGE_USAGE); - case EditCommand.COMMAND_WORD: return new HelpCommand(EditCommand.MESSAGE_USAGE); - case RemoveCommand.COMMAND_WORD: return new HelpCommand(RemoveCommand.MESSAGE_USAGE); - case DeleteCommand.COMMAND_WORD: return new HelpCommand(DeleteCommand.MESSAGE_USAGE); - case ClearCommand.COMMAND_WORD: return new HelpCommand(ClearCommand.MESSAGE_USAGE); - case FindCommand.COMMAND_WORD: return new HelpCommand(FindCommand.MESSAGE_USAGE); - case ListInventoryCommand.COMMAND_WORD: String messageUsage = ListInventoryCommand.MESSAGE_USAGE + "\n" - + ListTransactionCommand.MESSAGE_USAGE; + + ListTransactionCommand.MESSAGE_USAGE; return new HelpCommand(messageUsage); - case SortCommand.COMMAND_WORD: return new HelpCommand(SortCommand.MESSAGE_USAGE); - case ExitCommand.COMMAND_WORD: return new HelpCommand(ExitCommand.MESSAGE_USAGE); - case StartOrderCommand.COMMAND_WORD: return new HelpCommand(StartOrderCommand.MESSAGE_USAGE); - case AddToOrderCommand.COMMAND_WORD: return new HelpCommand(AddToOrderCommand.MESSAGE_USAGE); - case RemoveFromOrderCommand.COMMAND_WORD: return new HelpCommand(RemoveFromOrderCommand.MESSAGE_USAGE); - case EndAndTransactOrderCommand.COMMAND_WORD: return new HelpCommand(EndAndTransactOrderCommand.MESSAGE_USAGE); - } - if (trimmedArgs == "") { + case "": return new HelpCommand(""); + default: + return new HelpCommand("invalid"); } - return new HelpCommand("invalid"); } } From 607633c9de6219dae26f247eb8059cb1999426b4 Mon Sep 17 00:00:00 2001 From: wangpeialex Date: Thu, 4 Nov 2021 00:47:56 +0800 Subject: [PATCH 41/42] Fix styles --- docs/DeveloperGuide - AB3.md | 65 +++++++-------- docs/DeveloperGuide.md | 156 ++++++++++++++++++----------------- 2 files changed, 113 insertions(+), 108 deletions(-) diff --git a/docs/DeveloperGuide - AB3.md b/docs/DeveloperGuide - AB3.md index 15872db3f1c..6c5c6a81b66 100644 --- a/docs/DeveloperGuide - AB3.md +++ b/docs/DeveloperGuide - AB3.md @@ -1,7 +1,5 @@ --- -layout: page -title: Developer Guide -nav_exclude: true +layout: page title: Developer Guide nav_exclude: true --- * Table of Contents {:toc} @@ -154,22 +152,23 @@ The `Model` component - stores the inventory data i.e., all `Item` objects (which are contained in a `UniqueItemList` object). - stores the order data i.e., an optional `Order` which contains all `Items` added to it. -- stores the transaction history of orders i.e., a set of `TransactionRecord` objects. -- does not depend on any of the other three components (as the Model represents data entities of the domain, - they should make sense on their own without depending on other components) -- Is in charge of internal interactions of `Item`, `Inventory`, `Order` and `TrasactionRecord` objects. - i.e., updates `Inventory` when `Order` is placed by user, and note down `TransactionRecord`. - +- stores the transaction history of orders i.e., a set of `TransactionRecord` objects. +- does not depend on any of the other three components (as the Model represents data entities of the domain, they should + make sense on their own without depending on other components) +- Is in charge of internal interactions of `Item`, `Inventory`, `Order` and `TrasactionRecord` objects. i.e., + updates `Inventory` when `Order` is placed by user, and note down `TransactionRecord`. <<<<<<< HEAD + * stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to -======= + ======= * stores the inventory data i.e., all `Item` objects (which are contained in a `UniqueItemList` object). * stores the currently 'selected' `Item` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to ->>>>>>> master - this list so that the UI automatically updates when the data in the list change. + +> > > > > > > master this list so that the UI automatically updates when the data in the list change. + * stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. * does not depend on any of the other three components (as the `Model` represents data entities of the domain, they @@ -190,10 +189,9 @@ API** : [`Storage.java`](https://github.com/AY2122S1-CS2103-F10-2/tp/blob/master The `Storage` component, -* can save both inventory data and user preference data in json format, and read them back into corresponding - objects. -* inherits from both `InventoryStorage` and `UserPrefStorage`, which means it can be treated as either one (if only - the functionality of only one is needed). +* can save both inventory data and user preference data in json format, and read them back into corresponding objects. +* inherits from both `InventoryStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the + functionality of only one is needed). * depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) @@ -476,8 +474,8 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 2. BogoBogo sorts the inventory accordingly. Use case ends. - - **Extensions** + +**Extensions** * 1a. User specifies to sort by both name and count. * 1a1. BogoBogo notifies user that user can only sort by either name or count, not both. @@ -492,14 +490,14 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 2. BogoBogo shows the commands available to the user. Use case ends. - - **Extensions** + +**Extensions** * 1a. User specifies which command exactly he wants to know how to use. * 1a1. BogoBogo notifies the user what that exact command does. Use case ends. - + ### Non-Functional Requirements 1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. @@ -531,17 +529,17 @@ testers are expected to do more *exploratory* testing. 1. Initial launch - a. Download the jar file and copy into an empty folder + a. Download the jar file and copy into an empty folder - b. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be - optimum. + b. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be + optimum. 2. Saving window preferences - a. Resize the window to an optimum size. Move the window to a different location. Close the window. + a. Resize the window to an optimum size. Move the window to a different location. Close the window. - b. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. + b. Re-launch the app by double-clicking the jar file.
+ Expected: The most recent window size and location is retained. 3. _{ more test cases …​ }_ @@ -549,14 +547,15 @@ testers are expected to do more *exploratory* testing. 1. Adding a new item into the inventory - a. Test case: `add Milk id/111111 c/1 sp/2.4 cp/1.2`
- Expected: Item Milk is added to the list. Milk should have the id #111111, count 1, sales price $2.40, and cost price $1.20. + a. Test case: `add Milk id/111111 c/1 sp/2.4 cp/1.2`
+ Expected: Item Milk is added to the list. Milk should have the id #111111, count 1, sales price $2.40, and cost price + $1.20. - b. Test case: `add Milk c/1 sp/2.4 sp/1.2`
- Expected: No Milk added to the inventory. BogoBogo notifies user to specify id as well. + b. Test case: `add Milk c/1 sp/2.4 sp/1.2`
+ Expected: No Milk added to the inventory. BogoBogo notifies user to specify id as well. - c. Test case: `add n/Milk c/1 sp/2.4 sp/1.2`
- Expected: No Milk added to the inventory. BogoBogo notifies of incorrect command format. + c. Test case: `add n/Milk c/1 sp/2.4 sp/1.2`
+ Expected: No Milk added to the inventory. BogoBogo notifies of incorrect command format. 2. _{ more test cases …​ }_ diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index c1593c9fb2c..0dcef492663 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,8 +1,5 @@ --- -layout: page -title: Developer Guide -parent: For Developers -nav_order: 1 +layout: page title: Developer Guide parent: For Developers nav_order: 1 --- * Table of Contents {:toc} @@ -44,8 +41,8 @@ Given below is a quick overview of main components and how they interact with ea **`Main`** has two classes called [`Main`](https://github.com/nus-cs2103-AY2122S1/tp/blob/master/src/main/java/seedu/address/Main.java) -and [`MainApp`](https://github.com/nus-cs2103-AY2122S1/tp/blob/master/src/main/java/seedu/address/MainApp.java). It -is responsible for, +and [`MainApp`](https://github.com/nus-cs2103-AY2122S1/tp/blob/master/src/main/java/seedu/address/MainApp.java). 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 methods where necessary. @@ -156,16 +153,16 @@ The `Model` component - stores the inventory data i.e., all `Item` objects (which are contained in a `UniqueItemList` object). - stores the current order data i.e., an `Order` which contains all `Items` added to it. - stores the transaction history of orders i.e., a set of `TransactionRecord` objects. -- stores the currently 'selected' `Item` objects (e.g., results of a search query) +- stores the currently 'selected' `Item` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` - that can be 'observed' e.g. the UI can be bound to this list - so that the UI automatically updates when the data in the list change. -- stores a `UserPref` object that represents the user’s preferences. - This is exposed to the outside as a `ReadOnlyUserPref` objects. -- does not depend on any of the other three components (as the Model represents data entities of the domain, - they should make sense on their own without depending on other components) -- it is in charge of internal interactions of `Item`, `Inventory`, `Order` and `TrasactionRecord` objects. - i.e., updates `Inventory` when `Order` is placed by user, and keep the order histories as `TransactionRecord`. + that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the + list change. +- stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as + a `ReadOnlyUserPref` objects. +- does not depend on any of the other three components (as the Model represents data entities of the domain, they should + make sense on their own without depending on other components) +- it is in charge of internal interactions of `Item`, `Inventory`, `Order` and `TrasactionRecord` objects. i.e., + updates `Inventory` when `Order` is placed by user, and keep the order histories as `TransactionRecord`. ### Storage component @@ -176,10 +173,9 @@ API** : [`Storage.java`](https://github.com/AY2122S1-CS2103-F10-2/tp/blob/master The `Storage` component, -* can save both inventory data and user preference data in json format, and read them back into corresponding - objects. -* inherits from both `InventoryStorage` and `UserPrefStorage`, which means it can be treated as either one (if only - the functionality of only one is needed). +* can save both inventory data and user preference data in json format, and read them back into corresponding objects. +* inherits from both `InventoryStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the + functionality of only one is needed). * depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) @@ -197,8 +193,8 @@ This section describes some noteworthy details on how certain features are imple ### Implementation -When ModelManager is initialised, optionalOrder is set to Optional.empty(). -At this point, the user has 1 order record with 2 items in his transaction list. +When ModelManager is initialised, optionalOrder is set to Optional.empty(). At this point, the user has 1 order record +with 2 items in his transaction list. ![Initial_State](images/OrderInitialState.png) @@ -232,55 +228,65 @@ Step 4. The new transactions are saved to json file. This section explains how various commands update the list of items and display the result. -As a background context, all the item objects are contained in a `UniqueItemList` object which enforces uniqueness between -items and prevent duplicates. The `Inventory` manipulates the '`UniqueItemList` to update its content which then update the +As a background context, all the item objects are contained in a `UniqueItemList` object which enforces uniqueness +between items and prevent duplicates. The `Inventory` manipulates the '`UniqueItemList` to update its content which then +update the `ObservableList`. The `ObservableList` is bounded to the UI so that the UI automatically updates when the data in the list change. -`UniqueItemList` is involved when the items are manipulated to ensure the uniqueness of the items. This, the design needs to -ensure that every command mutates the `UniqueItemList` through `Inventory`. +`UniqueItemList` is involved when the items are manipulated to ensure the uniqueness of the items. This, the design +needs to ensure that every command mutates the `UniqueItemList` through `Inventory`. The general flow of inventory manipulation through AddCommand is as below: + 1. The `AddCommand` object in `Logic` component interacts with `Model` component by calling the `Model#addItem()` if a new item is added and `Model#restockItem()` if an existing item is restocked. -2. The `Model#addItem()` and `Model#restockItem()` methods then call methods with the same method signature in `Inventory`, `Inventory#addItem()` and `Inventory#restockItem()`. -3. The `Inventory` then manipulates the `UniqueItemList` by calling the methods with the same method signature, `UniqueItemList#addItem()` and `UniqueItemList#restockItem()`. -4. UniqueItemList then updates the `ObservableList#add` and `ObservableList#set` methods which updates the list to be returned to the user. - The returned list has added a new item or incremented the count of the existing item. - +2. The `Model#addItem()` and `Model#restockItem()` methods then call methods with the same method signature + in `Inventory`, `Inventory#addItem()` and `Inventory#restockItem()`. +3. The `Inventory` then manipulates the `UniqueItemList` by calling the methods with the same method + signature, `UniqueItemList#addItem()` and `UniqueItemList#restockItem()`. +4. UniqueItemList then updates the `ObservableList#add` and `ObservableList#set` methods which updates the list to be + returned to the user. The returned list has added a new item or incremented the count of the existing item. Flow:`AddCommand` -> `Model` -> `Inventory` -> `UniqueItemList` -> `ObservableList` -The above flow applies for all the other similar commands that manipulates the inventory. -The detailed flow for each command is found below: +The above flow applies for all the other similar commands that manipulates the inventory. The detailed flow for each +command is found below: **`AddCommand:`** AddCommand#execute() -> Model#addItem() or Model#restockItem() -> Inventory#addItem() or Inventory#restockItem() -> UniqueItemList#addItem() or UniqueItemList#setItem() -> ObservableList#add() or ObservableList#set() **`RemoveCommand:`** -RemoveCommand#execute() -> Model#removeItem() -> Inventory#removeItem() -> UniqueItemList#setItem() -> ObservableList#set() +RemoveCommand#execute() -> Model#removeItem() -> Inventory#removeItem() -> UniqueItemList#setItem() -> +ObservableList#set() **`EditCommand:`** -EditCommand#execute() -> Model#setItem() -> Inventory#setItem() -> UniqueItemList#setItem() -> ObservableList#set() +EditCommand#execute() -> Model#setItem() -> Inventory#setItem() -> UniqueItemList#setItem() -> ObservableList + +# set() **`ClearCommand:`** -ClearCommand#execute() -> Model#setItem() -> Inventory#resetData() -> Inventory#setItems() -> UniqueItemList#setItem() -> ObservableList#set() +ClearCommand#execute() -> Model#setItem() -> Inventory#resetData() -> Inventory#setItems() -> UniqueItemList#setItem() +-> ObservableList#set() **`DeleteCommand:`** -DeleteCommand#execute() -> Model#deleteItem() -> Inventory#deleteItems() -> UniqueItemList#removeItem() -> ObservableList#remove() +DeleteCommand#execute() -> Model#deleteItem() -> Inventory#deleteItems() -> UniqueItemList#removeItem() -> +ObservableList#remove() **`SortCommand:`** -SortCommand#execute() -> Model#sortItem() -> Inventory#sortItems() -> UniqueItemList#sortItem() -> ObservableList#sort() +SortCommand#execute() -> Model#sortItem() -> Inventory#sortItems() -> UniqueItemList#sortItem() -> ObservableList + +# sort() #### Design considerations: **Aspect:** * **Finding Multiple Names, Ids or Tags:** The FindCommand supports finding by multiple names, ids or tags. -`IdContainsNumberPredicate`, `NameContainsKeywordsPredicate` and `TagContainsKeywordsPredicate` takes in a list of -strings which allows storing of multiple predicates. The items in the list are then matched with each predicate to -update the filtered list. Thus, the displayed list contains items that matches multiple predicates given. + `IdContainsNumberPredicate`, `NameContainsKeywordsPredicate` and `TagContainsKeywordsPredicate` takes in a list of + strings which allows storing of multiple predicates. The items in the list are then matched with each predicate to + update the filtered list. Thus, the displayed list contains items that matches multiple predicates given. -------------------------------------------------------------------------------------------------------------------- @@ -409,7 +415,7 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli * 1b1. BogoBogo outputs an empty list. Use case ends. - + * 1c. User tries to find by 2 different fields at the same time. * 1b1. BogoBogo notifies user that only one field can be inputted. @@ -471,17 +477,17 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli * 1a. There is no order created. * 1a1. BogoBogo notifies user there is no order. - + Use case ends. - + * 2a. The item is specified in wrong format. * 2a1. BogoBogo notifies user the item specification format is wrong. - + Use case ends. * 3a. The specified item is not in the order * 3a1. BogoBogo notifies user the specified item is not in the order. - + Use case ends. **UC06 - Sorting** @@ -494,7 +500,7 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 2. BogoBogo sorts the inventory accordingly. Use case ends. - + **Extensions** * 1a. User specifies to sort by both name and count. @@ -512,14 +518,14 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 2. BogoBogo shows the commands available to the user. Use case ends. - - **Extensions** + +**Extensions** * 1a. User specifies which command exactly he wants to know how to use. * 1a1. BogoBogo notifies the user what that exact command does. Use case ends. - + * 1b. User specifies an inexistent command. * 1a1. BogoBogo notifies the user that the command does not exist, then proceed to display URL to userguide. @@ -535,26 +541,26 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 2. User enters the item name and amount. 3. BogoBogo removes the specified amount of that item. - Use case ends. + Use case ends. **Extensions** * 2a. The name and amount is specified in wrong format. * 2a1. BogoBogo notifies user the format is wrong. - - Use case ends. + + Use case ends. * 2b. There are multiple matching items in inventory. * 2b1. BogoBogo lists out all the matching items and let user choose one. * 2b2. User chooses the desired item. - * BogoBogo removes the specified amount of that item. + * BogoBogo removes the specified amount of that item. Use case ends. * 3a. The item is not in the inventory. * 3a1. BogoBogo notifies user there is on such item. - Use case ends. + Use case ends. * 3b. The specified amount is greater than what inventory has. * 3b1. BogoBogo notifies user the actual amount of item in the inventory. @@ -571,20 +577,20 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 2. User enters the item index, fields and new values to change. 3. BogoBogo updates the fields of the item at the specified index with the new values given. - Use case ends. + Use case ends. **Extensions** * 3a. The edited item is a duplicate of another item in the inventory. * 3a1. BogoBogo notifies user of the duplication. - + Use case ends * 3b. The specified index is invalid. * 3b1. BogoBogo notifies user the index is invalid. Use case ends - + * 3c. The specified change of the field is invalid. * 3b1. BogoBogo notifies user that the new value specified is invalid. @@ -615,9 +621,9 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli **Extensions** * 2a. There is currently no order to list. - * 2a1. BogoBogo notifies user there is currently no order. - - Use case ends. + * 2a1. BogoBogo notifies user there is currently no order. + + Use case ends. **UC11 - Exit the application** @@ -628,7 +634,7 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 1. User requests to exit the application 2. BogoBogo acknowledges the request and exits. - Use case ends. + Use case ends. **UC12 - Clear the inventory** @@ -638,9 +644,8 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 1. User requests to clear the inventory. 2. BogoBogo acknowledges the request and clears the inventory. - - Use case ends. + Use case ends. ### Non-Functional Requirements @@ -673,17 +678,17 @@ testers are expected to do more *exploratory* testing. 1. Initial launch - a. Download the jar file and copy into an empty folder + a. Download the jar file and copy into an empty folder - b. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be - optimum. + b. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be + optimum. 2. Saving window preferences - a. Resize the window to an optimum size. Move the window to a different location. Close the window. + a. Resize the window to an optimum size. Move the window to a different location. Close the window. - b. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. + b. Re-launch the app by double-clicking the jar file.
+ Expected: The most recent window size and location is retained. 3. _{ more test cases …​ }_ @@ -691,14 +696,15 @@ testers are expected to do more *exploratory* testing. 1. Adding a new item into the inventory - a. Test case: `add Milk id/111111 c/1 sp/2.4 cp/1.2`
- Expected: Item Milk is added to the list. Milk should have the id #111111, count 1, sales price $2.40, and cost price $1.20. + a. Test case: `add Milk id/111111 c/1 sp/2.4 cp/1.2`
+ Expected: Item Milk is added to the list. Milk should have the id #111111, count 1, sales price $2.40, and cost price + $1.20. - b. Test case: `add Milk c/1 sp/2.4 sp/1.2`
- Expected: No Milk added to the inventory. BogoBogo notifies user to specify id as well. + b. Test case: `add Milk c/1 sp/2.4 sp/1.2`
+ Expected: No Milk added to the inventory. BogoBogo notifies user to specify id as well. - c. Test case: `add n/Milk c/1 sp/2.4 sp/1.2`
- Expected: No Milk added to the inventory. BogoBogo notifies of incorrect command format. + c. Test case: `add n/Milk c/1 sp/2.4 sp/1.2`
+ Expected: No Milk added to the inventory. BogoBogo notifies of incorrect command format. 2. _{ more test cases …​ }_ From 3fc75862159bc181cacf71ccdf1658cff22c61a7 Mon Sep 17 00:00:00 2001 From: wangpeialex Date: Thu, 4 Nov 2021 00:48:15 +0800 Subject: [PATCH 42/42] Revert "Fix styles" This reverts commit 607633c9de6219dae26f247eb8059cb1999426b4. --- docs/DeveloperGuide - AB3.md | 65 ++++++++------- docs/DeveloperGuide.md | 156 +++++++++++++++++------------------ 2 files changed, 108 insertions(+), 113 deletions(-) diff --git a/docs/DeveloperGuide - AB3.md b/docs/DeveloperGuide - AB3.md index 6c5c6a81b66..15872db3f1c 100644 --- a/docs/DeveloperGuide - AB3.md +++ b/docs/DeveloperGuide - AB3.md @@ -1,5 +1,7 @@ --- -layout: page title: Developer Guide nav_exclude: true +layout: page +title: Developer Guide +nav_exclude: true --- * Table of Contents {:toc} @@ -152,23 +154,22 @@ The `Model` component - stores the inventory data i.e., all `Item` objects (which are contained in a `UniqueItemList` object). - stores the order data i.e., an optional `Order` which contains all `Items` added to it. -- stores the transaction history of orders i.e., a set of `TransactionRecord` objects. -- does not depend on any of the other three components (as the Model represents data entities of the domain, they should - make sense on their own without depending on other components) -- Is in charge of internal interactions of `Item`, `Inventory`, `Order` and `TrasactionRecord` objects. i.e., - updates `Inventory` when `Order` is placed by user, and note down `TransactionRecord`. +- stores the transaction history of orders i.e., a set of `TransactionRecord` objects. +- does not depend on any of the other three components (as the Model represents data entities of the domain, + they should make sense on their own without depending on other components) +- Is in charge of internal interactions of `Item`, `Inventory`, `Order` and `TrasactionRecord` objects. + i.e., updates `Inventory` when `Order` is placed by user, and note down `TransactionRecord`. -<<<<<<< HEAD +<<<<<<< HEAD * stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to - ======= +======= * stores the inventory data i.e., all `Item` objects (which are contained in a `UniqueItemList` object). * stores the currently 'selected' `Item` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to - -> > > > > > > master this list so that the UI automatically updates when the data in the list change. - +>>>>>>> master + this list so that the UI automatically updates when the data in the list change. * stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. * does not depend on any of the other three components (as the `Model` represents data entities of the domain, they @@ -189,9 +190,10 @@ API** : [`Storage.java`](https://github.com/AY2122S1-CS2103-F10-2/tp/blob/master The `Storage` component, -* can save both inventory data and user preference data in json format, and read them back into corresponding objects. -* inherits from both `InventoryStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the - functionality of only one is needed). +* can save both inventory data and user preference data in json format, and read them back into corresponding + objects. +* inherits from both `InventoryStorage` and `UserPrefStorage`, which means it can be treated as either one (if only + the functionality of only one is needed). * depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) @@ -474,8 +476,8 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 2. BogoBogo sorts the inventory accordingly. Use case ends. - -**Extensions** + + **Extensions** * 1a. User specifies to sort by both name and count. * 1a1. BogoBogo notifies user that user can only sort by either name or count, not both. @@ -490,14 +492,14 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 2. BogoBogo shows the commands available to the user. Use case ends. - -**Extensions** + + **Extensions** * 1a. User specifies which command exactly he wants to know how to use. * 1a1. BogoBogo notifies the user what that exact command does. Use case ends. - + ### Non-Functional Requirements 1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. @@ -529,17 +531,17 @@ testers are expected to do more *exploratory* testing. 1. Initial launch - a. Download the jar file and copy into an empty folder + a. Download the jar file and copy into an empty folder - b. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be - optimum. + b. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be + optimum. 2. Saving window preferences - a. Resize the window to an optimum size. Move the window to a different location. Close the window. + a. Resize the window to an optimum size. Move the window to a different location. Close the window. - b. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. + b. Re-launch the app by double-clicking the jar file.
+ Expected: The most recent window size and location is retained. 3. _{ more test cases …​ }_ @@ -547,15 +549,14 @@ testers are expected to do more *exploratory* testing. 1. Adding a new item into the inventory - a. Test case: `add Milk id/111111 c/1 sp/2.4 cp/1.2`
- Expected: Item Milk is added to the list. Milk should have the id #111111, count 1, sales price $2.40, and cost price - $1.20. + a. Test case: `add Milk id/111111 c/1 sp/2.4 cp/1.2`
+ Expected: Item Milk is added to the list. Milk should have the id #111111, count 1, sales price $2.40, and cost price $1.20. - b. Test case: `add Milk c/1 sp/2.4 sp/1.2`
- Expected: No Milk added to the inventory. BogoBogo notifies user to specify id as well. + b. Test case: `add Milk c/1 sp/2.4 sp/1.2`
+ Expected: No Milk added to the inventory. BogoBogo notifies user to specify id as well. - c. Test case: `add n/Milk c/1 sp/2.4 sp/1.2`
- Expected: No Milk added to the inventory. BogoBogo notifies of incorrect command format. + c. Test case: `add n/Milk c/1 sp/2.4 sp/1.2`
+ Expected: No Milk added to the inventory. BogoBogo notifies of incorrect command format. 2. _{ more test cases …​ }_ diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 0dcef492663..c1593c9fb2c 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,5 +1,8 @@ --- -layout: page title: Developer Guide parent: For Developers nav_order: 1 +layout: page +title: Developer Guide +parent: For Developers +nav_order: 1 --- * Table of Contents {:toc} @@ -41,8 +44,8 @@ Given below is a quick overview of main components and how they interact with ea **`Main`** has two classes called [`Main`](https://github.com/nus-cs2103-AY2122S1/tp/blob/master/src/main/java/seedu/address/Main.java) -and [`MainApp`](https://github.com/nus-cs2103-AY2122S1/tp/blob/master/src/main/java/seedu/address/MainApp.java). It is -responsible for, +and [`MainApp`](https://github.com/nus-cs2103-AY2122S1/tp/blob/master/src/main/java/seedu/address/MainApp.java). 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 methods where necessary. @@ -153,16 +156,16 @@ The `Model` component - stores the inventory data i.e., all `Item` objects (which are contained in a `UniqueItemList` object). - stores the current order data i.e., an `Order` which contains all `Items` added to it. - stores the transaction history of orders i.e., a set of `TransactionRecord` objects. -- stores the currently 'selected' `Item` objects (e.g., results of a search query) +- stores the currently 'selected' `Item` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` - that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the - list change. -- stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as - a `ReadOnlyUserPref` objects. -- does not depend on any of the other three components (as the Model represents data entities of the domain, they should - make sense on their own without depending on other components) -- it is in charge of internal interactions of `Item`, `Inventory`, `Order` and `TrasactionRecord` objects. i.e., - updates `Inventory` when `Order` is placed by user, and keep the order histories as `TransactionRecord`. + that can be 'observed' e.g. the UI can be bound to this list + so that the UI automatically updates when the data in the list change. +- stores a `UserPref` object that represents the user’s preferences. + This is exposed to the outside as a `ReadOnlyUserPref` objects. +- does not depend on any of the other three components (as the Model represents data entities of the domain, + they should make sense on their own without depending on other components) +- it is in charge of internal interactions of `Item`, `Inventory`, `Order` and `TrasactionRecord` objects. + i.e., updates `Inventory` when `Order` is placed by user, and keep the order histories as `TransactionRecord`. ### Storage component @@ -173,9 +176,10 @@ API** : [`Storage.java`](https://github.com/AY2122S1-CS2103-F10-2/tp/blob/master The `Storage` component, -* can save both inventory data and user preference data in json format, and read them back into corresponding objects. -* inherits from both `InventoryStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the - functionality of only one is needed). +* can save both inventory data and user preference data in json format, and read them back into corresponding + objects. +* inherits from both `InventoryStorage` and `UserPrefStorage`, which means it can be treated as either one (if only + the functionality of only one is needed). * depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) @@ -193,8 +197,8 @@ This section describes some noteworthy details on how certain features are imple ### Implementation -When ModelManager is initialised, optionalOrder is set to Optional.empty(). At this point, the user has 1 order record -with 2 items in his transaction list. +When ModelManager is initialised, optionalOrder is set to Optional.empty(). +At this point, the user has 1 order record with 2 items in his transaction list. ![Initial_State](images/OrderInitialState.png) @@ -228,65 +232,55 @@ Step 4. The new transactions are saved to json file. This section explains how various commands update the list of items and display the result. -As a background context, all the item objects are contained in a `UniqueItemList` object which enforces uniqueness -between items and prevent duplicates. The `Inventory` manipulates the '`UniqueItemList` to update its content which then -update the +As a background context, all the item objects are contained in a `UniqueItemList` object which enforces uniqueness between +items and prevent duplicates. The `Inventory` manipulates the '`UniqueItemList` to update its content which then update the `ObservableList`. The `ObservableList` is bounded to the UI so that the UI automatically updates when the data in the list change. -`UniqueItemList` is involved when the items are manipulated to ensure the uniqueness of the items. This, the design -needs to ensure that every command mutates the `UniqueItemList` through `Inventory`. +`UniqueItemList` is involved when the items are manipulated to ensure the uniqueness of the items. This, the design needs to +ensure that every command mutates the `UniqueItemList` through `Inventory`. The general flow of inventory manipulation through AddCommand is as below: - 1. The `AddCommand` object in `Logic` component interacts with `Model` component by calling the `Model#addItem()` if a new item is added and `Model#restockItem()` if an existing item is restocked. -2. The `Model#addItem()` and `Model#restockItem()` methods then call methods with the same method signature - in `Inventory`, `Inventory#addItem()` and `Inventory#restockItem()`. -3. The `Inventory` then manipulates the `UniqueItemList` by calling the methods with the same method - signature, `UniqueItemList#addItem()` and `UniqueItemList#restockItem()`. -4. UniqueItemList then updates the `ObservableList#add` and `ObservableList#set` methods which updates the list to be - returned to the user. The returned list has added a new item or incremented the count of the existing item. +2. The `Model#addItem()` and `Model#restockItem()` methods then call methods with the same method signature in `Inventory`, `Inventory#addItem()` and `Inventory#restockItem()`. +3. The `Inventory` then manipulates the `UniqueItemList` by calling the methods with the same method signature, `UniqueItemList#addItem()` and `UniqueItemList#restockItem()`. +4. UniqueItemList then updates the `ObservableList#add` and `ObservableList#set` methods which updates the list to be returned to the user. + The returned list has added a new item or incremented the count of the existing item. + Flow:`AddCommand` -> `Model` -> `Inventory` -> `UniqueItemList` -> `ObservableList` -The above flow applies for all the other similar commands that manipulates the inventory. The detailed flow for each -command is found below: +The above flow applies for all the other similar commands that manipulates the inventory. +The detailed flow for each command is found below: **`AddCommand:`** AddCommand#execute() -> Model#addItem() or Model#restockItem() -> Inventory#addItem() or Inventory#restockItem() -> UniqueItemList#addItem() or UniqueItemList#setItem() -> ObservableList#add() or ObservableList#set() **`RemoveCommand:`** -RemoveCommand#execute() -> Model#removeItem() -> Inventory#removeItem() -> UniqueItemList#setItem() -> -ObservableList#set() +RemoveCommand#execute() -> Model#removeItem() -> Inventory#removeItem() -> UniqueItemList#setItem() -> ObservableList#set() **`EditCommand:`** -EditCommand#execute() -> Model#setItem() -> Inventory#setItem() -> UniqueItemList#setItem() -> ObservableList - -# set() +EditCommand#execute() -> Model#setItem() -> Inventory#setItem() -> UniqueItemList#setItem() -> ObservableList#set() **`ClearCommand:`** -ClearCommand#execute() -> Model#setItem() -> Inventory#resetData() -> Inventory#setItems() -> UniqueItemList#setItem() --> ObservableList#set() +ClearCommand#execute() -> Model#setItem() -> Inventory#resetData() -> Inventory#setItems() -> UniqueItemList#setItem() -> ObservableList#set() **`DeleteCommand:`** -DeleteCommand#execute() -> Model#deleteItem() -> Inventory#deleteItems() -> UniqueItemList#removeItem() -> -ObservableList#remove() +DeleteCommand#execute() -> Model#deleteItem() -> Inventory#deleteItems() -> UniqueItemList#removeItem() -> ObservableList#remove() **`SortCommand:`** -SortCommand#execute() -> Model#sortItem() -> Inventory#sortItems() -> UniqueItemList#sortItem() -> ObservableList - -# sort() +SortCommand#execute() -> Model#sortItem() -> Inventory#sortItems() -> UniqueItemList#sortItem() -> ObservableList#sort() #### Design considerations: **Aspect:** * **Finding Multiple Names, Ids or Tags:** The FindCommand supports finding by multiple names, ids or tags. - `IdContainsNumberPredicate`, `NameContainsKeywordsPredicate` and `TagContainsKeywordsPredicate` takes in a list of - strings which allows storing of multiple predicates. The items in the list are then matched with each predicate to - update the filtered list. Thus, the displayed list contains items that matches multiple predicates given. +`IdContainsNumberPredicate`, `NameContainsKeywordsPredicate` and `TagContainsKeywordsPredicate` takes in a list of +strings which allows storing of multiple predicates. The items in the list are then matched with each predicate to +update the filtered list. Thus, the displayed list contains items that matches multiple predicates given. -------------------------------------------------------------------------------------------------------------------- @@ -415,7 +409,7 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli * 1b1. BogoBogo outputs an empty list. Use case ends. - + * 1c. User tries to find by 2 different fields at the same time. * 1b1. BogoBogo notifies user that only one field can be inputted. @@ -477,17 +471,17 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli * 1a. There is no order created. * 1a1. BogoBogo notifies user there is no order. - + Use case ends. - + * 2a. The item is specified in wrong format. * 2a1. BogoBogo notifies user the item specification format is wrong. - + Use case ends. * 3a. The specified item is not in the order * 3a1. BogoBogo notifies user the specified item is not in the order. - + Use case ends. **UC06 - Sorting** @@ -500,7 +494,7 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 2. BogoBogo sorts the inventory accordingly. Use case ends. - + **Extensions** * 1a. User specifies to sort by both name and count. @@ -518,14 +512,14 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 2. BogoBogo shows the commands available to the user. Use case ends. - -**Extensions** + + **Extensions** * 1a. User specifies which command exactly he wants to know how to use. * 1a1. BogoBogo notifies the user what that exact command does. Use case ends. - + * 1b. User specifies an inexistent command. * 1a1. BogoBogo notifies the user that the command does not exist, then proceed to display URL to userguide. @@ -541,26 +535,26 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 2. User enters the item name and amount. 3. BogoBogo removes the specified amount of that item. - Use case ends. + Use case ends. **Extensions** * 2a. The name and amount is specified in wrong format. * 2a1. BogoBogo notifies user the format is wrong. - - Use case ends. + + Use case ends. * 2b. There are multiple matching items in inventory. * 2b1. BogoBogo lists out all the matching items and let user choose one. * 2b2. User chooses the desired item. - * BogoBogo removes the specified amount of that item. + * BogoBogo removes the specified amount of that item. Use case ends. * 3a. The item is not in the inventory. * 3a1. BogoBogo notifies user there is on such item. - Use case ends. + Use case ends. * 3b. The specified amount is greater than what inventory has. * 3b1. BogoBogo notifies user the actual amount of item in the inventory. @@ -577,20 +571,20 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 2. User enters the item index, fields and new values to change. 3. BogoBogo updates the fields of the item at the specified index with the new values given. - Use case ends. + Use case ends. **Extensions** * 3a. The edited item is a duplicate of another item in the inventory. * 3a1. BogoBogo notifies user of the duplication. - + Use case ends * 3b. The specified index is invalid. * 3b1. BogoBogo notifies user the index is invalid. Use case ends - + * 3c. The specified change of the field is invalid. * 3b1. BogoBogo notifies user that the new value specified is invalid. @@ -621,9 +615,9 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli **Extensions** * 2a. There is currently no order to list. - * 2a1. BogoBogo notifies user there is currently no order. - - Use case ends. + * 2a1. BogoBogo notifies user there is currently no order. + + Use case ends. **UC11 - Exit the application** @@ -634,7 +628,7 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 1. User requests to exit the application 2. BogoBogo acknowledges the request and exits. - Use case ends. + Use case ends. **UC12 - Clear the inventory** @@ -644,8 +638,9 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli 1. User requests to clear the inventory. 2. BogoBogo acknowledges the request and clears the inventory. + + Use case ends. - Use case ends. ### Non-Functional Requirements @@ -678,17 +673,17 @@ testers are expected to do more *exploratory* testing. 1. Initial launch - a. Download the jar file and copy into an empty folder + a. Download the jar file and copy into an empty folder - b. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be - optimum. + b. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be + optimum. 2. Saving window preferences - a. Resize the window to an optimum size. Move the window to a different location. Close the window. + a. Resize the window to an optimum size. Move the window to a different location. Close the window. - b. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. + b. Re-launch the app by double-clicking the jar file.
+ Expected: The most recent window size and location is retained. 3. _{ more test cases …​ }_ @@ -696,15 +691,14 @@ testers are expected to do more *exploratory* testing. 1. Adding a new item into the inventory - a. Test case: `add Milk id/111111 c/1 sp/2.4 cp/1.2`
- Expected: Item Milk is added to the list. Milk should have the id #111111, count 1, sales price $2.40, and cost price - $1.20. + a. Test case: `add Milk id/111111 c/1 sp/2.4 cp/1.2`
+ Expected: Item Milk is added to the list. Milk should have the id #111111, count 1, sales price $2.40, and cost price $1.20. - b. Test case: `add Milk c/1 sp/2.4 sp/1.2`
- Expected: No Milk added to the inventory. BogoBogo notifies user to specify id as well. + b. Test case: `add Milk c/1 sp/2.4 sp/1.2`
+ Expected: No Milk added to the inventory. BogoBogo notifies user to specify id as well. - c. Test case: `add n/Milk c/1 sp/2.4 sp/1.2`
- Expected: No Milk added to the inventory. BogoBogo notifies of incorrect command format. + c. Test case: `add n/Milk c/1 sp/2.4 sp/1.2`
+ Expected: No Milk added to the inventory. BogoBogo notifies of incorrect command format. 2. _{ more test cases …​ }_