Fix sale-view loading error by suppressing error dialogs during initialization
This commit is contained in:
@@ -1,11 +1,37 @@
|
|||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
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.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.SelectionMode;
|
||||||
|
import javafx.scene.control.Spinner;
|
||||||
|
import javafx.scene.control.SpinnerValueFactory;
|
||||||
import javafx.scene.control.TableColumn;
|
import javafx.scene.control.TableColumn;
|
||||||
import javafx.scene.control.TableView;
|
import javafx.scene.control.TableView;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import org.example.petshopdesktop.auth.UserSession;
|
||||||
|
import org.example.petshopdesktop.database.InventoryDB;
|
||||||
|
import org.example.petshopdesktop.database.ProductDB;
|
||||||
|
import org.example.petshopdesktop.database.SaleDB;
|
||||||
|
import org.example.petshopdesktop.models.Inventory;
|
||||||
|
import org.example.petshopdesktop.models.Product;
|
||||||
|
import org.example.petshopdesktop.models.SaleCartItem;
|
||||||
|
import org.example.petshopdesktop.models.SaleLineItem;
|
||||||
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class SaleController {
|
public class SaleController {
|
||||||
|
|
||||||
@@ -13,38 +39,324 @@ public class SaleController {
|
|||||||
private Button btnRefresh;
|
private Button btnRefresh;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableColumn<?, ?> colCustomerName;
|
private Label lblModeNote;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableColumn<?, ?> colSaleDate;
|
private VBox vbCreateSale;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableColumn<?, ?> colSaleId;
|
private ComboBox<Product> cbProduct;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableColumn<?, ?> colSalePaymentType;
|
private Spinner<Integer> spQuantity;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableColumn<?, ?> colSaleQuantity;
|
private Button btnAddToCart;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableColumn<?, ?> colSaleTotal;
|
private Button btnRemoveSelected;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableColumn<?, ?> colSaleUnitPrice;
|
private TableView<SaleCartItem> tvCart;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableColumn<?, ?> colServiceProduct;
|
private TableColumn<SaleCartItem, String> colCartProduct;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableView<?> tvSales;
|
private TableColumn<SaleCartItem, Integer> colCartQty;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleCartItem, Double> colCartUnitPrice;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleCartItem, Double> colCartTotal;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ComboBox<String> cbPaymentMethod;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label lblCartTotal;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnClearCart;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnSaveSale;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleLineItem, Integer> colSaleId;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleLineItem, String> colSaleDate;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleLineItem, String> colEmployeeName;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleLineItem, String> colServiceProduct;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleLineItem, Integer> colSaleQuantity;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleLineItem, Double> colSaleUnitPrice;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleLineItem, Double> colSaleTotal;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleLineItem, String> colSalePaymentType;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableView<SaleLineItem> tvSales;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TextField txtSearch;
|
private TextField txtSearch;
|
||||||
|
|
||||||
|
private final ObservableList<SaleCartItem> cartItems = FXCollections.observableArrayList();
|
||||||
|
private final ObservableList<SaleLineItem> saleItems = FXCollections.observableArrayList();
|
||||||
|
private FilteredList<SaleLineItem> filteredSales;
|
||||||
|
|
||||||
|
private final Map<Integer, Integer> inventoryByProdId = new HashMap<>();
|
||||||
|
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
setupTables();
|
||||||
|
setupCreateSale();
|
||||||
|
applyRoleMode();
|
||||||
|
|
||||||
|
refreshInventory();
|
||||||
|
refreshSales();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupTables() {
|
||||||
|
colCartProduct.setCellValueFactory(new PropertyValueFactory<>("prodName"));
|
||||||
|
colCartQty.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||||
|
colCartUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
||||||
|
colCartTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
|
||||||
|
tvCart.setItems(cartItems);
|
||||||
|
tvCart.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||||
|
|
||||||
|
colSaleId.setCellValueFactory(new PropertyValueFactory<>("saleId"));
|
||||||
|
colSaleDate.setCellValueFactory(new PropertyValueFactory<>("saleDate"));
|
||||||
|
colEmployeeName.setCellValueFactory(new PropertyValueFactory<>("employeeName"));
|
||||||
|
colServiceProduct.setCellValueFactory(new PropertyValueFactory<>("itemName"));
|
||||||
|
colSaleQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||||
|
colSaleUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
||||||
|
colSaleTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
|
||||||
|
colSalePaymentType.setCellValueFactory(new PropertyValueFactory<>("paymentMethod"));
|
||||||
|
|
||||||
|
filteredSales = new FilteredList<>(saleItems, s -> true);
|
||||||
|
tvSales.setItems(filteredSales);
|
||||||
|
|
||||||
|
txtSearch.textProperty().addListener((obs, oldVal, newVal) -> applySalesFilter(newVal));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupCreateSale() {
|
||||||
|
spQuantity.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 999, 1));
|
||||||
|
spQuantity.setEditable(true);
|
||||||
|
|
||||||
|
cbPaymentMethod.setItems(FXCollections.observableArrayList("Cash", "Card"));
|
||||||
|
cbPaymentMethod.getSelectionModel().selectFirst();
|
||||||
|
|
||||||
|
updateCartTotal();
|
||||||
|
|
||||||
|
try {
|
||||||
|
cbProduct.setItems(ProductDB.getProducts());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
ActivityLogger.getInstance().logException("SaleController.setupCreateSale", e, "Loading products");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
ActivityLogger.getInstance().logException("SaleController.setupCreateSale", e, "Database connection");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyRoleMode() {
|
||||||
|
boolean isAdmin = UserSession.getInstance().isAdmin();
|
||||||
|
vbCreateSale.setVisible(!isAdmin);
|
||||||
|
vbCreateSale.setManaged(!isAdmin);
|
||||||
|
lblModeNote.setText(isAdmin ? "(View only)" : "(Staff can create sales)");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshInventory() {
|
||||||
|
inventoryByProdId.clear();
|
||||||
|
try {
|
||||||
|
for (Inventory inv : InventoryDB.getInventory()) {
|
||||||
|
inventoryByProdId.put(inv.getProdId(), inv.getQuantity());
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
ActivityLogger.getInstance().logException("SaleController.refreshInventory", e, "Loading inventory");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
ActivityLogger.getInstance().logException("SaleController.refreshInventory", e, "Database connection");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshSales() {
|
||||||
|
refreshSales(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshSales(boolean showErrorDialog) {
|
||||||
|
try {
|
||||||
|
saleItems.setAll(SaleDB.getSaleLineItems());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
ActivityLogger.getInstance().logException("SaleController.refreshSales", e, "Loading sales");
|
||||||
|
if (showErrorDialog) {
|
||||||
|
showError("Sales", "Could not load sales.");
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
ActivityLogger.getInstance().logException("SaleController.refreshSales", e, "Database connection");
|
||||||
|
if (showErrorDialog) {
|
||||||
|
showError("Sales", "Database is not connected.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void btnRefresh(ActionEvent event) {
|
void btnRefresh(ActionEvent event) {
|
||||||
|
refreshInventory();
|
||||||
|
refreshSales(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnAddToCart(ActionEvent event) {
|
||||||
|
Product product = cbProduct.getSelectionModel().getSelectedItem();
|
||||||
|
if (product == null) {
|
||||||
|
showError("Create Sale", "Select a product.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int requestedQty;
|
||||||
|
try {
|
||||||
|
requestedQty = spQuantity.getValue();
|
||||||
|
} catch (Exception e) {
|
||||||
|
showError("Create Sale", "Enter a valid quantity.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (requestedQty <= 0) {
|
||||||
|
showError("Create Sale", "Quantity must be at least 1.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int stock = inventoryByProdId.getOrDefault(product.getProdId(), 0);
|
||||||
|
int alreadyInCart = cartItems.stream()
|
||||||
|
.filter(i -> i.getProdId() == product.getProdId())
|
||||||
|
.mapToInt(SaleCartItem::getQuantity)
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
int available = stock - alreadyInCart;
|
||||||
|
if (requestedQty > available) {
|
||||||
|
showError("Create Sale", "Not enough stock. Available: " + Math.max(0, available));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SaleCartItem item : cartItems) {
|
||||||
|
if (item.getProdId() == product.getProdId()) {
|
||||||
|
item.setQuantity(item.getQuantity() + requestedQty);
|
||||||
|
tvCart.refresh();
|
||||||
|
updateCartTotal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cartItems.add(new SaleCartItem(product.getProdId(), product.getProdName(), requestedQty, product.getProdPrice()));
|
||||||
|
updateCartTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnRemoveSelected(ActionEvent event) {
|
||||||
|
SaleCartItem selected = tvCart.getSelectionModel().getSelectedItem();
|
||||||
|
if (selected != null) {
|
||||||
|
cartItems.remove(selected);
|
||||||
|
updateCartTotal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnClearCart(ActionEvent event) {
|
||||||
|
cartItems.clear();
|
||||||
|
updateCartTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnSaveSale(ActionEvent event) {
|
||||||
|
if (UserSession.getInstance().isAdmin()) {
|
||||||
|
showError("Create Sale", "This action is restricted to staff.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer employeeId = UserSession.getInstance().getEmployeeId();
|
||||||
|
if (employeeId == null || employeeId <= 0) {
|
||||||
|
showError("Create Sale", "Employee is not set for this account.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cartItems.isEmpty()) {
|
||||||
|
showError("Create Sale", "Add at least one item.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String payment = cbPaymentMethod.getSelectionModel().getSelectedItem();
|
||||||
|
if (payment == null || payment.isBlank()) {
|
||||||
|
showError("Create Sale", "Select a payment method.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
int saleId = SaleDB.createSale(employeeId, payment, cartItems);
|
||||||
|
showInfo("Sale saved", "Sale ID " + saleId + " was created.");
|
||||||
|
|
||||||
|
cartItems.clear();
|
||||||
|
updateCartTotal();
|
||||||
|
|
||||||
|
refreshInventory();
|
||||||
|
refreshSales(true);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", e, "Creating sale");
|
||||||
|
showError("Create Sale", e.getMessage() == null ? "Could not save the sale." : e.getMessage());
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", e, "Database connection");
|
||||||
|
showError("Create Sale", "Database is not connected.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCartTotal() {
|
||||||
|
double total = cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum();
|
||||||
|
lblCartTotal.setText(currency.format(total));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySalesFilter(String filter) {
|
||||||
|
String f = filter == null ? "" : filter.trim().toLowerCase();
|
||||||
|
if (f.isEmpty()) {
|
||||||
|
filteredSales.setPredicate(s -> true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredSales.setPredicate(s ->
|
||||||
|
String.valueOf(s.getSaleId()).contains(f)
|
||||||
|
|| safe(s.getSaleDate()).contains(f)
|
||||||
|
|| safe(s.getEmployeeName()).contains(f)
|
||||||
|
|| safe(s.getItemName()).contains(f)
|
||||||
|
|| safe(s.getPaymentMethod()).contains(f)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String safe(String v) {
|
||||||
|
return v == null ? "" : v.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showError(String title, String message) {
|
||||||
|
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||||
|
alert.setTitle(title);
|
||||||
|
alert.setHeaderText(null);
|
||||||
|
alert.setContentText(message);
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showInfo(String title, String message) {
|
||||||
|
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||||
|
alert.setTitle(title);
|
||||||
|
alert.setHeaderText(null);
|
||||||
|
alert.setContentText(message);
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user