Repository: AY2425S2-CS2103T-T08-3/tp
Pull request #188: Branch add drink fields
Author: login: rannn367
Created: 2025-03-20T13:57:08Z | Merged: 2025-03-20T15:25:35Z

fixes #187 

Linked issue #187: Add new fields for Drink

Diff:
diff --git a/src/main/java/seedu/address/model/drink/Category.java b/src/main/java/seedu/address/model/drink/Category.java
new file mode 100644
index 00000000000..262d424d0c4
--- /dev/null
+++ b/src/main/java/seedu/address/model/drink/Category.java
@@ -0,0 +1,55 @@
+package seedu.address.model.drink;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents a Drink's category.
+ * Guarantees: immutable; is valid as declared in {@link #isValidCategory(String)}.
+ */
+public class Category {
+
+    public static final String MESSAGE_CONSTRAINTS = "Category should not be blank";
+
+    public final String category;
+
+    /**
+     * Constructs a {@code Category}.
+     *
+     * @param category A valid category.
+     */
+    public Category(String category) {
+        requireNonNull(category);
+        if (!isValidCategory(category)) {
+            throw new IllegalArgumentException(MESSAGE_CONSTRAINTS);
+        }
+        this.category = category;
+    }
+
+    public String getCategory() {
+        return category;
+    }
+
+    /**
+     * Returns true if a given string is a valid category.
+     */
+    public static boolean isValidCategory(String test) {
+        return test != null && !test.trim().isEmpty();
+    }
+
+    @Override
+    public String toString() {
+        return category;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        return other == this
+                || (other instanceof Category
+                && category.equals(((Category) other).category));
+    }
+
+    @Override
+    public int hashCode() {
+        return category.hashCode();
+    }
+}
diff --git a/src/main/java/seedu/address/model/drink/DrinkName.java b/src/main/java/seedu/address/model/drink/DrinkName.java
new file mode 100644
index 00000000000..8dcb12093b4
--- /dev/null
+++ b/src/main/java/seedu/address/model/drink/DrinkName.java
@@ -0,0 +1,55 @@
+package seedu.address.model.drink;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents a Drink's name.
+ * Guarantees: immutable; is valid as declared in {@link #isValidName(String)}.
+ */
+public class DrinkName {
+
+    public static final String MESSAGE_CONSTRAINTS = "Drink names should not be blank";
+
+    public final String drinkName;
+
+    /**
+     * Constructs a {@code DrinkName}.
+     *
+     * @param drinkName A valid name.
+     */
+    public DrinkName(String drinkName) {
+        requireNonNull(drinkName);
+        if (!isValidName(drinkName)) {
+            throw new IllegalArgumentException(MESSAGE_CONSTRAINTS);
+        }
+        this.drinkName = drinkName;
+    }
+
+    /**
+     * Returns true if a given string is a valid name.
+     */
+    public static boolean isValidName(String test) {
+        return test != null && !test.trim().isEmpty();
+    }
+
+    public String getDrinkName() {
+        return drinkName;
+    }
+
+    @Override
+    public String toString() {
+        return drinkName;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        return other == this
+                || (other instanceof DrinkName
+                && drinkName.equals(((DrinkName) other).drinkName));
+    }
+
+    @Override
+    public int hashCode() {
+        return drinkName.hashCode();
+    }
+}
diff --git a/src/main/java/seedu/address/model/drink/Price.java b/src/main/java/seedu/address/model/drink/Price.java
new file mode 100644
index 00000000000..a6c912722ac
--- /dev/null
+++ b/src/main/java/seedu/address/model/drink/Price.java
@@ -0,0 +1,55 @@
+package seedu.address.model.drink;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents a Drink's price.
+ * Guarantees: immutable; is valid as declared in {@link #isValidPrice(double)}.
+ */
+public class Price {
+
+    public static final String MESSAGE_CONSTRAINTS = "Price should be greater than 0";
+
+    public final double price;
+
+    /**
+     * Constructs a {@code Price}.
+     *
+     * @param price A valid price.
+     */
+    public Price(double price) {
+        requireNonNull(price);
+        if (!isValidPrice(price)) {
+            throw new IllegalArgumentException(MESSAGE_CONSTRAINTS);
+        }
+        this.price = price;
+    }
+
+    public double getPrice() {
+        return price;
+    }
+
+    /**
+     * Returns true if a given price is valid.
+     */
+    public static boolean isValidPrice(double test) {
+        return test > 0;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%.2f", price);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        return other == this
+                || (other instanceof Price
+                && price == ((Price) other).price);
+    }
+
+    @Override
+    public int hashCode() {
+        return Double.hashCode(price);
+    }
+}
diff --git a/src/test/java/seedu/address/model/drink/CategoryTest.java b/src/test/java/seedu/address/model/drink/CategoryTest.java
new file mode 100644
index 00000000000..b86b1f3d4e5
--- /dev/null
+++ b/src/test/java/seedu/address/model/drink/CategoryTest.java
@@ -0,0 +1,63 @@
+package seedu.address.model.drink;
+
+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 org.junit.jupiter.api.Test;
+
+class CategoryTest {
+
+    @Test
+    void constructor_nullCategory_throwsNullPointerException() {
+        assertThrows(NullPointerException.class, () -> new Category(null));
+    }
+
+    @Test
+    void constructor_invalidCategory_throwsIllegalArgumentException() {
+        assertThrows(IllegalArgumentException.class, () -> new Category(""));
+        assertThrows(IllegalArgumentException.class, () -> new Category("   "));
+    }
+
+    @Test
+    void isValidCategory_validCases() {
+        assertTrue(Category.isValidCategory("Juice"));
+        assertTrue(Category.isValidCategory("Soft Drink"));
+        assertTrue(Category.isValidCategory("Tea"));
+    }
+
+    @Test
+    void isValidCategory_invalidCases() {
+        assertFalse(Category.isValidCategory(null));
+        assertFalse(Category.isValidCategory(""));
+        assertFalse(Category.isValidCategory("   "));
+    }
+
+    @Test
+    void equals_sameObject_returnsTrue() {
+        Category category = new Category("Coffee");
+        assertEquals(category, category);
+    }
+
+    @Test
+    void equals_differentObjectsSameValue_returnsTrue() {
+        assertEquals(new Category("Coffee"), new Category("Coffee"));
+    }
+
+    @Test
+    void equals_differentCategory_returnsFalse() {
+        assertFalse(new Category("Coffee").equals(new Category("Tea")));
+    }
+
+    @Test
+    void toString_correctFormat() {
+        Category category = new Category("Milkshake");
+        assertEquals("Milkshake", category.toString());
+    }
+
+    @Test
+    void hashCode_sameCategory_sameHashCode() {
+        assertEquals(new Category("Energy Drink").hashCode(), new Category("Energy Drink").hashCode());
+    }
+}
diff --git a/src/test/java/seedu/address/model/drink/DrinkNameTest.java b/src/test/java/seedu/address/model/drink/DrinkNameTest.java
new file mode 100644
index 00000000000..ffe6dc6b16b
--- /dev/null
+++ b/src/test/java/seedu/address/model/drink/DrinkNameTest.java
@@ -0,0 +1,63 @@
+package seedu.address.model.drink;
+
+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 org.junit.jupiter.api.Test;
+
+class DrinkNameTest {
+
+    @Test
+    void constructor_nullDrinkName_throwsNullPointerException() {
+        assertThrows(NullPointerException.class, () -> new DrinkName(null));
+    }
+
+    @Test
+    void constructor_invalidDrinkName_throwsIllegalArgumentException() {
+        assertThrows(IllegalArgumentException.class, () -> new DrinkName(""));
+        assertThrows(IllegalArgumentException.class, () -> new DrinkName("   "));
+    }
+
+    @Test
+    void isValidName_validCases() {
+        assertTrue(DrinkName.isValidName("Coca Cola"));
+        assertTrue(DrinkName.isValidName("Orange Juice"));
+        assertTrue(DrinkName.isValidName("Latte"));
+    }
+
+    @Test
+    void isValidName_invalidCases() {
+        assertFalse(DrinkName.isValidName(null));
+        assertFalse(DrinkName.isValidName(""));
+        assertFalse(DrinkName.isValidName("   "));
+    }
+
+    @Test
+    void equals_sameObject_returnsTrue() {
+        DrinkName drinkName = new DrinkName("Pepsi");
+        assertEquals(drinkName, drinkName);
+    }
+
+    @Test
+    void equals_differentObjectsSameValue_returnsTrue() {
+        assertEquals(new DrinkName("Pepsi"), new DrinkName("Pepsi"));
+    }
+
+    @Test
+    void equals_differentDrinkName_returnsFalse() {
+        assertFalse(new DrinkName("Pepsi").equals(new DrinkName("Sprite")));
+    }
+
+    @Test
+    void toString_correctFormat() {
+        DrinkName drinkName = new DrinkName("Fanta");
+        assertEquals("Fanta", drinkName.toString());
+    }
+
+    @Test
+    void hashCode_sameDrinkName_sameHashCode() {
+        assertEquals(new DrinkName("Root Beer").hashCode(), new DrinkName("Root Beer").hashCode());
+    }
+}
diff --git a/src/test/java/seedu/address/model/drink/PriceTest.java b/src/test/java/seedu/address/model/drink/PriceTest.java
new file mode 100644
index 00000000000..ca9fd9edf1f
--- /dev/null
+++ b/src/test/java/seedu/address/model/drink/PriceTest.java
@@ -0,0 +1,58 @@
+package seedu.address.model.drink;
+
+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 org.junit.jupiter.api.Test;
+
+class PriceTest {
+
+    @Test
+    void constructor_invalidPrice_throwsIllegalArgumentException() {
+        assertThrows(IllegalArgumentException.class, () -> new Price(0));
+        assertThrows(IllegalArgumentException.class, () -> new Price(-5.0));
+    }
+
+    @Test
+    void isValidPrice_validCases() {
+        assertTrue(Price.isValidPrice(0.01));
+        assertTrue(Price.isValidPrice(5.99));
+        assertTrue(Price.isValidPrice(100.0));
+    }
+
+    @Test
+    void isValidPrice_invalidCases() {
+        assertFalse(Price.isValidPrice(0));
+        assertFalse(Price.isValidPrice(-1.0));
+        assertFalse(Price.isValidPrice(-50.0));
+    }
+
+    @Test
+    void equals_sameObject_returnsTrue() {
+        Price price = new Price(10.0);
+        assertEquals(price, price);
+    }
+
+    @Test
+    void equals_differentObjectsSameValue_returnsTrue() {
+        assertEquals(new Price(10.0), new Price(10.0));
+    }
+
+    @Test
+    void equals_differentPrice_returnsFalse() {
+        assertFalse(new Price(10.0).equals(new Price(5.0)));
+    }
+
+    @Test
+    void toString_correctFormat() {
+        Price price = new Price(7.5);
+        assertEquals("7.50", price.toString());
+    }
+
+    @Test
+    void hashCode_samePrice_sameHashCode() {
+        assertEquals(new Price(15.99).hashCode(), new Price(15.99).hashCode());
+    }
+}


Reviews:
user: login: Jsjk112344
body: Great way to stresmline the code by adding specific models for the drink class
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-24T14:43:22Z
user: login: loyaltypollution
body: Note that we have several CodeCov issues. This is because we were unable to implement Price, DrinkName and Category constructors in existing classes. This is fine as we will work on it in upcoming 1.4
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-25T10:12:10Z

Review comments:
diff_hunk: @@ -0,0 +1,63 @@
+package seedu.address.model.drink;
path: src/test/java/seedu/address/model/drink/CategoryTest.java
user: login: Jsjk112344
body: LGTM, good use of testing to cover added lines of code
created_at: 2025-03-24T14:41:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,55 @@
+package seedu.address.model.drink;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents a Drink's price.
+ * Guarantees: immutable; is valid as declared in {@link #isValidPrice(double)}.
+ */
+public class Price {
path: src/main/java/seedu/address/model/drink/Price.java
user: login: Jsjk112344
body: LGTM, good use of Price field instead of integers to streamline the code
created_at: 2025-03-24T14:42:23Z
author_association: NONE
diff_hunk: @@ -0,0 +1,63 @@
+package seedu.address.model.drink;
+
+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 org.junit.jupiter.api.Test;
+
+class DrinkNameTest {
+
+    @Test
+    void constructor_nullDrinkName_throwsNullPointerException() {
+        assertThrows(NullPointerException.class, () -> new DrinkName(null));
+    }
+
+    @Test
+    void constructor_invalidDrinkName_throwsIllegalArgumentException() {
+        assertThrows(IllegalArgumentException.class, () -> new DrinkName(""));
+        assertThrows(IllegalArgumentException.class, () -> new DrinkName("   "));
+    }
+
+    @Test
+    void isValidName_validCases() {
path: src/test/java/seedu/address/model/drink/DrinkNameTest.java
user: login: Jsjk112344
body: LGTM Good test cases
created_at: 2025-03-24T14:42:38Z
author_association: NONE
diff_hunk: @@ -0,0 +1,58 @@
+package seedu.address.model.drink;
+
+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 org.junit.jupiter.api.Test;
+
+class PriceTest {
+
+    @Test
+    void constructor_invalidPrice_throwsIllegalArgumentException() {
+        assertThrows(IllegalArgumentException.class, () -> new Price(0));
+        assertThrows(IllegalArgumentException.class, () -> new Price(-5.0));
+    }
+
+    @Test
+    void isValidPrice_validCases() {
+        assertTrue(Price.isValidPrice(0.01));
path: src/test/java/seedu/address/model/drink/PriceTest.java
user: login: Jsjk112344
body: Good use of assertions - good defensive programming!
created_at: 2025-03-24T14:43:02Z
author_association: NONE
diff_hunk: @@ -1,4 +1,55 @@
 package seedu.address.model.drink;
 
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents a Drink's category.
+ * Guarantees: immutable; is valid as declared in {@link #isValidCategory(String)}.
+ */
 public class Category {
+
+    public static final String MESSAGE_CONSTRAINTS = "Category should not be blank";
+
+    public final String category;
+
+    /**
+     * Constructs a {@code Category}.
+     *
+     * @param category A valid category.
+     */
+    public Category(String category) {
path: src/main/java/seedu/address/model/drink/Category.java
user: login: loyaltypollution
body: In our IRL discussion, we were concerned that the object could be constructed multiple times, for the same item. For instance, for a drink Coffee and Latte of the same Category, we might have Category("Coffee") constructed twice.

We could avoid this with a factory method and a internal HashMap.
created_at: 2025-03-25T10:10:13Z
author_association: NONE
diff_hunk: @@ -1,4 +1,55 @@
 package seedu.address.model.drink;
 
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents a Drink's name.
+ * Guarantees: immutable; is valid as declared in {@link #isValidName(String)}.
+ */
 public class DrinkName {
+
+    public static final String MESSAGE_CONSTRAINTS = "Drink names should not be blank";
+
+    public final String drinkName;
+
+    /**
+     * Constructs a {@code DrinkName}.
+     *
+     * @param drinkName A valid name.
+     */
+    public DrinkName(String drinkName) {
path: src/main/java/seedu/address/model/drink/DrinkName.java
user: login: loyaltypollution
body: In our IRL discussion, we were concerned that the object could be constructed multiple times, for the same item. For instance, for two Coffees with the same DrinkName, we might have DrinkName("Coffee") constructed twice.

We could avoid this with a factory method and a internal HashMap.
created_at: 2025-03-25T10:10:50Z
author_association: NONE

PR comments:
user: login: codecov[bot]
created_at: 2025-03-20T13:58:44Z
body: ## [Codecov](https://app.codecov.io/gh/AY2425S2-CS2103T-T08-3/tp/pull/188?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T08-3) Report
Attention: Patch coverage is `82.85714%` with `6 lines` in your changes missing coverage. Please review.

| [Files with missing lines](https://app.codecov.io/gh/AY2425S2-CS2103T-T08-3/tp/pull/188?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T08-3) | Patch % | Lines |
|---|---|---|
| [.../main/java/seedu/address/model/drink/Category.java](https://app.codecov.io/gh/AY2425S2-CS2103T-T08-3/tp/pull/188?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fseedu%2Faddress%2Fmodel%2Fdrink%2FCategory.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T08-3#diff-c3JjL21haW4vamF2YS9zZWVkdS9hZGRyZXNzL21vZGVsL2RyaW5rL0NhdGVnb3J5LmphdmE=) | 83.33% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/AY2425S2-CS2103T-T08-3/tp/pull/188?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T08-3) |
| [...main/java/seedu/address/model/drink/DrinkName.java](https://app.codecov.io/gh/AY2425S2-CS2103T-T08-3/tp/pull/188?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fseedu%2Faddress%2Fmodel%2Fdrink%2FDrinkName.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T08-3#diff-c3JjL21haW4vamF2YS9zZWVkdS9hZGRyZXNzL21vZGVsL2RyaW5rL0RyaW5rTmFtZS5qYXZh) | 83.33% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/AY2425S2-CS2103T-T08-3/tp/pull/188?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T08-3) |
| [src/main/java/seedu/address/model/drink/Price.java](https://app.codecov.io/gh/AY2425S2-CS2103T-T08-3/tp/pull/188?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fseedu%2Faddress%2Fmodel%2Fdrink%2FPrice.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T08-3#diff-c3JjL21haW4vamF2YS9zZWVkdS9hZGRyZXNzL21vZGVsL2RyaW5rL1ByaWNlLmphdmE=) | 81.81% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/AY2425S2-CS2103T-T08-3/tp/pull/188?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T08-3) |

| [Files with missing lines](https://app.codecov.io/gh/AY2425S2-CS2103T-T08-3/tp/pull/188?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T08-3) | Coverage Δ | Complexity Δ | |
|---|---|---|---|
| [.../main/java/seedu/address/model/drink/Category.java](https://app.codecov.io/gh/AY2425S2-CS2103T-T08-3/tp/pull/188?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fseedu%2Faddress%2Fmodel%2Fdrink%2FCategory.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T08-3#diff-c3JjL21haW4vamF2YS9zZWVkdS9hZGRyZXNzL21vZGVsL2RyaW5rL0NhdGVnb3J5LmphdmE=) | `83.33% <83.33%> (ø)` | `10.00 <10.00> (?)` | |
| [...main/java/seedu/address/model/drink/DrinkName.java](https://app.codecov.io/gh/AY2425S2-CS2103T-T08-3/tp/pull/188?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fseedu%2Faddress%2Fmodel%2Fdrink%2FDrinkName.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T08-3#diff-c3JjL21haW4vamF2YS9zZWVkdS9hZGRyZXNzL21vZGVsL2RyaW5rL0RyaW5rTmFtZS5qYXZh) | `83.33% <83.33%> (ø)` | `10.00 <10.00> (?)` | |
| [src/main/java/seedu/address/model/drink/Price.java](https://app.codecov.io/gh/AY2425S2-CS2103T-T08-3/tp/pull/188?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fseedu%2Faddress%2Fmodel%2Fdrink%2FPrice.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T08-3#diff-c3JjL21haW4vamF2YS9zZWVkdS9hZGRyZXNzL21vZGVsL2RyaW5rL1ByaWNlLmphdmE=) | `81.81% <81.81%> (ø)` | `9.00 <9.00> (?)` | |

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: loyaltypollution
created_at: 2025-03-25T10:13:28Z
body: We are in the right direction with abstracting away drink fields into its own Constructors. In the upcoming weeks we need to
1. Implement constructors in related classes
2. Implement tests for these classes
author_association: NONE