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

@@ -132,8 +132,11 @@ CREATE TABLE sale (
paymentMethod VARCHAR(50) NOT NULL,
employeeId INT NOT NULL,
storeId INT NOT NULL,
isRefund BOOLEAN DEFAULT FALSE NOT NULL,
originalSaleId INT NULL,
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 (

14
refund_migration.sql Normal file
View 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;

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" />