Merge main into fix-productsupplier-table-name

This commit is contained in:
2026-02-25 10:42:20 -07:00
35 changed files with 469 additions and 216 deletions

View File

@@ -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;
}
}

View File

@@ -1,5 +1,3 @@
//Initial commmit
package org.example.petshopdesktop;
import javafx.application.Application;

View File

@@ -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)){

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -15,6 +15,21 @@ import org.example.petshopdesktop.util.ActivityLogger;
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;
@@ -180,6 +195,7 @@ public class MainLayoutController {
btnSalesHistory.setText(isAdmin ? "Sales History" : "Sales");
}
private void loadView(String fxmlFile) {
@@ -205,9 +221,6 @@ public class MainLayoutController {
}
private void updateButtons(Button activeButton) {
String base = "-fx-background-color: transparent; -fx-text-fill: #D5DDE6; -fx-cursor: hand; -fx-background-radius: 10; -fx-alignment: CENTER_LEFT;";
String active = "-fx-background-color: #FF6B6B; -fx-text-fill: white; -fx-cursor: hand; -fx-background-radius: 10; -fx-alignment: CENTER_LEFT;";
Button[] buttons = {
btnAdoptions,
btnPets,
@@ -224,12 +237,12 @@ public class MainLayoutController {
for (Button button : buttons) {
if (button != null) {
button.setStyle(base);
button.setStyle(NAV_BASE_STYLE);
}
}
if (activeButton != null) {
activeButton.setStyle(active);
activeButton.setStyle(NAV_ACTIVE_STYLE);
}
}

View File

@@ -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();

View File

@@ -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<SaleDTO> getSales() throws SQLException {
ObservableList<SaleDTO> 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<SaleDTO> getFilteredSales(String filter) throws SQLException {
ObservableList<SaleDTO> 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;
}
}

View File

@@ -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')