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 { public class PurchaseOrderDTO {
private IntegerProperty purchaseOrderId; private LongProperty purchaseOrderId;
private StringProperty supplierName; private StringProperty supplierName;
private StringProperty orderDate; private StringProperty orderDate;
private StringProperty status; private StringProperty status;
public PurchaseOrderDTO(int id, String supplierName, public PurchaseOrderDTO(long id, String supplierName,
String orderDate, String status) { String orderDate, String status) {
this.purchaseOrderId = new SimpleIntegerProperty(id); this.purchaseOrderId = new SimpleLongProperty(id);
this.supplierName = new SimpleStringProperty(supplierName); this.supplierName = new SimpleStringProperty(supplierName);
this.orderDate = new SimpleStringProperty(orderDate); this.orderDate = new SimpleStringProperty(orderDate);
this.status = new SimpleStringProperty(status); 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 getSupplierName() { return supplierName.get(); }
public String getOrderDate() { return orderDate.get(); } public String getOrderDate() { return orderDate.get(); }
public String getStatus() { return status.get(); } public String getStatus() { return status.get(); }

View File

@@ -4,6 +4,8 @@ import java.math.BigDecimal;
public class ProductSupplierResponse { public class ProductSupplierResponse {
private Long id; private Long id;
private Long productId;
private Long supplierId;
private String productName; private String productName;
private String supplierName; private String supplierName;
private BigDecimal supplierPrice; private BigDecimal supplierPrice;
@@ -16,6 +18,22 @@ public class ProductSupplierResponse {
this.id = id; 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() { public String getProductName() {
return productName; return productName;
} }

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers; package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
@@ -10,15 +11,16 @@ import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; 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.controllers.dialogcontrollers.AdoptionDialogController;
import org.example.petshopdesktop.database.AdoptionDB;
import org.example.petshopdesktop.models.Adoption; import org.example.petshopdesktop.models.Adoption;
import org.example.petshopdesktop.util.ActivityLogger; import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.util.List;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
public class AdoptionController { public class AdoptionController {
@@ -35,7 +37,7 @@ public class AdoptionController {
private TableColumn<Adoption, Integer> colAdoptionId; private TableColumn<Adoption, Integer> colAdoptionId;
@FXML @FXML
private TableColumn<Adoption, Integer> colPetId; private TableColumn<Adoption, String> colPetId;
@FXML @FXML
private TableColumn<Adoption, String> colCustomerName; private TableColumn<Adoption, String> colCustomerName;
@@ -66,7 +68,7 @@ public class AdoptionController {
tvAdoptions.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE); tvAdoptions.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
colAdoptionId.setCellValueFactory(new PropertyValueFactory<>("adoptionId")); colAdoptionId.setCellValueFactory(new PropertyValueFactory<>("adoptionId"));
colPetId.setCellValueFactory(new PropertyValueFactory<>("petId")); colPetId.setCellValueFactory(new PropertyValueFactory<>("petName"));
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName")); colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
colAdoptionDate.setCellValueFactory(new PropertyValueFactory<>("adoptionDate")); colAdoptionDate.setCellValueFactory(new PropertyValueFactory<>("adoptionDate"));
colAdoptionFee.setCellValueFactory(new PropertyValueFactory<>("adoptionFee")); colAdoptionFee.setCellValueFactory(new PropertyValueFactory<>("adoptionFee"));
@@ -118,47 +120,24 @@ public class AdoptionController {
//if confirmed, start deletion //if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) { if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0; List<Long> ids = selectedAdoptions.stream()
int failCount = 0; .map(a -> (long) a.getAdoptionId())
StringBuilder errors = new StringBuilder(); .collect(Collectors.toList());
for (Adoption adoption : selectedAdoptions) { try {
try { AdoptionApi.getInstance().deleteAdoptions(ids);
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) {
Alert alert = new Alert(Alert.AlertType.INFORMATION); Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed"); 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(); alert.showAndWait();
} }
@@ -181,35 +160,55 @@ public class AdoptionController {
} }
private void displayFilteredAdoptions(String filter) { private void displayFilteredAdoptions(String filter) {
data.clear(); if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
try { displayAdoptions();
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) { } else {
displayAdoptions(); new Thread(() -> {
} else { try {
data = AdoptionDB.getFilteredAdoptions(filter); List<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(filter);
tvAdoptions.setItems(data); List<Adoption> adoptionList = adoptions.stream()
} .map(this::mapToAdoption)
} catch (Exception e) { .collect(Collectors.toList());
ActivityLogger.getInstance().logException(
"AdoptionController.displayFilteredAdoptions", Platform.runLater(() -> {
e, data.setAll(adoptionList);
"Filtering adoptions with filter: " + filter); tvAdoptions.setItems(data);
System.out.println("Error while fetching table data: " + e.getMessage()); });
} 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() { private void displayAdoptions() {
data.clear(); new Thread(() -> {
try { try {
data = AdoptionDB.getAdoptions(); List<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(null);
} catch (SQLException e) { List<Adoption> adoptionList = adoptions.stream()
ActivityLogger.getInstance().logException( .map(this::mapToAdoption)
"AdoptionController.displayAdoptions", .collect(Collectors.toList());
e,
"Fetching adoption data for table display"); Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage()); data.setAll(adoptionList);
} tvAdoptions.setItems(data);
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) { private void openDialog(Adoption adoption, String mode) {
@@ -244,4 +243,17 @@ public class AdoptionController {
btnEdit.setDisable(true); btnEdit.setDisable(true);
txtSearch.setText(""); 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; package org.example.petshopdesktop.controllers;
import javafx.collections.ObservableList; import javafx.application.Platform;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.chart.*; import javafx.scene.chart.*;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import org.example.petshopdesktop.auth.UserSession; import org.example.petshopdesktop.api.dto.analytics.DailySales;
import org.example.petshopdesktop.database.SaleDB; import org.example.petshopdesktop.api.dto.analytics.DashboardResponse;
import org.example.petshopdesktop.models.analytics.*; 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 org.example.petshopdesktop.util.ActivityLogger;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Locale; import java.util.*;
import java.util.stream.Collectors;
public class AnalyticsController { public class AnalyticsController {
@@ -78,79 +84,111 @@ public class AnalyticsController {
private void loadAnalyticsData() { private void loadAnalyticsData() {
lblError.setVisible(false); lblError.setVisible(false);
try { new Thread(() -> {
loadSummaryData(); try {
loadSalesOverTime(); DashboardResponse dashboard = AnalyticsApi.getInstance().getDashboard(30, 10);
loadTopProductsByRevenue(); List<SaleResponse> sales = SaleApi.getInstance().listSales(0, Integer.MAX_VALUE, null);
loadTopProductsByQuantity();
loadPaymentMethodDistribution(); Platform.runLater(() -> {
loadEmployeePerformance(); try {
} catch (Exception e) { loadSummaryData(dashboard);
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data"); loadSalesOverTime(dashboard);
lblError.setText("Error loading analytics data. Please try again."); loadTopProductsByRevenue(dashboard);
lblError.setVisible(true); 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 { private void loadSalesOverTime(DashboardResponse dashboard) throws Exception {
SalesSummary summary = SaleDB.getSalesSummary(); List<DailySales> dailySales = dashboard.getDailySales() != null ? dashboard.getDailySales() : new ArrayList<>();
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();
XYChart.Series<String, Number> series = new XYChart.Series<>(); XYChart.Series<String, Number> series = new XYChart.Series<>();
series.setName("Daily Revenue"); series.setName("Daily Revenue");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd");
for (DailySalesData dailySale : data) { for (DailySales dailySale : dailySales) {
String dateStr = dailySale.getDate().format(formatter); 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().clear();
chartSalesOverTime.getData().add(series); chartSalesOverTime.getData().add(series);
} }
private void loadTopProductsByRevenue() throws Exception { private void loadTopProductsByRevenue(DashboardResponse dashboard) throws Exception {
ObservableList<ProductSalesData> data = SaleDB.getTopProductsByRevenue(10); List<TopProduct> topProducts = dashboard.getTopProducts() != null ? dashboard.getTopProducts() : new ArrayList<>();
XYChart.Series<Number, String> series = new XYChart.Series<>(); XYChart.Series<Number, String> series = new XYChart.Series<>();
series.setName("Revenue"); series.setName("Revenue");
for (ProductSalesData product : data) { for (TopProduct product : topProducts) {
series.getData().add(new XYChart.Data<>(product.getTotalRevenue(), product.getProductName())); BigDecimal totalRevenue = product.getTotalRevenue() != null ? product.getTotalRevenue() : BigDecimal.ZERO;
series.getData().add(new XYChart.Data<>(totalRevenue, product.getProductName()));
} }
chartTopRevenue.getData().clear(); chartTopRevenue.getData().clear();
chartTopRevenue.getData().add(series); chartTopRevenue.getData().add(series);
} }
private void loadTopProductsByQuantity() throws Exception { private void loadTopProductsByQuantity(DashboardResponse dashboard) throws Exception {
ObservableList<ProductSalesData> data = SaleDB.getTopProductsByQuantity(10); List<TopProduct> topProducts = dashboard.getTopProducts() != null ? dashboard.getTopProducts() : new ArrayList<>();
XYChart.Series<Number, String> series = new XYChart.Series<>(); XYChart.Series<Number, String> series = new XYChart.Series<>();
series.setName("Quantity"); series.setName("Quantity");
for (ProductSalesData product : data) { for (TopProduct product : topProducts) {
series.getData().add(new XYChart.Data<>(product.getTotalQuantity(), product.getProductName())); Integer quantitySold = product.getQuantitySold() != null ? product.getQuantitySold() : 0;
series.getData().add(new XYChart.Data<>(quantitySold, product.getProductName()));
} }
chartTopQuantity.getData().clear(); chartTopQuantity.getData().clear();
chartTopQuantity.getData().add(series); chartTopQuantity.getData().add(series);
} }
private void loadPaymentMethodDistribution() throws Exception { private void loadPaymentMethodDistribution(List<SaleResponse> sales) throws Exception {
ObservableList<PaymentMethodData> data = SaleDB.getPaymentMethodDistribution(); 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(); chartPaymentMethods.getData().clear();
for (PaymentMethodData payment : data) { for (Map.Entry<String, Long> entry : paymentMethodCount.entrySet()) {
PieChart.Data slice = new PieChart.Data( PieChart.Data slice = new PieChart.Data(
payment.getPaymentMethod() + " (" + payment.getTransactionCount() + ")", entry.getKey() + " (" + entry.getValue() + ")",
payment.getTransactionCount() entry.getValue()
); );
chartPaymentMethods.getData().add(slice); chartPaymentMethods.getData().add(slice);
} }

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers; package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.FilteredList;
@@ -13,10 +14,14 @@ import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.AppointmentDTO; 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.controllers.dialogcontrollers.AppointmentDialogController;
import org.example.petshopdesktop.database.AppointmentDB;
import org.example.petshopdesktop.util.ActivityLogger; import org.example.petshopdesktop.util.ActivityLogger;
import java.util.List;
import java.util.stream.Collectors;
public class AppointmentController { public class AppointmentController {
@FXML private TableView<AppointmentDTO> tvAppointments; @FXML private TableView<AppointmentDTO> tvAppointments;
@@ -71,41 +76,50 @@ public class AppointmentController {
} }
private void loadAppointments(){ private void loadAppointments(){
try{ new Thread(() -> {
appointments.setAll(AppointmentDB.getAppointmentDTOs()); try{
}catch(Exception e){ List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(null);
ActivityLogger.getInstance().logException( List<AppointmentDTO> appointmentDTOs = responses.stream()
"AppointmentController.loadAppointments", .map(this::mapToAppointmentDTO)
e, .collect(Collectors.toList());
"Loading appointments for table display");
e.printStackTrace(); 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) { private void applyFilter(String text) {
if (filtered == null) { String query = text == null || text.trim().isEmpty() ? null : text.trim();
return; 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(); Platform.runLater(() -> {
if (q.isEmpty()) { appointments.setAll(appointmentDTOs);
filtered.setPredicate(a -> true); });
return; } catch (Exception e) {
} Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
filtered.setPredicate(a -> "AppointmentController.applyFilter",
String.valueOf(a.getAppointmentId()).contains(q) e,
|| safe(a.getPetName()).contains(q) String.format("Filtering appointments with query: %s", query));
|| safe(a.getServiceName()).contains(q) e.printStackTrace();
|| safe(a.getAppointmentDate()).contains(q) });
|| safe(a.getAppointmentTime()).contains(q) }
|| safe(a.getCustomerName()).contains(q) }).start();
|| safe(a.getAppointmentStatus()).contains(q)
);
}
private static String safe(String v) {
return v == null ? "" : v.toLowerCase();
} }
@FXML @FXML
@@ -145,35 +159,24 @@ public class AppointmentController {
//if confirmed, start deletion //if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) { if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0; List<Long> ids = selectedAppointments.stream()
int failCount = 0; .map(a -> (long) a.getAppointmentId())
StringBuilder errors = new StringBuilder(); .collect(Collectors.toList());
for (AppointmentDTO appointment : selectedAppointments) { try {
try{ AppointmentApi.getInstance().deleteAppointments(ids);
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) {
Alert alert = new Alert(Alert.AlertType.INFORMATION); Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed"); 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(); alert.showAndWait();
} }
@@ -225,4 +228,19 @@ public class AppointmentController {
alert.setContentText(msg); alert.setContentText(msg);
alert.showAndWait(); 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; package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
@@ -10,15 +11,16 @@ import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; 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.controllers.dialogcontrollers.InventoryDialogController;
import org.example.petshopdesktop.database.InventoryDB;
import org.example.petshopdesktop.models.Inventory; import org.example.petshopdesktop.models.Inventory;
import org.example.petshopdesktop.util.ActivityLogger; import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.util.List;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
public class InventoryController { public class InventoryController {
@@ -58,11 +60,9 @@ public class InventoryController {
//Loads upon view bootup //Loads upon view bootup
@FXML @FXML
void initialize() { void initialize() {
//Buttons disabled until row is selected
btnEdit.setDisable(true); btnEdit.setDisable(true);
btnDelete.setDisable(true); btnDelete.setDisable(true);
//Enable multiple selection tvInventory.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.SINGLE);
tvInventory.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
colInventoryId.setCellValueFactory(new PropertyValueFactory<>("inventoryId")); colInventoryId.setCellValueFactory(new PropertyValueFactory<>("inventoryId"));
colProductId.setCellValueFactory(new PropertyValueFactory<>("prodId")); colProductId.setCellValueFactory(new PropertyValueFactory<>("prodId"));
@@ -71,19 +71,16 @@ public class InventoryController {
displayInventory(); displayInventory();
//Enables buttons when row is selected
tvInventory.getSelectionModel().selectedItemProperty().addListener( tvInventory.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> { (observable, oldValue, newValue) -> {
btnEdit.setDisable(false); btnEdit.setDisable(false);
btnDelete.setDisable(false); btnDelete.setDisable(false);
}); });
//Filter as user types
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> { txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
displayFilteredInventory(newValue); displayFilteredInventory(newValue);
}); });
//EventListener for DELETE key
tvInventory.setOnKeyPressed(event -> { tvInventory.setOnKeyPressed(event -> {
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) { if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
if (tvInventory.getSelectionModel().getSelectedItem() != null) { if (tvInventory.getSelectionModel().getSelectedItem() != null) {
@@ -100,71 +97,35 @@ public class InventoryController {
openDialog(null, mode); openDialog(null, mode);
} }
//Prompts user for confirmation prior to deletion
@FXML @FXML
void btnDeleteClicked(ActionEvent event) { void btnDeleteClicked(ActionEvent event) {
//get selected inventory records Inventory selectedInventory = tvInventory.getSelectionModel().getSelectedItem();
var selectedInventory = tvInventory.getSelectionModel().getSelectedItems(); if (selectedInventory == null) return;
if (selectedInventory.isEmpty()) return;
//ask user to confirm
Alert question = new Alert(Alert.AlertType.CONFIRMATION); Alert question = new Alert(Alert.AlertType.CONFIRMATION);
question.setHeaderText("Please confirm delete"); question.setHeaderText("Please confirm delete");
String message = selectedInventory.size() == 1 question.setContentText("Are you sure you want to delete this inventory record?");
? "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.getDialogPane().lookupButton(ButtonType.OK).requestFocus(); question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
Optional<ButtonType> result = question.showAndWait(); Optional<ButtonType> result = question.showAndWait();
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) { if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0; try {
int failCount = 0; InventoryApi.getInstance().deleteInventory((long) selectedInventory.getInventoryId());
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) {
Alert alert = new Alert(Alert.AlertType.INFORMATION); Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed"); 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(); alert.showAndWait();
} }
//refresh display and reset inputs
displayInventory(); displayInventory();
btnDelete.setDisable(true); btnDelete.setDisable(true);
btnEdit.setDisable(true); btnEdit.setDisable(true);
@@ -183,66 +144,72 @@ public class InventoryController {
} }
} }
//Search filter
private void displayFilteredInventory(String filter) { private void displayFilteredInventory(String filter) {
data.clear(); if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
try { displayInventory();
//If search box is empty, display all inventory } else {
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) { new Thread(() -> {
displayInventory(); try {
} List<InventoryResponse> inventories = InventoryApi.getInstance().listInventory(filter);
List<Inventory> inventoryList = inventories.stream()
.map(this::mapToInventory)
.collect(Collectors.toList());
else { Platform.runLater(() -> {
data = InventoryDB.getFilteredInventory(filter); data.setAll(inventoryList);
tvInventory.setItems(data); tvInventory.setItems(data);
} });
} } catch (Exception e) {
Platform.runLater(() -> {
catch (Exception e) { System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException( ActivityLogger.getInstance().logException(
"InventoryController.displayFilteredInventory", "InventoryController.displayFilteredInventory",
e, e,
"Filtering inventory with filter: " + filter); String.format("Filtering inventory with keyword: %s", filter));
System.out.println("Error while fetching table data: " + e.getMessage()); });
}
}).start();
} }
} }
//Displays all records from DB
private void displayInventory() { private void displayInventory() {
data.clear(); new Thread(() -> {
try { try {
data = InventoryDB.getInventory(); List<InventoryResponse> inventories = InventoryApi.getInstance().listInventory(null);
} List<Inventory> inventoryList = inventories.stream()
.map(this::mapToInventory)
.collect(Collectors.toList());
catch (SQLException e) { Platform.runLater(() -> {
ActivityLogger.getInstance().logException( data.setAll(inventoryList);
"InventoryController.displayInventory", tvInventory.setItems(data);
e, });
"Fetching inventory data for table display"); } catch (Exception e) {
System.out.println("Error while fetching table data: " + e.getMessage()); Platform.runLater(() -> {
} System.out.println("Error while fetching table data: " + e.getMessage());
tvInventory.setItems(data); ActivityLogger.getInstance().logException(
"InventoryController.displayInventory",
e,
"Fetching inventory data for table display");
});
}
}).start();
} }
//Opens inventory-dialog-view
private void openDialog(Inventory inventory, String mode) { private void openDialog(Inventory inventory, String mode) {
//Opens FXML
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/inventory-dialog-view.fxml")); FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/inventory-dialog-view.fxml"));
Scene scene = null; Scene scene = null;
try { try {
scene = new Scene(fxmlLoader.load()); scene = new Scene(fxmlLoader.load());
} } catch (IOException e) {
catch (IOException e) {
ActivityLogger.getInstance().logException( ActivityLogger.getInstance().logException(
"InventoryController.openDialog", "InventoryController.openDialog",
e, e,
"Loading inventory dialog in " + mode + " mode"); String.format("Loading inventory dialog view in %s mode", mode));
throw new RuntimeException(e); throw new RuntimeException(e);
} }
//Passes data and mode to the view
InventoryDialogController dialogController = fxmlLoader.getController(); InventoryDialogController dialogController = fxmlLoader.getController();
dialogController.setMode(mode); dialogController.setMode(mode);
@@ -256,10 +223,22 @@ public class InventoryController {
dialogStage.setScene(scene); dialogStage.setScene(scene);
dialogStage.showAndWait(); dialogStage.showAndWait();
//Refresh inventory
displayInventory(); displayInventory();
btnDelete.setDisable(true); btnDelete.setDisable(true);
btnEdit.setDisable(true); btnEdit.setDisable(true);
txtSearch.setText(""); 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; package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
@@ -10,19 +11,16 @@ import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.ProductDTO;
import org.example.petshopdesktop.DTOs.ProductSupplierDTO; 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.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 org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.util.List;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
public class ProductSupplierController { public class ProductSupplierController {
@@ -107,22 +105,27 @@ public class ProductSupplierController {
* Display the ProductSupplierDTO to table view * Display the ProductSupplierDTO to table view
*/ */
private void displayProductSupplier() { private void displayProductSupplier() {
//Erase old content new Thread(() -> {
data.clear(); try {
List<ProductSupplierResponse> productSuppliers = ProductSupplierApi.getInstance().listProductSuppliers(null);
List<ProductSupplierDTO> productSupplierDTOs = productSuppliers.stream()
.map(this::mapToProductSupplierDTO)
.collect(Collectors.toList());
//get ProductSupplier from database Platform.runLater(() -> {
try{ data.setAll(productSupplierDTOs);
data = ProductSupplierDB.getProductSupplierDTO(); tvProductSuppliers.setItems(data);
} catch (SQLException e) { });
ActivityLogger.getInstance().logException( } catch (Exception e) {
"ProductSupplierController.displayProductSupplier", Platform.runLater(() -> {
e, System.out.println("Error while fetching table data: " + e.getMessage());
"Fetching product-supplier data for table display"); ActivityLogger.getInstance().logException(
System.out.println("Error while fetching table data: " + e.getMessage()); "ProductSupplierController.displayProductSupplier",
} e,
"Fetching product-supplier data for table display");
//put data in the table });
tvProductSuppliers.setItems(data); }
}).start();
} }
/** /**
@@ -130,22 +133,30 @@ public class ProductSupplierController {
* @param filter word to filter table * @param filter word to filter table
*/ */
private void displayFilteredProductSupplier(String filter){ private void displayFilteredProductSupplier(String filter){
data.clear(); if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
try{ displayProductSupplier();
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){ } else {
displayProductSupplier(); //If search bar is empty just display everything new Thread(() -> {
} try {
else{ List<ProductSupplierResponse> productSuppliers = ProductSupplierApi.getInstance().listProductSuppliers(filter);
//Filter the using the keyword List<ProductSupplierDTO> productSupplierDTOs = productSuppliers.stream()
data = ProductSupplierDB.getFilteredProductSupplierDTO(filter); .map(this::mapToProductSupplierDTO)
tvProductSuppliers.setItems(data); .collect(Collectors.toList());
}
} catch (Exception e) { Platform.runLater(() -> {
ActivityLogger.getInstance().logException( data.setAll(productSupplierDTOs);
"ProductSupplierController.displayFilteredProductSupplier", tvProductSuppliers.setItems(data);
e, });
"Filtering product-supplier data with filter: " + filter); } catch (Exception e) {
System.out.println("Error while fetching table data: " + e.getMessage()); 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 confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) { if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0; List<Long> ids = selectedProductSuppliers.stream()
int failCount = 0; .map(ps -> (long) ps.getSupId())
StringBuilder errors = new StringBuilder(); .collect(Collectors.toList());
for (ProductSupplierDTO productSupplier : selectedProductSuppliers) { try {
try{ ProductSupplierApi.getInstance().deleteProductSuppliers(ids);
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) {
Alert alert = new Alert(Alert.AlertType.INFORMATION); Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed"); 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(); alert.showAndWait();
} }
@@ -297,4 +280,14 @@ public class ProductSupplierController {
txtSearch.setText(""); 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; package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.FilteredList;
@@ -7,9 +8,13 @@ import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.PropertyValueFactory;
import org.example.petshopdesktop.DTOs.PurchaseOrderDTO; 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 org.example.petshopdesktop.util.ActivityLogger;
import java.util.List;
import java.util.stream.Collectors;
public class PurchaseOrderController { public class PurchaseOrderController {
@FXML private Button btnRefresh; @FXML private Button btnRefresh;
@@ -19,7 +24,7 @@ public class PurchaseOrderController {
@FXML private TableView<PurchaseOrderDTO> tvPurchaseOrders; @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> colSupplier;
@FXML private TableColumn<PurchaseOrderDTO,String> colOrderDate; @FXML private TableColumn<PurchaseOrderDTO,String> colOrderDate;
@FXML private TableColumn<PurchaseOrderDTO,String> colStatus; @FXML private TableColumn<PurchaseOrderDTO,String> colStatus;
@@ -53,17 +58,28 @@ public class PurchaseOrderController {
} }
private void loadPurchaseOrders() { private void loadPurchaseOrders() {
try { new Thread(() -> {
purchaseOrders.setAll(PurchaseOrderDB.getPurchaseOrders()); try {
} catch (Exception e) { List<PurchaseOrderResponse> responses = PurchaseOrderApi.getInstance().listPurchaseOrders(null);
ActivityLogger.getInstance().logException( List<PurchaseOrderDTO> dtos = responses.stream()
"PurchaseOrderController.loadPurchaseOrders", .map(this::mapToPurchaseOrderDTO)
e, .collect(Collectors.toList());
"Loading purchase orders for table display");
e.printStackTrace(); Platform.runLater(() -> {
new Alert(Alert.AlertType.ERROR, purchaseOrders.setAll(dtos);
"Unable to load purchase orders").showAndWait(); 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) { private void applyFilter(String text) {
@@ -93,4 +109,13 @@ public class PurchaseOrderController {
void btnRefresh() { void btnRefresh() {
loadPurchaseOrders(); 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.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.example.petshopdesktop.auth.UserSession; import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.database.InventoryDB; import javafx.concurrent.Task;
import org.example.petshopdesktop.database.ProductDB; import org.example.petshopdesktop.api.endpoints.ProductApi;
import org.example.petshopdesktop.database.SaleDB; import org.example.petshopdesktop.api.endpoints.SaleApi;
import org.example.petshopdesktop.models.Inventory; 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.Product;
import org.example.petshopdesktop.models.SaleCartItem; import org.example.petshopdesktop.models.SaleCartItem;
import org.example.petshopdesktop.models.SaleLineItem; import org.example.petshopdesktop.models.SaleLineItem;
import org.example.petshopdesktop.util.ActivityLogger; import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException; import java.math.BigDecimal;
import java.text.NumberFormat; 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.Locale;
import java.util.Map;
public class SaleController { public class SaleController {
@@ -124,8 +129,8 @@ public class SaleController {
private final ObservableList<SaleLineItem> saleItems = FXCollections.observableArrayList(); private final ObservableList<SaleLineItem> saleItems = FXCollections.observableArrayList();
private FilteredList<SaleLineItem> filteredSales; private FilteredList<SaleLineItem> filteredSales;
private final Map<Integer, Integer> inventoryByProdId = new HashMap<>();
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA); private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@FXML @FXML
public void initialize() { public void initialize() {
@@ -133,7 +138,6 @@ public class SaleController {
setupCreateSale(); setupCreateSale();
applyRoleMode(); applyRoleMode();
refreshInventory();
refreshSales(); refreshSales();
} }
@@ -170,11 +174,20 @@ public class SaleController {
updateCartTotal(); updateCartTotal();
try { try {
cbProduct.setItems(ProductDB.getProducts()); List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
} catch (SQLException e) { 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"); 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)"); 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() { private void refreshSales() {
refreshSales(false); refreshSales(false);
} }
private void refreshSales(boolean showErrorDialog) { private void refreshSales(boolean showErrorDialog) {
try { Task<List<SaleLineItem>> task = new Task<List<SaleLineItem>>() {
saleItems.setAll(SaleDB.getSaleLineItems()); @Override
} catch (SQLException e) { 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"); ActivityLogger.getInstance().logException("SaleController.refreshSales", e, "Loading sales");
if (showErrorDialog) { 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) { new Thread(task).start();
showError("Sales", "Database is not connected.");
}
}
} }
@FXML @FXML
void btnRefresh(ActionEvent event) { void btnRefresh(ActionEvent event) {
refreshInventory();
refreshSales(true); refreshSales(true);
} }
@@ -244,18 +274,6 @@ public class SaleController {
return; 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) { for (SaleCartItem item : cartItems) {
if (item.getProdId() == product.getProdId()) { if (item.getProdId() == product.getProdId()) {
item.setQuantity(item.getQuantity() + requestedQty); item.setQuantity(item.getQuantity() + requestedQty);
@@ -291,9 +309,9 @@ public class SaleController {
return; return;
} }
Integer employeeId = UserSession.getInstance().getEmployeeId(); Long storeId = UserSession.getInstance().getStoreId();
if (employeeId == null || employeeId <= 0) { if (storeId == null || storeId <= 0) {
showError("Create Sale", "Employee is not set for this account."); showError("Create Sale", "Store is not set for this account.");
return; return;
} }
@@ -309,20 +327,35 @@ public class SaleController {
} }
try { try {
int saleId = SaleDB.createSale(employeeId, payment, cartItems); SaleRequest request = new SaleRequest();
showInfo("Sale saved", "Sale ID " + saleId + " was created."); 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(); cartItems.clear();
updateCartTotal(); updateCartTotal();
refreshInventory();
refreshSales(true); refreshSales(true);
} catch (SQLException e) { } catch (Exception e) {
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", e, "Creating sale"); ActivityLogger.getInstance().logException("SaleController.btnSaveSale", e, "Creating sale");
showError("Create Sale", e.getMessage() == null ? "Could not save the sale." : e.getMessage()); String errorMsg = e.getMessage();
} catch (RuntimeException e) { if (errorMsg != null && errorMsg.contains("Insufficient inventory")) {
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", e, "Database connection"); showError("Create Sale", "Insufficient stock for one or more items.");
showError("Create Sale", "Database is not connected."); } else {
showError("Create Sale", errorMsg != null ? errorMsg : "Could not save the sale.");
}
} }
} }

View File

@@ -1,8 +1,8 @@
package org.example.petshopdesktop.controllers; package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
@@ -10,12 +10,17 @@ import javafx.scene.Scene;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.example.petshopdesktop.database.ServiceDB; import org.example.petshopdesktop.DTOs.ServiceDTO;
import org.example.petshopdesktop.models.Service; 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.controllers.dialogcontrollers.ServiceDialogController;
import org.example.petshopdesktop.util.ActivityLogger; import org.example.petshopdesktop.util.ActivityLogger;
import javafx.stage.Modality; import javafx.stage.Modality;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class ServiceController { public class ServiceController {
@@ -23,22 +28,23 @@ public class ServiceController {
@FXML private Button btnDelete; @FXML private Button btnDelete;
@FXML private Button btnEdit; @FXML private Button btnEdit;
@FXML private TableColumn<Service, Integer> colServiceId; @FXML private TableColumn<ServiceDTO, Integer> colServiceId;
@FXML private TableColumn<Service, String> colServiceName; @FXML private TableColumn<ServiceDTO, String> colServiceName;
@FXML private TableColumn<Service, String> colServiceDesc; @FXML private TableColumn<ServiceDTO, String> colServiceDesc;
@FXML private TableColumn<Service, Integer> colServiceDuration; @FXML private TableColumn<ServiceDTO, Integer> colServiceDuration;
@FXML private TableColumn<Service, Double> colServicePrice; @FXML private TableColumn<ServiceDTO, Double> colServicePrice;
@FXML private TableView<Service> tvServices; @FXML private TableView<ServiceDTO> tvServices;
@FXML private TextField txtSearch; @FXML private TextField txtSearch;
private final ObservableList<Service> services = FXCollections.observableArrayList(); private ObservableList<ServiceDTO> data = FXCollections.observableArrayList();
private FilteredList<Service> filtered; private String mode = null;
@FXML @FXML
public void initialize() { public void initialize() {
//Enable multiple selection btnEdit.setDisable(true);
btnDelete.setDisable(true);
tvServices.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE); tvServices.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
colServiceId.setCellValueFactory(new PropertyValueFactory<>("serviceId")); colServiceId.setCellValueFactory(new PropertyValueFactory<>("serviceId"));
@@ -47,14 +53,19 @@ public class ServiceController {
colServiceDuration.setCellValueFactory(new PropertyValueFactory<>("serviceDuration")); colServiceDuration.setCellValueFactory(new PropertyValueFactory<>("serviceDuration"));
colServicePrice.setCellValueFactory(new PropertyValueFactory<>("servicePrice")); colServicePrice.setCellValueFactory(new PropertyValueFactory<>("servicePrice"));
filtered = new FilteredList<>(services, s -> true); displayServices();
tvServices.setItems(filtered);
if (txtSearch != null) { tvServices.getSelectionModel().selectedItemProperty().addListener(
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n)); (observable, oldValue, newValue) -> {
} btnEdit.setDisable(false);
btnDelete.setDisable(false);
}
);
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
displayFilteredServices(newValue);
});
//EventListener for DELETE key
tvServices.setOnKeyPressed(event -> { tvServices.setOnKeyPressed(event -> {
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) { if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
if (tvServices.getSelectionModel().getSelectedItem() != null) { if (tvServices.getSelectionModel().getSelectedItem() != null) {
@@ -62,75 +73,82 @@ public class ServiceController {
} }
} }
}); });
loadServices();
} }
private void loadServices() { private void displayServices() {
try { new Thread(() -> {
services.setAll(ServiceDB.getServices()); try {
} catch (Exception e) { List<ServiceResponse> services = ServiceApi.getInstance().listServices(null);
ActivityLogger.getInstance().logException( List<ServiceDTO> serviceDTOs = services.stream()
"ServiceController.loadServices", .map(this::mapToServiceDTO)
e, .collect(Collectors.toList());
"Loading services for table display");
showAlert("Database Error", "Unable to load services."); Platform.runLater(() -> {
e.printStackTrace(); 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) { private void displayFilteredServices(String filter) {
if (filtered == null) { if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
return; 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 @FXML
void btnAddClicked(ActionEvent event) { void btnAddClicked(ActionEvent event) {
openDialog(null, "Add"); mode = "Add";
loadServices(); openDialog(null, mode);
} }
@FXML @FXML
void btnEditClicked(ActionEvent event) { void btnEditClicked(ActionEvent event) {
ServiceDTO selected = tvServices.getSelectionModel().getSelectedItem();
Service selected = tvServices.getSelectionModel().getSelectedItem(); if (selected != null) {
mode = "Edit";
if (selected == null) { openDialog(selected, mode);
showAlert("Select Service", "Please select a service to edit.");
return;
} }
openDialog(selected, "Edit");
loadServices();
} }
@FXML @FXML
void btnDeleteClicked(ActionEvent e) { void btnDeleteClicked(ActionEvent event) {
//get selected services
var selectedServices = tvServices.getSelectionModel().getSelectedItems(); var selectedServices = tvServices.getSelectionModel().getSelectedItems();
if (selectedServices.isEmpty()) return; if (selectedServices.isEmpty()) return;
//ask user to confirm
Alert question = new Alert(Alert.AlertType.CONFIRMATION); Alert question = new Alert(Alert.AlertType.CONFIRMATION);
question.setHeaderText("Please confirm delete"); question.setHeaderText("Please confirm delete");
String message = selectedServices.size() == 1 String message = selectedServices.size() == 1
@@ -138,82 +156,78 @@ public class ServiceController {
: "Are you sure you want to delete " + selectedServices.size() + " services?"; : "Are you sure you want to delete " + selectedServices.size() + " services?";
question.setContentText(message); question.setContentText(message);
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus(); 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) { if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0; List<Long> ids = selectedServices.stream()
int failCount = 0; .map(s -> (long) s.getServiceId())
StringBuilder errors = new StringBuilder(); .collect(Collectors.toList());
for (Service service : selectedServices) { try {
try { ServiceApi.getInstance().deleteServices(ids);
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) {
Alert alert = new Alert(Alert.AlertType.INFORMATION); Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed"); 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(); alert.showAndWait();
} }
//refresh display displayServices();
loadServices(); 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 { try {
FXMLLoader loader = new FXMLLoader( scene = new Scene(fxmlLoader.load());
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();
} catch (Exception e) { } catch (Exception e) {
ActivityLogger.getInstance().logException( ActivityLogger.getInstance().logException(
"ServiceController.openDialog", "ServiceController.openDialog",
e, e,
"Opening service dialog in " + mode + " mode"); String.format("Loading service dialog view in %s mode", mode));
e.printStackTrace(); 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); private ServiceDTO mapToServiceDTO(ServiceResponse response) {
alert.setTitle(title); return new ServiceDTO(
alert.setHeaderText(null); response.getId().intValue(),
alert.setContentText(msg); response.getServiceName(),
alert.showAndWait(); response.getDescription(),
0,
response.getPrice().doubleValue()
);
} }
} }

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers; package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.FilteredList;
@@ -15,12 +16,16 @@ import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; 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.auth.UserSession;
import org.example.petshopdesktop.database.UserDB;
import org.example.petshopdesktop.models.StaffAccount; import org.example.petshopdesktop.models.StaffAccount;
import org.example.petshopdesktop.util.ActivityLogger; 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 { public class StaffAccountsController {
@@ -107,15 +112,54 @@ public class StaffAccountsController {
private void refresh() { private void refresh() {
lblError.setText(""); lblError.setText("");
try { tvStaff.setDisable(true);
staffAccounts.setAll(UserDB.getStaffAccounts());
} catch (SQLException e) { new Thread(() -> {
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading staff accounts"); try {
lblError.setText("Could not load staff accounts."); List<UserResponse> users = UserApi.getInstance().listUsers(null);
} catch (RuntimeException e) { List<StaffAccount> accounts = users.stream()
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Database connection"); .map(this::mapToStaffAccount)
lblError.setText("Database is not connected."); .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) { private void applyFilter(String text) {

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers; package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
@@ -10,15 +11,16 @@ import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; 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.controllers.dialogcontrollers.SupplierDialogController;
import org.example.petshopdesktop.database.SupplierDB;
import org.example.petshopdesktop.models.Supplier; import org.example.petshopdesktop.models.Supplier;
import org.example.petshopdesktop.util.ActivityLogger; import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.util.List;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
/** /**
* The controller for any operations in the supplier view * The controller for any operations in the supplier view
@@ -105,19 +107,27 @@ public class SupplierController {
* Display the suppliers to table view * Display the suppliers to table view
*/ */
private void displaySupplier(){ 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{ Platform.runLater(() -> {
data = SupplierDB.getSuppliers(); data.setAll(supplierList);
} catch (SQLException e) { tvSuppliers.setItems(data);
ActivityLogger.getInstance().logException( });
"SupplierController.displaySupplier", } catch (Exception e) {
e, Platform.runLater(() -> {
"Fetching supplier data for table display"); System.out.println("Error while fetching table data: " + e.getMessage());
System.out.println("Error while fetching table data: " + e.getMessage()); ActivityLogger.getInstance().logException(
} "SupplierController.displaySupplier",
e,
tvSuppliers.setItems(data); "Fetching supplier data for table display");
});
}
}).start();
} }
/** /**
@@ -125,22 +135,30 @@ public class SupplierController {
* @param filter word to filter table * @param filter word to filter table
*/ */
private void displayFilteredSupplier(String filter){ private void displayFilteredSupplier(String filter){
data.clear(); if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
try{ displaySupplier();
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){ } else {
displaySupplier(); //If search bar is empty just display everything new Thread(() -> {
} try {
else{ List<SupplierResponse> suppliers = SupplierApi.getInstance().listSuppliers(filter);
//Filter the using the keyword List<Supplier> supplierList = suppliers.stream()
data = SupplierDB.getFilteredSuppliers(filter); .map(this::mapToSupplier)
tvSuppliers.setItems(data); .collect(Collectors.toList());
}
} catch (Exception e) { Platform.runLater(() -> {
ActivityLogger.getInstance().logException( data.setAll(supplierList);
"SupplierController.displayFilteredSupplier", tvSuppliers.setItems(data);
e, });
"Filtering suppliers with filter: " + filter); } catch (Exception e) {
System.out.println("Error while fetching table data: " + e.getMessage()); 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 confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) { if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0; List<Long> ids = selectedSuppliers.stream()
int failCount = 0; .map(s -> (long) s.getSupId())
StringBuilder errors = new StringBuilder(); .collect(Collectors.toList());
for (Supplier supplier : selectedSuppliers) { try {
try{ SupplierApi.getInstance().deleteSuppliers(ids);
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) {
Alert alert = new Alert(Alert.AlertType.INFORMATION); Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed"); 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(); alert.showAndWait();
} }
@@ -290,4 +284,20 @@ public class SupplierController {
txtSearch.setText(""); 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; package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.event.EventHandler; import javafx.event.EventHandler;
@@ -12,16 +13,15 @@ import javafx.scene.control.DatePicker;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.util.StringConverter; import org.example.petshopdesktop.api.dto.adoption.AdoptionRequest;
import org.example.petshopdesktop.database.AdoptionDB; import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.database.PetDB; 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.Adoption;
import org.example.petshopdesktop.models.Customer;
import org.example.petshopdesktop.models.Pet;
import org.example.petshopdesktop.util.ActivityLogger; import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
public class AdoptionDialogController { public class AdoptionDialogController {
@@ -36,10 +36,10 @@ public class AdoptionDialogController {
private ComboBox<String> cbAdoptionStatus; private ComboBox<String> cbAdoptionStatus;
@FXML @FXML
private ComboBox<Customer> cbCustomer; private ComboBox<DropdownOption> cbCustomer;
@FXML @FXML
private ComboBox<Pet> cbPet; private ComboBox<DropdownOption> cbPet;
@FXML @FXML
private DatePicker dpAdoptionDate; private DatePicker dpAdoptionDate;
@@ -58,52 +58,47 @@ public class AdoptionDialogController {
"Pending", "Completed", "Cancelled" "Pending", "Completed", "Cancelled"
); );
//Loads upon boot
@FXML @FXML
void initialize() { void initialize() {
//Loads statusList into combo box
cbAdoptionStatus.setItems(statusList); cbAdoptionStatus.setItems(statusList);
//Pet objects are converted into readable text for combobox (PetID + PetName) new Thread(() -> {
cbPet.setConverter(new StringConverter<Pet>() { try {
@Override List<DropdownOption> pets = DropdownApi.getInstance().getPets();
public String toString(Pet pet) { Platform.runLater(() -> {
return pet == null ? "" : pet.getPetId() + ": " + pet.getPetName(); 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 new Thread(() -> {
@Override try {
public Pet fromString(String string) { return null; } 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>() { btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override @Override
public void handle(MouseEvent mouseEvent) { public void handle(MouseEvent mouseEvent) {
@@ -111,7 +106,6 @@ public class AdoptionDialogController {
} }
}); });
//Cancel button handler, closes dialog view
btnCancel.setOnMouseClicked(new EventHandler<MouseEvent>() { btnCancel.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override @Override
public void handle(MouseEvent mouseEvent) { public void handle(MouseEvent mouseEvent) {
@@ -120,12 +114,9 @@ public class AdoptionDialogController {
}); });
} }
//Handles logic when clicking Save
private void buttonSaveClicked(MouseEvent mouseEvent) { private void buttonSaveClicked(MouseEvent mouseEvent) {
int numRow = 0;
String errorMsg = ""; String errorMsg = "";
//Validation: checks if anything is missing
if (cbPet.getSelectionModel().getSelectedItem() == null) { if (cbPet.getSelectionModel().getSelectedItem() == null) {
errorMsg += "Pet is required.\n"; errorMsg += "Pet is required.\n";
} }
@@ -142,60 +133,37 @@ public class AdoptionDialogController {
errorMsg += "Status is required.\n"; errorMsg += "Status is required.\n";
} }
//If no errors, attempt DB operation
if (errorMsg.isEmpty()) { 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")) {
if (mode.equals("Add")) { AdoptionApi.getInstance().createAdoption(request);
try { } else {
numRow = AdoptionDB.insertAdoption(adoption); 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 alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Saved"); alert.setHeaderText("Saved");
alert.setContentText(mode + " succeeded"); alert.setContentText(mode + " succeeded");
alert.showAndWait(); alert.showAndWait();
closeStage(mouseEvent); 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();
} }
} } else {
//If there are errors, display them
else {
Alert alert = new Alert(Alert.AlertType.ERROR); Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Input Error"); alert.setHeaderText("Input Error");
alert.setContentText(errorMsg); 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) { private void closeStage(MouseEvent mouseEvent) {
Node node = (Node) mouseEvent.getSource(); Node node = (Node) mouseEvent.getSource();
@@ -234,35 +178,28 @@ public class AdoptionDialogController {
stage.close(); stage.close();
} }
//Edit mode
//Inserts data into fields
public void displayAdoptionDetails(Adoption adoption) { public void displayAdoptionDetails(Adoption adoption) {
if (adoption != null) { if (adoption != null) {
//Displays adoption ID
lblAdoptionId.setText("ID: " + adoption.getAdoptionId()); lblAdoptionId.setText("ID: " + adoption.getAdoptionId());
//Select pet for (DropdownOption pet : cbPet.getItems()) {
for (Pet pet : cbPet.getItems()) { if (pet.getLabel().equals(adoption.getPetName())) {
if (pet.getPetId() == adoption.getPetId()) {
cbPet.getSelectionModel().select(pet); cbPet.getSelectionModel().select(pet);
break; break;
} }
} }
//Select customer for (DropdownOption customer : cbCustomer.getItems()) {
for (Customer customer : cbCustomer.getItems()) { if (customer.getLabel().equals(adoption.getCustomerName())) {
if (customer.getCustomerId() == adoption.getCustomerId()) {
cbCustomer.getSelectionModel().select(customer); cbCustomer.getSelectionModel().select(customer);
break; break;
} }
} }
//Select adoption date
if (adoption.getAdoptionDate() != null && !adoption.getAdoptionDate().isEmpty()) { if (adoption.getAdoptionDate() != null && !adoption.getAdoptionDate().isEmpty()) {
dpAdoptionDate.setValue(LocalDate.parse(adoption.getAdoptionDate())); dpAdoptionDate.setValue(LocalDate.parse(adoption.getAdoptionDate()));
} }
//Select adoption status
for (String status : cbAdoptionStatus.getItems()) { for (String status : cbAdoptionStatus.getItems()) {
if (status.equals(adoption.getAdoptionStatus())) { if (status.equals(adoption.getAdoptionStatus())) {
cbAdoptionStatus.getSelectionModel().select(status); 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) { public void setMode(String mode) {
this.mode = mode; this.mode = mode;
lblMode.setText(mode + " Adoption"); lblMode.setText(mode + " Adoption");

View File

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

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers.dialogcontrollers; package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.event.EventHandler; import javafx.event.EventHandler;
@@ -10,16 +11,14 @@ import javafx.scene.input.MouseEvent;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.ProductSupplierDTO; import org.example.petshopdesktop.DTOs.ProductSupplierDTO;
import org.example.petshopdesktop.Validator; import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.database.ProductDB; import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.database.ProductSupplierDB; import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierRequest;
import org.example.petshopdesktop.database.SupplierDB; import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierResponse;
import org.example.petshopdesktop.models.Product; import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.models.ProductSupplier; import org.example.petshopdesktop.api.endpoints.ProductSupplierApi;
import org.example.petshopdesktop.models.Supplier;
import org.example.petshopdesktop.util.ActivityLogger; import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException; import java.math.BigDecimal;
import java.sql.SQLIntegrityConstraintViolationException;
public class ProductSupplierDialogController { public class ProductSupplierDialogController {
@@ -30,10 +29,10 @@ public class ProductSupplierDialogController {
private Button btnSave; private Button btnSave;
@FXML @FXML
private ComboBox<Product> cbProduct; private ComboBox<DropdownOption> cbProduct;
@FXML @FXML
private ComboBox<Supplier> cbSupplier; private ComboBox<DropdownOption> cbSupplier;
@FXML @FXML
private Label lblMode; private Label lblMode;
@@ -47,6 +46,7 @@ public class ProductSupplierDialogController {
private String mode = null; private String mode = null;
private int selectedSupId = -1; private int selectedSupId = -1;
private int selectedProdId = -1; private int selectedProdId = -1;
private Long selectedId = null;
/** /**
* add event listeners to buttons and set up combobox * add event listeners to buttons and set up combobox
@@ -67,26 +67,74 @@ public class ProductSupplierDialogController {
} }
}); });
//Set up combobox for selecting product and supplier cbSupplier.setButtonCell(new ListCell<DropdownOption>() {
try{ @Override
ObservableList<Supplier> suppliers = FXCollections.observableArrayList(); //empty list protected void updateItem(DropdownOption item, boolean empty) {
ObservableList<Product> products = FXCollections.observableArrayList(); //empty list 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 cbProduct.setButtonCell(new ListCell<DropdownOption>() {
suppliers = SupplierDB.getSuppliers(); @Override
products = ProductDB.getProducts(); 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 new Thread(() -> {
cbSupplier.setItems(suppliers); try {
cbProduct.setItems(products); var suppliers = DropdownApi.getInstance().getSuppliers();
} var products = DropdownApi.getInstance().getProducts();
catch(SQLException e){
ActivityLogger.getInstance().logException( Platform.runLater(() -> {
"ProductSupplierDialogController.initialize", cbSupplier.setItems(FXCollections.observableArrayList(suppliers));
e, cbProduct.setItems(FXCollections.observableArrayList(products));
"Loading suppliers and products for combo boxes"); });
throw new RuntimeException(e); } 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 * @param mouseEvent click event for save button
*/ */
private void buttonSaveClicked(MouseEvent mouseEvent) { private void buttonSaveClicked(MouseEvent mouseEvent) {
int numRows = 0; String errorMsg = "";
String errorMsg = ""; //error message for validation
//Check Validation (input required)
errorMsg += Validator.isPresent(txtCost.getText(), "Cost"); errorMsg += Validator.isPresent(txtCost.getText(), "Cost");
if (cbProduct.getSelectionModel().getSelectedItem() == null) { if (cbProduct.getSelectionModel().getSelectedItem() == null) {
errorMsg += "Product is required \n"; errorMsg += "Product is required \n";
@@ -108,82 +154,41 @@ public class ProductSupplierDialogController {
errorMsg += "Supplier is required \n"; errorMsg += "Supplier is required \n";
} }
//Check validation (length size)
errorMsg += Validator.isLessThanVarChars(txtCost.getText(), "Cost", 12); errorMsg += Validator.isLessThanVarChars(txtCost.getText(), "Cost", 12);
//Check validation (format)
errorMsg += Validator.isNonNegativeDouble(txtCost.getText(), "Cost"); errorMsg += Validator.isNonNegativeDouble(txtCost.getText(), "Cost");
if(errorMsg.isEmpty()){ //no validation errors if(errorMsg.isEmpty()){
ProductSupplier productSupplier = collectProductSupplier(); //get productSupplier info ProductSupplierRequest request = collectProductSupplierRequest();
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 no rows were affected then there was an error (prompt user of error) new Thread(() -> {
if (numRows == 0){ try {
Alert alert = new Alert(Alert.AlertType.ERROR); if (mode.equals("Add")) {
alert.setHeaderText("Database Operation Error"); ProductSupplierApi.getInstance().createProductSupplier(request);
alert.setContentText(mode + " failed"); } else {
alert.showAndWait(); ProductSupplierApi.getInstance().updateProductSupplier(selectedId, request);
} }
else if (numRows > 0){
//tell the user operation was successful Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.INFORMATION); Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Saved"); alert.setHeaderText("Saved");
alert.setContentText(mode + " succeeded"); alert.setContentText(mode + " succeeded");
alert.showAndWait(); alert.showAndWait();
closeStage(mouseEvent); closeStage(mouseEvent);
} });
} } catch (Exception e) {
else { //Display validation errors 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 alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Input Error"); alert.setHeaderText("Input Error");
alert.setContentText(errorMsg); alert.setContentText(errorMsg);
@@ -193,18 +198,14 @@ public class ProductSupplierDialogController {
/** /**
* collect the data for new/updated productSupplier * collect the data for new/updated productSupplier
* @return productSupplier entity with data * @return productSupplier request with data
*/ */
private ProductSupplier collectProductSupplier() { private ProductSupplierRequest collectProductSupplierRequest() {
ProductSupplier productSupplier = null; ProductSupplierRequest request = new ProductSupplierRequest();
request.setSupplierId(cbSupplier.getSelectionModel().getSelectedItem().getId());
productSupplier = new ProductSupplier( request.setProductId(cbProduct.getSelectionModel().getSelectedItem().getId());
cbSupplier.getSelectionModel().getSelectedItem().getSupId(), request.setSupplierPrice(new BigDecimal(txtCost.getText()));
cbProduct.getSelectionModel().getSelectedItem().getProdId(), return request;
Double.parseDouble(txtCost.getText())
);
return productSupplier;
} }
/** /**
@@ -216,20 +217,17 @@ public class ProductSupplierDialogController {
txtCost.setText(productSupplier.getCost() + ""); txtCost.setText(productSupplier.getCost() + "");
} }
//Get the right combobox selection (product) for (DropdownOption product : cbProduct.getItems()) {
for (Product product : cbProduct.getItems()) { if(product.getId() == productSupplier.getProdId()){
if(product.getProdId() == productSupplier.getProdId()){
cbProduct.getSelectionModel().select(product); cbProduct.getSelectionModel().select(product);
} }
} }
//Get the right combobox selection (supplier) for (DropdownOption supplier : cbSupplier.getItems()) {
for (Supplier supplier : cbSupplier.getItems()) { if (supplier.getId() == productSupplier.getSupId()) {
if (supplier.getSupId() == productSupplier.getSupId()) {
cbSupplier.getSelectionModel().select(supplier); cbSupplier.getSelectionModel().select(supplier);
} }
} }
} }
/** /**
@@ -260,6 +258,7 @@ public class ProductSupplierDialogController {
public void setSelectedIds(int supId, int prodId){ public void setSelectedIds(int supId, int prodId){
this.selectedSupId = supId; this.selectedSupId = supId;
this.selectedProdId = prodId; 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.*;
import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage; 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.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 org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException; import java.math.BigDecimal;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;
@@ -31,19 +35,19 @@ public class RefundDialogController {
private Label lblSaleInfo; private Label lblSaleInfo;
@FXML @FXML
private TableView<SaleDetail.SaleDetailItem> tvOriginalItems; private TableView<SaleItemResponse> tvOriginalItems;
@FXML @FXML
private TableColumn<SaleDetail.SaleDetailItem, String> colOriginalProduct; private TableColumn<SaleItemResponse, String> colOriginalProduct;
@FXML @FXML
private TableColumn<SaleDetail.SaleDetailItem, Integer> colOriginalQuantity; private TableColumn<SaleItemResponse, Integer> colOriginalQuantity;
@FXML @FXML
private TableColumn<SaleDetail.SaleDetailItem, Double> colOriginalUnitPrice; private TableColumn<SaleItemResponse, BigDecimal> colOriginalUnitPrice;
@FXML @FXML
private TableColumn<SaleDetail.SaleDetailItem, Double> colOriginalTotal; private TableColumn<SaleItemResponse, BigDecimal> colOriginalTotal;
@FXML @FXML
private Button btnAddToRefund; private Button btnAddToRefund;
@@ -78,7 +82,7 @@ public class RefundDialogController {
@FXML @FXML
private Button btnCancel; private Button btnCancel;
private SaleDetail currentSale; private SaleResponse currentSale;
private final ObservableList<RefundItem> refundItems = FXCollections.observableArrayList(); private final ObservableList<RefundItem> refundItems = FXCollections.observableArrayList();
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA); private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
@@ -94,7 +98,7 @@ public class RefundDialogController {
colOriginalProduct.setCellValueFactory(new PropertyValueFactory<>("productName")); colOriginalProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
colOriginalQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity")); colOriginalQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
colOriginalUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice")); colOriginalUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
colOriginalTotal.setCellValueFactory(new PropertyValueFactory<>("total")); colOriginalTotal.setCellValueFactory(new PropertyValueFactory<>("lineTotal"));
tvOriginalItems.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); tvOriginalItems.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
colRefundProduct.setCellValueFactory(new PropertyValueFactory<>("productName")); colRefundProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
@@ -113,21 +117,25 @@ public class RefundDialogController {
return; return;
} }
int saleId; Long saleId;
try { try {
saleId = Integer.parseInt(saleIdText); saleId = Long.parseLong(saleIdText);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
showError("Load Sale", "Invalid transaction ID."); showError("Load Sale", "Invalid transaction ID.");
return; return;
} }
try { 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."); showError("Load Sale", "This sale has already been refunded.");
return; return;
} }
currentSale = SaleDB.getSaleById(saleId); currentSale = SaleApi.getInstance().getSale(saleId);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
String saleInfo = String.format("Sale Date: %s | Employee: %s | Original Total: %s | Payment: %s", String saleInfo = String.format("Sale Date: %s | Employee: %s | Original Total: %s | Payment: %s",
@@ -137,13 +145,13 @@ public class RefundDialogController {
currentSale.getPaymentMethod()); currentSale.getPaymentMethod());
lblSaleInfo.setText(saleInfo); lblSaleInfo.setText(saleInfo);
tvOriginalItems.setItems(currentSale.getItems()); tvOriginalItems.setItems(FXCollections.observableArrayList(currentSale.getItems()));
cbPaymentMethod.getSelectionModel().select(currentSale.getPaymentMethod()); cbPaymentMethod.getSelectionModel().select(currentSale.getPaymentMethod());
refundItems.clear(); refundItems.clear();
updateRefundTotal(); updateRefundTotal();
} catch (SQLException e) { } catch (Exception e) {
ActivityLogger.getInstance().logException("RefundDialogController.btnLoadSaleClicked", e, "Loading sale"); ActivityLogger.getInstance().logException("RefundDialogController.btnLoadSaleClicked", e, "Loading sale");
showError("Load Sale", e.getMessage() != null ? e.getMessage() : "Could not load sale."); showError("Load Sale", e.getMessage() != null ? e.getMessage() : "Could not load sale.");
} }
@@ -156,14 +164,14 @@ public class RefundDialogController {
return; return;
} }
SaleDetail.SaleDetailItem selected = tvOriginalItems.getSelectionModel().getSelectedItem(); SaleItemResponse selected = tvOriginalItems.getSelectionModel().getSelectedItem();
if (selected == null) { if (selected == null) {
showError("Add to Refund", "Select an item from the original sale."); showError("Add to Refund", "Select an item from the original sale.");
return; return;
} }
int alreadyRefunded = refundItems.stream() int alreadyRefunded = refundItems.stream()
.filter(r -> r.getProdId() == selected.getProdId()) .filter(r -> r.getProductId().equals(selected.getId()))
.mapToInt(RefundItem::getQuantity) .mapToInt(RefundItem::getQuantity)
.sum(); .sum();
@@ -192,7 +200,7 @@ public class RefundDialogController {
} }
refundItems.add(new RefundItem( refundItems.add(new RefundItem(
selected.getProdId(), selected.getId(),
selected.getProductName(), selected.getProductName(),
quantity, quantity,
selected.getUnitPrice() selected.getUnitPrice()
@@ -226,9 +234,9 @@ public class RefundDialogController {
return; return;
} }
Integer employeeId = UserSession.getInstance().getEmployeeId(); Long storeId = UserSession.getInstance().getStoreId();
if (employeeId == null || employeeId <= 0) { if (storeId == null || storeId <= 0) {
showError("Process Refund", "Employee is not set for this account."); showError("Process Refund", "Store is not set for this account.");
return; return;
} }
@@ -240,7 +248,7 @@ public class RefundDialogController {
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION); Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
confirm.setTitle("Confirm Refund"); 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()); confirm.setContentText("Refund amount: " + lblRefundTotal.getText());
Optional<ButtonType> confirmResult = confirm.showAndWait(); Optional<ButtonType> confirmResult = confirm.showAndWait();
@@ -249,22 +257,33 @@ public class RefundDialogController {
} }
try { try {
ObservableList<SaleCartItem> cartItems = FXCollections.observableArrayList(); SaleRequest request = new SaleRequest();
for (RefundItem item : refundItems) { request.setStoreId(storeId);
cartItems.add(new SaleCartItem(item.getProdId(), item.getProductName(), item.getQuantity(), item.getUnitPrice())); 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); Alert success = new Alert(Alert.AlertType.INFORMATION);
success.setTitle("Refund Processed"); success.setTitle("Refund Processed");
success.setHeaderText(null); success.setHeaderText(null);
success.setContentText("Refund ID " + refundId + " was created successfully."); success.setContentText("Refund ID " + refundResponse.getId() + " was created successfully.");
success.showAndWait(); success.showAndWait();
closeDialog(); closeDialog();
} catch (SQLException e) { } catch (Exception e) {
ActivityLogger.getInstance().logException("RefundDialogController.btnProcessRefundClicked", e, "Processing refund"); ActivityLogger.getInstance().logException("RefundDialogController.btnProcessRefundClicked", e, "Processing refund");
showError("Process Refund", e.getMessage() != null ? e.getMessage() : "Could not process 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; package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.Node; import javafx.scene.control.Alert;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.example.petshopdesktop.database.ServiceDB; import org.example.petshopdesktop.DTOs.ServiceDTO;
import org.example.petshopdesktop.models.Service; import org.example.petshopdesktop.api.dto.service.ServiceRequest;
import org.example.petshopdesktop.api.endpoints.ServiceApi;
import org.example.petshopdesktop.util.ActivityLogger; import org.example.petshopdesktop.util.ActivityLogger;
import javafx.scene.control.Alert;
import javafx.scene.control.ComboBox; import java.math.BigDecimal;
public class ServiceDialogController { public class ServiceDialogController {
@@ -45,7 +46,7 @@ public class ServiceDialogController {
private ComboBox<Integer> cbMinutes; private ComboBox<Integer> cbMinutes;
private String mode; 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; this.selectedService = service;
lblServiceId.setText("ID: " + service.getServiceId()); lblServiceId.setText("ID: " + service.getServiceId());

View File

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

View File

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

View File

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

View File

@@ -7,14 +7,21 @@ public class Inventory {
private SimpleIntegerProperty inventoryId; private SimpleIntegerProperty inventoryId;
private SimpleIntegerProperty prodId; private SimpleIntegerProperty prodId;
private SimpleStringProperty prodName; private SimpleStringProperty prodName;
private SimpleStringProperty categoryName;
private SimpleIntegerProperty storeId;
private SimpleStringProperty storeName;
private SimpleIntegerProperty quantity; private SimpleIntegerProperty quantity;
private SimpleIntegerProperty reorderLevel;
//Constructor public Inventory(int inventoryId, int prodId, String prodName, String categoryName, int storeId, String storeName, int quantity, int reorderLevel) {
public Inventory(int inventoryId, int prodId, String prodName, int quantity) {
this.inventoryId = new SimpleIntegerProperty(inventoryId); this.inventoryId = new SimpleIntegerProperty(inventoryId);
this.prodId = new SimpleIntegerProperty(prodId); this.prodId = new SimpleIntegerProperty(prodId);
this.prodName = new SimpleStringProperty(prodName); 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.quantity = new SimpleIntegerProperty(quantity);
this.reorderLevel = new SimpleIntegerProperty(reorderLevel);
} }
public int getInventoryId() { return inventoryId.get(); } public int getInventoryId() { return inventoryId.get(); }
@@ -35,9 +42,33 @@ public class Inventory {
public SimpleStringProperty prodNameProperty() { return prodName; } 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 int getQuantity() { return quantity.get(); }
public void setQuantity(int quantity) { this.quantity.set(quantity); } public void setQuantity(int quantity) { this.quantity.set(quantity); }
public SimpleIntegerProperty quantityProperty() { return 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; package org.example.petshopdesktop.models;
import java.math.BigDecimal;
import java.time.LocalDate;
public class PurchaseOrder { public class PurchaseOrder {
private int purchaseOrderId; private long purchaseOrderId;
private int supId; private String supplierName;
private String orderDate; private LocalDate orderDate;
private LocalDate expectedDeliveryDate;
private String status; private String status;
private BigDecimal totalAmount;
public PurchaseOrder(int purchaseOrderId, public PurchaseOrder(long purchaseOrderId,
int supId, String supplierName,
String orderDate, LocalDate orderDate,
String status) { LocalDate expectedDeliveryDate,
String status,
BigDecimal totalAmount) {
this.purchaseOrderId = purchaseOrderId; this.purchaseOrderId = purchaseOrderId;
this.supId = supId; this.supplierName = supplierName;
this.orderDate = orderDate; this.orderDate = orderDate;
this.expectedDeliveryDate = expectedDeliveryDate;
this.status = status; this.status = status;
this.totalAmount = totalAmount;
} }
public int getPurchaseOrderId() { public long getPurchaseOrderId() {
return purchaseOrderId; return purchaseOrderId;
} }
public int getSupId() { public String getSupplierName() {
return supId; return supplierName;
} }
public String getOrderDate() { public LocalDate getOrderDate() {
return orderDate; return orderDate;
} }
public LocalDate getExpectedDeliveryDate() {
return expectedDeliveryDate;
}
public String getStatus() { public String getStatus() {
return status; return status;
} }
public BigDecimal getTotalAmount() {
return totalAmount;
}
} }

View File

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