Refund polish #58

Merged
RecentRunner merged 3 commits from refund-layout-spacing into main 2026-03-30 09:41:16 -06:00
8 changed files with 313 additions and 52 deletions
Showing only changes of commit 33c9555564 - Show all commits

View File

@@ -18,6 +18,7 @@ import org.example.petshopdesktop.models.Adoption;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -168,6 +169,7 @@ public class AdoptionController {
List<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(filter);
List<Adoption> adoptionList = adoptions.stream()
.map(this::mapToAdoption)
.sorted(Comparator.comparing(Adoption::getAdoptionDate).reversed())
.collect(Collectors.toList());
Platform.runLater(() -> {
@@ -193,6 +195,7 @@ public class AdoptionController {
List<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(null);
List<Adoption> adoptionList = adoptions.stream()
.map(this::mapToAdoption)
.sorted(Comparator.comparing(Adoption::getAdoptionDate).reversed())
.collect(Collectors.toList());
Platform.runLater(() -> {

View File

@@ -20,6 +20,7 @@ import org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialo
import org.example.petshopdesktop.util.ActivityLogger;
import java.util.List;
import java.util.Comparator;
import java.util.stream.Collectors;
public class AppointmentController {
@@ -81,6 +82,7 @@ public class AppointmentController {
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(null);
List<AppointmentDTO> appointmentDTOs = responses.stream()
.map(this::mapToAppointmentDTO)
.sorted(Comparator.comparing((AppointmentDTO a) -> a.getAppointmentDate() + "T" + a.getAppointmentTime()).reversed())
.collect(Collectors.toList());
Platform.runLater(() -> {
@@ -105,6 +107,7 @@ public class AppointmentController {
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(query);
List<AppointmentDTO> appointmentDTOs = responses.stream()
.map(this::mapToAppointmentDTO)
.sorted(Comparator.comparing((AppointmentDTO a) -> a.getAppointmentDate() + "T" + a.getAppointmentTime()).reversed())
.collect(Collectors.toList());
Platform.runLater(() -> {

View File

@@ -13,6 +13,7 @@ import org.example.petshopdesktop.api.endpoints.PurchaseOrderApi;
import org.example.petshopdesktop.util.ActivityLogger;
import java.util.List;
import java.util.Comparator;
import java.util.stream.Collectors;
public class PurchaseOrderController {
@@ -63,6 +64,7 @@ public class PurchaseOrderController {
List<PurchaseOrderResponse> responses = PurchaseOrderApi.getInstance().listPurchaseOrders(null);
List<PurchaseOrderDTO> dtos = responses.stream()
.map(this::mapToPurchaseOrderDTO)
.sorted(Comparator.comparing(PurchaseOrderDTO::getOrderDate).reversed())
.collect(Collectors.toList());
Platform.runLater(() -> {
@@ -118,4 +120,4 @@ public class PurchaseOrderController {
response.getOrderStatus()
);
}
}
}

View File

@@ -6,6 +6,8 @@ import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.application.Platform;
import javafx.scene.input.MouseButton;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
@@ -32,6 +34,7 @@ import org.example.petshopdesktop.api.dto.sale.SaleRequest;
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.models.SaleCartItem;
import org.example.petshopdesktop.models.SaleDetail;
import org.example.petshopdesktop.models.SaleLineItem;
import org.example.petshopdesktop.util.ActivityLogger;
@@ -39,6 +42,7 @@ import java.math.BigDecimal;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
@@ -128,6 +132,7 @@ public class SaleController {
private final ObservableList<SaleCartItem> cartItems = FXCollections.observableArrayList();
private final ObservableList<SaleLineItem> saleItems = FXCollections.observableArrayList();
private FilteredList<SaleLineItem> filteredSales;
private boolean saleSaveInProgress;
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@@ -165,6 +170,15 @@ public class SaleController {
filteredSales = new FilteredList<>(saleItems, s -> true);
tvSales.setItems(filteredSales);
tvSales.setOnMouseClicked(event -> {
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
SaleLineItem selected = tvSales.getSelectionModel().getSelectedItem();
if (selected != null) {
openSaleDetailDialog(selected.getSaleId());
}
}
});
txtSearch.textProperty().addListener((obs, oldVal, newVal) -> applySalesFilter(newVal));
}
@@ -177,22 +191,43 @@ public class SaleController {
updateCartTotal();
try {
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
ObservableList<Product> products = FXCollections.observableArrayList();
for (ProductResponse pr : productResponses) {
products.add(new Product(
pr.getProdId().intValue(),
pr.getProdName(),
pr.getProdPrice().doubleValue(),
0,
pr.getProdDesc()
));
setCreateSaleControlsDisabled(true);
Task<ObservableList<Product>> task = new Task<>() {
@Override
protected ObservableList<Product> call() throws Exception {
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
ObservableList<Product> products = FXCollections.observableArrayList();
for (ProductResponse pr : productResponses) {
products.add(new Product(
pr.getProdId().intValue(),
pr.getProdName(),
pr.getProdPrice().doubleValue(),
0,
pr.getProdDesc()
));
}
return products;
}
cbProduct.setItems(products);
} catch (Exception e) {
ActivityLogger.getInstance().logException("SaleController.setupCreateSale", e, "Loading products");
}
};
task.setOnSucceeded(event -> {
cbProduct.setItems(task.getValue());
setCreateSaleControlsDisabled(false);
});
task.setOnFailed(event -> {
Throwable e = task.getException();
ActivityLogger.getInstance().logException(
"SaleController.setupCreateSale",
e instanceof Exception ? (Exception) e : new RuntimeException(e),
"Loading products"
);
setCreateSaleControlsDisabled(false);
showError("Sales", "Could not load products. Check the backend connection and refresh the view.");
});
new Thread(task).start();
}
private void applyRoleMode() {
@@ -207,10 +242,13 @@ public class SaleController {
}
private void refreshSales(boolean showErrorDialog) {
btnRefresh.setDisable(true);
Task<List<SaleLineItem>> task = new Task<List<SaleLineItem>>() {
@Override
protected List<SaleLineItem> call() throws Exception {
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, 1000, null);
List<SaleResponse> sales = SaleApi.getInstance().listAllSales(null);
sales.sort(Comparator.comparing(SaleResponse::getSaleDate, Comparator.nullsLast(Comparator.reverseOrder()))
.thenComparing(SaleResponse::getSaleId, Comparator.nullsLast(Comparator.reverseOrder())));
List<SaleLineItem> lineItems = new ArrayList<>();
for (SaleResponse sale : sales) {
@@ -242,14 +280,14 @@ public class SaleController {
task.setOnSucceeded(event -> {
saleItems.setAll(task.getValue());
btnRefresh.setDisable(false);
});
task.setOnFailed(event -> {
Throwable e = task.getException();
ActivityLogger.getInstance().logException("SaleController.refreshSales", (Exception) e, "Loading sales");
if (showErrorDialog) {
showError("Sales", "Could not load sales: " + e.getMessage());
}
btnRefresh.setDisable(false);
showError("Sales", "Could not load sales: " + e.getMessage());
});
new Thread(task).start();
@@ -310,6 +348,9 @@ public class SaleController {
@FXML
void btnSaveSale(ActionEvent event) {
if (saleSaveInProgress) {
return;
}
if (UserSession.getInstance().isAdmin()) {
showError("Create Sale", "This action is restricted to staff.");
return;
@@ -332,36 +373,57 @@ public class SaleController {
return;
}
try {
SaleRequest request = new SaleRequest();
request.setStoreId(storeId);
request.setPaymentMethod(payment);
SaleRequest request = new SaleRequest();
request.setStoreId(storeId);
request.setPaymentMethod(payment);
List<SaleItemRequest> itemRequests = new ArrayList<>();
for (SaleCartItem cartItem : cartItems) {
SaleItemRequest itemRequest = new SaleItemRequest();
itemRequest.setProdId((long) cartItem.getProdId());
itemRequest.setQuantity(cartItem.getQuantity());
itemRequests.add(itemRequest);
List<SaleItemRequest> itemRequests = new ArrayList<>();
for (SaleCartItem cartItem : cartItems) {
SaleItemRequest itemRequest = new SaleItemRequest();
itemRequest.setProdId((long) cartItem.getProdId());
itemRequest.setQuantity(cartItem.getQuantity());
itemRequests.add(itemRequest);
}
request.setItems(itemRequests);
saleSaveInProgress = true;
setCreateSaleControlsDisabled(true);
btnRefund.setDisable(true);
Task<SaleResponse> task = new Task<>() {
@Override
protected SaleResponse call() throws Exception {
return SaleApi.getInstance().createSale(request);
}
request.setItems(itemRequests);
};
SaleResponse response = SaleApi.getInstance().createSale(request);
task.setOnSucceeded(evt -> {
saleSaveInProgress = false;
setCreateSaleControlsDisabled(false);
btnRefund.setDisable(false);
SaleResponse response = task.getValue();
showInfo("Sale saved", "Sale ID " + response.getSaleId() + " was created.");
cartItems.clear();
updateCartTotal();
refreshSales(true);
} catch (Exception e) {
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", e, "Creating sale");
String errorMsg = e.getMessage();
});
task.setOnFailed(evt -> {
saleSaveInProgress = false;
setCreateSaleControlsDisabled(false);
btnRefund.setDisable(false);
Throwable e = task.getException();
Exception ex = e instanceof Exception ? (Exception) e : new RuntimeException(e);
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", ex, "Creating sale");
String errorMsg = e != null ? e.getMessage() : null;
if (errorMsg != null && errorMsg.contains("Insufficient inventory")) {
showError("Create Sale", "Insufficient stock for one or more items.");
} else {
showError("Create Sale", errorMsg != null ? errorMsg : "Could not save the sale.");
}
}
});
new Thread(task).start();
}
@FXML
@@ -384,11 +446,13 @@ public class SaleController {
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setTitle("Process Refund");
dialog.setScene(new Scene(loader.load()));
var controller = loader.<org.example.petshopdesktop.controllers.dialogcontrollers.RefundDialogController>getController();
if (selectedSale != null) {
loader.<org.example.petshopdesktop.controllers.dialogcontrollers.RefundDialogController>getController()
.prefillSale((long) selectedSale.getSaleId());
controller.prefillSale((long) selectedSale.getSaleId());
}
dialog.setResizable(false);
dialog.setMinWidth(860);
dialog.setMinHeight(680);
dialog.setResizable(true);
dialog.showAndWait();
refreshSales(true);
@@ -397,11 +461,83 @@ public class SaleController {
}
}
private void openSaleDetailDialog(int saleId) {
Task<SaleResponse> task = new Task<>() {
@Override
protected SaleResponse call() throws Exception {
return SaleApi.getInstance().getSale((long) saleId);
}
};
task.setOnSucceeded(event -> {
try {
SaleResponse sale = task.getValue();
FXMLLoader loader = new FXMLLoader(getClass().getResource(
"/org/example/petshopdesktop/dialogviews/sale-detail-dialog-view.fxml"));
Stage dialog = new Stage();
dialog.initOwner(tvSales.getScene().getWindow());
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setTitle("Sale Details");
dialog.setScene(new Scene(loader.load()));
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.SaleDetailDialogController) loader.getController();
controller.displaySaleDetails(mapToSaleDetail(sale));
dialog.setResizable(false);
dialog.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException("SaleController.openSaleDetailDialog", e, "Opening sale detail dialog");
showError("Sale Details", "Could not open the sale details.");
}
});
task.setOnFailed(event -> {
Throwable e = task.getException();
ActivityLogger.getInstance().logException("SaleController.openSaleDetailDialog", (Exception) e, "Loading sale detail");
showError("Sale Details", "Could not open the sale details.");
});
new Thread(task).start();
}
private SaleDetail mapToSaleDetail(SaleResponse sale) {
ObservableList<SaleDetail.SaleDetailItem> items = FXCollections.observableArrayList();
if (sale.getItems() != null) {
for (SaleItemResponse item : sale.getItems()) {
double unitPrice = item.getUnitPrice() != null ? item.getUnitPrice().doubleValue() : 0.0;
int quantity = item.getQuantity() != null ? item.getQuantity() : 0;
items.add(new SaleDetail.SaleDetailItem(
item.getProdId() != null ? item.getProdId().intValue() : 0,
item.getProductName(),
quantity,
unitPrice,
unitPrice * quantity
));
}
}
return new SaleDetail(
sale.getSaleId().intValue(),
sale.getSaleDate(),
sale.getTotalAmount() != null ? sale.getTotalAmount().doubleValue() : 0.0,
sale.getPaymentMethod(),
sale.getEmployeeName(),
items
);
}
private void updateCartTotal() {
double total = cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum();
lblCartTotal.setText(currency.format(total));
}
private void setCreateSaleControlsDisabled(boolean disabled) {
cbProduct.setDisable(disabled);
spQuantity.setDisable(disabled);
btnAddToCart.setDisable(disabled);
btnRemoveSelected.setDisable(disabled);
cbPaymentMethod.setDisable(disabled);
btnClearCart.setDisable(disabled);
btnSaveSale.setDisable(disabled);
}
private void applySalesFilter(String filter) {
String f = filter == null ? "" : filter.trim().toLowerCase();
if (f.isEmpty()) {

View File

@@ -25,6 +25,7 @@ import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.Timestamp;
import java.time.ZoneId;
import java.util.List;
import java.util.Comparator;
import java.util.stream.Collectors;
public class StaffAccountsController {
@@ -161,6 +162,7 @@ public class StaffAccountsController {
List<EmployeeResponse> employees = EmployeeApi.getInstance().listEmployees(null);
List<StaffAccount> accounts = employees.stream()
.map(this::mapToStaffAccount)
.sorted(Comparator.comparing(StaffAccount::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder())))
.collect(Collectors.toList());
Platform.runLater(() -> {

View File

@@ -0,0 +1,54 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import org.example.petshopdesktop.models.SaleDetail;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class SaleDetailDialogController {
@FXML private Label lblSaleId;
@FXML private Label lblSaleDate;
@FXML private Label lblEmployee;
@FXML private Label lblPayment;
@FXML private Label lblTotal;
@FXML private TableView<SaleDetail.SaleDetailItem> tvItems;
@FXML private TableColumn<SaleDetail.SaleDetailItem, String> colProduct;
@FXML private TableColumn<SaleDetail.SaleDetailItem, Integer> colQuantity;
@FXML private TableColumn<SaleDetail.SaleDetailItem, Double> colUnitPrice;
@FXML private TableColumn<SaleDetail.SaleDetailItem, Double> colLineTotal;
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@FXML
public void initialize() {
tvItems.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
colProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
colQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
colUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
colLineTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
}
public void displaySaleDetails(SaleDetail sale) {
lblSaleId.setText(String.valueOf(sale.getSaleId()));
lblSaleDate.setText(sale.getSaleDate() != null ? sale.getSaleDate().format(DATE_FORMATTER) : "");
lblEmployee.setText(sale.getEmployeeName() != null ? sale.getEmployeeName() : "");
lblPayment.setText(sale.getPaymentMethod() != null ? sale.getPaymentMethod() : "");
lblTotal.setText(currency.format(sale.getTotalAmount()));
tvItems.setItems(sale.getItems());
}
@FXML
void btnCloseClicked() {
Stage stage = (Stage) tvItems.getScene().getWindow();
stage.close();
}
}

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox prefHeight="520.0" prefWidth="760.0" spacing="18.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.SaleDetailDialogController">
<padding>
<Insets bottom="18.0" left="18.0" right="18.0" top="18.0" />
</padding>
<children>
<HBox alignment="CENTER_LEFT" spacing="12.0" style="-fx-background-color: #4ECDC4; -fx-background-radius: 12;">
<padding>
<Insets bottom="14.0" left="16.0" right="16.0" top="14.0" />
</padding>
<children>
<Label text="Sale Details" textFill="WHITE">
<font>
<Font name="System Bold" size="24.0" />
</font>
</Label>
<Region HBox.hgrow="ALWAYS" />
<Button mnemonicParsing="false" onAction="#btnCloseClicked" style="-fx-background-color: white; -fx-text-fill: #2c3e50; -fx-background-radius: 8;" text="Close" />
</children>
</HBox>
<GridPane hgap="16.0" vgap="12.0" style="-fx-background-color: white; -fx-background-radius: 12; -fx-border-color: #e6e6e6; -fx-border-radius: 12; -fx-border-width: 1;">
<padding>
<Insets bottom="16.0" left="16.0" right="16.0" top="16.0" />
</padding>
<children>
<Label text="Sale ID" GridPane.columnIndex="0" GridPane.rowIndex="0" />
<Label fx:id="lblSaleId" GridPane.columnIndex="1" GridPane.rowIndex="0" />
<Label text="Date" GridPane.columnIndex="2" GridPane.rowIndex="0" />
<Label fx:id="lblSaleDate" GridPane.columnIndex="3" GridPane.rowIndex="0" />
<Label text="Employee" GridPane.columnIndex="0" GridPane.rowIndex="1" />
<Label fx:id="lblEmployee" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label text="Payment" GridPane.columnIndex="2" GridPane.rowIndex="1" />
<Label fx:id="lblPayment" GridPane.columnIndex="3" GridPane.rowIndex="1" />
<Label text="Total" GridPane.columnIndex="0" GridPane.rowIndex="2" />
<Label fx:id="lblTotal" GridPane.columnIndex="1" GridPane.rowIndex="2" />
</children>
</GridPane>
<TableView fx:id="tvItems" prefHeight="320.0" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colProduct" text="Product" prefWidth="330.0" />
<TableColumn fx:id="colQuantity" text="Qty" prefWidth="90.0" />
<TableColumn fx:id="colUnitPrice" text="Unit Price" prefWidth="130.0" />
<TableColumn fx:id="colLineTotal" text="Total" prefWidth="130.0" />
</columns>
</TableView>
</children>
</VBox>

View File

@@ -37,7 +37,7 @@
<Insets top="10.0" />
</padding>
</Label>
<Region prefHeight="200.0" prefWidth="200.0" HBox.hgrow="ALWAYS" />
<Region 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" />
@@ -89,7 +89,7 @@
</Button>
</children>
</HBox>
<TableView fx:id="tvCart" prefHeight="170.0" style="-fx-background-color: white; -fx-background-radius: 10;" VBox.vgrow="NEVER">
<TableView fx:id="tvCart" prefHeight="170.0" style="-fx-background-color: white; -fx-background-radius: 10;" VBox.vgrow="NEVER">
<columns>
<TableColumn fx:id="colCartProduct" prefWidth="310.0" text="Product" />
<TableColumn fx:id="colCartQty" prefWidth="90.0" text="Qty" />
@@ -151,16 +151,16 @@
</TextField>
</children>
</HBox>
<TableView fx:id="tvSales" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12; -fx-padding: 6;" VBox.vgrow="ALWAYS">
<TableView fx:id="tvSales" prefHeight="362.0" style="-fx-background-color: white; -fx-background-radius: 12; -fx-padding: 6;" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colSaleId" minWidth="56.0" prefWidth="72.0" text="ID" />
<TableColumn fx:id="colSaleDate" minWidth="150.0" prefWidth="180.0" text="Date" />
<TableColumn fx:id="colEmployeeName" minWidth="150.0" prefWidth="180.0" text="Employee" />
<TableColumn fx:id="colServiceProduct" minWidth="190.0" prefWidth="240.0" text="Product" />
<TableColumn fx:id="colSaleQuantity" minWidth="70.0" prefWidth="80.0" text="Qty" />
<TableColumn fx:id="colSaleUnitPrice" minWidth="110.0" prefWidth="130.0" text="Unit Price" />
<TableColumn fx:id="colSaleTotal" minWidth="100.0" prefWidth="120.0" text="Total" />
<TableColumn fx:id="colSalePaymentType" minWidth="100.0" prefWidth="120.0" text="Payment" />
<TableColumn fx:id="colSaleId" minWidth="54.0" prefWidth="62.0" text="ID" />
<TableColumn fx:id="colSaleDate" minWidth="145.0" prefWidth="165.0" text="Date" />
<TableColumn fx:id="colEmployeeName" minWidth="130.0" prefWidth="155.0" text="Employee" />
<TableColumn fx:id="colServiceProduct" minWidth="180.0" prefWidth="240.0" text="Product" />
<TableColumn fx:id="colSaleQuantity" minWidth="62.0" prefWidth="72.0" text="Qty" />
<TableColumn fx:id="colSaleUnitPrice" minWidth="95.0" prefWidth="115.0" text="Unit Price" />
<TableColumn fx:id="colSaleTotal" minWidth="90.0" prefWidth="108.0" text="Total" />
<TableColumn fx:id="colSalePaymentType" minWidth="88.0" prefWidth="100.0" text="Payment" />
</columns>
</TableView>
</children>