Implement refund system for sales
This commit is contained in:
@@ -132,8 +132,11 @@ CREATE TABLE sale (
|
|||||||
paymentMethod VARCHAR(50) NOT NULL,
|
paymentMethod VARCHAR(50) NOT NULL,
|
||||||
employeeId INT NOT NULL,
|
employeeId INT NOT NULL,
|
||||||
storeId INT NOT NULL,
|
storeId INT NOT NULL,
|
||||||
|
isRefund BOOLEAN DEFAULT FALSE NOT NULL,
|
||||||
|
originalSaleId INT NULL,
|
||||||
FOREIGN KEY (employeeId) REFERENCES employee(employeeId),
|
FOREIGN KEY (employeeId) REFERENCES employee(employeeId),
|
||||||
FOREIGN KEY (storeId) REFERENCES storeLocation(storeId)
|
FOREIGN KEY (storeId) REFERENCES storeLocation(storeId),
|
||||||
|
FOREIGN KEY (originalSaleId) REFERENCES sale(saleId)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE saleItem (
|
CREATE TABLE saleItem (
|
||||||
|
|||||||
14
refund_migration.sql
Normal file
14
refund_migration.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- Database Migration Script for Refund System
|
||||||
|
-- Run this on existing Petstoredb to add refund functionality
|
||||||
|
|
||||||
|
USE Petstoredb;
|
||||||
|
|
||||||
|
-- Add refund columns to sale table
|
||||||
|
ALTER TABLE sale
|
||||||
|
ADD COLUMN isRefund BOOLEAN DEFAULT FALSE NOT NULL,
|
||||||
|
ADD COLUMN originalSaleId INT NULL,
|
||||||
|
ADD CONSTRAINT fk_original_sale
|
||||||
|
FOREIGN KEY (originalSaleId) REFERENCES sale(saleId);
|
||||||
|
|
||||||
|
-- Verify the changes
|
||||||
|
DESCRIBE sale;
|
||||||
@@ -5,6 +5,8 @@ import javafx.collections.ObservableList;
|
|||||||
import javafx.collections.transformation.FilteredList;
|
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.scene.Scene;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
@@ -17,6 +19,8 @@ import javafx.scene.control.TableView;
|
|||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.control.cell.PropertyValueFactory;
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.Stage;
|
||||||
import org.example.petshopdesktop.auth.UserSession;
|
import org.example.petshopdesktop.auth.UserSession;
|
||||||
import org.example.petshopdesktop.database.InventoryDB;
|
import org.example.petshopdesktop.database.InventoryDB;
|
||||||
import org.example.petshopdesktop.database.ProductDB;
|
import org.example.petshopdesktop.database.ProductDB;
|
||||||
@@ -38,6 +42,9 @@ public class SaleController {
|
|||||||
@FXML
|
@FXML
|
||||||
private Button btnRefresh;
|
private Button btnRefresh;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnRefund;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label lblModeNote;
|
private Label lblModeNote;
|
||||||
|
|
||||||
@@ -319,6 +326,30 @@ public class SaleController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnRefund(ActionEvent event) {
|
||||||
|
openRefundDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openRefundDialog() {
|
||||||
|
try {
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||||
|
"/org/example/petshopdesktop/dialogviews/refund-dialog-view.fxml"));
|
||||||
|
Stage dialog = new Stage();
|
||||||
|
dialog.initOwner(btnRefund.getScene().getWindow());
|
||||||
|
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||||
|
dialog.setTitle("Process Refund");
|
||||||
|
dialog.setScene(new Scene(loader.load()));
|
||||||
|
dialog.setResizable(false);
|
||||||
|
dialog.showAndWait();
|
||||||
|
|
||||||
|
refreshInventory();
|
||||||
|
refreshSales(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ActivityLogger.getInstance().logException("SaleController.openRefundDialog", e, "Opening refund dialog");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateCartTotal() {
|
private void updateCartTotal() {
|
||||||
double total = cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum();
|
double total = cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum();
|
||||||
lblCartTotal.setText(currency.format(total));
|
lblCartTotal.setText(currency.format(total));
|
||||||
|
|||||||
@@ -0,0 +1,329 @@
|
|||||||
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.example.petshopdesktop.auth.UserSession;
|
||||||
|
import org.example.petshopdesktop.database.SaleDB;
|
||||||
|
import org.example.petshopdesktop.models.SaleCartItem;
|
||||||
|
import org.example.petshopdesktop.models.SaleDetail;
|
||||||
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class RefundDialogController {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField txtSaleId;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnLoadSale;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label lblSaleInfo;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableView<SaleDetail.SaleDetailItem> tvOriginalItems;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleDetail.SaleDetailItem, String> colOriginalProduct;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleDetail.SaleDetailItem, Integer> colOriginalQuantity;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleDetail.SaleDetailItem, Double> colOriginalUnitPrice;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleDetail.SaleDetailItem, Double> colOriginalTotal;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnAddToRefund;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableView<RefundItem> tvRefundItems;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<RefundItem, String> colRefundProduct;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<RefundItem, Integer> colRefundQuantity;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<RefundItem, Double> colRefundUnitPrice;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<RefundItem, Double> colRefundTotal;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnRemoveFromRefund;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ComboBox<String> cbPaymentMethod;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label lblRefundTotal;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnProcessRefund;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnCancel;
|
||||||
|
|
||||||
|
private SaleDetail currentSale;
|
||||||
|
private final ObservableList<RefundItem> refundItems = FXCollections.observableArrayList();
|
||||||
|
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
setupTables();
|
||||||
|
cbPaymentMethod.setItems(FXCollections.observableArrayList("Cash", "Card", "Debit"));
|
||||||
|
cbPaymentMethod.getSelectionModel().selectFirst();
|
||||||
|
updateRefundTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupTables() {
|
||||||
|
colOriginalProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
|
||||||
|
colOriginalQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||||
|
colOriginalUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
||||||
|
colOriginalTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
|
||||||
|
tvOriginalItems.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||||
|
|
||||||
|
colRefundProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
|
||||||
|
colRefundQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||||
|
colRefundUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
||||||
|
colRefundTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
|
||||||
|
tvRefundItems.setItems(refundItems);
|
||||||
|
tvRefundItems.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnLoadSaleClicked(ActionEvent event) {
|
||||||
|
String saleIdText = txtSaleId.getText().trim();
|
||||||
|
if (saleIdText.isEmpty()) {
|
||||||
|
showError("Load Sale", "Enter a transaction ID.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int saleId;
|
||||||
|
try {
|
||||||
|
saleId = Integer.parseInt(saleIdText);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
showError("Load Sale", "Invalid transaction ID.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (SaleDB.isRefunded(saleId)) {
|
||||||
|
showError("Load Sale", "This sale has already been refunded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSale = SaleDB.getSaleById(saleId);
|
||||||
|
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||||
|
String saleInfo = String.format("Sale Date: %s | Employee: %s | Original Total: %s | Payment: %s",
|
||||||
|
currentSale.getSaleDate().format(formatter),
|
||||||
|
currentSale.getEmployeeName(),
|
||||||
|
currency.format(currentSale.getTotalAmount()),
|
||||||
|
currentSale.getPaymentMethod());
|
||||||
|
lblSaleInfo.setText(saleInfo);
|
||||||
|
|
||||||
|
tvOriginalItems.setItems(currentSale.getItems());
|
||||||
|
cbPaymentMethod.getSelectionModel().select(currentSale.getPaymentMethod());
|
||||||
|
|
||||||
|
refundItems.clear();
|
||||||
|
updateRefundTotal();
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
ActivityLogger.getInstance().logException("RefundDialogController.btnLoadSaleClicked", e, "Loading sale");
|
||||||
|
showError("Load Sale", e.getMessage() != null ? e.getMessage() : "Could not load sale.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnAddToRefundClicked(ActionEvent event) {
|
||||||
|
if (currentSale == null) {
|
||||||
|
showError("Add to Refund", "Load a sale first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SaleDetail.SaleDetailItem selected = tvOriginalItems.getSelectionModel().getSelectedItem();
|
||||||
|
if (selected == null) {
|
||||||
|
showError("Add to Refund", "Select an item from the original sale.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int alreadyRefunded = refundItems.stream()
|
||||||
|
.filter(r -> r.getProdId() == selected.getProdId())
|
||||||
|
.mapToInt(RefundItem::getQuantity)
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
int available = selected.getQuantity() - alreadyRefunded;
|
||||||
|
if (available <= 0) {
|
||||||
|
showError("Add to Refund", "All items of this product are already in the refund list.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextInputDialog dialog = new TextInputDialog(String.valueOf(available));
|
||||||
|
dialog.setTitle("Refund Quantity");
|
||||||
|
dialog.setHeaderText("Product: " + selected.getProductName());
|
||||||
|
dialog.setContentText("Enter quantity to refund (max " + available + "):");
|
||||||
|
|
||||||
|
Optional<String> result = dialog.showAndWait();
|
||||||
|
if (result.isPresent()) {
|
||||||
|
try {
|
||||||
|
int quantity = Integer.parseInt(result.get().trim());
|
||||||
|
if (quantity <= 0) {
|
||||||
|
showError("Add to Refund", "Quantity must be at least 1.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (quantity > available) {
|
||||||
|
showError("Add to Refund", "Cannot refund more than " + available + " items.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
refundItems.add(new RefundItem(
|
||||||
|
selected.getProdId(),
|
||||||
|
selected.getProductName(),
|
||||||
|
quantity,
|
||||||
|
selected.getUnitPrice()
|
||||||
|
));
|
||||||
|
updateRefundTotal();
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
showError("Add to Refund", "Invalid quantity.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnRemoveFromRefundClicked(ActionEvent event) {
|
||||||
|
RefundItem selected = tvRefundItems.getSelectionModel().getSelectedItem();
|
||||||
|
if (selected != null) {
|
||||||
|
refundItems.remove(selected);
|
||||||
|
updateRefundTotal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnProcessRefundClicked(ActionEvent event) {
|
||||||
|
if (currentSale == null) {
|
||||||
|
showError("Process Refund", "Load a sale first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refundItems.isEmpty()) {
|
||||||
|
showError("Process Refund", "Add at least one item to refund.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer employeeId = UserSession.getInstance().getEmployeeId();
|
||||||
|
if (employeeId == null || employeeId <= 0) {
|
||||||
|
showError("Process Refund", "Employee is not set for this account.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String payment = cbPaymentMethod.getSelectionModel().getSelectedItem();
|
||||||
|
if (payment == null || payment.isBlank()) {
|
||||||
|
showError("Process Refund", "Select a payment method.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
|
||||||
|
confirm.setTitle("Confirm Refund");
|
||||||
|
confirm.setHeaderText("Process refund for sale ID " + currentSale.getSaleId() + "?");
|
||||||
|
confirm.setContentText("Refund amount: " + lblRefundTotal.getText());
|
||||||
|
|
||||||
|
Optional<ButtonType> confirmResult = confirm.showAndWait();
|
||||||
|
if (confirmResult.isEmpty() || confirmResult.get() != ButtonType.OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ObservableList<SaleCartItem> cartItems = FXCollections.observableArrayList();
|
||||||
|
for (RefundItem item : refundItems) {
|
||||||
|
cartItems.add(new SaleCartItem(item.getProdId(), item.getProductName(), item.getQuantity(), item.getUnitPrice()));
|
||||||
|
}
|
||||||
|
|
||||||
|
int refundId = SaleDB.createRefund(currentSale.getSaleId(), employeeId, payment, cartItems);
|
||||||
|
|
||||||
|
Alert success = new Alert(Alert.AlertType.INFORMATION);
|
||||||
|
success.setTitle("Refund Processed");
|
||||||
|
success.setHeaderText(null);
|
||||||
|
success.setContentText("Refund ID " + refundId + " was created successfully.");
|
||||||
|
success.showAndWait();
|
||||||
|
|
||||||
|
closeDialog();
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
ActivityLogger.getInstance().logException("RefundDialogController.btnProcessRefundClicked", e, "Processing refund");
|
||||||
|
showError("Process Refund", e.getMessage() != null ? e.getMessage() : "Could not process refund.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnCancelClicked(ActionEvent event) {
|
||||||
|
closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateRefundTotal() {
|
||||||
|
double total = refundItems.stream().mapToDouble(RefundItem::getTotal).sum();
|
||||||
|
lblRefundTotal.setText(currency.format(total));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeDialog() {
|
||||||
|
Stage stage = (Stage) btnCancel.getScene().getWindow();
|
||||||
|
stage.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RefundItem {
|
||||||
|
private final int prodId;
|
||||||
|
private final String productName;
|
||||||
|
private final int quantity;
|
||||||
|
private final double unitPrice;
|
||||||
|
|
||||||
|
public RefundItem(int prodId, String productName, int quantity, double unitPrice) {
|
||||||
|
this.prodId = prodId;
|
||||||
|
this.productName = productName;
|
||||||
|
this.quantity = quantity;
|
||||||
|
this.unitPrice = unitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProdId() {
|
||||||
|
return prodId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductName() {
|
||||||
|
return productName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQuantity() {
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getUnitPrice() {
|
||||||
|
return unitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTotal() {
|
||||||
|
return quantity * unitPrice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import javafx.collections.FXCollections;
|
|||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import org.example.petshopdesktop.DTOs.SaleDTO;
|
import org.example.petshopdesktop.DTOs.SaleDTO;
|
||||||
import org.example.petshopdesktop.models.SaleCartItem;
|
import org.example.petshopdesktop.models.SaleCartItem;
|
||||||
|
import org.example.petshopdesktop.models.SaleDetail;
|
||||||
import org.example.petshopdesktop.models.SaleLineItem;
|
import org.example.petshopdesktop.models.SaleLineItem;
|
||||||
import org.example.petshopdesktop.models.analytics.*;
|
import org.example.petshopdesktop.models.analytics.*;
|
||||||
import org.example.petshopdesktop.util.ActivityLogger;
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
@@ -129,7 +130,8 @@ public class SaleDB {
|
|||||||
si.quantity,
|
si.quantity,
|
||||||
si.unitPrice,
|
si.unitPrice,
|
||||||
(si.quantity * si.unitPrice) as total,
|
(si.quantity * si.unitPrice) as total,
|
||||||
s.paymentMethod
|
s.paymentMethod,
|
||||||
|
s.isRefund
|
||||||
FROM sale s
|
FROM sale s
|
||||||
JOIN saleItem si ON s.saleId = si.saleId
|
JOIN saleItem si ON s.saleId = si.saleId
|
||||||
JOIN product p ON si.prodId = p.prodId
|
JOIN product p ON si.prodId = p.prodId
|
||||||
@@ -149,7 +151,8 @@ public class SaleDB {
|
|||||||
rs.getInt("quantity"),
|
rs.getInt("quantity"),
|
||||||
rs.getDouble("unitPrice"),
|
rs.getDouble("unitPrice"),
|
||||||
rs.getDouble("total"),
|
rs.getDouble("total"),
|
||||||
rs.getString("paymentMethod")
|
rs.getString("paymentMethod"),
|
||||||
|
rs.getBoolean("isRefund")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,4 +400,173 @@ public class SaleDB {
|
|||||||
conn.close();
|
conn.close();
|
||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SaleDetail getSaleById(int saleId) throws SQLException {
|
||||||
|
Connection conn = ConnectionDB.getConnection();
|
||||||
|
|
||||||
|
String saleSql = """
|
||||||
|
SELECT s.saleId, s.saleDate, s.totalAmount, s.paymentMethod,
|
||||||
|
CONCAT(e.firstName, ' ', e.lastName) as employeeName,
|
||||||
|
s.isRefund
|
||||||
|
FROM sale s
|
||||||
|
JOIN employee e ON s.employeeId = e.employeeId
|
||||||
|
WHERE s.saleId = ?
|
||||||
|
""";
|
||||||
|
|
||||||
|
PreparedStatement saleStmt = conn.prepareStatement(saleSql);
|
||||||
|
saleStmt.setInt(1, saleId);
|
||||||
|
ResultSet saleRs = saleStmt.executeQuery();
|
||||||
|
|
||||||
|
if (!saleRs.next()) {
|
||||||
|
conn.close();
|
||||||
|
throw new SQLException("Sale not found with ID: " + saleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isRefund = saleRs.getBoolean("isRefund");
|
||||||
|
if (isRefund) {
|
||||||
|
conn.close();
|
||||||
|
throw new SQLException("Cannot refund a refund transaction");
|
||||||
|
}
|
||||||
|
|
||||||
|
SaleDetail detail = new SaleDetail(
|
||||||
|
saleRs.getInt("saleId"),
|
||||||
|
saleRs.getTimestamp("saleDate").toLocalDateTime(),
|
||||||
|
saleRs.getDouble("totalAmount"),
|
||||||
|
saleRs.getString("paymentMethod"),
|
||||||
|
saleRs.getString("employeeName"),
|
||||||
|
FXCollections.observableArrayList()
|
||||||
|
);
|
||||||
|
|
||||||
|
String itemsSql = """
|
||||||
|
SELECT si.prodId, p.prodName, si.quantity, si.unitPrice,
|
||||||
|
(si.quantity * si.unitPrice) as total
|
||||||
|
FROM saleItem si
|
||||||
|
JOIN product p ON si.prodId = p.prodId
|
||||||
|
WHERE si.saleId = ?
|
||||||
|
""";
|
||||||
|
|
||||||
|
PreparedStatement itemsStmt = conn.prepareStatement(itemsSql);
|
||||||
|
itemsStmt.setInt(1, saleId);
|
||||||
|
ResultSet itemsRs = itemsStmt.executeQuery();
|
||||||
|
|
||||||
|
while (itemsRs.next()) {
|
||||||
|
detail.getItems().add(new SaleDetail.SaleDetailItem(
|
||||||
|
itemsRs.getInt("prodId"),
|
||||||
|
itemsRs.getString("prodName"),
|
||||||
|
itemsRs.getInt("quantity"),
|
||||||
|
itemsRs.getDouble("unitPrice"),
|
||||||
|
itemsRs.getDouble("total")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.close();
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isRefunded(int saleId) throws SQLException {
|
||||||
|
Connection conn = ConnectionDB.getConnection();
|
||||||
|
|
||||||
|
String sql = """
|
||||||
|
SELECT COUNT(*) as refundCount
|
||||||
|
FROM sale
|
||||||
|
WHERE originalSaleId = ? AND isRefund = TRUE
|
||||||
|
""";
|
||||||
|
|
||||||
|
PreparedStatement pstmt = conn.prepareStatement(sql);
|
||||||
|
pstmt.setInt(1, saleId);
|
||||||
|
ResultSet rs = pstmt.executeQuery();
|
||||||
|
|
||||||
|
boolean refunded = false;
|
||||||
|
if (rs.next()) {
|
||||||
|
refunded = rs.getInt("refundCount") > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.close();
|
||||||
|
return refunded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int createRefund(int originalSaleId, int employeeId, String paymentMethod, ObservableList<SaleCartItem> refundItems) throws SQLException {
|
||||||
|
if (refundItems.isEmpty()) {
|
||||||
|
throw new SQLException("Cannot create refund with empty items");
|
||||||
|
}
|
||||||
|
|
||||||
|
Connection conn = ConnectionDB.getConnection();
|
||||||
|
|
||||||
|
SaleDetail originalSale = getSaleById(originalSaleId);
|
||||||
|
conn = ConnectionDB.getConnection();
|
||||||
|
|
||||||
|
if (isRefunded(originalSaleId)) {
|
||||||
|
throw new SQLException("This sale has already been refunded");
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.setAutoCommit(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
double totalAmount = -refundItems.stream().mapToDouble(SaleCartItem::getTotal).sum();
|
||||||
|
|
||||||
|
String insertSale = """
|
||||||
|
INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId, isRefund, originalSaleId)
|
||||||
|
VALUES (NOW(), ?, ?, ?, 1, TRUE, ?)
|
||||||
|
""";
|
||||||
|
|
||||||
|
PreparedStatement saleStmt = conn.prepareStatement(insertSale, Statement.RETURN_GENERATED_KEYS);
|
||||||
|
saleStmt.setDouble(1, totalAmount);
|
||||||
|
saleStmt.setString(2, paymentMethod);
|
||||||
|
saleStmt.setInt(3, employeeId);
|
||||||
|
saleStmt.setInt(4, originalSaleId);
|
||||||
|
saleStmt.executeUpdate();
|
||||||
|
|
||||||
|
ResultSet rs = saleStmt.getGeneratedKeys();
|
||||||
|
if (!rs.next()) {
|
||||||
|
throw new SQLException("Failed to get generated refund ID");
|
||||||
|
}
|
||||||
|
int refundId = rs.getInt(1);
|
||||||
|
|
||||||
|
String insertItem = """
|
||||||
|
INSERT INTO saleItem (saleId, prodId, quantity, unitPrice)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
""";
|
||||||
|
|
||||||
|
String updateInventory = """
|
||||||
|
UPDATE inventory
|
||||||
|
SET quantity = quantity + ?
|
||||||
|
WHERE prodId = ?
|
||||||
|
""";
|
||||||
|
|
||||||
|
PreparedStatement itemStmt = conn.prepareStatement(insertItem);
|
||||||
|
PreparedStatement invStmt = conn.prepareStatement(updateInventory);
|
||||||
|
|
||||||
|
for (SaleCartItem item : refundItems) {
|
||||||
|
itemStmt.setInt(1, refundId);
|
||||||
|
itemStmt.setInt(2, item.getProdId());
|
||||||
|
itemStmt.setInt(3, -item.getQuantity());
|
||||||
|
itemStmt.setDouble(4, item.getUnitPrice());
|
||||||
|
itemStmt.executeUpdate();
|
||||||
|
|
||||||
|
invStmt.setInt(1, item.getQuantity());
|
||||||
|
invStmt.setInt(2, item.getProdId());
|
||||||
|
int updated = invStmt.executeUpdate();
|
||||||
|
|
||||||
|
if (updated == 0) {
|
||||||
|
throw new SQLException("Failed to update inventory for product ID " + item.getProdId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.commit();
|
||||||
|
|
||||||
|
ActivityLogger.getInstance().logInsert("sale",
|
||||||
|
String.format("Refund ID: %d", refundId),
|
||||||
|
String.format("Created refund for sale ID %d with %d items, total: $%.2f", originalSaleId, refundItems.size(), Math.abs(totalAmount)));
|
||||||
|
|
||||||
|
return refundId;
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
conn.rollback();
|
||||||
|
ActivityLogger.getInstance().logException("SaleDB.createRefund", e, "Creating refund");
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
conn.setAutoCommit(true);
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package org.example.petshopdesktop.models;
|
||||||
|
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class SaleDetail {
|
||||||
|
private final int saleId;
|
||||||
|
private final LocalDateTime saleDate;
|
||||||
|
private final double totalAmount;
|
||||||
|
private final String paymentMethod;
|
||||||
|
private final String employeeName;
|
||||||
|
private final ObservableList<SaleDetailItem> items;
|
||||||
|
|
||||||
|
public SaleDetail(int saleId, LocalDateTime saleDate, double totalAmount, String paymentMethod, String employeeName, ObservableList<SaleDetailItem> items) {
|
||||||
|
this.saleId = saleId;
|
||||||
|
this.saleDate = saleDate;
|
||||||
|
this.totalAmount = totalAmount;
|
||||||
|
this.paymentMethod = paymentMethod;
|
||||||
|
this.employeeName = employeeName;
|
||||||
|
this.items = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSaleId() {
|
||||||
|
return saleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getSaleDate() {
|
||||||
|
return saleDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTotalAmount() {
|
||||||
|
return totalAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPaymentMethod() {
|
||||||
|
return paymentMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmployeeName() {
|
||||||
|
return employeeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableList<SaleDetailItem> getItems() {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SaleDetailItem {
|
||||||
|
private final int prodId;
|
||||||
|
private final String productName;
|
||||||
|
private final int quantity;
|
||||||
|
private final double unitPrice;
|
||||||
|
private final double total;
|
||||||
|
|
||||||
|
public SaleDetailItem(int prodId, String productName, int quantity, double unitPrice, double total) {
|
||||||
|
this.prodId = prodId;
|
||||||
|
this.productName = productName;
|
||||||
|
this.quantity = quantity;
|
||||||
|
this.unitPrice = unitPrice;
|
||||||
|
this.total = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProdId() {
|
||||||
|
return prodId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductName() {
|
||||||
|
return productName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQuantity() {
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getUnitPrice() {
|
||||||
|
return unitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTotal() {
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,8 +9,9 @@ public class SaleLineItem {
|
|||||||
private final double unitPrice;
|
private final double unitPrice;
|
||||||
private final double total;
|
private final double total;
|
||||||
private final String paymentMethod;
|
private final String paymentMethod;
|
||||||
|
private final boolean isRefund;
|
||||||
|
|
||||||
public SaleLineItem(int saleId, String saleDate, String employeeName, String itemName, int quantity, double unitPrice, double total, String paymentMethod) {
|
public SaleLineItem(int saleId, String saleDate, String employeeName, String itemName, int quantity, double unitPrice, double total, String paymentMethod, boolean isRefund) {
|
||||||
this.saleId = saleId;
|
this.saleId = saleId;
|
||||||
this.saleDate = saleDate;
|
this.saleDate = saleDate;
|
||||||
this.employeeName = employeeName;
|
this.employeeName = employeeName;
|
||||||
@@ -19,6 +20,7 @@ public class SaleLineItem {
|
|||||||
this.unitPrice = unitPrice;
|
this.unitPrice = unitPrice;
|
||||||
this.total = total;
|
this.total = total;
|
||||||
this.paymentMethod = paymentMethod;
|
this.paymentMethod = paymentMethod;
|
||||||
|
this.isRefund = isRefund;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSaleId() {
|
public int getSaleId() {
|
||||||
@@ -52,4 +54,8 @@ public class SaleLineItem {
|
|||||||
public String getPaymentMethod() {
|
public String getPaymentMethod() {
|
||||||
return paymentMethod;
|
return paymentMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRefund() {
|
||||||
|
return isRefund;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ComboBox?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.Separator?>
|
||||||
|
<?import javafx.scene.control.TableColumn?>
|
||||||
|
<?import javafx.scene.control.TableView?>
|
||||||
|
<?import javafx.scene.control.TextField?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
|
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="900.0" spacing="20.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.RefundDialogController">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="20.0" style="-fx-background-color: #FF6b6b; -fx-background-radius: 14;">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<VBox alignment="CENTER_LEFT">
|
||||||
|
<children>
|
||||||
|
<Label text="Process Refund" textFill="WHITE">
|
||||||
|
<font>
|
||||||
|
<Font name="Comic Sans MS Bold" size="30.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
|
<Button fx:id="btnCancel" mnemonicParsing="false" onAction="#btnCancelClicked" style="-fx-background-color: #E74c3c; -fx-cursor: hand; -fx-background-radius: 8;" text="Cancel" textFill="WHITE">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<VBox spacing="15.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 2; -fx-border-color: #FF6b6b; -fx-border-radius: 14;">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<Label text="Load Sale" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="18.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10.0">
|
||||||
|
<children>
|
||||||
|
<Label text="Transaction ID:" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<TextField fx:id="txtSaleId" prefWidth="150.0" promptText="Enter sale ID" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10;">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="7.0" left="10.0" right="10.0" top="7.0" />
|
||||||
|
</padding>
|
||||||
|
</TextField>
|
||||||
|
<Button fx:id="btnLoadSale" mnemonicParsing="false" onAction="#btnLoadSaleClicked" style="-fx-background-color: #4ECDC4; -fx-cursor: hand; -fx-background-radius: 8;" text="Load Sale" textFill="WHITE">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="13.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="8.0" left="18.0" right="18.0" top="8.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<Label fx:id="lblSaleInfo" text="" textFill="#7f8c8d">
|
||||||
|
<font>
|
||||||
|
<Font size="14.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<VBox spacing="10.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 2; -fx-border-color: #FF6b6b; -fx-border-radius: 14;" VBox.vgrow="ALWAYS">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10.0">
|
||||||
|
<children>
|
||||||
|
<Label text="Original Items" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="16.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
|
<Button fx:id="btnAddToRefund" mnemonicParsing="false" onAction="#btnAddToRefundClicked" style="-fx-background-color: #FF6b6b; -fx-cursor: hand; -fx-background-radius: 8;" text="Add to Refund" textFill="WHITE">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="13.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="8.0" left="18.0" right="18.0" top="8.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<TableView fx:id="tvOriginalItems" prefHeight="150.0" style="-fx-background-color: white; -fx-background-radius: 10;">
|
||||||
|
<columns>
|
||||||
|
<TableColumn fx:id="colOriginalProduct" prefWidth="350.0" text="Product Name" />
|
||||||
|
<TableColumn fx:id="colOriginalQuantity" prefWidth="120.0" text="Quantity" />
|
||||||
|
<TableColumn fx:id="colOriginalUnitPrice" prefWidth="150.0" text="Unit Price" />
|
||||||
|
<TableColumn fx:id="colOriginalTotal" prefWidth="150.0" text="Total" />
|
||||||
|
</columns>
|
||||||
|
</TableView>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10.0">
|
||||||
|
<children>
|
||||||
|
<Label text="Refund Items" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="16.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
|
<Button fx:id="btnRemoveFromRefund" mnemonicParsing="false" onAction="#btnRemoveFromRefundClicked" style="-fx-background-color: #34495E; -fx-cursor: hand; -fx-background-radius: 8;" text="Remove Selected" textFill="WHITE">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="13.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="8.0" left="18.0" right="18.0" top="8.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<TableView fx:id="tvRefundItems" prefHeight="150.0" style="-fx-background-color: white; -fx-background-radius: 10;">
|
||||||
|
<columns>
|
||||||
|
<TableColumn fx:id="colRefundProduct" prefWidth="350.0" text="Product Name" />
|
||||||
|
<TableColumn fx:id="colRefundQuantity" prefWidth="120.0" text="Quantity" />
|
||||||
|
<TableColumn fx:id="colRefundUnitPrice" prefWidth="150.0" text="Unit Price" />
|
||||||
|
<TableColumn fx:id="colRefundTotal" prefWidth="150.0" text="Total" />
|
||||||
|
</columns>
|
||||||
|
</TableView>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<VBox spacing="12.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 2; -fx-border-color: #FF6b6b; -fx-border-radius: 14;">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="15.0">
|
||||||
|
<children>
|
||||||
|
<Label text="Payment Method:" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<ComboBox fx:id="cbPaymentMethod" prefWidth="160.0" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
|
||||||
|
</padding>
|
||||||
|
</ComboBox>
|
||||||
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
|
<Label text="Refund Total:" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="lblRefundTotal" text="\$0.00" textFill="#FF6b6b">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="18.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Region prefWidth="20.0" />
|
||||||
|
<Button fx:id="btnProcessRefund" mnemonicParsing="false" onAction="#btnProcessRefundClicked" style="-fx-background-color: #FF6b6b; -fx-cursor: hand; -fx-background-radius: 8;" text="Process Refund" textFill="WHITE">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
@@ -38,6 +38,14 @@
|
|||||||
</padding>
|
</padding>
|
||||||
</Label>
|
</Label>
|
||||||
<Region prefHeight="200.0" prefWidth="200.0" HBox.hgrow="ALWAYS" />
|
<Region prefHeight="200.0" prefWidth="200.0" HBox.hgrow="ALWAYS" />
|
||||||
|
<Button fx:id="btnRefund" mnemonicParsing="false" onAction="#btnRefund" prefHeight="44.0" style="-fx-background-color: #FF6b6b; -fx-cursor: hand; -fx-background-radius: 8;" text="Process Refund" textFill="WHITE">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
<Button fx:id="btnRefresh" mnemonicParsing="false" onAction="#btnRefresh" prefHeight="44.0" prefWidth="118.0" style="-fx-background-color: #4ECDC4; -fx-cursor: hand; -fx-background-radius: 8;" text="Refresh" textFill="WHITE">
|
<Button fx:id="btnRefresh" mnemonicParsing="false" onAction="#btnRefresh" prefHeight="44.0" prefWidth="118.0" style="-fx-background-color: #4ECDC4; -fx-cursor: hand; -fx-background-radius: 8;" text="Refresh" textFill="WHITE">
|
||||||
<font>
|
<font>
|
||||||
<Font name="System Bold" size="14.0" />
|
<Font name="System Bold" size="14.0" />
|
||||||
|
|||||||
Reference in New Issue
Block a user