Merge conflict resolution and updates
This commit is contained in:
93
src/main/java/org/example/petshopdesktop/DTOs/SaleDTO.java
Normal file
93
src/main/java/org/example/petshopdesktop/DTOs/SaleDTO.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
//Initial commmit
|
||||
|
||||
package org.example.petshopdesktop;
|
||||
|
||||
import javafx.application.Application;
|
||||
|
||||
BIN
src/main/java/org/example/petshopdesktop/Validator.class
Normal file
BIN
src/main/java/org/example/petshopdesktop/Validator.class
Normal file
Binary file not shown.
@@ -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)){
|
||||
|
||||
BIN
src/main/java/org/example/petshopdesktop/auth/Role.class
Normal file
BIN
src/main/java/org/example/petshopdesktop/auth/Role.class
Normal file
Binary file not shown.
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<SaleDTO, String> colCustomerName;
|
||||
|
||||
@FXML
|
||||
private TableColumn<?, ?> colSaleDate;
|
||||
private TableColumn<SaleDTO, String> colSaleDate;
|
||||
|
||||
@FXML
|
||||
private TableColumn<?, ?> colSaleId;
|
||||
private TableColumn<SaleDTO, Integer> colSaleId;
|
||||
|
||||
@FXML
|
||||
private TableColumn<?, ?> colSalePaymentType;
|
||||
private TableColumn<SaleDTO, String> colSalePaymentType;
|
||||
|
||||
@FXML
|
||||
private TableColumn<?, ?> colSaleQuantity;
|
||||
private TableColumn<SaleDTO, Integer> colSaleQuantity;
|
||||
|
||||
@FXML
|
||||
private TableColumn<?, ?> colSaleTotal;
|
||||
private TableColumn<SaleDTO, Double> colSaleTotal;
|
||||
|
||||
@FXML
|
||||
private TableColumn<?, ?> colSaleUnitPrice;
|
||||
private TableColumn<SaleDTO, Double> colSaleUnitPrice;
|
||||
|
||||
@FXML
|
||||
private TableColumn<?, ?> colServiceProduct;
|
||||
private TableColumn<SaleDTO, String> colServiceProduct;
|
||||
|
||||
@FXML
|
||||
private TableView<?> tvSales;
|
||||
private TableView<SaleDTO> tvSales;
|
||||
|
||||
@FXML
|
||||
private TextField txtSearch;
|
||||
|
||||
@FXML
|
||||
void btnRefresh(ActionEvent event) {
|
||||
private ObservableList<SaleDTO> 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<SaleDTO, Double>() {
|
||||
@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<SaleDTO, Double>() {
|
||||
@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<SaleDTO> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
113
src/main/java/org/example/petshopdesktop/database/SaleDB.java
Normal file
113
src/main/java/org/example/petshopdesktop/database/SaleDB.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user