Implement refund system for sales

This commit is contained in:
2026-02-28 19:28:25 -07:00
parent e2973729b4
commit cbed5e4e0c
9 changed files with 841 additions and 4 deletions

View File

@@ -5,6 +5,8 @@ import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
@@ -17,6 +19,8 @@ import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.database.InventoryDB;
import org.example.petshopdesktop.database.ProductDB;
@@ -38,6 +42,9 @@ public class SaleController {
@FXML
private Button btnRefresh;
@FXML
private Button btnRefund;
@FXML
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() {
double total = cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum();
lblCartTotal.setText(currency.format(total));

View File

@@ -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;
}
}
}

View File

@@ -4,6 +4,7 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.DTOs.SaleDTO;
import org.example.petshopdesktop.models.SaleCartItem;
import org.example.petshopdesktop.models.SaleDetail;
import org.example.petshopdesktop.models.SaleLineItem;
import org.example.petshopdesktop.models.analytics.*;
import org.example.petshopdesktop.util.ActivityLogger;
@@ -129,7 +130,8 @@ public class SaleDB {
si.quantity,
si.unitPrice,
(si.quantity * si.unitPrice) as total,
s.paymentMethod
s.paymentMethod,
s.isRefund
FROM sale s
JOIN saleItem si ON s.saleId = si.saleId
JOIN product p ON si.prodId = p.prodId
@@ -149,7 +151,8 @@ public class SaleDB {
rs.getInt("quantity"),
rs.getDouble("unitPrice"),
rs.getDouble("total"),
rs.getString("paymentMethod")
rs.getString("paymentMethod"),
rs.getBoolean("isRefund")
));
}
@@ -397,4 +400,173 @@ public class SaleDB {
conn.close();
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();
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -9,8 +9,9 @@ public class SaleLineItem {
private final double unitPrice;
private final double total;
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.saleDate = saleDate;
this.employeeName = employeeName;
@@ -19,6 +20,7 @@ public class SaleLineItem {
this.unitPrice = unitPrice;
this.total = total;
this.paymentMethod = paymentMethod;
this.isRefund = isRefund;
}
public int getSaleId() {
@@ -52,4 +54,8 @@ public class SaleLineItem {
public String getPaymentMethod() {
return paymentMethod;
}
public boolean isRefund() {
return isRefund;
}
}

View File

@@ -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>

View File

@@ -38,6 +38,14 @@
</padding>
</Label>
<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">
<font>
<Font name="System Bold" size="14.0" />