Migrate all controllers to REST API

This commit is contained in:
2026-03-07 17:02:53 -07:00
parent 87f616d08e
commit 2b8cd22204
23 changed files with 1308 additions and 1096 deletions

View File

@@ -4,21 +4,21 @@ import javafx.beans.property.*;
public class PurchaseOrderDTO {
private IntegerProperty purchaseOrderId;
private LongProperty purchaseOrderId;
private StringProperty supplierName;
private StringProperty orderDate;
private StringProperty status;
public PurchaseOrderDTO(int id, String supplierName,
public PurchaseOrderDTO(long id, String supplierName,
String orderDate, String status) {
this.purchaseOrderId = new SimpleIntegerProperty(id);
this.purchaseOrderId = new SimpleLongProperty(id);
this.supplierName = new SimpleStringProperty(supplierName);
this.orderDate = new SimpleStringProperty(orderDate);
this.status = new SimpleStringProperty(status);
}
public int getPurchaseOrderId() { return purchaseOrderId.get(); }
public long getPurchaseOrderId() { return purchaseOrderId.get(); }
public String getSupplierName() { return supplierName.get(); }
public String getOrderDate() { return orderDate.get(); }
public String getStatus() { return status.get(); }

View File

@@ -4,6 +4,8 @@ import java.math.BigDecimal;
public class ProductSupplierResponse {
private Long id;
private Long productId;
private Long supplierId;
private String productName;
private String supplierName;
private BigDecimal supplierPrice;
@@ -16,6 +18,22 @@ public class ProductSupplierResponse {
this.id = id;
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public Long getSupplierId() {
return supplierId;
}
public void setSupplierId(Long supplierId) {
this.supplierId = supplierId;
}
public String getProductName() {
return productName;
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
@@ -10,15 +11,16 @@ import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.adoption.AdoptionResponse;
import org.example.petshopdesktop.api.endpoints.AdoptionApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.AdoptionDialogController;
import org.example.petshopdesktop.database.AdoptionDB;
import org.example.petshopdesktop.models.Adoption;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class AdoptionController {
@@ -35,7 +37,7 @@ public class AdoptionController {
private TableColumn<Adoption, Integer> colAdoptionId;
@FXML
private TableColumn<Adoption, Integer> colPetId;
private TableColumn<Adoption, String> colPetId;
@FXML
private TableColumn<Adoption, String> colCustomerName;
@@ -66,7 +68,7 @@ public class AdoptionController {
tvAdoptions.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
colAdoptionId.setCellValueFactory(new PropertyValueFactory<>("adoptionId"));
colPetId.setCellValueFactory(new PropertyValueFactory<>("petId"));
colPetId.setCellValueFactory(new PropertyValueFactory<>("petName"));
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
colAdoptionDate.setCellValueFactory(new PropertyValueFactory<>("adoptionDate"));
colAdoptionFee.setCellValueFactory(new PropertyValueFactory<>("adoptionFee"));
@@ -118,47 +120,24 @@ public class AdoptionController {
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0;
int failCount = 0;
StringBuilder errors = new StringBuilder();
List<Long> ids = selectedAdoptions.stream()
.map(a -> (long) a.getAdoptionId())
.collect(Collectors.toList());
for (Adoption adoption : selectedAdoptions) {
try {
int numRows = AdoptionDB.deleteAdoption(adoption.getAdoptionId());
if (numRows > 0) {
successCount++;
} else {
failCount++;
}
}
catch (SQLIntegrityConstraintViolationException e) {
ActivityLogger.getInstance().logException(
"AdoptionController.btnDeleteClicked",
e,
String.format("Attempting to delete adoption ID %d - foreign key constraint", adoption.getAdoptionId()));
failCount++;
errors.append("Adoption ID ").append(adoption.getAdoptionId()).append(" is referenced in another table\n");
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"AdoptionController.btnDeleteClicked",
e,
String.format("Attempting to delete adoption ID %d", adoption.getAdoptionId()));
failCount++;
errors.append("Failed to delete adoption ID ").append(adoption.getAdoptionId()).append("\n");
}
}
//show results
if (failCount > 0) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText("Delete Operation Completed with Errors");
alert.setContentText(String.format("Deleted: %d\nFailed: %d\n\n%s",
successCount, failCount, errors.toString()));
alert.showAndWait();
} else if (successCount > 0) {
try {
AdoptionApi.getInstance().deleteAdoptions(ids);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed");
alert.setContentText("Successfully deleted " + successCount + " adoption record(s)");
alert.setContentText("Successfully deleted " + ids.size() + " adoption record(s)");
alert.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AdoptionController.btnDeleteClicked",
e,
"Deleting adoptions");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Delete Operation Failed");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
@@ -181,35 +160,55 @@ public class AdoptionController {
}
private void displayFilteredAdoptions(String filter) {
data.clear();
try {
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
displayAdoptions();
} else {
data = AdoptionDB.getFilteredAdoptions(filter);
tvAdoptions.setItems(data);
}
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AdoptionController.displayFilteredAdoptions",
e,
"Filtering adoptions with filter: " + filter);
System.out.println("Error while fetching table data: " + e.getMessage());
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
displayAdoptions();
} else {
new Thread(() -> {
try {
List<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(filter);
List<Adoption> adoptionList = adoptions.stream()
.map(this::mapToAdoption)
.collect(Collectors.toList());
Platform.runLater(() -> {
data.setAll(adoptionList);
tvAdoptions.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"AdoptionController.displayFilteredAdoptions",
e,
"Filtering adoptions with filter: " + filter);
});
}
}).start();
}
}
private void displayAdoptions() {
data.clear();
try {
data = AdoptionDB.getAdoptions();
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"AdoptionController.displayAdoptions",
e,
"Fetching adoption data for table display");
System.out.println("Error while fetching table data: " + e.getMessage());
}
tvAdoptions.setItems(data);
new Thread(() -> {
try {
List<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(null);
List<Adoption> adoptionList = adoptions.stream()
.map(this::mapToAdoption)
.collect(Collectors.toList());
Platform.runLater(() -> {
data.setAll(adoptionList);
tvAdoptions.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"AdoptionController.displayAdoptions",
e,
"Fetching adoption data for table display");
});
}
}).start();
}
private void openDialog(Adoption adoption, String mode) {
@@ -244,4 +243,17 @@ public class AdoptionController {
btnEdit.setDisable(true);
txtSearch.setText("");
}
private Adoption mapToAdoption(AdoptionResponse response) {
return new Adoption(
response.getId().intValue(),
0,
0,
response.getPetName(),
response.getCustomerName(),
response.getAdoptionDate() != null ? response.getAdoptionDate().toString() : "",
0.0,
response.getAdoptionStatus()
);
}
}

View File

@@ -1,19 +1,25 @@
package org.example.petshopdesktop.controllers;
import javafx.collections.ObservableList;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.chart.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.database.SaleDB;
import org.example.petshopdesktop.models.analytics.*;
import org.example.petshopdesktop.api.dto.analytics.DailySales;
import org.example.petshopdesktop.api.dto.analytics.DashboardResponse;
import org.example.petshopdesktop.api.dto.analytics.TopProduct;
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
import org.example.petshopdesktop.api.endpoints.AnalyticsApi;
import org.example.petshopdesktop.api.endpoints.SaleApi;
import org.example.petshopdesktop.util.ActivityLogger;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.*;
import java.util.stream.Collectors;
public class AnalyticsController {
@@ -78,79 +84,111 @@ public class AnalyticsController {
private void loadAnalyticsData() {
lblError.setVisible(false);
try {
loadSummaryData();
loadSalesOverTime();
loadTopProductsByRevenue();
loadTopProductsByQuantity();
loadPaymentMethodDistribution();
loadEmployeePerformance();
} catch (Exception e) {
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
lblError.setText("Error loading analytics data. Please try again.");
lblError.setVisible(true);
new Thread(() -> {
try {
DashboardResponse dashboard = AnalyticsApi.getInstance().getDashboard(30, 10);
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, Integer.MAX_VALUE, null);
Platform.runLater(() -> {
try {
loadSummaryData(dashboard);
loadSalesOverTime(dashboard);
loadTopProductsByRevenue(dashboard);
loadTopProductsByQuantity(dashboard);
loadPaymentMethodDistribution(sales);
loadEmployeePerformance(sales);
} catch (Exception e) {
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
lblError.setText("Error loading analytics data. Please try again.");
lblError.setVisible(true);
}
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
lblError.setText("Error loading analytics data. Please try again.");
lblError.setVisible(true);
});
}
}).start();
}
private void loadSummaryData(DashboardResponse dashboard) throws Exception {
if (dashboard != null) {
BigDecimal totalRevenue = dashboard.getTotalRevenue() != null ? dashboard.getTotalRevenue() : BigDecimal.ZERO;
Long totalSales = dashboard.getTotalSales() != null ? dashboard.getTotalSales() : 0L;
Long totalProducts = dashboard.getTotalProducts() != null ? dashboard.getTotalProducts() : 0L;
lblTotalRevenue.setText(currency.format(totalRevenue));
lblTotalTransactions.setText(wholeNumber.format(totalSales));
BigDecimal avgTransaction = BigDecimal.ZERO;
if (totalSales > 0) {
avgTransaction = totalRevenue.divide(BigDecimal.valueOf(totalSales), 2, RoundingMode.HALF_UP);
}
lblAvgTransaction.setText(currency.format(avgTransaction));
lblTotalItems.setText(wholeNumber.format(totalProducts));
}
}
private void loadSummaryData() throws Exception {
SalesSummary summary = SaleDB.getSalesSummary();
if (summary != null) {
lblTotalRevenue.setText(currency.format(summary.getTotalRevenue()));
lblTotalTransactions.setText(wholeNumber.format(summary.getTotalTransactions()));
lblAvgTransaction.setText(currency.format(summary.getAvgTransactionValue()));
lblTotalItems.setText(wholeNumber.format(summary.getTotalItemsSold()));
}
}
private void loadSalesOverTime() throws Exception {
ObservableList<DailySalesData> data = SaleDB.getDailySalesRevenue();
private void loadSalesOverTime(DashboardResponse dashboard) throws Exception {
List<DailySales> dailySales = dashboard.getDailySales() != null ? dashboard.getDailySales() : new ArrayList<>();
XYChart.Series<String, Number> series = new XYChart.Series<>();
series.setName("Daily Revenue");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd");
for (DailySalesData dailySale : data) {
for (DailySales dailySale : dailySales) {
String dateStr = dailySale.getDate().format(formatter);
series.getData().add(new XYChart.Data<>(dateStr, dailySale.getRevenue()));
BigDecimal totalSales = dailySale.getTotalSales() != null ? dailySale.getTotalSales() : BigDecimal.ZERO;
series.getData().add(new XYChart.Data<>(dateStr, totalSales));
}
chartSalesOverTime.getData().clear();
chartSalesOverTime.getData().add(series);
}
private void loadTopProductsByRevenue() throws Exception {
ObservableList<ProductSalesData> data = SaleDB.getTopProductsByRevenue(10);
private void loadTopProductsByRevenue(DashboardResponse dashboard) throws Exception {
List<TopProduct> topProducts = dashboard.getTopProducts() != null ? dashboard.getTopProducts() : new ArrayList<>();
XYChart.Series<Number, String> series = new XYChart.Series<>();
series.setName("Revenue");
for (ProductSalesData product : data) {
series.getData().add(new XYChart.Data<>(product.getTotalRevenue(), product.getProductName()));
for (TopProduct product : topProducts) {
BigDecimal totalRevenue = product.getTotalRevenue() != null ? product.getTotalRevenue() : BigDecimal.ZERO;
series.getData().add(new XYChart.Data<>(totalRevenue, product.getProductName()));
}
chartTopRevenue.getData().clear();
chartTopRevenue.getData().add(series);
}
private void loadTopProductsByQuantity() throws Exception {
ObservableList<ProductSalesData> data = SaleDB.getTopProductsByQuantity(10);
private void loadTopProductsByQuantity(DashboardResponse dashboard) throws Exception {
List<TopProduct> topProducts = dashboard.getTopProducts() != null ? dashboard.getTopProducts() : new ArrayList<>();
XYChart.Series<Number, String> series = new XYChart.Series<>();
series.setName("Quantity");
for (ProductSalesData product : data) {
series.getData().add(new XYChart.Data<>(product.getTotalQuantity(), product.getProductName()));
for (TopProduct product : topProducts) {
Integer quantitySold = product.getQuantitySold() != null ? product.getQuantitySold() : 0;
series.getData().add(new XYChart.Data<>(quantitySold, product.getProductName()));
}
chartTopQuantity.getData().clear();
chartTopQuantity.getData().add(series);
}
private void loadPaymentMethodDistribution() throws Exception {
ObservableList<PaymentMethodData> data = SaleDB.getPaymentMethodDistribution();
private void loadPaymentMethodDistribution(List<SaleResponse> sales) throws Exception {
Map<String, Long> paymentMethodCount = sales.stream()
.filter(sale -> sale.getIsRefund() == null || !sale.getIsRefund())
.collect(Collectors.groupingBy(
sale -> sale.getPaymentMethod() != null ? sale.getPaymentMethod() : "Unknown",
Collectors.counting()
));
chartPaymentMethods.getData().clear();
for (PaymentMethodData payment : data) {
for (Map.Entry<String, Long> entry : paymentMethodCount.entrySet()) {
PieChart.Data slice = new PieChart.Data(
payment.getPaymentMethod() + " (" + payment.getTransactionCount() + ")",
payment.getTransactionCount()
entry.getKey() + " (" + entry.getValue() + ")",
entry.getValue()
);
chartPaymentMethods.getData().add(slice);
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
@@ -13,10 +14,14 @@ import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.AppointmentDTO;
import org.example.petshopdesktop.api.dto.appointment.AppointmentResponse;
import org.example.petshopdesktop.api.endpoints.AppointmentApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialogController;
import org.example.petshopdesktop.database.AppointmentDB;
import org.example.petshopdesktop.util.ActivityLogger;
import java.util.List;
import java.util.stream.Collectors;
public class AppointmentController {
@FXML private TableView<AppointmentDTO> tvAppointments;
@@ -71,41 +76,50 @@ public class AppointmentController {
}
private void loadAppointments(){
try{
appointments.setAll(AppointmentDB.getAppointmentDTOs());
}catch(Exception e){
ActivityLogger.getInstance().logException(
"AppointmentController.loadAppointments",
e,
"Loading appointments for table display");
e.printStackTrace();
}
new Thread(() -> {
try{
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(null);
List<AppointmentDTO> appointmentDTOs = responses.stream()
.map(this::mapToAppointmentDTO)
.collect(Collectors.toList());
Platform.runLater(() -> {
appointments.setAll(appointmentDTOs);
});
}catch(Exception e){
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentController.loadAppointments",
e,
"Loading appointments for table display");
e.printStackTrace();
});
}
}).start();
}
private void applyFilter(String text) {
if (filtered == null) {
return;
}
String query = text == null || text.trim().isEmpty() ? null : text.trim();
new Thread(() -> {
try {
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(query);
List<AppointmentDTO> appointmentDTOs = responses.stream()
.map(this::mapToAppointmentDTO)
.collect(Collectors.toList());
String q = text == null ? "" : text.trim().toLowerCase();
if (q.isEmpty()) {
filtered.setPredicate(a -> true);
return;
}
filtered.setPredicate(a ->
String.valueOf(a.getAppointmentId()).contains(q)
|| safe(a.getPetName()).contains(q)
|| safe(a.getServiceName()).contains(q)
|| safe(a.getAppointmentDate()).contains(q)
|| safe(a.getAppointmentTime()).contains(q)
|| safe(a.getCustomerName()).contains(q)
|| safe(a.getAppointmentStatus()).contains(q)
);
}
private static String safe(String v) {
return v == null ? "" : v.toLowerCase();
Platform.runLater(() -> {
appointments.setAll(appointmentDTOs);
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentController.applyFilter",
e,
String.format("Filtering appointments with query: %s", query));
e.printStackTrace();
});
}
}).start();
}
@FXML
@@ -145,35 +159,24 @@ public class AppointmentController {
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0;
int failCount = 0;
StringBuilder errors = new StringBuilder();
List<Long> ids = selectedAppointments.stream()
.map(a -> (long) a.getAppointmentId())
.collect(Collectors.toList());
for (AppointmentDTO appointment : selectedAppointments) {
try{
AppointmentDB.deleteAppointment(appointment.getAppointmentId());
successCount++;
}catch(Exception e){
ActivityLogger.getInstance().logException(
"AppointmentController.btnDeleteClicked",
e,
String.format("Attempting to delete appointment ID %d", appointment.getAppointmentId()));
failCount++;
errors.append("Failed to delete appointment ID ").append(appointment.getAppointmentId()).append("\n");
}
}
//show results
if (failCount > 0) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText("Delete Operation Completed with Errors");
alert.setContentText(String.format("Deleted: %d\nFailed: %d\n\n%s",
successCount, failCount, errors.toString()));
alert.showAndWait();
} else if (successCount > 0) {
try {
AppointmentApi.getInstance().deleteAppointments(ids);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed");
alert.setContentText("Successfully deleted " + successCount + " appointment(s)");
alert.setContentText("Successfully deleted " + ids.size() + " appointment(s)");
alert.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AppointmentController.btnDeleteClicked",
e,
"Deleting appointments");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Delete Operation Failed");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
@@ -225,4 +228,19 @@ public class AppointmentController {
alert.setContentText(msg);
alert.showAndWait();
}
private AppointmentDTO mapToAppointmentDTO(AppointmentResponse response) {
return new AppointmentDTO(
response.getId().intValue(),
0,
response.getCustomerName(),
0,
response.getPetNames(),
0,
response.getServiceName(),
response.getAppointmentDate().toString(),
response.getAppointmentTime().toString(),
response.getAppointmentStatus()
);
}
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
@@ -10,15 +11,16 @@ import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.inventory.InventoryResponse;
import org.example.petshopdesktop.api.endpoints.InventoryApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.InventoryDialogController;
import org.example.petshopdesktop.database.InventoryDB;
import org.example.petshopdesktop.models.Inventory;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class InventoryController {
@@ -58,11 +60,9 @@ public class InventoryController {
//Loads upon view bootup
@FXML
void initialize() {
//Buttons disabled until row is selected
btnEdit.setDisable(true);
btnDelete.setDisable(true);
//Enable multiple selection
tvInventory.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
tvInventory.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.SINGLE);
colInventoryId.setCellValueFactory(new PropertyValueFactory<>("inventoryId"));
colProductId.setCellValueFactory(new PropertyValueFactory<>("prodId"));
@@ -71,19 +71,16 @@ public class InventoryController {
displayInventory();
//Enables buttons when row is selected
tvInventory.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
btnEdit.setDisable(false);
btnDelete.setDisable(false);
});
//Filter as user types
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
displayFilteredInventory(newValue);
});
//EventListener for DELETE key
tvInventory.setOnKeyPressed(event -> {
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
if (tvInventory.getSelectionModel().getSelectedItem() != null) {
@@ -100,71 +97,35 @@ public class InventoryController {
openDialog(null, mode);
}
//Prompts user for confirmation prior to deletion
@FXML
void btnDeleteClicked(ActionEvent event) {
//get selected inventory records
var selectedInventory = tvInventory.getSelectionModel().getSelectedItems();
if (selectedInventory.isEmpty()) return;
Inventory selectedInventory = tvInventory.getSelectionModel().getSelectedItem();
if (selectedInventory == null) return;
//ask user to confirm
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
question.setHeaderText("Please confirm delete");
String message = selectedInventory.size() == 1
? "Are you sure you want to delete this inventory record?"
: "Are you sure you want to delete " + selectedInventory.size() + " inventory records?";
question.setContentText(message);
question.setContentText("Are you sure you want to delete this inventory record?");
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
Optional<ButtonType> result = question.showAndWait();
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0;
int failCount = 0;
StringBuilder errors = new StringBuilder();
for (Inventory inventory : selectedInventory) {
try {
int numRows = InventoryDB.deleteInventory(inventory.getInventoryId());
if (numRows > 0) {
successCount++;
} else {
failCount++;
}
}
catch (SQLIntegrityConstraintViolationException e) {
ActivityLogger.getInstance().logException(
"InventoryController.btnDeleteClicked",
e,
String.format("Attempting to delete inventory ID %d - foreign key constraint", inventory.getInventoryId()));
failCount++;
errors.append("Inventory record '").append(inventory.getProdName()).append("' is referenced in another table\n");
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"InventoryController.btnDeleteClicked",
e,
String.format("Attempting to delete inventory ID %d", inventory.getInventoryId()));
failCount++;
errors.append("Failed to delete '").append(inventory.getProdName()).append("'\n");
}
}
//show results
if (failCount > 0) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText("Delete Operation Completed with Errors");
alert.setContentText(String.format("Deleted: %d\nFailed: %d\n\n%s",
successCount, failCount, errors.toString()));
alert.showAndWait();
} else if (successCount > 0) {
try {
InventoryApi.getInstance().deleteInventory((long) selectedInventory.getInventoryId());
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed");
alert.setContentText("Successfully deleted " + successCount + " inventory record(s)");
alert.setContentText("Successfully deleted inventory record");
alert.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"InventoryController.btnDeleteClicked",
e,
"Deleting inventory");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Delete Operation Failed");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
//refresh display and reset inputs
displayInventory();
btnDelete.setDisable(true);
btnEdit.setDisable(true);
@@ -183,66 +144,72 @@ public class InventoryController {
}
}
//Search filter
private void displayFilteredInventory(String filter) {
data.clear();
try {
//If search box is empty, display all inventory
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
displayInventory();
}
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
displayInventory();
} else {
new Thread(() -> {
try {
List<InventoryResponse> inventories = InventoryApi.getInstance().listInventory(filter);
List<Inventory> inventoryList = inventories.stream()
.map(this::mapToInventory)
.collect(Collectors.toList());
else {
data = InventoryDB.getFilteredInventory(filter);
tvInventory.setItems(data);
}
}
catch (Exception e) {
ActivityLogger.getInstance().logException(
"InventoryController.displayFilteredInventory",
e,
"Filtering inventory with filter: " + filter);
System.out.println("Error while fetching table data: " + e.getMessage());
Platform.runLater(() -> {
data.setAll(inventoryList);
tvInventory.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"InventoryController.displayFilteredInventory",
e,
String.format("Filtering inventory with keyword: %s", filter));
});
}
}).start();
}
}
//Displays all records from DB
private void displayInventory() {
data.clear();
try {
data = InventoryDB.getInventory();
}
new Thread(() -> {
try {
List<InventoryResponse> inventories = InventoryApi.getInstance().listInventory(null);
List<Inventory> inventoryList = inventories.stream()
.map(this::mapToInventory)
.collect(Collectors.toList());
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"InventoryController.displayInventory",
e,
"Fetching inventory data for table display");
System.out.println("Error while fetching table data: " + e.getMessage());
}
tvInventory.setItems(data);
Platform.runLater(() -> {
data.setAll(inventoryList);
tvInventory.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"InventoryController.displayInventory",
e,
"Fetching inventory data for table display");
});
}
}).start();
}
//Opens inventory-dialog-view
private void openDialog(Inventory inventory, String mode) {
//Opens FXML
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/inventory-dialog-view.fxml"));
Scene scene = null;
try {
scene = new Scene(fxmlLoader.load());
}
catch (IOException e) {
} catch (IOException e) {
ActivityLogger.getInstance().logException(
"InventoryController.openDialog",
e,
"Loading inventory dialog in " + mode + " mode");
String.format("Loading inventory dialog view in %s mode", mode));
throw new RuntimeException(e);
}
//Passes data and mode to the view
InventoryDialogController dialogController = fxmlLoader.getController();
dialogController.setMode(mode);
@@ -256,10 +223,22 @@ public class InventoryController {
dialogStage.setScene(scene);
dialogStage.showAndWait();
//Refresh inventory
displayInventory();
btnDelete.setDisable(true);
btnEdit.setDisable(true);
txtSearch.setText("");
}
private Inventory mapToInventory(InventoryResponse response) {
return new Inventory(
response.getId().intValue(),
0,
response.getProductName(),
response.getCategoryName() != null ? response.getCategoryName() : "",
0,
response.getStoreName() != null ? response.getStoreName() : "",
response.getStockQuantity() != null ? response.getStockQuantity() : 0,
response.getReorderLevel() != null ? response.getReorderLevel() : 0
);
}
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
@@ -10,19 +11,16 @@ import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.ProductDTO;
import org.example.petshopdesktop.DTOs.ProductSupplierDTO;
import org.example.petshopdesktop.controllers.dialogcontrollers.ProductDialogController;
import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierResponse;
import org.example.petshopdesktop.api.endpoints.ProductSupplierApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.ProductSupplierDialogController;
import org.example.petshopdesktop.database.ProductDB;
import org.example.petshopdesktop.database.ProductSupplierDB;
import org.example.petshopdesktop.models.ProductSupplier;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class ProductSupplierController {
@@ -107,22 +105,27 @@ public class ProductSupplierController {
* Display the ProductSupplierDTO to table view
*/
private void displayProductSupplier() {
//Erase old content
data.clear();
new Thread(() -> {
try {
List<ProductSupplierResponse> productSuppliers = ProductSupplierApi.getInstance().listProductSuppliers(null);
List<ProductSupplierDTO> productSupplierDTOs = productSuppliers.stream()
.map(this::mapToProductSupplierDTO)
.collect(Collectors.toList());
//get ProductSupplier from database
try{
data = ProductSupplierDB.getProductSupplierDTO();
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"ProductSupplierController.displayProductSupplier",
e,
"Fetching product-supplier data for table display");
System.out.println("Error while fetching table data: " + e.getMessage());
}
//put data in the table
tvProductSuppliers.setItems(data);
Platform.runLater(() -> {
data.setAll(productSupplierDTOs);
tvProductSuppliers.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"ProductSupplierController.displayProductSupplier",
e,
"Fetching product-supplier data for table display");
});
}
}).start();
}
/**
@@ -130,22 +133,30 @@ public class ProductSupplierController {
* @param filter word to filter table
*/
private void displayFilteredProductSupplier(String filter){
data.clear();
try{
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
displayProductSupplier(); //If search bar is empty just display everything
}
else{
//Filter the using the keyword
data = ProductSupplierDB.getFilteredProductSupplierDTO(filter);
tvProductSuppliers.setItems(data);
}
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ProductSupplierController.displayFilteredProductSupplier",
e,
"Filtering product-supplier data with filter: " + filter);
System.out.println("Error while fetching table data: " + e.getMessage());
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
displayProductSupplier();
} else {
new Thread(() -> {
try {
List<ProductSupplierResponse> productSuppliers = ProductSupplierApi.getInstance().listProductSuppliers(filter);
List<ProductSupplierDTO> productSupplierDTOs = productSuppliers.stream()
.map(this::mapToProductSupplierDTO)
.collect(Collectors.toList());
Platform.runLater(() -> {
data.setAll(productSupplierDTOs);
tvProductSuppliers.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"ProductSupplierController.displayFilteredProductSupplier",
e,
"Filtering product-supplier data with filter: " + filter);
});
}
}).start();
}
}
@@ -181,52 +192,24 @@ public class ProductSupplierController {
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0;
int failCount = 0;
StringBuilder errors = new StringBuilder();
List<Long> ids = selectedProductSuppliers.stream()
.map(ps -> (long) ps.getSupId())
.collect(Collectors.toList());
for (ProductSupplierDTO productSupplier : selectedProductSuppliers) {
try{
int numRows = ProductSupplierDB.deleteProductSupplier(productSupplier.getSupId(), productSupplier.getProdId());
if (numRows > 0) {
successCount++;
} else {
failCount++;
}
}
catch (SQLIntegrityConstraintViolationException e){
ActivityLogger.getInstance().logException(
"ProductSupplierController.btnDeleteClicked",
e,
String.format("Attempting to delete product-supplier - SupID: %d, ProdID: %d - foreign key constraint",
productSupplier.getSupId(), productSupplier.getProdId()));
failCount++;
errors.append(String.format("Product-Supplier '%s - %s' is referenced in another table\n",
productSupplier.getProdName(), productSupplier.getSupCompany()));
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"ProductSupplierController.btnDeleteClicked",
e,
String.format("Attempting to delete product-supplier - SupID: %d, ProdID: %d",
productSupplier.getSupId(), productSupplier.getProdId()));
failCount++;
errors.append(String.format("Failed to delete '%s - %s'\n",
productSupplier.getProdName(), productSupplier.getSupCompany()));
}
}
//show results
if (failCount > 0) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText("Delete Operation Completed with Errors");
alert.setContentText(String.format("Deleted: %d\nFailed: %d\n\n%s",
successCount, failCount, errors.toString()));
alert.showAndWait();
} else if (successCount > 0) {
try {
ProductSupplierApi.getInstance().deleteProductSuppliers(ids);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed");
alert.setContentText("Successfully deleted " + successCount + " product-supplier(s)");
alert.setContentText("Successfully deleted " + ids.size() + " product-supplier(s)");
alert.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ProductSupplierController.btnDeleteClicked",
e,
"Deleting product-suppliers");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Delete Operation Failed");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
@@ -297,4 +280,14 @@ public class ProductSupplierController {
txtSearch.setText("");
}
private ProductSupplierDTO mapToProductSupplierDTO(ProductSupplierResponse response) {
return new ProductSupplierDTO(
response.getSupplierId().intValue(),
response.getProductId().intValue(),
response.getSupplierName(),
response.getProductName(),
response.getSupplierPrice().doubleValue()
);
}
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
@@ -7,9 +8,13 @@ import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import org.example.petshopdesktop.DTOs.PurchaseOrderDTO;
import org.example.petshopdesktop.database.PurchaseOrderDB;
import org.example.petshopdesktop.api.dto.purchaseorder.PurchaseOrderResponse;
import org.example.petshopdesktop.api.endpoints.PurchaseOrderApi;
import org.example.petshopdesktop.util.ActivityLogger;
import java.util.List;
import java.util.stream.Collectors;
public class PurchaseOrderController {
@FXML private Button btnRefresh;
@@ -19,7 +24,7 @@ public class PurchaseOrderController {
@FXML private TableView<PurchaseOrderDTO> tvPurchaseOrders;
@FXML private TableColumn<PurchaseOrderDTO,Integer> colOrderId;
@FXML private TableColumn<PurchaseOrderDTO,Long> colOrderId;
@FXML private TableColumn<PurchaseOrderDTO,String> colSupplier;
@FXML private TableColumn<PurchaseOrderDTO,String> colOrderDate;
@FXML private TableColumn<PurchaseOrderDTO,String> colStatus;
@@ -53,17 +58,28 @@ public class PurchaseOrderController {
}
private void loadPurchaseOrders() {
try {
purchaseOrders.setAll(PurchaseOrderDB.getPurchaseOrders());
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"PurchaseOrderController.loadPurchaseOrders",
e,
"Loading purchase orders for table display");
e.printStackTrace();
new Alert(Alert.AlertType.ERROR,
"Unable to load purchase orders").showAndWait();
}
new Thread(() -> {
try {
List<PurchaseOrderResponse> responses = PurchaseOrderApi.getInstance().listPurchaseOrders(null);
List<PurchaseOrderDTO> dtos = responses.stream()
.map(this::mapToPurchaseOrderDTO)
.collect(Collectors.toList());
Platform.runLater(() -> {
purchaseOrders.setAll(dtos);
tvPurchaseOrders.setItems(filtered);
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"PurchaseOrderController.loadPurchaseOrders",
e,
"Loading purchase orders for table display");
new Alert(Alert.AlertType.ERROR,
"Unable to load purchase orders").showAndWait();
});
}
}).start();
}
private void applyFilter(String text) {
@@ -93,4 +109,13 @@ public class PurchaseOrderController {
void btnRefresh() {
loadPurchaseOrders();
}
private PurchaseOrderDTO mapToPurchaseOrderDTO(PurchaseOrderResponse response) {
return new PurchaseOrderDTO(
response.getId(),
response.getSupplierName(),
response.getOrderDate() != null ? response.getOrderDate().toString() : "",
response.getOrderStatus()
);
}
}

View File

@@ -22,20 +22,25 @@ import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.database.InventoryDB;
import org.example.petshopdesktop.database.ProductDB;
import org.example.petshopdesktop.database.SaleDB;
import org.example.petshopdesktop.models.Inventory;
import javafx.concurrent.Task;
import org.example.petshopdesktop.api.endpoints.ProductApi;
import org.example.petshopdesktop.api.endpoints.SaleApi;
import org.example.petshopdesktop.api.dto.product.ProductResponse;
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
import org.example.petshopdesktop.api.dto.sale.SaleRequest;
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.models.SaleCartItem;
import org.example.petshopdesktop.models.SaleLineItem;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.HashMap;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class SaleController {
@@ -124,8 +129,8 @@ public class SaleController {
private final ObservableList<SaleLineItem> saleItems = FXCollections.observableArrayList();
private FilteredList<SaleLineItem> filteredSales;
private final Map<Integer, Integer> inventoryByProdId = new HashMap<>();
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@FXML
public void initialize() {
@@ -133,7 +138,6 @@ public class SaleController {
setupCreateSale();
applyRoleMode();
refreshInventory();
refreshSales();
}
@@ -170,11 +174,20 @@ public class SaleController {
updateCartTotal();
try {
cbProduct.setItems(ProductDB.getProducts());
} catch (SQLException e) {
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
ObservableList<Product> products = FXCollections.observableArrayList();
for (ProductResponse pr : productResponses) {
products.add(new Product(
pr.getId().intValue(),
pr.getProductName(),
pr.getPrice().doubleValue(),
0,
pr.getDescription()
));
}
cbProduct.setItems(products);
} catch (Exception e) {
ActivityLogger.getInstance().logException("SaleController.setupCreateSale", e, "Loading products");
} catch (RuntimeException e) {
ActivityLogger.getInstance().logException("SaleController.setupCreateSale", e, "Database connection");
}
}
@@ -185,42 +198,59 @@ public class SaleController {
lblModeNote.setText(isAdmin ? "(View only)" : "(Staff can create sales)");
}
private void refreshInventory() {
inventoryByProdId.clear();
try {
for (Inventory inv : InventoryDB.getInventory()) {
inventoryByProdId.put(inv.getProdId(), inv.getQuantity());
}
} catch (SQLException e) {
ActivityLogger.getInstance().logException("SaleController.refreshInventory", e, "Loading inventory");
} catch (RuntimeException e) {
ActivityLogger.getInstance().logException("SaleController.refreshInventory", e, "Database connection");
}
}
private void refreshSales() {
refreshSales(false);
}
private void refreshSales(boolean showErrorDialog) {
try {
saleItems.setAll(SaleDB.getSaleLineItems());
} catch (SQLException e) {
Task<List<SaleLineItem>> task = new Task<List<SaleLineItem>>() {
@Override
protected List<SaleLineItem> call() throws Exception {
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, 1000, null);
List<SaleLineItem> lineItems = new ArrayList<>();
for (SaleResponse sale : sales) {
String saleDate = sale.getSaleDate() != null
? sale.getSaleDate().format(DATE_FORMATTER)
: "";
if (sale.getItems() != null && !sale.getItems().isEmpty()) {
for (SaleItemResponse item : sale.getItems()) {
lineItems.add(new SaleLineItem(
sale.getId().intValue(),
saleDate,
sale.getEmployeeName(),
item.getProductName(),
item.getQuantity(),
item.getUnitPrice().doubleValue(),
item.getLineTotal().doubleValue(),
sale.getPaymentMethod(),
sale.getIsRefund() != null && sale.getIsRefund()
));
}
}
}
return lineItems;
}
};
task.setOnSucceeded(event -> {
saleItems.setAll(task.getValue());
});
task.setOnFailed(event -> {
Throwable e = task.getException();
ActivityLogger.getInstance().logException("SaleController.refreshSales", e, "Loading sales");
if (showErrorDialog) {
showError("Sales", "Could not load sales.");
showError("Sales", "Could not load sales: " + e.getMessage());
}
} catch (RuntimeException e) {
ActivityLogger.getInstance().logException("SaleController.refreshSales", e, "Database connection");
if (showErrorDialog) {
showError("Sales", "Database is not connected.");
}
}
});
new Thread(task).start();
}
@FXML
void btnRefresh(ActionEvent event) {
refreshInventory();
refreshSales(true);
}
@@ -244,18 +274,6 @@ public class SaleController {
return;
}
int stock = inventoryByProdId.getOrDefault(product.getProdId(), 0);
int alreadyInCart = cartItems.stream()
.filter(i -> i.getProdId() == product.getProdId())
.mapToInt(SaleCartItem::getQuantity)
.sum();
int available = stock - alreadyInCart;
if (requestedQty > available) {
showError("Create Sale", "Not enough stock. Available: " + Math.max(0, available));
return;
}
for (SaleCartItem item : cartItems) {
if (item.getProdId() == product.getProdId()) {
item.setQuantity(item.getQuantity() + requestedQty);
@@ -291,9 +309,9 @@ public class SaleController {
return;
}
Integer employeeId = UserSession.getInstance().getEmployeeId();
if (employeeId == null || employeeId <= 0) {
showError("Create Sale", "Employee is not set for this account.");
Long storeId = UserSession.getInstance().getStoreId();
if (storeId == null || storeId <= 0) {
showError("Create Sale", "Store is not set for this account.");
return;
}
@@ -309,20 +327,35 @@ public class SaleController {
}
try {
int saleId = SaleDB.createSale(employeeId, payment, cartItems);
showInfo("Sale saved", "Sale ID " + saleId + " was created.");
SaleRequest request = new SaleRequest();
request.setStoreId(storeId);
request.setPaymentMethod(payment);
List<SaleItemRequest> itemRequests = new ArrayList<>();
for (SaleCartItem cartItem : cartItems) {
SaleItemRequest itemRequest = new SaleItemRequest();
itemRequest.setProductId((long) cartItem.getProdId());
itemRequest.setQuantity(cartItem.getQuantity());
itemRequest.setUnitPrice(BigDecimal.valueOf(cartItem.getUnitPrice()));
itemRequests.add(itemRequest);
}
request.setItems(itemRequests);
SaleResponse response = SaleApi.getInstance().createSale(request);
showInfo("Sale saved", "Sale ID " + response.getId() + " was created.");
cartItems.clear();
updateCartTotal();
refreshInventory();
refreshSales(true);
} catch (SQLException e) {
} catch (Exception e) {
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", e, "Creating sale");
showError("Create Sale", e.getMessage() == null ? "Could not save the sale." : e.getMessage());
} catch (RuntimeException e) {
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", e, "Database connection");
showError("Create Sale", "Database is not connected.");
String errorMsg = e.getMessage();
if (errorMsg != null && errorMsg.contains("Insufficient inventory")) {
showError("Create Sale", "Insufficient stock for one or more items.");
} else {
showError("Create Sale", errorMsg != null ? errorMsg : "Could not save the sale.");
}
}
}

View File

@@ -1,8 +1,8 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
@@ -10,12 +10,17 @@ import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import org.example.petshopdesktop.database.ServiceDB;
import org.example.petshopdesktop.models.Service;
import org.example.petshopdesktop.DTOs.ServiceDTO;
import org.example.petshopdesktop.api.dto.service.ServiceResponse;
import org.example.petshopdesktop.api.endpoints.ServiceApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.ServiceDialogController;
import org.example.petshopdesktop.util.ActivityLogger;
import javafx.stage.Modality;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class ServiceController {
@@ -23,22 +28,23 @@ public class ServiceController {
@FXML private Button btnDelete;
@FXML private Button btnEdit;
@FXML private TableColumn<Service, Integer> colServiceId;
@FXML private TableColumn<Service, String> colServiceName;
@FXML private TableColumn<Service, String> colServiceDesc;
@FXML private TableColumn<Service, Integer> colServiceDuration;
@FXML private TableColumn<Service, Double> colServicePrice;
@FXML private TableColumn<ServiceDTO, Integer> colServiceId;
@FXML private TableColumn<ServiceDTO, String> colServiceName;
@FXML private TableColumn<ServiceDTO, String> colServiceDesc;
@FXML private TableColumn<ServiceDTO, Integer> colServiceDuration;
@FXML private TableColumn<ServiceDTO, Double> colServicePrice;
@FXML private TableView<Service> tvServices;
@FXML private TableView<ServiceDTO> tvServices;
@FXML private TextField txtSearch;
private final ObservableList<Service> services = FXCollections.observableArrayList();
private FilteredList<Service> filtered;
private ObservableList<ServiceDTO> data = FXCollections.observableArrayList();
private String mode = null;
@FXML
public void initialize() {
//Enable multiple selection
btnEdit.setDisable(true);
btnDelete.setDisable(true);
tvServices.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
colServiceId.setCellValueFactory(new PropertyValueFactory<>("serviceId"));
@@ -47,14 +53,19 @@ public class ServiceController {
colServiceDuration.setCellValueFactory(new PropertyValueFactory<>("serviceDuration"));
colServicePrice.setCellValueFactory(new PropertyValueFactory<>("servicePrice"));
filtered = new FilteredList<>(services, s -> true);
tvServices.setItems(filtered);
displayServices();
if (txtSearch != null) {
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
}
tvServices.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
btnEdit.setDisable(false);
btnDelete.setDisable(false);
}
);
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
displayFilteredServices(newValue);
});
//EventListener for DELETE key
tvServices.setOnKeyPressed(event -> {
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
if (tvServices.getSelectionModel().getSelectedItem() != null) {
@@ -62,75 +73,82 @@ public class ServiceController {
}
}
});
loadServices();
}
private void loadServices() {
try {
services.setAll(ServiceDB.getServices());
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ServiceController.loadServices",
e,
"Loading services for table display");
showAlert("Database Error", "Unable to load services.");
e.printStackTrace();
}
private void displayServices() {
new Thread(() -> {
try {
List<ServiceResponse> services = ServiceApi.getInstance().listServices(null);
List<ServiceDTO> serviceDTOs = services.stream()
.map(this::mapToServiceDTO)
.collect(Collectors.toList());
Platform.runLater(() -> {
data.setAll(serviceDTOs);
tvServices.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"ServiceController.displayServices",
e,
"Fetching service data for table display");
});
}
}).start();
}
private void applyFilter(String text) {
if (filtered == null) {
return;
private void displayFilteredServices(String filter) {
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
displayServices();
} else {
new Thread(() -> {
try {
List<ServiceResponse> services = ServiceApi.getInstance().listServices(filter);
List<ServiceDTO> serviceDTOs = services.stream()
.map(this::mapToServiceDTO)
.collect(Collectors.toList());
Platform.runLater(() -> {
data.setAll(serviceDTOs);
tvServices.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"ServiceController.displayFilteredServices",
e,
String.format("Filtering services with keyword: %s", filter));
});
}
}).start();
}
String q = text == null ? "" : text.trim().toLowerCase();
if (q.isEmpty()) {
filtered.setPredicate(s -> true);
return;
}
filtered.setPredicate(s ->
String.valueOf(s.getServiceId()).contains(q)
|| safe(s.getServiceName()).contains(q)
|| safe(s.getServiceDesc()).contains(q)
|| String.valueOf(s.getServiceDuration()).contains(q)
|| String.valueOf(s.getServicePrice()).contains(q)
);
}
private static String safe(String v) {
return v == null ? "" : v.toLowerCase();
}
@FXML
void btnAddClicked(ActionEvent event) {
openDialog(null, "Add");
loadServices();
mode = "Add";
openDialog(null, mode);
}
@FXML
void btnEditClicked(ActionEvent event) {
ServiceDTO selected = tvServices.getSelectionModel().getSelectedItem();
Service selected = tvServices.getSelectionModel().getSelectedItem();
if (selected == null) {
showAlert("Select Service", "Please select a service to edit.");
return;
if (selected != null) {
mode = "Edit";
openDialog(selected, mode);
}
openDialog(selected, "Edit");
loadServices();
}
@FXML
void btnDeleteClicked(ActionEvent e) {
//get selected services
void btnDeleteClicked(ActionEvent event) {
var selectedServices = tvServices.getSelectionModel().getSelectedItems();
if (selectedServices.isEmpty()) return;
//ask user to confirm
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
question.setHeaderText("Please confirm delete");
String message = selectedServices.size() == 1
@@ -138,82 +156,78 @@ public class ServiceController {
: "Are you sure you want to delete " + selectedServices.size() + " services?";
question.setContentText(message);
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
java.util.Optional<ButtonType> result = question.showAndWait();
Optional<ButtonType> result = question.showAndWait();
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0;
int failCount = 0;
StringBuilder errors = new StringBuilder();
List<Long> ids = selectedServices.stream()
.map(s -> (long) s.getServiceId())
.collect(Collectors.toList());
for (Service service : selectedServices) {
try {
ServiceDB.deleteService(service.getServiceId());
successCount++;
} catch (Exception ex) {
ActivityLogger.getInstance().logException(
"ServiceController.btnDeleteClicked",
ex,
String.format("Attempting to delete service ID %d", service.getServiceId()));
failCount++;
errors.append("Failed to delete '").append(service.getServiceName()).append("'\n");
}
}
//show results
if (failCount > 0) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText("Delete Operation Completed with Errors");
alert.setContentText(String.format("Deleted: %d\nFailed: %d\n\n%s",
successCount, failCount, errors.toString()));
alert.showAndWait();
} else if (successCount > 0) {
try {
ServiceApi.getInstance().deleteServices(ids);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed");
alert.setContentText("Successfully deleted " + successCount + " service(s)");
alert.setContentText("Successfully deleted " + ids.size() + " service(s)");
alert.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ServiceController.btnDeleteClicked",
e,
"Deleting services");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Delete Operation Failed");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
//refresh display
loadServices();
displayServices();
btnDelete.setDisable(true);
btnEdit.setDisable(true);
txtSearch.setText("");
}
}
private void openDialog(Service service, String mode) {
private void openDialog(ServiceDTO service, String mode) {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml"));
Scene scene = null;
try {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml")
);
Stage stage = new Stage();
stage.setScene(new Scene(loader.load()));
ServiceDialogController controller = loader.getController();
controller.setMode(mode);
if (mode.equals("Edit")) {
controller.setService(service);
}
stage.initModality(Modality.APPLICATION_MODAL);
stage.showAndWait();
loadServices();
scene = new Scene(fxmlLoader.load());
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ServiceController.openDialog",
e,
"Opening service dialog in " + mode + " mode");
e.printStackTrace();
String.format("Loading service dialog view in %s mode", mode));
throw new RuntimeException(e);
}
ServiceDialogController dialogController = fxmlLoader.getController();
dialogController.setMode(mode);
if (mode.equals("Edit")) {
dialogController.setService(service);
}
Stage dialogStage = new Stage();
dialogStage.initModality(Modality.APPLICATION_MODAL);
if (mode.equals("Add")) {
dialogStage.setTitle("Add Service");
} else {
dialogStage.setTitle("Edit Service");
}
dialogStage.setScene(scene);
dialogStage.showAndWait();
displayServices();
btnDelete.setDisable(true);
btnEdit.setDisable(true);
txtSearch.setText("");
}
private void showAlert(String title, String msg) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(msg);
alert.showAndWait();
private ServiceDTO mapToServiceDTO(ServiceResponse response) {
return new ServiceDTO(
response.getId().intValue(),
response.getServiceName(),
response.getDescription(),
0,
response.getPrice().doubleValue()
);
}
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
@@ -15,12 +16,16 @@ import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.user.UserResponse;
import org.example.petshopdesktop.api.endpoints.UserApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.database.UserDB;
import org.example.petshopdesktop.models.StaffAccount;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.ZoneId;
import java.util.List;
import java.util.stream.Collectors;
public class StaffAccountsController {
@@ -107,15 +112,54 @@ public class StaffAccountsController {
private void refresh() {
lblError.setText("");
try {
staffAccounts.setAll(UserDB.getStaffAccounts());
} catch (SQLException e) {
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading staff accounts");
lblError.setText("Could not load staff accounts.");
} catch (RuntimeException e) {
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Database connection");
lblError.setText("Database is not connected.");
tvStaff.setDisable(true);
new Thread(() -> {
try {
List<UserResponse> users = UserApi.getInstance().listUsers(null);
List<StaffAccount> accounts = users.stream()
.map(this::mapToStaffAccount)
.collect(Collectors.toList());
Platform.runLater(() -> {
staffAccounts.setAll(accounts);
tvStaff.setDisable(false);
});
} catch (Exception e) {
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading staff accounts");
Platform.runLater(() -> {
lblError.setText("Could not load staff accounts.");
tvStaff.setDisable(false);
});
}
}).start();
}
private StaffAccount mapToStaffAccount(UserResponse user) {
long id = user.getId() != null ? user.getId() : 0L;
String username = user.getUsername();
String fullName = user.getFullName() != null ? user.getFullName() : "";
String[] names = splitFullName(fullName);
String firstName = names[0];
String lastName = names[1];
String email = "";
String phone = "";
boolean active = user.getActive() != null ? user.getActive() : false;
Timestamp createdAt = user.getCreatedAt() != null
? Timestamp.from(user.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())
: null;
return new StaffAccount(id, id, username, firstName, lastName, email, phone, active, createdAt);
}
private String[] splitFullName(String fullName) {
if (fullName == null || fullName.trim().isEmpty()) {
return new String[]{"", ""};
}
String[] parts = fullName.trim().split("\\s+", 2);
String firstName = parts.length > 0 ? parts[0] : "";
String lastName = parts.length > 1 ? parts[1] : "";
return new String[]{firstName, lastName};
}
private void applyFilter(String text) {

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
@@ -10,15 +11,16 @@ import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.supplier.SupplierResponse;
import org.example.petshopdesktop.api.endpoints.SupplierApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.SupplierDialogController;
import org.example.petshopdesktop.database.SupplierDB;
import org.example.petshopdesktop.models.Supplier;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* The controller for any operations in the supplier view
@@ -105,19 +107,27 @@ public class SupplierController {
* Display the suppliers to table view
*/
private void displaySupplier(){
data.clear();
new Thread(() -> {
try {
List<SupplierResponse> suppliers = SupplierApi.getInstance().listSuppliers(null);
List<Supplier> supplierList = suppliers.stream()
.map(this::mapToSupplier)
.collect(Collectors.toList());
try{
data = SupplierDB.getSuppliers();
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"SupplierController.displaySupplier",
e,
"Fetching supplier data for table display");
System.out.println("Error while fetching table data: " + e.getMessage());
}
tvSuppliers.setItems(data);
Platform.runLater(() -> {
data.setAll(supplierList);
tvSuppliers.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"SupplierController.displaySupplier",
e,
"Fetching supplier data for table display");
});
}
}).start();
}
/**
@@ -125,22 +135,30 @@ public class SupplierController {
* @param filter word to filter table
*/
private void displayFilteredSupplier(String filter){
data.clear();
try{
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
displaySupplier(); //If search bar is empty just display everything
}
else{
//Filter the using the keyword
data = SupplierDB.getFilteredSuppliers(filter);
tvSuppliers.setItems(data);
}
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"SupplierController.displayFilteredSupplier",
e,
"Filtering suppliers with filter: " + filter);
System.out.println("Error while fetching table data: " + e.getMessage());
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
displaySupplier();
} else {
new Thread(() -> {
try {
List<SupplierResponse> suppliers = SupplierApi.getInstance().listSuppliers(filter);
List<Supplier> supplierList = suppliers.stream()
.map(this::mapToSupplier)
.collect(Collectors.toList());
Platform.runLater(() -> {
data.setAll(supplierList);
tvSuppliers.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"SupplierController.displayFilteredSupplier",
e,
"Filtering suppliers with filter: " + filter);
});
}
}).start();
}
}
@@ -177,48 +195,24 @@ public class SupplierController {
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0;
int failCount = 0;
StringBuilder errors = new StringBuilder();
List<Long> ids = selectedSuppliers.stream()
.map(s -> (long) s.getSupId())
.collect(Collectors.toList());
for (Supplier supplier : selectedSuppliers) {
try{
int numRows = SupplierDB.deleteSupplier(supplier.getSupId());
if (numRows > 0) {
successCount++;
} else {
failCount++;
}
}
catch (SQLIntegrityConstraintViolationException e){
ActivityLogger.getInstance().logException(
"SupplierController.btnDeleteClicked",
e,
String.format("Attempting to delete supplier ID %d - foreign key constraint", supplier.getSupId()));
failCount++;
errors.append("Supplier '").append(supplier.getSupCompany()).append("' is referenced in another table\n");
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"SupplierController.btnDeleteClicked",
e,
String.format("Attempting to delete supplier ID %d", supplier.getSupId()));
failCount++;
errors.append("Failed to delete '").append(supplier.getSupCompany()).append("'\n");
}
}
//show results
if (failCount > 0) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText("Delete Operation Completed with Errors");
alert.setContentText(String.format("Deleted: %d\nFailed: %d\n\n%s",
successCount, failCount, errors.toString()));
alert.showAndWait();
} else if (successCount > 0) {
try {
SupplierApi.getInstance().deleteSuppliers(ids);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed");
alert.setContentText("Successfully deleted " + successCount + " supplier(s)");
alert.setContentText("Successfully deleted " + ids.size() + " supplier(s)");
alert.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"SupplierController.btnDeleteClicked",
e,
"Deleting suppliers");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Delete Operation Failed");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
@@ -290,4 +284,20 @@ public class SupplierController {
txtSearch.setText("");
}
private Supplier mapToSupplier(SupplierResponse response) {
String contactPerson = response.getContactPerson() != null ? response.getContactPerson() : "";
String[] nameParts = contactPerson.split(" ", 2);
String firstName = nameParts.length > 0 ? nameParts[0] : "";
String lastName = nameParts.length > 1 ? nameParts[1] : "";
return new Supplier(
response.getId().intValue(),
response.getSupplierName(),
firstName,
lastName,
response.getEmail(),
response.getPhone()
);
}
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
@@ -12,16 +13,15 @@ import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import org.example.petshopdesktop.database.AdoptionDB;
import org.example.petshopdesktop.database.PetDB;
import org.example.petshopdesktop.api.dto.adoption.AdoptionRequest;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.endpoints.AdoptionApi;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.models.Adoption;
import org.example.petshopdesktop.models.Customer;
import org.example.petshopdesktop.models.Pet;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.List;
public class AdoptionDialogController {
@@ -36,10 +36,10 @@ public class AdoptionDialogController {
private ComboBox<String> cbAdoptionStatus;
@FXML
private ComboBox<Customer> cbCustomer;
private ComboBox<DropdownOption> cbCustomer;
@FXML
private ComboBox<Pet> cbPet;
private ComboBox<DropdownOption> cbPet;
@FXML
private DatePicker dpAdoptionDate;
@@ -58,52 +58,47 @@ public class AdoptionDialogController {
"Pending", "Completed", "Cancelled"
);
//Loads upon boot
@FXML
void initialize() {
//Loads statusList into combo box
cbAdoptionStatus.setItems(statusList);
//Pet objects are converted into readable text for combobox (PetID + PetName)
cbPet.setConverter(new StringConverter<Pet>() {
@Override
public String toString(Pet pet) {
return pet == null ? "" : pet.getPetId() + ": " + pet.getPetName();
new Thread(() -> {
try {
List<DropdownOption> pets = DropdownApi.getInstance().getPets();
Platform.runLater(() -> {
ObservableList<DropdownOption> petsObs = FXCollections.observableArrayList(pets);
cbPet.setItems(petsObs);
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.initialize",
e,
"Loading pets for combo box");
System.out.println("Error loading pets: " + e.getMessage());
});
}
}).start();
//Not used
@Override
public Pet fromString(String string) { return null; }
});
new Thread(() -> {
try {
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
Platform.runLater(() -> {
ObservableList<DropdownOption> customersObs = FXCollections.observableArrayList(customers);
cbCustomer.setItems(customersObs);
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.initialize",
e,
"Loading customers for combo box");
System.out.println("Error loading customers: " + e.getMessage());
});
}
}).start();
//Load pets from DB into pet combobox
try {
cbPet.setItems(PetDB.getPets());
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.initialize",
e,
"Loading pets for combo box");
System.out.println("Error loading pets: " + e.getMessage());
}
//Load customers from DB into customer combobox
try {
cbCustomer.setItems(AdoptionDB.getCustomers());
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.initialize",
e,
"Loading customers for combo box");
System.out.println("Error loading customers: " + e.getMessage());
}
//Save button handler
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
@@ -111,7 +106,6 @@ public class AdoptionDialogController {
}
});
//Cancel button handler, closes dialog view
btnCancel.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
@@ -120,12 +114,9 @@ public class AdoptionDialogController {
});
}
//Handles logic when clicking Save
private void buttonSaveClicked(MouseEvent mouseEvent) {
int numRow = 0;
String errorMsg = "";
//Validation: checks if anything is missing
if (cbPet.getSelectionModel().getSelectedItem() == null) {
errorMsg += "Pet is required.\n";
}
@@ -142,60 +133,37 @@ public class AdoptionDialogController {
errorMsg += "Status is required.\n";
}
//If no errors, attempt DB operation
if (errorMsg.isEmpty()) {
Adoption adoption = collectAdoption();
try {
AdoptionRequest request = new AdoptionRequest();
request.setPetId(cbPet.getSelectionModel().getSelectedItem().getId());
request.setCustomerId(cbCustomer.getSelectionModel().getSelectedItem().getId());
request.setAdoptionDate(dpAdoptionDate.getValue());
request.setAdoptionStatus(cbAdoptionStatus.getValue());
//Try inserting into DB
if (mode.equals("Add")) {
try {
numRow = AdoptionDB.insertAdoption(adoption);
if (mode.equals("Add")) {
AdoptionApi.getInstance().createAdoption(request);
} else {
Long adoptionId = Long.parseLong(lblAdoptionId.getText().split(": ")[1]);
AdoptionApi.getInstance().updateAdoption(adoptionId, request);
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.buttonSaveClicked",
e,
"Inserting new adoption record");
throw new RuntimeException(e);
}
}
//Try updating adoption
else {
try {
numRow = AdoptionDB.updateAdoption(adoption.getAdoptionId(), adoption);
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.buttonSaveClicked",
e,
"Updating adoption with ID: " + adoption.getAdoptionId());
throw new RuntimeException(e);
}
}
//If no rows are affected, an issue has occurred
if (numRow == 0) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(mode + " failed");
alert.showAndWait();
}
//DB operation worked!
else {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Saved");
alert.setContentText(mode + " succeeded");
alert.showAndWait();
closeStage(mouseEvent);
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.buttonSaveClicked",
e,
mode + " adoption");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
}
//If there are errors, display them
else {
} else {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Input Error");
alert.setContentText(errorMsg);
@@ -203,30 +171,6 @@ public class AdoptionDialogController {
}
}
//Collects user input, builds an Adoption object
private Adoption collectAdoption() {
int adoptionId = 0;
//Only grab adoption ID if in edit mode
if (lblAdoptionId.isVisible()) {
adoptionId = Integer.parseInt(lblAdoptionId.getText().split(": ")[1]);
}
Pet selectedPet = cbPet.getSelectionModel().getSelectedItem();
Customer selectedCustomer = cbCustomer.getSelectionModel().getSelectedItem();
String date = dpAdoptionDate.getValue().toString();
String status = cbAdoptionStatus.getValue();
return new Adoption(
adoptionId,
selectedPet.getPetId(),
selectedCustomer.getCustomerId(),
selectedCustomer.toString(),
date,
selectedPet.getPetPrice(),
status
);
}
private void closeStage(MouseEvent mouseEvent) {
Node node = (Node) mouseEvent.getSource();
@@ -234,35 +178,28 @@ public class AdoptionDialogController {
stage.close();
}
//Edit mode
//Inserts data into fields
public void displayAdoptionDetails(Adoption adoption) {
if (adoption != null) {
//Displays adoption ID
lblAdoptionId.setText("ID: " + adoption.getAdoptionId());
//Select pet
for (Pet pet : cbPet.getItems()) {
if (pet.getPetId() == adoption.getPetId()) {
for (DropdownOption pet : cbPet.getItems()) {
if (pet.getLabel().equals(adoption.getPetName())) {
cbPet.getSelectionModel().select(pet);
break;
}
}
//Select customer
for (Customer customer : cbCustomer.getItems()) {
if (customer.getCustomerId() == adoption.getCustomerId()) {
for (DropdownOption customer : cbCustomer.getItems()) {
if (customer.getLabel().equals(adoption.getCustomerName())) {
cbCustomer.getSelectionModel().select(customer);
break;
}
}
//Select adoption date
if (adoption.getAdoptionDate() != null && !adoption.getAdoptionDate().isEmpty()) {
dpAdoptionDate.setValue(LocalDate.parse(adoption.getAdoptionDate()));
}
//Select adoption status
for (String status : cbAdoptionStatus.getItems()) {
if (status.equals(adoption.getAdoptionStatus())) {
cbAdoptionStatus.getSelectionModel().select(status);
@@ -272,8 +209,6 @@ public class AdoptionDialogController {
}
}
//Sets dialog mode
//Also updates label and adoption ID visibility
public void setMode(String mode) {
this.mode = mode;
lblMode.setText(mode + " Adoption");

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
@@ -10,11 +11,16 @@ import javafx.stage.Stage;
import javafx.scene.control.ListCell;
import org.example.petshopdesktop.DTOs.AppointmentDTO;
import org.example.petshopdesktop.database.*;
import org.example.petshopdesktop.models.*;
import org.example.petshopdesktop.api.dto.appointment.AppointmentRequest;
import org.example.petshopdesktop.api.dto.appointment.AppointmentResponse;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.endpoints.AppointmentApi;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.Time;
import java.time.LocalTime;
import java.util.Collections;
import java.util.List;
public class AppointmentDialogController {
@@ -25,9 +31,9 @@ public class AppointmentDialogController {
@FXML private Button btnCancel;
@FXML private Button btnSave;
@FXML private ComboBox<Service> cbService;
@FXML private ComboBox<Customer> cbCustomer;
@FXML private ComboBox<Pet> cbPet;
@FXML private ComboBox<DropdownOption> cbService;
@FXML private ComboBox<DropdownOption> cbCustomer;
@FXML private ComboBox<DropdownOption> cbPet;
@FXML private ComboBox<Integer> cbHour;
@FXML private ComboBox<Integer> cbMinute;
@@ -67,17 +73,27 @@ public class AppointmentDialogController {
@FXML
public void initialize() {
try {
cbService.setItems(ServiceDB.getServices());
cbCustomer.setItems(CustomerDB.getCustomers());
cbPet.setItems(PetDB.getPets());
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.initialize",
e,
"Loading combo box data for services, customers, and pets");
e.printStackTrace();
}
new Thread(() -> {
try {
List<DropdownOption> services = DropdownApi.getInstance().getServices();
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
List<DropdownOption> pets = DropdownApi.getInstance().getPets();
Platform.runLater(() -> {
cbService.setItems(FXCollections.observableArrayList(services));
cbCustomer.setItems(FXCollections.observableArrayList(customers));
cbPet.setItems(FXCollections.observableArrayList(pets));
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.initialize",
e,
"Loading combo box data for services, customers, and pets");
e.printStackTrace();
});
}
}).start();
cbAppointmentStatus.setItems(statusList);
@@ -88,20 +104,49 @@ public class AppointmentDialogController {
cbMinute.getItems().addAll(0, 15, 30, 45);
// Show pet name
cbPet.setCellFactory(param -> new ListCell<>() {
// Show dropdown labels
cbService.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(Pet pet, boolean empty) {
super.updateItem(pet, empty);
setText(empty || pet == null ? null : pet.getPetName());
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbService.setButtonCell(new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbCustomer.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbCustomer.setButtonCell(new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbPet.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbPet.setButtonCell(new ListCell<>() {
@Override
protected void updateItem(Pet pet, boolean empty) {
super.updateItem(pet, empty);
setText(empty || pet == null ? null : pet.getPetName());
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
@@ -124,20 +169,20 @@ public class AppointmentDialogController {
cbAppointmentStatus.setValue(appt.getAppointmentStatus());
Time time = Time.valueOf(appt.getAppointmentTime());
cbHour.setValue(time.toLocalTime().getHour());
cbMinute.setValue(time.toLocalTime().getMinute());
LocalTime time = LocalTime.parse(appt.getAppointmentTime());
cbHour.setValue(time.getHour());
cbMinute.setValue(time.getMinute());
cbService.getItems().forEach(s -> {
if (s.getServiceId() == appt.getServiceId()) cbService.setValue(s);
if (s.getId() == appt.getServiceId()) cbService.setValue(s);
});
cbCustomer.getItems().forEach(c -> {
if (c.getCustomerId() == appt.getCustomerId()) cbCustomer.setValue(c);
if (c.getId() == appt.getCustomerId()) cbCustomer.setValue(c);
});
cbPet.getItems().forEach(p -> {
if (p.getPetId() == appt.getPetId()) cbPet.setValue(p);
if (p.getId() == appt.getPetId()) cbPet.setValue(p);
});
}
@@ -159,45 +204,40 @@ public class AppointmentDialogController {
return;
}
Time appointmentTime =
Time.valueOf(String.format(
"%02d:%02d:00",
cbHour.getValue(),
cbMinute.getValue()
));
LocalTime appointmentTime = LocalTime.of(cbHour.getValue(), cbMinute.getValue());
Appointment appt = new Appointment(
selectedAppointment == null ? 0 : selectedAppointment.getAppointmentId(),
cbService.getValue().getServiceId(),
cbCustomer.getValue().getCustomerId(),
dpAppointmentDate.getValue().toString(),
appointmentTime.toString(),
cbAppointmentStatus.getValue()
);
AppointmentRequest request = new AppointmentRequest();
request.setPetIds(Collections.singletonList(cbPet.getValue().getId()));
request.setCustomerId(cbCustomer.getValue().getId());
request.setServiceId(cbService.getValue().getId());
request.setAppointmentDate(dpAppointmentDate.getValue());
request.setAppointmentTime(appointmentTime);
request.setAppointmentStatus(cbAppointmentStatus.getValue());
try {
new Thread(() -> {
try {
if (mode.equals("Add")) {
AppointmentApi.getInstance().createAppointment(request);
} else {
AppointmentApi.getInstance().updateAppointment(
(long) selectedAppointment.getAppointmentId(),
request
);
}
if (mode.equals("Add")) {
int newId = AppointmentDB.insertAppointment(appt);
AppointmentDB.insertAppointmentPet(newId, cbPet.getValue().getPetId());
} else {
AppointmentDB.updateAppointment(
selectedAppointment.getAppointmentId(),
appt,
cbPet.getValue().getPetId()
);
Platform.runLater(() -> closeStage(e));
} catch (Exception ex) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.buttonSaveClicked",
ex,
"Saving appointment in " + mode + " mode");
ex.printStackTrace();
showError("Error saving appointment: " + ex.getMessage());
});
}
closeStage(e);
} catch (Exception ex) {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.buttonSaveClicked",
ex,
"Saving appointment in " + mode + " mode");
ex.printStackTrace();
showError("Error saving appointment");
}
}).start();
}
//

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
@@ -10,16 +11,14 @@ import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.ProductSupplierDTO;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.database.ProductDB;
import org.example.petshopdesktop.database.ProductSupplierDB;
import org.example.petshopdesktop.database.SupplierDB;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.models.ProductSupplier;
import org.example.petshopdesktop.models.Supplier;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierRequest;
import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierResponse;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.api.endpoints.ProductSupplierApi;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.math.BigDecimal;
public class ProductSupplierDialogController {
@@ -30,10 +29,10 @@ public class ProductSupplierDialogController {
private Button btnSave;
@FXML
private ComboBox<Product> cbProduct;
private ComboBox<DropdownOption> cbProduct;
@FXML
private ComboBox<Supplier> cbSupplier;
private ComboBox<DropdownOption> cbSupplier;
@FXML
private Label lblMode;
@@ -47,6 +46,7 @@ public class ProductSupplierDialogController {
private String mode = null;
private int selectedSupId = -1;
private int selectedProdId = -1;
private Long selectedId = null;
/**
* add event listeners to buttons and set up combobox
@@ -67,26 +67,74 @@ public class ProductSupplierDialogController {
}
});
//Set up combobox for selecting product and supplier
try{
ObservableList<Supplier> suppliers = FXCollections.observableArrayList(); //empty list
ObservableList<Product> products = FXCollections.observableArrayList(); //empty list
cbSupplier.setButtonCell(new ListCell<DropdownOption>() {
@Override
protected void updateItem(DropdownOption item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
setText(item.getLabel());
}
}
});
cbSupplier.setCellFactory(lv -> new ListCell<DropdownOption>() {
@Override
protected void updateItem(DropdownOption item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
setText(item.getLabel());
}
}
});
//get suppliers and products from DB
suppliers = SupplierDB.getSuppliers();
products = ProductDB.getProducts();
cbProduct.setButtonCell(new ListCell<DropdownOption>() {
@Override
protected void updateItem(DropdownOption item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
setText(item.getLabel());
}
}
});
cbProduct.setCellFactory(lv -> new ListCell<DropdownOption>() {
@Override
protected void updateItem(DropdownOption item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
setText(item.getLabel());
}
}
});
//Populate combobox
cbSupplier.setItems(suppliers);
cbProduct.setItems(products);
}
catch(SQLException e){
ActivityLogger.getInstance().logException(
"ProductSupplierDialogController.initialize",
e,
"Loading suppliers and products for combo boxes");
throw new RuntimeException(e);
}
new Thread(() -> {
try {
var suppliers = DropdownApi.getInstance().getSuppliers();
var products = DropdownApi.getInstance().getProducts();
Platform.runLater(() -> {
cbSupplier.setItems(FXCollections.observableArrayList(suppliers));
cbProduct.setItems(FXCollections.observableArrayList(products));
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"ProductSupplierDialogController.initialize",
e,
"Loading suppliers and products for combo boxes");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Initialization Error");
alert.setContentText("Failed to load dropdown data: " + e.getMessage());
alert.showAndWait();
});
}
}).start();
}
@@ -96,10 +144,8 @@ public class ProductSupplierDialogController {
* @param mouseEvent click event for save button
*/
private void buttonSaveClicked(MouseEvent mouseEvent) {
int numRows = 0;
String errorMsg = ""; //error message for validation
String errorMsg = "";
//Check Validation (input required)
errorMsg += Validator.isPresent(txtCost.getText(), "Cost");
if (cbProduct.getSelectionModel().getSelectedItem() == null) {
errorMsg += "Product is required \n";
@@ -108,82 +154,41 @@ public class ProductSupplierDialogController {
errorMsg += "Supplier is required \n";
}
//Check validation (length size)
errorMsg += Validator.isLessThanVarChars(txtCost.getText(), "Cost", 12);
//Check validation (format)
errorMsg += Validator.isNonNegativeDouble(txtCost.getText(), "Cost");
if(errorMsg.isEmpty()){ //no validation errors
ProductSupplier productSupplier = collectProductSupplier(); //get productSupplier info
if (mode.equals("Add")) { //add mode
try{
numRows = ProductSupplierDB.insertProductSupplier(productSupplier);
}
catch(SQLIntegrityConstraintViolationException e){
ActivityLogger.getInstance().logException(
"ProductSupplierDialogController.buttonSaveClicked",
e,
"Inserting product-supplier (integrity constraint violation)");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText("Add failed \n" +
"the product-supplier link is already in the database");
alert.showAndWait();
numRows = -1; //Update numRow so alert only shows once
closeStage(mouseEvent);
}
catch(SQLException e){
ActivityLogger.getInstance().logException(
"ProductSupplierDialogController.buttonSaveClicked",
e,
"Inserting new product-supplier record");
throw new RuntimeException(e);
}
}
else { //edit
try{
numRows = ProductSupplierDB.updateProductSupplier(selectedSupId, selectedProdId, productSupplier);
}
catch(SQLIntegrityConstraintViolationException e){
ActivityLogger.getInstance().logException(
"ProductSupplierDialogController.buttonSaveClicked",
e,
"Updating product-supplier (integrity constraint violation) - SupID: " + selectedSupId + ", ProdID: " + selectedProdId);
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText("Edit failed \n" +
"the product-supplier link is already in the database");
alert.showAndWait();
numRows = -1; //Update numRow so alert only shows once
closeStage(mouseEvent);
}
catch(SQLException e){
ActivityLogger.getInstance().logException(
"ProductSupplierDialogController.buttonSaveClicked",
e,
"Updating product-supplier - SupID: " + selectedSupId + ", ProdID: " + selectedProdId);
throw new RuntimeException(e);
}
}
if(errorMsg.isEmpty()){
ProductSupplierRequest request = collectProductSupplierRequest();
//if no rows were affected then there was an error (prompt user of error)
if (numRows == 0){
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(mode + " failed");
alert.showAndWait();
}
else if (numRows > 0){
//tell the user operation was successful
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Saved");
alert.setContentText(mode + " succeeded");
alert.showAndWait();
closeStage(mouseEvent);
}
}
else { //Display validation errors
new Thread(() -> {
try {
if (mode.equals("Add")) {
ProductSupplierApi.getInstance().createProductSupplier(request);
} else {
ProductSupplierApi.getInstance().updateProductSupplier(selectedId, request);
}
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Saved");
alert.setContentText(mode + " succeeded");
alert.showAndWait();
closeStage(mouseEvent);
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"ProductSupplierDialogController.buttonSaveClicked",
e,
mode + " product-supplier");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(mode + " failed: " + e.getMessage());
alert.showAndWait();
});
}
}).start();
} else {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Input Error");
alert.setContentText(errorMsg);
@@ -193,18 +198,14 @@ public class ProductSupplierDialogController {
/**
* collect the data for new/updated productSupplier
* @return productSupplier entity with data
* @return productSupplier request with data
*/
private ProductSupplier collectProductSupplier() {
ProductSupplier productSupplier = null;
productSupplier = new ProductSupplier(
cbSupplier.getSelectionModel().getSelectedItem().getSupId(),
cbProduct.getSelectionModel().getSelectedItem().getProdId(),
Double.parseDouble(txtCost.getText())
);
return productSupplier;
private ProductSupplierRequest collectProductSupplierRequest() {
ProductSupplierRequest request = new ProductSupplierRequest();
request.setSupplierId(cbSupplier.getSelectionModel().getSelectedItem().getId());
request.setProductId(cbProduct.getSelectionModel().getSelectedItem().getId());
request.setSupplierPrice(new BigDecimal(txtCost.getText()));
return request;
}
/**
@@ -216,20 +217,17 @@ public class ProductSupplierDialogController {
txtCost.setText(productSupplier.getCost() + "");
}
//Get the right combobox selection (product)
for (Product product : cbProduct.getItems()) {
if(product.getProdId() == productSupplier.getProdId()){
for (DropdownOption product : cbProduct.getItems()) {
if(product.getId() == productSupplier.getProdId()){
cbProduct.getSelectionModel().select(product);
}
}
//Get the right combobox selection (supplier)
for (Supplier supplier : cbSupplier.getItems()) {
if (supplier.getSupId() == productSupplier.getSupId()) {
for (DropdownOption supplier : cbSupplier.getItems()) {
if (supplier.getId() == productSupplier.getSupId()) {
cbSupplier.getSelectionModel().select(supplier);
}
}
}
/**
@@ -260,6 +258,7 @@ public class ProductSupplierDialogController {
public void setSelectedIds(int supId, int prodId){
this.selectedSupId = supId;
this.selectedProdId = prodId;
this.selectedId = (long) supId;
}
}

View File

@@ -7,15 +7,19 @@ import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
import org.example.petshopdesktop.api.dto.sale.SaleRequest;
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
import org.example.petshopdesktop.api.endpoints.SaleApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.database.SaleDB;
import org.example.petshopdesktop.models.SaleCartItem;
import org.example.petshopdesktop.models.SaleDetail;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
@@ -31,19 +35,19 @@ public class RefundDialogController {
private Label lblSaleInfo;
@FXML
private TableView<SaleDetail.SaleDetailItem> tvOriginalItems;
private TableView<SaleItemResponse> tvOriginalItems;
@FXML
private TableColumn<SaleDetail.SaleDetailItem, String> colOriginalProduct;
private TableColumn<SaleItemResponse, String> colOriginalProduct;
@FXML
private TableColumn<SaleDetail.SaleDetailItem, Integer> colOriginalQuantity;
private TableColumn<SaleItemResponse, Integer> colOriginalQuantity;
@FXML
private TableColumn<SaleDetail.SaleDetailItem, Double> colOriginalUnitPrice;
private TableColumn<SaleItemResponse, BigDecimal> colOriginalUnitPrice;
@FXML
private TableColumn<SaleDetail.SaleDetailItem, Double> colOriginalTotal;
private TableColumn<SaleItemResponse, BigDecimal> colOriginalTotal;
@FXML
private Button btnAddToRefund;
@@ -78,7 +82,7 @@ public class RefundDialogController {
@FXML
private Button btnCancel;
private SaleDetail currentSale;
private SaleResponse currentSale;
private final ObservableList<RefundItem> refundItems = FXCollections.observableArrayList();
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
@@ -94,7 +98,7 @@ public class RefundDialogController {
colOriginalProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
colOriginalQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
colOriginalUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
colOriginalTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
colOriginalTotal.setCellValueFactory(new PropertyValueFactory<>("lineTotal"));
tvOriginalItems.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
colRefundProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
@@ -113,21 +117,25 @@ public class RefundDialogController {
return;
}
int saleId;
Long saleId;
try {
saleId = Integer.parseInt(saleIdText);
saleId = Long.parseLong(saleIdText);
} catch (NumberFormatException e) {
showError("Load Sale", "Invalid transaction ID.");
return;
}
try {
if (SaleDB.isRefunded(saleId)) {
List<SaleResponse> allSales = SaleApi.getInstance().listSales(0, 1000, null);
boolean alreadyRefunded = allSales.stream()
.anyMatch(s -> Boolean.TRUE.equals(s.getIsRefund()) && saleId.equals(s.getOriginalSaleId()));
if (alreadyRefunded) {
showError("Load Sale", "This sale has already been refunded.");
return;
}
currentSale = SaleDB.getSaleById(saleId);
currentSale = SaleApi.getInstance().getSale(saleId);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
String saleInfo = String.format("Sale Date: %s | Employee: %s | Original Total: %s | Payment: %s",
@@ -137,13 +145,13 @@ public class RefundDialogController {
currentSale.getPaymentMethod());
lblSaleInfo.setText(saleInfo);
tvOriginalItems.setItems(currentSale.getItems());
tvOriginalItems.setItems(FXCollections.observableArrayList(currentSale.getItems()));
cbPaymentMethod.getSelectionModel().select(currentSale.getPaymentMethod());
refundItems.clear();
updateRefundTotal();
} catch (SQLException e) {
} catch (Exception e) {
ActivityLogger.getInstance().logException("RefundDialogController.btnLoadSaleClicked", e, "Loading sale");
showError("Load Sale", e.getMessage() != null ? e.getMessage() : "Could not load sale.");
}
@@ -156,14 +164,14 @@ public class RefundDialogController {
return;
}
SaleDetail.SaleDetailItem selected = tvOriginalItems.getSelectionModel().getSelectedItem();
SaleItemResponse selected = tvOriginalItems.getSelectionModel().getSelectedItem();
if (selected == null) {
showError("Add to Refund", "Select an item from the original sale.");
return;
}
int alreadyRefunded = refundItems.stream()
.filter(r -> r.getProdId() == selected.getProdId())
.filter(r -> r.getProductId().equals(selected.getId()))
.mapToInt(RefundItem::getQuantity)
.sum();
@@ -192,7 +200,7 @@ public class RefundDialogController {
}
refundItems.add(new RefundItem(
selected.getProdId(),
selected.getId(),
selected.getProductName(),
quantity,
selected.getUnitPrice()
@@ -226,9 +234,9 @@ public class RefundDialogController {
return;
}
Integer employeeId = UserSession.getInstance().getEmployeeId();
if (employeeId == null || employeeId <= 0) {
showError("Process Refund", "Employee is not set for this account.");
Long storeId = UserSession.getInstance().getStoreId();
if (storeId == null || storeId <= 0) {
showError("Process Refund", "Store is not set for this account.");
return;
}
@@ -240,7 +248,7 @@ public class RefundDialogController {
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
confirm.setTitle("Confirm Refund");
confirm.setHeaderText("Process refund for sale ID " + currentSale.getSaleId() + "?");
confirm.setHeaderText("Process refund for sale ID " + currentSale.getId() + "?");
confirm.setContentText("Refund amount: " + lblRefundTotal.getText());
Optional<ButtonType> confirmResult = confirm.showAndWait();
@@ -249,22 +257,33 @@ public class RefundDialogController {
}
try {
ObservableList<SaleCartItem> cartItems = FXCollections.observableArrayList();
for (RefundItem item : refundItems) {
cartItems.add(new SaleCartItem(item.getProdId(), item.getProductName(), item.getQuantity(), item.getUnitPrice()));
}
SaleRequest request = new SaleRequest();
request.setStoreId(storeId);
request.setPaymentMethod(payment);
request.setIsRefund(true);
request.setOriginalSaleId(currentSale.getId());
int refundId = SaleDB.createRefund(currentSale.getSaleId(), employeeId, payment, cartItems);
List<SaleItemRequest> items = new ArrayList<>();
for (RefundItem item : refundItems) {
SaleItemRequest saleItem = new SaleItemRequest();
saleItem.setProductId(item.getProductId());
saleItem.setQuantity(-item.getQuantity());
saleItem.setUnitPrice(item.getUnitPrice());
items.add(saleItem);
}
request.setItems(items);
SaleResponse refundResponse = SaleApi.getInstance().createSale(request);
Alert success = new Alert(Alert.AlertType.INFORMATION);
success.setTitle("Refund Processed");
success.setHeaderText(null);
success.setContentText("Refund ID " + refundId + " was created successfully.");
success.setContentText("Refund ID " + refundResponse.getId() + " was created successfully.");
success.showAndWait();
closeDialog();
} catch (SQLException e) {
} catch (Exception e) {
ActivityLogger.getInstance().logException("RefundDialogController.btnProcessRefundClicked", e, "Processing refund");
showError("Process Refund", e.getMessage() != null ? e.getMessage() : "Could not process refund.");
}

View File

@@ -1,18 +1,19 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import org.example.petshopdesktop.database.ServiceDB;
import org.example.petshopdesktop.models.Service;
import org.example.petshopdesktop.DTOs.ServiceDTO;
import org.example.petshopdesktop.api.dto.service.ServiceRequest;
import org.example.petshopdesktop.api.endpoints.ServiceApi;
import org.example.petshopdesktop.util.ActivityLogger;
import javafx.scene.control.Alert;
import javafx.scene.control.ComboBox;
import java.math.BigDecimal;
public class ServiceDialogController {
@@ -45,7 +46,7 @@ public class ServiceDialogController {
private ComboBox<Integer> cbMinutes;
private String mode;
private Service selectedService;
private ServiceDTO selectedService;
@@ -68,7 +69,7 @@ public class ServiceDialogController {
}
}
public void setService(Service service) {
public void setService(ServiceDTO service) {
this.selectedService = service;
lblServiceId.setText("ID: " + service.getServiceId());

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
@@ -8,11 +9,10 @@ import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import org.example.petshopdesktop.database.UserDB;
import org.example.petshopdesktop.api.dto.user.UserRequest;
import org.example.petshopdesktop.api.endpoints.UserApi;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
public class StaffRegisterDialogController {
@FXML
@@ -48,8 +48,6 @@ public class StaffRegisterDialogController {
String firstName = value(txtFirstName);
String lastName = value(txtLastName);
String email = value(txtEmail);
String phone = value(txtPhone);
String username = value(txtUsername);
String password = txtPassword.getText() == null ? "" : txtPassword.getText();
String confirm = txtPasswordConfirm.getText() == null ? "" : txtPasswordConfirm.getText();
@@ -58,14 +56,6 @@ public class StaffRegisterDialogController {
lblError.setText("First name and last name are required.");
return;
}
if (email.isBlank()) {
lblError.setText("Email is required.");
return;
}
if (phone.isBlank()) {
lblError.setText("Phone is required.");
return;
}
if (username.isBlank()) {
lblError.setText("Username is required.");
return;
@@ -79,26 +69,41 @@ public class StaffRegisterDialogController {
return;
}
try {
UserDB.createStaffAccount(firstName, lastName, email, phone, username, password);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Staff Account");
alert.setHeaderText(null);
alert.setContentText("Staff account created. You can log in now.");
alert.showAndWait();
close();
} catch (SQLException e) {
ActivityLogger.getInstance().logException("StaffRegisterDialogController.btnCreateClicked", e, "Creating staff account");
String msg = e.getMessage() == null ? "Could not create staff account." : e.getMessage();
if (msg.toLowerCase().contains("duplicate") || msg.toLowerCase().contains("unique")) {
lblError.setText("Username already exists.");
} else {
lblError.setText(msg);
btnCreate.setDisable(true);
new Thread(() -> {
try {
UserRequest request = new UserRequest();
request.setUsername(username);
request.setPassword(password);
request.setFirstName(firstName);
request.setLastName(lastName);
request.setRole("STAFF");
request.setActive(true);
UserApi.getInstance().createUser(request);
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Staff Account");
alert.setHeaderText(null);
alert.setContentText("Staff account created. You can log in now.");
alert.showAndWait();
close();
});
} catch (Exception e) {
ActivityLogger.getInstance().logException("StaffRegisterDialogController.btnCreateClicked", e, "Creating staff account");
String msg = e.getMessage() == null ? "Could not create staff account." : e.getMessage();
Platform.runLater(() -> {
if (msg.toLowerCase().contains("duplicate") || msg.toLowerCase().contains("unique")) {
lblError.setText("Username already exists.");
} else {
lblError.setText(msg);
}
btnCreate.setDisable(false);
});
}
} catch (RuntimeException e) {
ActivityLogger.getInstance().logException("StaffRegisterDialogController.btnCreateClicked", e, "Database connection");
lblError.setText("Database is not connected.");
}
}).start();
}
@FXML

View File

@@ -10,12 +10,12 @@ import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.database.SupplierDB;
import org.example.petshopdesktop.api.dto.supplier.SupplierRequest;
import org.example.petshopdesktop.api.dto.supplier.SupplierResponse;
import org.example.petshopdesktop.api.endpoints.SupplierApi;
import org.example.petshopdesktop.models.Supplier;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
public class SupplierDialogController {
@FXML
@@ -74,7 +74,6 @@ public class SupplierDialogController {
* @param mouseEvent click event for save button
*/
private void buttonSaveClicked(MouseEvent mouseEvent) {
int numRow = 0; //how many rows affected
String errorMsg = ""; //error message for validation
//Check validation (input required)
@@ -95,44 +94,29 @@ public class SupplierDialogController {
errorMsg += Validator.isValidPhoneNumber(txtPhone.getText(), "Phone Number");
if(errorMsg.isEmpty()){ //no validation errors detected
Supplier supplier = collectSupplier(); //get supplier info
if (mode.equals("Add")) { //add mode
try{
numRow = SupplierDB.insertSupplier(supplier);
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"SupplierDialogController.buttonSaveClicked",
e,
"Inserting new supplier record");
throw new RuntimeException(e);
SupplierRequest request = createSupplierRequest();
try {
if (mode.equals("Add")) {
SupplierApi.getInstance().createSupplier(request);
} else {
Long supplierId = Long.parseLong(lblSupId.getText().split(": ")[1]);
SupplierApi.getInstance().updateSupplier(supplierId, request);
}
}
else{ //edit mode
try{
numRow = SupplierDB.updateSupplier(supplier.getSupId(),supplier);
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"SupplierDialogController.buttonSaveClicked",
e,
"Updating supplier with ID: " + supplier.getSupId());
throw new RuntimeException(e);
}
}
//if no rows were affected then there was an error (prompt user of error)
if (numRow == 0){
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(mode + " failed");
alert.showAndWait();
}
else {
//tell the user operation was successful
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Saved");
alert.setContentText(mode + " succeeded");
alert.showAndWait();
closeStage(mouseEvent);
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"SupplierDialogController.buttonSaveClicked",
e,
mode.equals("Add") ? "Inserting new supplier record" : "Updating supplier record");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(mode + " failed: " + e.getMessage());
alert.showAndWait();
}
}
else{ //Display validation errors
@@ -154,26 +138,16 @@ public class SupplierDialogController {
}
/**
* Collect the supplier info
* @return supplier info with the id or the new supplier
* Create a supplier request from the form inputs
* @return supplier request for API call
*/
private Supplier collectSupplier(){
int supId = 0;
Supplier supplier = null;
if(lblSupId.isVisible()){ //Edit mode
//get supplier id from lblId (split the string so we only get the int)
supId = Integer.parseInt(lblSupId.getText().split(": ")[1]);
}
supplier = new Supplier(
supId,
txtCompanyName.getText(),
txtContactFirstName.getText(),
txtContactLastName.getText(),
txtEmail.getText(),
txtPhone.getText()
);
return supplier;
private SupplierRequest createSupplierRequest(){
SupplierRequest request = new SupplierRequest();
request.setSupplierName(txtCompanyName.getText());
request.setContactPerson(txtContactFirstName.getText() + " " + txtContactLastName.getText());
request.setEmail(txtEmail.getText());
request.setPhone(txtPhone.getText());
return request;
}
/**

View File

@@ -8,16 +8,17 @@ public class Adoption {
private SimpleIntegerProperty adoptionId;
private SimpleIntegerProperty petId;
private SimpleIntegerProperty customerId;
private SimpleStringProperty petName;
private SimpleStringProperty customerName;
private SimpleStringProperty adoptionDate;
private SimpleDoubleProperty adoptionFee;
private SimpleStringProperty adoptionStatus;
//Constructor
public Adoption(int adoptionId, int petId, int customerId, String customerName, String adoptionDate, double adoptionFee, String adoptionStatus) {
public Adoption(int adoptionId, int petId, int customerId, String petName, String customerName, String adoptionDate, double adoptionFee, String adoptionStatus) {
this.adoptionId = new SimpleIntegerProperty(adoptionId);
this.petId = new SimpleIntegerProperty(petId);
this.customerId = new SimpleIntegerProperty(customerId);
this.petName = new SimpleStringProperty(petName);
this.customerName = new SimpleStringProperty(customerName);
this.adoptionDate = new SimpleStringProperty(adoptionDate);
this.adoptionFee = new SimpleDoubleProperty(adoptionFee);
@@ -42,6 +43,12 @@ public class Adoption {
public SimpleIntegerProperty customerIdProperty() { return customerId; }
public String getPetName() { return petName.get(); }
public void setPetName(String petName) { this.petName.set(petName); }
public SimpleStringProperty petNameProperty() { return petName; }
public String getCustomerName() { return customerName.get(); }
public void setCustomerName(String customerName) { this.customerName.set(customerName); }

View File

@@ -7,14 +7,21 @@ public class Inventory {
private SimpleIntegerProperty inventoryId;
private SimpleIntegerProperty prodId;
private SimpleStringProperty prodName;
private SimpleStringProperty categoryName;
private SimpleIntegerProperty storeId;
private SimpleStringProperty storeName;
private SimpleIntegerProperty quantity;
private SimpleIntegerProperty reorderLevel;
//Constructor
public Inventory(int inventoryId, int prodId, String prodName, int quantity) {
public Inventory(int inventoryId, int prodId, String prodName, String categoryName, int storeId, String storeName, int quantity, int reorderLevel) {
this.inventoryId = new SimpleIntegerProperty(inventoryId);
this.prodId = new SimpleIntegerProperty(prodId);
this.prodName = new SimpleStringProperty(prodName);
this.categoryName = new SimpleStringProperty(categoryName);
this.storeId = new SimpleIntegerProperty(storeId);
this.storeName = new SimpleStringProperty(storeName);
this.quantity = new SimpleIntegerProperty(quantity);
this.reorderLevel = new SimpleIntegerProperty(reorderLevel);
}
public int getInventoryId() { return inventoryId.get(); }
@@ -35,9 +42,33 @@ public class Inventory {
public SimpleStringProperty prodNameProperty() { return prodName; }
public String getCategoryName() { return categoryName.get(); }
public void setCategoryName(String categoryName) { this.categoryName.set(categoryName); }
public SimpleStringProperty categoryNameProperty() { return categoryName; }
public int getStoreId() { return storeId.get(); }
public void setStoreId(int storeId) { this.storeId.set(storeId); }
public SimpleIntegerProperty storeIdProperty() { return storeId; }
public String getStoreName() { return storeName.get(); }
public void setStoreName(String storeName) { this.storeName.set(storeName); }
public SimpleStringProperty storeNameProperty() { return storeName; }
public int getQuantity() { return quantity.get(); }
public void setQuantity(int quantity) { this.quantity.set(quantity); }
public SimpleIntegerProperty quantityProperty() { return quantity; }
public int getReorderLevel() { return reorderLevel.get(); }
public void setReorderLevel(int reorderLevel) { this.reorderLevel.set(reorderLevel); }
public SimpleIntegerProperty reorderLevelProperty() { return reorderLevel; }
}

View File

@@ -1,35 +1,52 @@
package org.example.petshopdesktop.models;
import java.math.BigDecimal;
import java.time.LocalDate;
public class PurchaseOrder {
private int purchaseOrderId;
private int supId;
private String orderDate;
private long purchaseOrderId;
private String supplierName;
private LocalDate orderDate;
private LocalDate expectedDeliveryDate;
private String status;
private BigDecimal totalAmount;
public PurchaseOrder(int purchaseOrderId,
int supId,
String orderDate,
String status) {
public PurchaseOrder(long purchaseOrderId,
String supplierName,
LocalDate orderDate,
LocalDate expectedDeliveryDate,
String status,
BigDecimal totalAmount) {
this.purchaseOrderId = purchaseOrderId;
this.supId = supId;
this.supplierName = supplierName;
this.orderDate = orderDate;
this.expectedDeliveryDate = expectedDeliveryDate;
this.status = status;
this.totalAmount = totalAmount;
}
public int getPurchaseOrderId() {
public long getPurchaseOrderId() {
return purchaseOrderId;
}
public int getSupId() {
return supId;
public String getSupplierName() {
return supplierName;
}
public String getOrderDate() {
public LocalDate getOrderDate() {
return orderDate;
}
public LocalDate getExpectedDeliveryDate() {
return expectedDeliveryDate;
}
public String getStatus() {
return status;
}
public BigDecimal getTotalAmount() {
return totalAmount;
}
}

View File

@@ -3,8 +3,8 @@ package org.example.petshopdesktop.models;
import java.sql.Timestamp;
public class StaffAccount {
private final int userId;
private final int employeeId;
private final long userId;
private final long employeeId;
private final String username;
private final String firstName;
private final String lastName;
@@ -13,7 +13,7 @@ public class StaffAccount {
private final boolean active;
private final Timestamp createdAt;
public StaffAccount(int userId, int employeeId, String username, String firstName, String lastName, String email, String phone, boolean active, Timestamp createdAt) {
public StaffAccount(long userId, long employeeId, String username, String firstName, String lastName, String email, String phone, boolean active, Timestamp createdAt) {
this.userId = userId;
this.employeeId = employeeId;
this.username = username;
@@ -25,11 +25,11 @@ public class StaffAccount {
this.createdAt = createdAt;
}
public int getUserId() {
public long getUserId() {
return userId;
}
public int getEmployeeId() {
public long getEmployeeId() {
return employeeId;
}