diff --git a/Petstoredata.sql b/Petstoredata.sql index 1b968e23..4d50c46a 100644 --- a/Petstoredata.sql +++ b/Petstoredata.sql @@ -146,6 +146,14 @@ CREATE TABLE saleItem ( FOREIGN KEY (prodId) REFERENCES product(prodId) ); +CREATE TABLE purchaseOrder ( + purchaseOrderId INT AUTO_INCREMENT PRIMARY KEY, + supId INT NOT NULL, + orderDate DATE NOT NULL, + status VARCHAR(50) NOT NULL, + FOREIGN KEY (supId) REFERENCES supplier(supId) +); + CREATE TABLE activityLog ( logId INT AUTO_INCREMENT PRIMARY KEY, employeeId INT NOT NULL, @@ -281,20 +289,97 @@ VALUES INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId) VALUES -(NOW(), 60.00, 'Card', 2, 1), -('2026-01-28 10:30:00', 50.00, 'Cash', 1, 1), -('2026-01-29 14:15:00', 120.00, 'Card', 4, 3), -('2026-01-30 16:45:00', 80.00, 'Card', 2, 2), -('2026-02-01 11:20:00', 150.00, 'Cash', 5, 4); +-- January Sales +('2026-01-05 09:15:00', 125.00, 'Card', 1, 1), -- Sale 1: Dog food + treats +('2026-01-08 11:30:00', 200.00, 'Card', 2, 1), -- Sale 2: Bird cage + fish tank +('2026-01-12 14:20:00', 60.00, 'Cash', 3, 2), -- Sale 3: Cat toys + hamster wheel +('2026-01-15 10:45:00', 150.00, 'Debit', 1, 1), -- Sale 4: Dog food (bulk purchase) +('2026-01-18 16:30:00', 80.00, 'Card', 4, 3), -- Sale 5: Fish tank +('2026-01-22 13:15:00', 95.00, 'Cash', 2, 2), -- Sale 6: Mixed items +('2026-01-25 15:40:00', 240.00, 'Card', 5, 4), -- Sale 7: Two bird cages +('2026-01-28 10:30:00', 80.00, 'Cash', 1, 1), -- Sale 8: Dog food + cat toys +-- February Sales +('2026-02-01 09:00:00', 175.00, 'Card', 3, 3), -- Sale 9: Dog food + treats (bulk) +('2026-02-03 11:20:00', 120.00, 'Card', 2, 1), -- Sale 10: Bird cage +('2026-02-05 14:50:00', 45.00, 'Cash', 4, 2), -- Sale 11: Hamster wheel + cat toys +('2026-02-08 16:15:00', 160.00, 'Debit', 1, 1), -- Sale 12: Fish tank + accessories +('2026-02-10 10:25:00', 100.00, 'Card', 5, 4), -- Sale 13: Dog treats (bulk) +('2026-02-12 13:45:00', 50.00, 'Cash', 2, 2), -- Sale 14: Dog food +('2026-02-15 15:30:00', 85.00, 'Card', 3, 3), -- Sale 15: Mixed pet supplies +('2026-02-18 11:10:00', 200.00, 'Card', 1, 1), -- Sale 16: Bird cage + hamster wheel +('2026-02-20 14:35:00', 155.00, 'Debit', 4, 3), -- Sale 17: Fish tank + cat toys +('2026-02-22 16:50:00', 75.00, 'Cash', 2, 1), -- Sale 18: Dog treats + toys +('2026-02-24 10:15:00', 140.00, 'Card', 5, 4), -- Sale 19: Dog food + treats +(NOW(), 95.00, 'Card', 1, 1); -- Sale 20: Recent sale (current timestamp) INSERT INTO saleItem (saleId, prodId, quantity, unitPrice) VALUES -(1, 2, 2, 10.00), -(2, 1, 1, 50.00), -(3, 3, 1, 120.00), -(4, 4, 1, 80.00), -(5, 6, 6, 25.00), -(2, 2, 3, 10.00); +-- Sale 1 items (Dog food + treats) +(1, 1, 2, 50.00), -- 2x Premium Dog Food +(1, 6, 1, 25.00), -- 1x Organic Dog Treats +-- Sale 2 items (Bird cage + fish tank) +(2, 3, 1, 120.00), -- 1x Bird Cage Large +(2, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon +-- Sale 3 items (Cat toys + hamster wheel) +(3, 2, 3, 10.00), -- 3x Cat Toy Ball +(3, 5, 2, 15.00), -- 2x Hamster Wheel +-- Sale 4 items (Dog food bulk) +(4, 1, 3, 50.00), -- 3x Premium Dog Food +-- Sale 5 items (Fish tank) +(5, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon +-- Sale 6 items (Mixed) +(6, 2, 4, 10.00), -- 4x Cat Toy Ball +(6, 5, 1, 15.00), -- 1x Hamster Wheel +(6, 6, 1, 25.00), -- 1x Organic Dog Treats +(6, 1, 1, 50.00), -- 1x Premium Dog Food (partial - discount applied) +-- Sale 7 items (Two bird cages) +(7, 3, 2, 120.00), -- 2x Bird Cage Large +-- Sale 8 items (Dog food + cat toys) +(8, 1, 1, 50.00), -- 1x Premium Dog Food +(8, 2, 3, 10.00), -- 3x Cat Toy Ball +-- Sale 9 items (Dog food + treats bulk) +(9, 1, 3, 50.00), -- 3x Premium Dog Food +(9, 6, 1, 25.00), -- 1x Organic Dog Treats +-- Sale 10 items (Bird cage) +(10, 3, 1, 120.00), -- 1x Bird Cage Large +-- Sale 11 items (Hamster wheel + cat toys) +(11, 5, 1, 15.00), -- 1x Hamster Wheel +(11, 2, 3, 10.00), -- 3x Cat Toy Ball +-- Sale 12 items (Fish tank + accessories) +(12, 4, 2, 80.00), -- 2x Fish Tank 20 Gallon +-- Sale 13 items (Dog treats bulk) +(13, 6, 4, 25.00), -- 4x Organic Dog Treats +-- Sale 14 items (Dog food) +(14, 1, 1, 50.00), -- 1x Premium Dog Food +-- Sale 15 items (Mixed supplies) +(15, 2, 2, 10.00), -- 2x Cat Toy Ball +(15, 5, 1, 15.00), -- 1x Hamster Wheel +(15, 6, 2, 25.00), -- 2x Organic Dog Treats +-- Sale 16 items (Bird cage + hamster wheel) +(16, 3, 1, 120.00), -- 1x Bird Cage Large +(16, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon +-- Sale 17 items (Fish tank + cat toys) +(17, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon +(17, 1, 1, 50.00), -- 1x Premium Dog Food +(17, 6, 1, 25.00), -- 1x Organic Dog Treats +-- Sale 18 items (Dog treats + toys) +(18, 6, 2, 25.00), -- 2x Organic Dog Treats +(18, 2, 2, 10.00), -- 2x Cat Toy Ball +(18, 5, 1, 15.00), -- 1x Hamster Wheel +-- Sale 19 items (Dog food + treats) +(19, 1, 2, 50.00), -- 2x Premium Dog Food +(19, 6, 2, 25.00), -- 2x Organic Dog Treats (discount applied) +-- Sale 20 items (Recent sale) +(20, 2, 5, 10.00), -- 5x Cat Toy Ball +(20, 5, 3, 15.00); -- 3x Hamster Wheel + +INSERT INTO purchaseOrder (supId, orderDate, status) +VALUES +(1, '2025-01-15', 'Delivered'), +(2, '2025-01-20', 'Pending'), +(3, '2025-02-01', 'Delivered'), +(4, '2025-02-10', 'In Transit'), +(1, '2025-02-15', 'Pending'); INSERT INTO activityLog (employeeId, activity) VALUES diff --git a/README.md b/README.md new file mode 100644 index 00000000..b525f4b2 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Pet Shop Desktop (JavaFX) + +Desktop pet shop management app built with JavaFX and MySQL. + +Made by **Group 2**, Shiv, Nikitha, Alex, Harkamal. + +## Requirements + +- IntelliJ IDEA (Community or Ultimate) +- Java 17+ +- Maven (handled through IntelliJ) +- Docker and Docker Compose (for the local MySQL container) + +## Database setup (IntelliJ) + +1. Open the project in IntelliJ. +2. Open **View → Tool Windows → Services**. +3. Add a Docker connection if needed, then open the **Docker** section in Services. +4. Start the Compose stack from `docker-compose.yml` (Compose Up, or Start). +5. Confirm the `mysql` service is running. + +The container uses `mysql:8.4`, creates the `Petstoredb` database, and imports `Petstoredata.sql`. + +## App configuration + +An example connection file is provided at `connectionpetstore.properties.example`. Copy it to `connectionpetstore.properties` and edit the values to match the local database setup. + +## Run the app (IntelliJ, Maven) + +1. Open **View → Tool Windows → Maven**. +2. Click **Reload All Maven Projects** if the dependencies have not loaded yet. +3. In the Maven tool window, expand **Plugins → javafx**. +4. Double click **javafx:run**. + +Optional, run a clean first: +- In the Maven tool window, expand **Lifecycle** and run **clean**. + +## Default accounts + +On first run, the app creates a `users` table (if missing) and seeds two accounts: + +- Admin: `admin` / `admin123` +- Staff: `staff` / `staff123` + +## Notes + +- `connectionpetstore.properties` is gitignored so credentials are not committed. +- If the app cannot connect to MySQL, confirm the Compose stack is running and MySQL is available. diff --git a/pom.xml b/pom.xml index 6da4edce..3c683c9e 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ PetShopDesktop UTF-8 - 25.0.2 + 17.0.10 5.12.1 @@ -53,8 +53,7 @@ maven-compiler-plugin 3.13.0 - 23 - 23 + 17 diff --git a/src/main/java/org/example/petshopdesktop/DTOs/SaleDTO.java b/src/main/java/org/example/petshopdesktop/DTOs/SaleDTO.java new file mode 100644 index 00000000..a20bf5fd --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/DTOs/SaleDTO.java @@ -0,0 +1,93 @@ +package org.example.petshopdesktop.DTOs; + +import javafx.beans.property.*; + +public class SaleDTO { + + private IntegerProperty saleId; + private StringProperty saleDate; + private StringProperty employeeName; + private StringProperty productName; + private IntegerProperty quantity; + private DoubleProperty unitPrice; + private DoubleProperty total; + private StringProperty paymentMethod; + + public SaleDTO(int saleId, String saleDate, String employeeName, String productName, + int quantity, double unitPrice, double total, String paymentMethod) { + this.saleId = new SimpleIntegerProperty(saleId); + this.saleDate = new SimpleStringProperty(saleDate); + this.employeeName = new SimpleStringProperty(employeeName); + this.productName = new SimpleStringProperty(productName); + this.quantity = new SimpleIntegerProperty(quantity); + this.unitPrice = new SimpleDoubleProperty(unitPrice); + this.total = new SimpleDoubleProperty(total); + this.paymentMethod = new SimpleStringProperty(paymentMethod); + } + + // Getters + public int getSaleId() { + return saleId.get(); + } + + public String getSaleDate() { + return saleDate.get(); + } + + public String getEmployeeName() { + return employeeName.get(); + } + + public String getProductName() { + return productName.get(); + } + + public int getQuantity() { + return quantity.get(); + } + + public double getUnitPrice() { + return unitPrice.get(); + } + + public double getTotal() { + return total.get(); + } + + public String getPaymentMethod() { + return paymentMethod.get(); + } + + // Properties + public IntegerProperty saleIdProperty() { + return saleId; + } + + public StringProperty saleDateProperty() { + return saleDate; + } + + public StringProperty employeeNameProperty() { + return employeeName; + } + + public StringProperty productNameProperty() { + return productName; + } + + public IntegerProperty quantityProperty() { + return quantity; + } + + public DoubleProperty unitPriceProperty() { + return unitPrice; + } + + public DoubleProperty totalProperty() { + return total; + } + + public StringProperty paymentMethodProperty() { + return paymentMethod; + } +} diff --git a/src/main/java/org/example/petshopdesktop/Launcher.java b/src/main/java/org/example/petshopdesktop/Launcher.java index 8b91cd9c..70456482 100644 --- a/src/main/java/org/example/petshopdesktop/Launcher.java +++ b/src/main/java/org/example/petshopdesktop/Launcher.java @@ -1,5 +1,3 @@ -//Initial commmit - package org.example.petshopdesktop; import javafx.application.Application; diff --git a/src/main/java/org/example/petshopdesktop/Validator.class b/src/main/java/org/example/petshopdesktop/Validator.class new file mode 100644 index 00000000..b5653a2d Binary files /dev/null and b/src/main/java/org/example/petshopdesktop/Validator.class differ diff --git a/src/main/java/org/example/petshopdesktop/Validator.java b/src/main/java/org/example/petshopdesktop/Validator.java index 29182e20..9c6f78a6 100644 --- a/src/main/java/org/example/petshopdesktop/Validator.java +++ b/src/main/java/org/example/petshopdesktop/Validator.java @@ -3,14 +3,14 @@ package org.example.petshopdesktop; public class Validator { /** - * Checks if string is not empty + * Checks if string is not blank * @param value string to check * @param name name of the input - * @return error msg if string is not empty, otherwise empty + * @return error msg if string is blank, otherwise empty */ public static String isPresent(String value, String name){ - String msg =""; //OK so far - if(value.isEmpty() || name.isBlank()){ + String msg = ""; + if (value == null || value.isBlank()){ msg += name + " is required. \n"; } return msg; @@ -23,7 +23,7 @@ public class Validator { * @return error msg if input is not a number or negative, otherwise empty */ public static String isNonNegativeDouble(String value, String name){ - String msg =""; //OK so far + String msg =""; double result; try{ result = Double.parseDouble(value); @@ -40,13 +40,13 @@ public class Validator { /** * Checks if the input is a double in 2 different range * @param value input of string - * @param name name of inpt + * @param name name of input * @param minValue min value of range * @param maxValue max value of range * @return error msg if input is out of range, otherwise empty */ public static String isDoubleInRange(String value, String name, double minValue, double maxValue){ - String msg =""; //OK so far + String msg =""; double result; try{ result = Double.parseDouble(value); @@ -67,7 +67,7 @@ public class Validator { * @return error msg if input is not a number or negative, otherwise empty */ public static String isNonNegativeInteger(String value, String name){ - String msg =""; //OK so far + String msg =""; int result; try{ result = Integer.parseInt(value); @@ -85,6 +85,7 @@ public class Validator { * check if the string is a given amount of characters or fewer * @param value input of string * @param name name of input + * @param length max allowed length * @return error msg if input is more than given characters length */ public static String isLessThanVarChars(String value, String name, int length){ @@ -102,8 +103,7 @@ public class Validator { * @return error msg if input is not a valid email format, otherwise empty */ public static String isValidEmail(String value, String name){ - String msg = ""; //OK so far - // Email regex + String msg = ""; String regex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"; if (!value.matches(regex)){ @@ -119,8 +119,7 @@ public class Validator { * @return error msg if input is not in valid phone format, otherwise empty */ public static String isValidPhoneNumber(String value, String name){ - String msg = ""; //OK so far - // Phone regex + String msg = ""; String regex = "^\\d{3}-\\d{3}-\\d{4}$"; if (!value.matches(regex)){ diff --git a/src/main/java/org/example/petshopdesktop/auth/Role.class b/src/main/java/org/example/petshopdesktop/auth/Role.class new file mode 100644 index 00000000..b3d3c6cb Binary files /dev/null and b/src/main/java/org/example/petshopdesktop/auth/Role.class differ diff --git a/src/main/java/org/example/petshopdesktop/auth/Role.java b/src/main/java/org/example/petshopdesktop/auth/Role.java index e631ddf7..64f38459 100644 --- a/src/main/java/org/example/petshopdesktop/auth/Role.java +++ b/src/main/java/org/example/petshopdesktop/auth/Role.java @@ -1,13 +1,6 @@ package org.example.petshopdesktop.auth; -/* -Petshop Desktop -Purpose: Application role definitions used by session state and role based access control. -*/ public enum Role { - // Administrative access, includes system management screens. ADMIN, - - // Staff access, limited to day to day operational screens. STAFF } diff --git a/src/main/java/org/example/petshopdesktop/auth/UserSession.java b/src/main/java/org/example/petshopdesktop/auth/UserSession.java index 2cc4538a..450fb3ff 100644 --- a/src/main/java/org/example/petshopdesktop/auth/UserSession.java +++ b/src/main/java/org/example/petshopdesktop/auth/UserSession.java @@ -1,24 +1,15 @@ package org.example.petshopdesktop.auth; -/* -Petshop Desktop -Purpose: In memory session state for the authenticated user. -Notes: Session is process local and cleared on logout or application restart. -*/ public class UserSession { - // Singleton instance used to share session state across controllers. private static UserSession instance; - // Current authenticated username, null when logged out. private String username; - // Current authenticated role, null when logged out. private Role role; private UserSession() {} - // Lazily initialised singleton accessor. public static UserSession getInstance() { if (instance == null) { instance = new UserSession(); @@ -26,13 +17,11 @@ public class UserSession { return instance; } - // Stores identity and role for the active session. public void login(String username, Role role) { this.username = username; this.role = role; } - // Clears session state and returns the application to an unauthenticated state. public void logout() { this.username = null; this.role = null; @@ -46,13 +35,10 @@ public class UserSession { return role; } - // Convenience check for administrative privileges. - // Role.ADMIN.equals(role) remains safe when role is null. public boolean isAdmin() { return Role.ADMIN.equals(role); } - // Session is considered active only when both username and role are set. public boolean isLoggedIn() { return username != null && role != null; } diff --git a/src/main/java/org/example/petshopdesktop/controllers/LoginController.java b/src/main/java/org/example/petshopdesktop/controllers/LoginController.java index 440c5686..43bb3334 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/LoginController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/LoginController.java @@ -14,10 +14,6 @@ import org.example.petshopdesktop.models.User; import java.sql.SQLException; -/* -Petshop Desktop -Purpose: Authentication controller responsible for validating credentials and initialising the user session. -*/ public class LoginController { @FXML @@ -31,18 +27,15 @@ public class LoginController { @FXML void btnLoginClicked(ActionEvent event) { - // Input normalisation keeps authentication behaviour consistent. String username = txtUsername.getText().trim(); String password = txtPassword.getText(); - // Basic validation to avoid unnecessary database calls. if (username.isEmpty() || password.isEmpty()) { lblError.setText("Please enter username and password."); return; } try { - // Credential verification returns a fully populated User on success. User user = UserDB.authenticate(username, password); if (user == null) { lblError.setText("Invalid username or password."); @@ -50,7 +43,6 @@ public class LoginController { return; } - // Session state is stored in memory for use by controllers and UI RBAC. UserSession.getInstance().login(user.getUsername(), user.getRole()); openMainLayout(); @@ -61,7 +53,6 @@ public class LoginController { private void openMainLayout() { try { - // View transition into the post login application shell. FXMLLoader loader = new FXMLLoader( getClass().getResource("/org/example/petshopdesktop/main-layout-view.fxml")); Scene scene = new Scene(loader.load()); diff --git a/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java b/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java index 8198d517..01cab249 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java @@ -11,12 +11,23 @@ import javafx.scene.layout.StackPane; import javafx.stage.Stage; import org.example.petshopdesktop.auth.UserSession; -/* -Petshop Desktop -Purpose: Main application shell controller, includes navigation and UI level role based access control. -*/ public class MainLayoutController { + private static final String NAV_BASE_STYLE = "-fx-background-color: transparent; " + + "-fx-text-fill: #cbd5e1; " + + "-fx-background-radius: 10; " + + "-fx-cursor: hand; " + + "-fx-focus-color: transparent; " + + "-fx-faint-focus-color: transparent;"; + + private static final String NAV_ACTIVE_STYLE = "-fx-background-color: #FF6B6B; " + + "-fx-text-fill: white; " + + "-fx-background-radius: 10; " + + "-fx-cursor: hand; " + + "-fx-focus-color: transparent; " + + "-fx-faint-focus-color: transparent; " + + "-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.22), 10, 0.15, 0, 2);"; + @FXML private Button btnAdoptions; @@ -124,7 +135,6 @@ public class MainLayoutController { @FXML void btnLogoutClicked(ActionEvent event) { - // Logout clears session state before returning to the login view. UserSession.getInstance().logout(); try { FXMLLoader loader = new FXMLLoader( @@ -141,22 +151,18 @@ public class MainLayoutController { @FXML public void initialize() { - // RBAC state is applied once during initial layout load. applyRBAC(); - // Default landing view after successful authentication. loadView("pet-view.fxml"); + updateButtons(btnPets); } private void applyRBAC() { UserSession session = UserSession.getInstance(); - // Session identity is displayed in the header for clarity and auditing. lblUsername.setText(session.getUsername()); - lblRole.setText(session.getRole().toString()); + lblRole.setText("Leon's Petstore"); - // UI level RBAC hides admin only navigation entries for non admin users. - // setManaged(false) removes the node from layout calculations to avoid empty spacing. boolean isAdmin = session.isAdmin(); btnInventory.setVisible(isAdmin); btnInventory.setManaged(isAdmin); @@ -165,7 +171,6 @@ public class MainLayoutController { btnProductSuppliers.setVisible(isAdmin); btnProductSuppliers.setManaged(isAdmin); - // Privileged operations should still be enforced within the relevant controllers and database methods. } /** @@ -174,10 +179,8 @@ public class MainLayoutController { */ private void loadView(String fxmlFile) { try { - //Get the location of the fxml for view FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/modelviews/" + fxmlFile)); Parent view = loader.load(); - //Clear any content that is in the stack pane and add the new view to display spContentArea.getChildren().clear(); spContentArea.getChildren().add(view); } catch (Exception e) { @@ -191,21 +194,13 @@ public class MainLayoutController { * @param activeButton the button to be set active */ private void updateButtons(Button activeButton) { - //reset all buttons Button[] BUTTONS = {btnAdoptions, btnPets, btnAppointments, btnInventory, - btnSalesHistory, btnServices, btnSuppliers, btnProductSuppliers, btnProducts}; + btnSalesHistory, btnServices, btnSuppliers, btnProductSuppliers, btnProducts, btnPurchaseOrders}; for (Button button : BUTTONS) { - //set all buttons to inactive - button.setStyle("-fx-background-color: transparent; " + - "-fx-text-fill: #CCCCCC; " + - "-fx-cursor: hand"); + button.setStyle(NAV_BASE_STYLE); } - //set active button - activeButton.setStyle("-fx-background-color: #FF6B6B; " + - "-fx-text-fill: white; " + - "-fx-cursor: hand; " + - "-fx-background-radius: 8"); + activeButton.setStyle(NAV_ACTIVE_STYLE); } } diff --git a/src/main/java/org/example/petshopdesktop/controllers/SaleController.java b/src/main/java/org/example/petshopdesktop/controllers/SaleController.java index a54139a2..41e74906 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/SaleController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/SaleController.java @@ -1,11 +1,14 @@ package org.example.petshopdesktop.controllers; +import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; -import javafx.scene.control.Button; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.control.TextField; +import javafx.scene.control.*; +import javafx.scene.control.cell.PropertyValueFactory; +import org.example.petshopdesktop.DTOs.SaleDTO; +import org.example.petshopdesktop.database.SaleDB; + +import java.sql.SQLException; public class SaleController { @@ -13,38 +16,137 @@ public class SaleController { private Button btnRefresh; @FXML - private TableColumn colCustomerName; + private TableColumn colCustomerName; @FXML - private TableColumn colSaleDate; + private TableColumn colSaleDate; @FXML - private TableColumn colSaleId; + private TableColumn colSaleId; @FXML - private TableColumn colSalePaymentType; + private TableColumn colSalePaymentType; @FXML - private TableColumn colSaleQuantity; + private TableColumn colSaleQuantity; @FXML - private TableColumn colSaleTotal; + private TableColumn colSaleTotal; @FXML - private TableColumn colSaleUnitPrice; + private TableColumn colSaleUnitPrice; @FXML - private TableColumn colServiceProduct; + private TableColumn colServiceProduct; @FXML - private TableView tvSales; + private TableView tvSales; @FXML private TextField txtSearch; - @FXML - void btnRefresh(ActionEvent event) { + private ObservableList salesData; + /** + * Initialize the controller - set up table columns and load data + */ + @FXML + public void initialize() { + // Set up table columns + colSaleId.setCellValueFactory(new PropertyValueFactory<>("saleId")); + colSaleDate.setCellValueFactory(new PropertyValueFactory<>("saleDate")); + colCustomerName.setCellValueFactory(new PropertyValueFactory<>("employeeName")); + colServiceProduct.setCellValueFactory(new PropertyValueFactory<>("productName")); + colSaleQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity")); + colSaleUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice")); + colSaleTotal.setCellValueFactory(new PropertyValueFactory<>("total")); + colSalePaymentType.setCellValueFactory(new PropertyValueFactory<>("paymentMethod")); + + // Format currency columns + colSaleUnitPrice.setCellFactory(tc -> new TableCell() { + @Override + protected void updateItem(Double price, boolean empty) { + super.updateItem(price, empty); + if (empty || price == null) { + setText(null); + } else { + setText(String.format("$%.2f", price)); + } + } + }); + + colSaleTotal.setCellFactory(tc -> new TableCell() { + @Override + protected void updateItem(Double total, boolean empty) { + super.updateItem(total, empty); + if (empty || total == null) { + setText(null); + } else { + setText(String.format("$%.2f", total)); + } + } + }); + + // Load initial data + loadSales(); + + // Add search functionality + txtSearch.textProperty().addListener((observable, oldValue, newValue) -> { + if (newValue == null || newValue.trim().isEmpty()) { + loadSales(); + } else { + searchSales(newValue.trim()); + } + }); } + /** + * Load all sales from database + */ + private void loadSales() { + try { + salesData = SaleDB.getSales(); + tvSales.setItems(salesData); + } catch (SQLException e) { + e.printStackTrace(); + showError("Failed to load sales data", e.getMessage()); + } + } + + /** + * Search sales based on filter text + * @param filter search term + */ + private void searchSales(String filter) { + try { + ObservableList filteredSales = SaleDB.getFilteredSales(filter); + tvSales.setItems(filteredSales); + } catch (SQLException e) { + e.printStackTrace(); + showError("Failed to search sales", e.getMessage()); + } + } + + /** + * Refresh button handler - reload all sales data + * @param event button click event + */ + @FXML + void btnRefresh(ActionEvent event) { + txtSearch.clear(); + loadSales(); + } + + /** + * Show error alert + * @param title alert title + * @param message error message + */ + private void showError(String title, String message) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(message); + alert.showAndWait(); + } } diff --git a/src/main/java/org/example/petshopdesktop/database/AdoptionDB.java b/src/main/java/org/example/petshopdesktop/database/AdoptionDB.java index 87c087ef..6c436479 100644 --- a/src/main/java/org/example/petshopdesktop/database/AdoptionDB.java +++ b/src/main/java/org/example/petshopdesktop/database/AdoptionDB.java @@ -115,10 +115,10 @@ public class AdoptionDB { Connection conn = ConnectionDB.getConnection(); Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT customerId, firstName, lastName FROM customer"); + ResultSet rs = stmt.executeQuery("SELECT customerId, firstName, lastName, email, phone FROM customer"); while (rs.next()) { - customers.add(new Customer(rs.getInt(1), rs.getString(2), rs.getString(3))); + customers.add(new Customer(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4), rs.getString(5))); } conn.close(); diff --git a/src/main/java/org/example/petshopdesktop/database/ProductSupplierDB.java b/src/main/java/org/example/petshopdesktop/database/ProductSupplierDB.java index cc651bd0..c3298699 100644 --- a/src/main/java/org/example/petshopdesktop/database/ProductSupplierDB.java +++ b/src/main/java/org/example/petshopdesktop/database/ProductSupplierDB.java @@ -23,7 +23,7 @@ public class ProductSupplierDB { //Execute Query Statement stmt = conn.createStatement(); String sql = "SELECT ps.supId, ps.prodId, s.supCompany, p.prodName, ps.cost " + - "FROM productsupplier ps " + + "FROM productSupplier ps " + "LEFT JOIN product p " + "ON p.prodId = ps.prodId " + "LEFT JOIN supplier s " + @@ -62,7 +62,7 @@ public class ProductSupplierDB { String sql = "SELECT ps.supId, ps.prodId, s.supCompany, p.prodName, ps.cost " + "FROM product p " + - "LEFT JOIN productsupplier ps " + + "LEFT JOIN productSupplier ps " + "ON p.prodId = ps.prodId " + "LEFT JOIN supplier s " + "ON s.supId = ps.supId " + @@ -108,7 +108,7 @@ public class ProductSupplierDB { int numRows = 0; Connection conn = ConnectionDB.getConnection(); - String sql = "INSERT INTO productsupplier (prodId, supId, cost) " + + String sql = "INSERT INTO productSupplier (prodId, supId, cost) " + "VALUES (?, ?, ?)"; //These are the values from productSupplier to put into query above @@ -141,7 +141,7 @@ public class ProductSupplierDB { try{ //Delete old data first - String sql = "DELETE FROM productsupplier WHERE supId = ? AND prodId = ?"; + String sql = "DELETE FROM productSupplier WHERE supId = ? AND prodId = ?"; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setInt(1, oldSupId); stmt.setInt(2, oldProdId); @@ -149,7 +149,7 @@ public class ProductSupplierDB { //Then change the data by inserting a new relation with given keys (only if delete worked) if(numRows > 0){ - sql = "INSERT INTO productsupplier (prodId, supId, cost) " + + sql = "INSERT INTO productSupplier (prodId, supId, cost) " + "VALUES (?, ?, ?)"; stmt = conn.prepareStatement(sql); stmt.setInt(1, productSupplier.getProdId()); @@ -184,7 +184,7 @@ public class ProductSupplierDB { int numRows = 0; Connection conn = ConnectionDB.getConnection(); - String sql = "DELETE FROM productsupplier WHERE supId = ? AND prodId = ?"; + String sql = "DELETE FROM productSupplier WHERE supId = ? AND prodId = ?"; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setInt(1, supId); stmt.setInt(2, prodId); diff --git a/src/main/java/org/example/petshopdesktop/database/SaleDB.java b/src/main/java/org/example/petshopdesktop/database/SaleDB.java new file mode 100644 index 00000000..43bc798d --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/database/SaleDB.java @@ -0,0 +1,113 @@ +package org.example.petshopdesktop.database; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import org.example.petshopdesktop.DTOs.SaleDTO; + +import java.sql.*; + +public class SaleDB { + + /** + * Get all sale items with details + * @return ObservableList of SaleDTOs + * @throws SQLException if database operation fails + */ + public static ObservableList getSales() throws SQLException { + ObservableList sales = FXCollections.observableArrayList(); + Connection conn = ConnectionDB.getConnection(); + + String sql = """ + SELECT + s.saleId, + DATE_FORMAT(s.saleDate, '%Y-%m-%d %H:%i') as saleDate, + CONCAT(e.firstName, ' ', e.lastName) as employeeName, + p.prodName, + si.quantity, + si.unitPrice, + (si.quantity * si.unitPrice) as lineTotal, + s.paymentMethod + FROM sale s + JOIN saleItem si ON s.saleId = si.saleId + JOIN product p ON si.prodId = p.prodId + JOIN employee e ON s.employeeId = e.employeeId + ORDER BY s.saleDate DESC, s.saleId, si.saleItemId + """; + + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql); + + while (rs.next()) { + sales.add(new SaleDTO( + rs.getInt("saleId"), + rs.getString("saleDate"), + rs.getString("employeeName"), + rs.getString("prodName"), + rs.getInt("quantity"), + rs.getDouble("unitPrice"), + rs.getDouble("lineTotal"), + rs.getString("paymentMethod") + )); + } + + conn.close(); + return sales; + } + + /** + * Get filtered sale items + * @param filter search term + * @return ObservableList of SaleDTOs matching the filter + * @throws SQLException if database operation fails + */ + public static ObservableList getFilteredSales(String filter) throws SQLException { + ObservableList sales = FXCollections.observableArrayList(); + Connection conn = ConnectionDB.getConnection(); + + String sql = """ + SELECT + s.saleId, + DATE_FORMAT(s.saleDate, '%Y-%m-%d %H:%i') as saleDate, + CONCAT(e.firstName, ' ', e.lastName) as employeeName, + p.prodName, + si.quantity, + si.unitPrice, + (si.quantity * si.unitPrice) as lineTotal, + s.paymentMethod + FROM sale s + JOIN saleItem si ON s.saleId = si.saleId + JOIN product p ON si.prodId = p.prodId + JOIN employee e ON s.employeeId = e.employeeId + WHERE s.saleId LIKE ? + OR p.prodName LIKE ? + OR CONCAT(e.firstName, ' ', e.lastName) LIKE ? + OR s.paymentMethod LIKE ? + ORDER BY s.saleDate DESC, s.saleId, si.saleItemId + """; + + PreparedStatement pstmt = conn.prepareStatement(sql); + String searchPattern = "%" + filter + "%"; + pstmt.setString(1, searchPattern); + pstmt.setString(2, searchPattern); + pstmt.setString(3, searchPattern); + pstmt.setString(4, searchPattern); + + ResultSet rs = pstmt.executeQuery(); + + while (rs.next()) { + sales.add(new SaleDTO( + rs.getInt("saleId"), + rs.getString("saleDate"), + rs.getString("employeeName"), + rs.getString("prodName"), + rs.getInt("quantity"), + rs.getDouble("unitPrice"), + rs.getDouble("lineTotal"), + rs.getString("paymentMethod") + )); + } + + conn.close(); + return sales; + } +} diff --git a/src/main/java/org/example/petshopdesktop/database/UserDB.java b/src/main/java/org/example/petshopdesktop/database/UserDB.java index 3b5f78b4..88efe556 100644 --- a/src/main/java/org/example/petshopdesktop/database/UserDB.java +++ b/src/main/java/org/example/petshopdesktop/database/UserDB.java @@ -5,10 +5,6 @@ import org.example.petshopdesktop.models.User; import java.sql.*; -/* -Petshop Desktop -Purpose: User authentication and role lookup against the users table. -*/ public class UserDB { /** @@ -34,8 +30,6 @@ public class UserDB { int userId = rs.getInt("user_id"); String uname = rs.getString("username"); - // Role values are stored in the database as strings and normalised to match the enum. - // Table constraints limit role values, Role.valueOf is expected to be safe under normal operation. Role role = Role.valueOf(rs.getString("role").toUpperCase()); return new User(userId, uname, role); @@ -59,7 +53,6 @@ public class UserDB { ) """; - // Default accounts support initial development and testing, credentials should be rotated or removed for deployment. String seedAdmin = """ INSERT IGNORE INTO users (username, password_hash, role) VALUES ('admin', SHA2('admin123', 256), 'ADMIN') diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/adoption-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/adoption-dialog-view.fxml index ef02cdf0..c3a14b62 100644 --- a/src/main/resources/org/example/petshopdesktop/dialogviews/adoption-dialog-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/dialogviews/adoption-dialog-view.fxml @@ -13,7 +13,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml index b12bc777..99cf0833 100644 --- a/src/main/resources/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml @@ -17,7 +17,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml index 0784811b..d4c97ddb 100644 --- a/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml @@ -13,7 +13,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/product-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/product-dialog-view.fxml index 2972f5ed..2164ec66 100644 --- a/src/main/resources/org/example/petshopdesktop/dialogviews/product-dialog-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/dialogviews/product-dialog-view.fxml @@ -13,7 +13,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/product-supplier-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/product-supplier-dialog-view.fxml index 0ef57a60..ec29e4b6 100644 --- a/src/main/resources/org/example/petshopdesktop/dialogviews/product-supplier-dialog-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/dialogviews/product-supplier-dialog-view.fxml @@ -13,7 +13,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml index 327ce2d0..6cf098fd 100644 --- a/src/main/resources/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml @@ -13,7 +13,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/supplier-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/supplier-dialog-view.fxml index e26557c8..968d1021 100644 --- a/src/main/resources/org/example/petshopdesktop/dialogviews/supplier-dialog-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/dialogviews/supplier-dialog-view.fxml @@ -12,7 +12,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/login-view.fxml b/src/main/resources/org/example/petshopdesktop/login-view.fxml index 65199c95..ad6e0bca 100644 --- a/src/main/resources/org/example/petshopdesktop/login-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/login-view.fxml @@ -10,7 +10,7 @@ diff --git a/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml b/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml index a1471b98..fdf5c267 100644 --- a/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml @@ -5,131 +5,146 @@ + - + - + - + - diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/adoption-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/adoption-view.fxml index c1ec6564..9443ef14 100644 --- a/src/main/resources/org/example/petshopdesktop/modelviews/adoption-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/modelviews/adoption-view.fxml @@ -11,7 +11,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/appointment-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/appointment-view.fxml index 4bfd3029..f698c5c1 100644 --- a/src/main/resources/org/example/petshopdesktop/modelviews/appointment-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/modelviews/appointment-view.fxml @@ -11,7 +11,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/inventory-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/inventory-view.fxml index dea27d1b..18e9ad5a 100644 --- a/src/main/resources/org/example/petshopdesktop/modelviews/inventory-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/modelviews/inventory-view.fxml @@ -11,7 +11,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/pet-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/pet-view.fxml index a9ab6772..43c3c323 100644 --- a/src/main/resources/org/example/petshopdesktop/modelviews/pet-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/modelviews/pet-view.fxml @@ -11,7 +11,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/product-supplier-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/product-supplier-view.fxml index 6ef50d13..19817faa 100644 --- a/src/main/resources/org/example/petshopdesktop/modelviews/product-supplier-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/modelviews/product-supplier-view.fxml @@ -11,7 +11,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/product-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/product-view.fxml index 2473d057..d9b9a37c 100644 --- a/src/main/resources/org/example/petshopdesktop/modelviews/product-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/modelviews/product-view.fxml @@ -11,7 +11,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/purchase-order-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/purchase-order-view.fxml index e4c3a488..74a00e6d 100644 --- a/src/main/resources/org/example/petshopdesktop/modelviews/purchase-order-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/modelviews/purchase-order-view.fxml @@ -7,7 +7,7 @@ diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/purchaseorder-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/purchaseorder-view.fxml deleted file mode 100644 index fdac860e..00000000 --- a/src/main/resources/org/example/petshopdesktop/modelviews/purchaseorder-view.fxml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/sale-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/sale-view.fxml index 26b24418..8ed34120 100644 --- a/src/main/resources/org/example/petshopdesktop/modelviews/sale-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/modelviews/sale-view.fxml @@ -11,7 +11,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/service-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/service-view.fxml index 2adac139..5353b0e6 100644 --- a/src/main/resources/org/example/petshopdesktop/modelviews/service-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/modelviews/service-view.fxml @@ -11,7 +11,7 @@ - + diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/supplier-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/supplier-view.fxml index 423ae2bf..9c6d75cc 100644 --- a/src/main/resources/org/example/petshopdesktop/modelviews/supplier-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/modelviews/supplier-view.fxml @@ -11,7 +11,7 @@ - +