updated sales on desktop, and fixed sales with points again on back end

This commit is contained in:
Alex
2026-04-14 03:32:29 -06:00
parent a3fcebfa15
commit a860a1c247
20 changed files with 943 additions and 101 deletions

View File

@@ -8,6 +8,9 @@ public class SaleRequest {
private List<SaleItemRequest> items;
private Boolean isRefund;
private Long originalSaleId;
private Long customerId;
private Long couponId;
private Integer pointsUsed;
public SaleRequest() {
}
@@ -51,4 +54,28 @@ public class SaleRequest {
public void setOriginalSaleId(Long originalSaleId) {
this.originalSaleId = originalSaleId;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public Long getCouponId() {
return couponId;
}
public void setCouponId(Long couponId) {
this.couponId = couponId;
}
public Integer getPointsUsed() {
return pointsUsed;
}
public void setPointsUsed(Integer pointsUsed) {
this.pointsUsed = pointsUsed;
}
}

View File

@@ -14,6 +14,13 @@ public class SaleResponse {
private Boolean isRefund;
private Long originalSaleId;
private List<SaleItemResponse> items;
private Long customerId;
private String customerName;
private BigDecimal couponDiscountAmount;
private BigDecimal loyaltyDiscountAmount;
private Integer pointsUsed;
private Integer pointsEarned;
private BigDecimal subtotalAmount;
public SaleResponse() {
}
@@ -89,4 +96,60 @@ public class SaleResponse {
public void setItems(List<SaleItemResponse> items) {
this.items = items;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public BigDecimal getCouponDiscountAmount() {
return couponDiscountAmount;
}
public void setCouponDiscountAmount(BigDecimal couponDiscountAmount) {
this.couponDiscountAmount = couponDiscountAmount;
}
public BigDecimal getLoyaltyDiscountAmount() {
return loyaltyDiscountAmount;
}
public void setLoyaltyDiscountAmount(BigDecimal loyaltyDiscountAmount) {
this.loyaltyDiscountAmount = loyaltyDiscountAmount;
}
public Integer getPointsUsed() {
return pointsUsed;
}
public void setPointsUsed(Integer pointsUsed) {
this.pointsUsed = pointsUsed;
}
public Integer getPointsEarned() {
return pointsEarned;
}
public void setPointsEarned(Integer pointsEarned) {
this.pointsEarned = pointsEarned;
}
public BigDecimal getSubtotalAmount() {
return subtotalAmount;
}
public void setSubtotalAmount(BigDecimal subtotalAmount) {
this.subtotalAmount = subtotalAmount;
}
}

View File

@@ -3,6 +3,8 @@ package org.example.petshopdesktop.api.dto.user;
public class UserRequest {
private String username;
private String password;
private String firstName;
private String lastName;
private String fullName;
private String email;
private String phone;
@@ -29,6 +31,22 @@ public class UserRequest {
this.password = password;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFullName() {
return fullName;
}

View File

@@ -39,4 +39,8 @@ public class CouponApi {
public void deleteCoupon(Long id) throws Exception {
apiClient.delete("/api/v1/coupons/" + id);
}
public CouponResponse getCouponByCode(String code) throws Exception {
return apiClient.get("/api/v1/coupons/code/" + code, CouponResponse.class);
}
}

View File

@@ -45,4 +45,8 @@ public class CustomerApi {
public UserResponse createCustomer(UserRequest request) throws Exception {
return apiClient.post("/api/v1/customers", request, UserResponse.class);
}
public UserResponse getCustomerById(Long id) throws Exception {
return apiClient.get("/api/v1/customers/" + id, UserResponse.class);
}
}

View File

@@ -107,6 +107,14 @@ public class DropdownApi {
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getAdoptionPets(Long storeId) throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/adoption-pets?storeId=" + storeId);
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from adoption pets endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getCustomerPets(Long customerId) throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/customers/" + customerId + "/pets");
if (response == null || response.isEmpty()) {

View File

@@ -10,6 +10,7 @@ import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode;
@@ -19,18 +20,25 @@ import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.auth.UserSession;
import javafx.concurrent.Task;
import org.example.petshopdesktop.api.endpoints.CustomerApi;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.api.endpoints.ProductApi;
import org.example.petshopdesktop.api.endpoints.SaleApi;
import org.example.petshopdesktop.api.endpoints.CouponApi;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.dto.coupon.CouponResponse;
import org.example.petshopdesktop.api.dto.product.ProductResponse;
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
import org.example.petshopdesktop.api.dto.sale.SaleRequest;
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
import org.example.petshopdesktop.api.dto.user.UserResponse;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.models.SaleCartItem;
import org.example.petshopdesktop.models.SaleDetail;
@@ -111,6 +119,9 @@ public class SaleController {
@FXML
private TableColumn<SaleLineItem, String> colEmployeeName;
@FXML
private TableColumn<SaleLineItem, String> colCustomerName;
@FXML
private TableColumn<SaleLineItem, String> colServiceProduct;
@@ -135,11 +146,53 @@ public class SaleController {
@FXML
private TextField txtSearch;
@FXML
private ComboBox<DropdownOption> cbCustomer;
@FXML
private CheckBox chkUseLoyaltyPoints;
@FXML
private Label lblLoyaltyPoints;
@FXML
private TextField txtCouponCode;
@FXML
private Button btnApplyCoupon;
@FXML
private Button btnClearCoupon;
@FXML
private Label lblCouponStatus;
@FXML
private Label lblSubtotal;
@FXML
private HBox hbCouponDiscount;
@FXML
private Label lblCouponDiscount;
@FXML
private HBox hbLoyaltyDiscount;
@FXML
private Label lblLoyaltyDiscountLabel;
@FXML
private Label lblLoyaltyDiscount;
private final ObservableList<SaleCartItem> cartItems = FXCollections.observableArrayList();
private final ObservableList<SaleLineItem> saleItems = FXCollections.observableArrayList();
private FilteredList<SaleLineItem> filteredSales;
private boolean saleSaveInProgress;
private CouponResponse appliedCoupon = null;
private UserResponse selectedCustomerData = null;
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@@ -173,6 +226,8 @@ public class SaleController {
colSaleId.setCellValueFactory(new PropertyValueFactory<>("saleId"));
colSaleDate.setCellValueFactory(new PropertyValueFactory<>("saleDate"));
colEmployeeName.setCellValueFactory(new PropertyValueFactory<>("employeeName"));
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
colCustomerName.setMinWidth(120);
colServiceProduct.setCellValueFactory(new PropertyValueFactory<>("itemName"));
colSaleQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
colSaleUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
@@ -214,7 +269,23 @@ public class SaleController {
setCreateSaleControlsDisabled(true);
Task<ObservableList<Product>> task = new Task<>() {
cbCustomer.valueProperty().addListener((obs, oldVal, newVal) -> {
if (newVal != null) {
loadCustomerDetails(newVal.getId());
} else {
selectedCustomerData = null;
lblLoyaltyPoints.setVisible(false);
lblLoyaltyPoints.setManaged(false);
chkUseLoyaltyPoints.setVisible(false);
chkUseLoyaltyPoints.setManaged(false);
chkUseLoyaltyPoints.setSelected(false);
updateCartTotal();
}
});
chkUseLoyaltyPoints.selectedProperty().addListener((obs, oldVal, newVal) -> updateCartTotal());
Task<ObservableList<Product>> productsTask = new Task<>() {
@Override
protected ObservableList<Product> call() throws Exception {
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
@@ -232,13 +303,13 @@ public class SaleController {
}
};
task.setOnSucceeded(event -> {
cbProduct.setItems(task.getValue());
productsTask.setOnSucceeded(event -> {
cbProduct.setItems(productsTask.getValue());
setCreateSaleControlsDisabled(false);
});
task.setOnFailed(event -> {
Throwable e = task.getException();
productsTask.setOnFailed(event -> {
Throwable e = productsTask.getException();
ActivityLogger.getInstance().logException(
"SaleController.setupCreateSale",
e instanceof Exception ? (Exception) e : new RuntimeException(e),
@@ -248,6 +319,70 @@ public class SaleController {
showError("Sales", "Could not load products. Check the backend connection and refresh the view.");
});
new Thread(productsTask).start();
Task<List<DropdownOption>> customersTask = new Task<>() {
@Override
protected List<DropdownOption> call() throws Exception {
return DropdownApi.getInstance().getCustomers();
}
};
customersTask.setOnSucceeded(event -> {
List<DropdownOption> customers = customersTask.getValue();
ObservableList<DropdownOption> customerOptions = FXCollections.observableArrayList();
customerOptions.addAll(customers);
cbCustomer.setItems(customerOptions);
});
customersTask.setOnFailed(event -> {
Throwable e = customersTask.getException();
ActivityLogger.getInstance().logException(
"SaleController.setupCreateSale",
e instanceof Exception ? (Exception) e : new RuntimeException(e),
"Loading customers"
);
});
new Thread(customersTask).start();
}
private void loadCustomerDetails(Long customerId) {
Task<UserResponse> task = new Task<>() {
@Override
protected UserResponse call() throws Exception {
return CustomerApi.getInstance().getCustomerById(customerId);
}
};
task.setOnSucceeded(event -> {
selectedCustomerData = task.getValue();
if (selectedCustomerData != null && selectedCustomerData.getLoyaltyPoints() != null && selectedCustomerData.getLoyaltyPoints() >= 20) {
lblLoyaltyPoints.setText(selectedCustomerData.getLoyaltyPoints() + " pts available");
lblLoyaltyPoints.setVisible(true);
lblLoyaltyPoints.setManaged(true);
chkUseLoyaltyPoints.setVisible(true);
chkUseLoyaltyPoints.setManaged(true);
} else {
lblLoyaltyPoints.setVisible(false);
lblLoyaltyPoints.setManaged(false);
chkUseLoyaltyPoints.setVisible(false);
chkUseLoyaltyPoints.setManaged(false);
chkUseLoyaltyPoints.setSelected(false);
}
updateCartTotal();
});
task.setOnFailed(event -> {
selectedCustomerData = null;
Throwable e = task.getException();
ActivityLogger.getInstance().logException(
"SaleController.loadCustomerDetails",
e instanceof Exception ? (Exception) e : new RuntimeException(e),
"Loading customer details"
);
});
new Thread(task).start();
}
@@ -281,6 +416,9 @@ public class SaleController {
: "";
if (sale.getItems() != null && !sale.getItems().isEmpty()) {
double saleSubtotal = sale.getSubtotalAmount() != null ? Math.abs(sale.getSubtotalAmount().doubleValue()) : 0;
double saleActualTotal = sale.getTotalAmount() != null ? Math.abs(sale.getTotalAmount().doubleValue()) : 0;
double discountRatio = saleSubtotal > 0 ? saleActualTotal / saleSubtotal : 1.0;
for (SaleItemResponse item : sale.getItems()) {
boolean isRefund = sale.getIsRefund() != null && sale.getIsRefund();
double unitPrice = item.getUnitPrice() != null ? item.getUnitPrice().doubleValue() : 0.0;
@@ -288,7 +426,7 @@ public class SaleController {
if (isRefund && quantity > 0) {
quantity = -quantity;
}
double lineTotal = unitPrice * quantity;
double lineTotal = unitPrice * quantity * discountRatio;
lineItems.add(new SaleLineItem(
sale.getSaleId().intValue(),
saleDate,
@@ -299,7 +437,8 @@ public class SaleController {
lineTotal,
sale.getPaymentMethod(),
isRefund,
sale.getStoreName()
sale.getStoreName(),
sale.getCustomerName()
));
}
}
@@ -379,6 +518,73 @@ public class SaleController {
updateCartTotal();
}
@FXML
void btnApplyCoupon(ActionEvent event) {
String code = txtCouponCode.getText().trim();
if (code.isEmpty()) {
lblCouponStatus.setText("Enter a coupon code.");
lblCouponStatus.setTextFill(javafx.scene.paint.Color.web("#e74c3c"));
return;
}
BigDecimal subtotal = BigDecimal.valueOf(cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum());
Task<CouponResponse> task = new Task<>() {
@Override
protected CouponResponse call() throws Exception {
return CouponApi.getInstance().getCouponByCode(code);
}
};
task.setOnSucceeded(evt -> {
CouponResponse coupon = task.getValue();
if (coupon == null) {
lblCouponStatus.setText("Coupon not found.");
lblCouponStatus.setTextFill(javafx.scene.paint.Color.web("#e74c3c"));
return;
}
if (!Boolean.TRUE.equals(coupon.getActive())) {
lblCouponStatus.setText("Coupon is not active.");
lblCouponStatus.setTextFill(javafx.scene.paint.Color.web("#e74c3c"));
return;
}
if (coupon.getMinOrderAmount() != null && subtotal.compareTo(coupon.getMinOrderAmount()) < 0) {
lblCouponStatus.setText("Order minimum not met ($" + coupon.getMinOrderAmount() + ").");
lblCouponStatus.setTextFill(javafx.scene.paint.Color.web("#e74c3c"));
return;
}
appliedCoupon = coupon;
txtCouponCode.setDisable(true);
lblCouponStatus.setText("Coupon applied: " + coupon.getCouponCode());
lblCouponStatus.setTextFill(javafx.scene.paint.Color.web("#27ae60"));
updateCartTotal();
});
task.setOnFailed(evt -> {
Throwable e = task.getException();
lblCouponStatus.setText("Invalid or not found.");
lblCouponStatus.setTextFill(javafx.scene.paint.Color.web("#e74c3c"));
ActivityLogger.getInstance().logException(
"SaleController.btnApplyCoupon",
e instanceof Exception ? (Exception) e : new RuntimeException(e),
"Applying coupon"
);
});
new Thread(task).start();
}
@FXML
void btnClearCoupon(ActionEvent event) {
appliedCoupon = null;
txtCouponCode.clear();
txtCouponCode.setDisable(false);
lblCouponStatus.setText("");
hbCouponDiscount.setVisible(false);
hbCouponDiscount.setManaged(false);
updateCartTotal();
}
@FXML
void btnSaveSale(ActionEvent event) {
if (saleSaveInProgress) {
@@ -409,6 +615,7 @@ public class SaleController {
SaleRequest request = new SaleRequest();
request.setStoreId(storeId);
request.setPaymentMethod(payment);
request.setIsRefund(false);
List<SaleItemRequest> itemRequests = new ArrayList<>();
for (SaleCartItem cartItem : cartItems) {
@@ -419,6 +626,20 @@ public class SaleController {
}
request.setItems(itemRequests);
DropdownOption customerOption = cbCustomer.getValue();
if (customerOption != null) {
request.setCustomerId(customerOption.getId());
}
if (appliedCoupon != null) {
request.setCouponId(appliedCoupon.getCouponId());
}
if (chkUseLoyaltyPoints.isSelected() && selectedCustomerData != null) {
BigDecimal subtotal = BigDecimal.valueOf(cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum());
BigDecimal couponDiscount = calculateCouponDiscount(subtotal);
BigDecimal subtotalAfterCoupon = subtotal.subtract(couponDiscount);
request.setPointsUsed(calculatePointsToUse(subtotalAfterCoupon));
}
saleSaveInProgress = true;
setCreateSaleControlsDisabled(true);
btnRefund.setDisable(true);
@@ -437,6 +658,21 @@ public class SaleController {
SaleResponse response = task.getValue();
showInfo("Sale saved", "Sale ID " + response.getSaleId() + " was created.");
cartItems.clear();
appliedCoupon = null;
txtCouponCode.clear();
txtCouponCode.setDisable(false);
lblCouponStatus.setText("");
hbCouponDiscount.setVisible(false);
hbCouponDiscount.setManaged(false);
hbLoyaltyDiscount.setVisible(false);
hbLoyaltyDiscount.setManaged(false);
cbCustomer.setValue(null);
selectedCustomerData = null;
lblLoyaltyPoints.setVisible(false);
lblLoyaltyPoints.setManaged(false);
chkUseLoyaltyPoints.setVisible(false);
chkUseLoyaltyPoints.setManaged(false);
chkUseLoyaltyPoints.setSelected(false);
updateCartTotal();
refreshSales(true);
});
@@ -536,6 +772,9 @@ public class SaleController {
private SaleDetail mapToSaleDetail(SaleResponse sale) {
ObservableList<SaleDetail.SaleDetailItem> items = FXCollections.observableArrayList();
double saleSubtotal = sale.getSubtotalAmount() != null ? Math.abs(sale.getSubtotalAmount().doubleValue()) : 0;
double saleActualTotal = sale.getTotalAmount() != null ? Math.abs(sale.getTotalAmount().doubleValue()) : 0;
double discountRatio = saleSubtotal > 0 ? saleActualTotal / saleSubtotal : 1.0;
if (sale.getItems() != null) {
boolean isRefund = sale.getIsRefund() != null && sale.getIsRefund();
for (SaleItemResponse item : sale.getItems()) {
@@ -549,10 +788,13 @@ public class SaleController {
item.getProductName(),
quantity,
unitPrice,
unitPrice * quantity
unitPrice * quantity * discountRatio
));
}
}
double subtotal = sale.getSubtotalAmount() != null ? sale.getSubtotalAmount().doubleValue() : 0.0;
double couponDiscount = sale.getCouponDiscountAmount() != null ? sale.getCouponDiscountAmount().doubleValue() : 0.0;
double loyaltyDiscount = sale.getLoyaltyDiscountAmount() != null ? sale.getLoyaltyDiscountAmount().doubleValue() : 0.0;
return new SaleDetail(
sale.getSaleId().intValue(),
sale.getSaleDate(),
@@ -560,18 +802,76 @@ public class SaleController {
sale.getPaymentMethod(),
sale.getEmployeeName(),
Boolean.TRUE.equals(sale.getIsRefund()),
items
items,
sale.getCustomerName(),
subtotal,
couponDiscount,
loyaltyDiscount
);
}
private void updateCartTotal() {
double total = cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum();
lblCartTotal.setText(currency.format(total));
BigDecimal subtotal = BigDecimal.valueOf(cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum());
BigDecimal couponDiscount = calculateCouponDiscount(subtotal);
BigDecimal subtotalAfterCoupon = subtotal.subtract(couponDiscount);
BigDecimal loyaltyDiscount = calculateLoyaltyDiscount(subtotalAfterCoupon);
BigDecimal total = subtotalAfterCoupon.subtract(loyaltyDiscount);
lblSubtotal.setText(currency.format(subtotal.doubleValue()));
if (couponDiscount.compareTo(BigDecimal.ZERO) > 0) {
lblCouponDiscount.setText("-" + currency.format(couponDiscount.doubleValue()));
hbCouponDiscount.setVisible(true);
hbCouponDiscount.setManaged(true);
} else {
hbCouponDiscount.setVisible(false);
hbCouponDiscount.setManaged(false);
}
if (loyaltyDiscount.compareTo(BigDecimal.ZERO) > 0) {
int pointsToUse = calculatePointsToUse(subtotalAfterCoupon);
lblLoyaltyDiscountLabel.setText("Loyalty Discount (" + pointsToUse + " pts):");
lblLoyaltyDiscount.setText("-" + currency.format(loyaltyDiscount.doubleValue()));
hbLoyaltyDiscount.setVisible(true);
hbLoyaltyDiscount.setManaged(true);
} else {
hbLoyaltyDiscount.setVisible(false);
hbLoyaltyDiscount.setManaged(false);
}
lblCartTotal.setText(currency.format(Math.max(0, total.doubleValue())));
}
private BigDecimal calculateCouponDiscount(BigDecimal subtotal) {
if (appliedCoupon == null) {
return BigDecimal.ZERO;
}
if ("PERCENTAGE".equalsIgnoreCase(appliedCoupon.getDiscountType())) {
return subtotal.multiply(appliedCoupon.getDiscountValue()).divide(BigDecimal.valueOf(100));
} else {
return appliedCoupon.getDiscountValue().min(subtotal);
}
}
private BigDecimal calculateLoyaltyDiscount(BigDecimal subtotalAfterCoupon) {
if (!chkUseLoyaltyPoints.isSelected() || selectedCustomerData == null) {
return BigDecimal.ZERO;
}
int pointsToUse = calculatePointsToUse(subtotalAfterCoupon);
return BigDecimal.valueOf(pointsToUse).multiply(BigDecimal.valueOf(0.05));
}
private int calculatePointsToUse(BigDecimal subtotalAfterCoupon) {
if (selectedCustomerData == null || selectedCustomerData.getLoyaltyPoints() == null) {
return 0;
}
int maxPointsNeeded = subtotalAfterCoupon.multiply(BigDecimal.valueOf(20)).intValue();
return Math.min(selectedCustomerData.getLoyaltyPoints(), maxPointsNeeded);
}
private void updateSalesColumnWidths(double tableWidth) {
double available = Math.max(tableWidth - 28.0, 0.0);
double baseWidth = 1255.0;
double baseWidth = 1395.0;
if (available <= 0) {
return;
}
@@ -580,6 +880,7 @@ public class SaleController {
colSaleId.setPrefWidth(60.0);
colSaleDate.setPrefWidth(170.0);
colEmployeeName.setPrefWidth(160.0);
colCustomerName.setPrefWidth(140.0);
colServiceProduct.setPrefWidth(320.0);
colSaleQuantity.setPrefWidth(70.0);
colSaleUnitPrice.setPrefWidth(115.0);
@@ -591,14 +892,15 @@ public class SaleController {
double extra = available - baseWidth;
colSaleId.setPrefWidth(60.0);
colSaleDate.setPrefWidth(170.0 + extra * 0.16);
colEmployeeName.setPrefWidth(160.0 + extra * 0.16);
colServiceProduct.setPrefWidth(320.0 + extra * 0.38);
colSaleDate.setPrefWidth(170.0 + extra * 0.14);
colEmployeeName.setPrefWidth(160.0 + extra * 0.14);
colCustomerName.setPrefWidth(140.0 + extra * 0.12);
colServiceProduct.setPrefWidth(320.0 + extra * 0.34);
colSaleQuantity.setPrefWidth(70.0);
colSaleUnitPrice.setPrefWidth(115.0 + extra * 0.08);
colSaleTotal.setPrefWidth(120.0 + extra * 0.08);
colSalePaymentType.setPrefWidth(110.0 + extra * 0.06);
colStoreName.setPrefWidth(130.0 + extra * 0.08);
colStoreName.setPrefWidth(130.0 + extra * 0.04);
}
private void updateCartColumnWidths(double tableWidth) {
@@ -631,6 +933,11 @@ public class SaleController {
cbPaymentMethod.setDisable(disabled);
btnClearCart.setDisable(disabled);
btnSaveSale.setDisable(disabled);
cbCustomer.setDisable(disabled);
txtCouponCode.setDisable(disabled);
btnApplyCoupon.setDisable(disabled);
btnClearCoupon.setDisable(disabled);
chkUseLoyaltyPoints.setDisable(disabled);
}
private void applySalesFilter(String filter) {
@@ -644,6 +951,7 @@ public class SaleController {
String.valueOf(s.getSaleId()).contains(f)
|| safe(s.getSaleDate()).contains(f)
|| safe(s.getEmployeeName()).contains(f)
|| safe(s.getCustomerName()).contains(f)
|| safe(s.getItemName()).contains(f)
|| safe(s.getPaymentMethod()).contains(f)
);

View File

@@ -49,6 +49,7 @@ public class AdoptionDialogController {
private Adoption selectedAdoption = null;
private boolean suppressStatusListener = false;
private Long pendingStoreId = null;
private boolean isEditing = false;
private final ObservableList<String> statusList = FXCollections.observableArrayList(
"Pending", "Completed", "Missed", "Cancelled"
@@ -63,7 +64,6 @@ public class AdoptionDialogController {
}
});
cbEmployee.setPromptText("Select an employee");
txtAdoptionFee.setDisable(true);
LocalDate today = LocalDate.now();
@@ -91,6 +91,12 @@ public class AdoptionDialogController {
if (UserSession.getInstance().isAdmin()) {
vbStore.setVisible(true);
vbStore.setManaged(true);
cbPet.setDisable(true);
cbPet.setPromptText("Select a store first");
cbEmployee.setDisable(true);
cbEmployee.setPromptText("Select a store first");
} else {
cbEmployee.setPromptText("Select an employee");
}
loadDropdownsAsync();
@@ -126,49 +132,12 @@ public class AdoptionDialogController {
}
private void loadDropdownsAsync() {
new Thread(() -> {
try {
List<DropdownOption> pets = DropdownApi.getInstance().getAdoptionPets();
Platform.runLater(() -> {
if (pets != null) {
ObservableList<DropdownOption> petsObs = FXCollections.observableArrayList(pets);
ensureSelectedPetOption(petsObs);
cbPet.setItems(petsObs);
applySelectedPet();
}
});
} catch (Exception e) {
Platform.runLater(() -> ActivityLogger.getInstance().logException(
"AdoptionDialogController.loadDropdownsAsync", e, "Loading pets"));
}
}).start();
new Thread(() -> {
try {
List<DropdownOption> employees = DropdownApi.getInstance().getEmployees();
Platform.runLater(() -> {
ObservableList<DropdownOption> employeesObs = FXCollections.observableArrayList(employees);
ensureSelectedEmployeeOption(employeesObs);
cbEmployee.setItems(employeesObs);
applySelectedEmployee();
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.loadDropdownsAsync", e, "Loading employees");
cbEmployee.setDisable(true);
cbEmployee.setPromptText("Unable to load employees");
});
}
}).start();
new Thread(() -> {
try {
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
Platform.runLater(() -> {
if (customers != null) {
ObservableList<DropdownOption> customersObs = FXCollections.observableArrayList(customers);
cbCustomer.setItems(customersObs);
cbCustomer.setItems(FXCollections.observableArrayList(customers));
applySelectedCustomer();
}
});
@@ -185,6 +154,44 @@ public class AdoptionDialogController {
Platform.runLater(() -> {
if (stores != null) {
cbStore.setItems(FXCollections.observableArrayList(stores));
cbStore.valueProperty().addListener((obs, oldVal, newVal) -> {
Long sid = newVal != null ? newVal.getId() : null;
cbEmployee.setValue(null);
cbEmployee.setItems(FXCollections.observableArrayList());
if (sid != null) {
String status = cbAdoptionStatus.getValue();
boolean locked = "Cancelled".equalsIgnoreCase(status)
|| "Completed".equalsIgnoreCase(status)
|| "Missed".equalsIgnoreCase(status);
if (!locked) {
cbEmployee.setDisable(false);
cbEmployee.setPromptText("Select an employee");
}
loadEmployeesForStore(sid);
if (!isEditing) {
cbPet.setValue(null);
cbPet.setItems(FXCollections.observableArrayList());
cbPet.setDisable(true);
cbPet.setPromptText("Select a pet");
loadAdoptionPetsForStore(sid);
}
} else {
String status = cbAdoptionStatus.getValue();
boolean locked = "Cancelled".equalsIgnoreCase(status)
|| "Completed".equalsIgnoreCase(status)
|| "Missed".equalsIgnoreCase(status);
if (!locked) {
cbEmployee.setDisable(true);
cbEmployee.setPromptText("Select a store first");
}
if (!isEditing) {
cbPet.setValue(null);
cbPet.setItems(FXCollections.observableArrayList());
cbPet.setDisable(true);
cbPet.setPromptText("Select a store first");
}
}
});
if (pendingStoreId != null) {
for (DropdownOption store : cbStore.getItems()) {
if (pendingStoreId.equals(store.getId())) {
@@ -201,9 +208,98 @@ public class AdoptionDialogController {
"AdoptionDialogController.loadDropdownsAsync", e, "Loading stores"));
}
}).start();
} else {
Long storeId = UserSession.getInstance().getStoreId();
if (storeId != null && storeId > 0) {
new Thread(() -> {
try {
List<DropdownOption> employees = DropdownApi.getInstance().getStoreEmployees(storeId);
Platform.runLater(() -> {
ObservableList<DropdownOption> employeesObs = FXCollections.observableArrayList(employees != null ? employees : List.of());
ensureSelectedEmployeeOption(employeesObs);
cbEmployee.setItems(employeesObs);
applySelectedEmployee();
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.loadDropdownsAsync", e, "Loading employees");
cbEmployee.setDisable(true);
cbEmployee.setPromptText("Unable to load employees");
});
}
}).start();
new Thread(() -> {
try {
List<DropdownOption> pets = DropdownApi.getInstance().getAdoptionPets(storeId);
Platform.runLater(() -> {
if (!isEditing && pets != null) {
ObservableList<DropdownOption> petsObs = FXCollections.observableArrayList(pets);
cbPet.setItems(petsObs);
if (petsObs.isEmpty()) {
cbPet.setDisable(true);
cbPet.setPromptText("No available pets for this store");
}
}
});
} catch (Exception e) {
Platform.runLater(() -> ActivityLogger.getInstance().logException(
"AdoptionDialogController.loadDropdownsAsync", e, "Loading pets"));
}
}).start();
}
}
}
private void loadEmployeesForStore(Long storeId) {
new Thread(() -> {
try {
List<DropdownOption> employees = DropdownApi.getInstance().getStoreEmployees(storeId);
Platform.runLater(() -> {
ObservableList<DropdownOption> employeesObs = FXCollections.observableArrayList(employees != null ? employees : List.of());
ensureSelectedEmployeeOption(employeesObs);
cbEmployee.setItems(employeesObs);
applySelectedEmployee();
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.loadEmployeesForStore", e, "Loading employees for store");
cbEmployee.setDisable(true);
cbEmployee.setPromptText("Unable to load employees");
});
}
}).start();
}
private void loadAdoptionPetsForStore(Long storeId) {
new Thread(() -> {
try {
List<DropdownOption> pets = DropdownApi.getInstance().getAdoptionPets(storeId);
Platform.runLater(() -> {
if (pets != null) {
ObservableList<DropdownOption> petsObs = FXCollections.observableArrayList(pets);
cbPet.setItems(petsObs);
if (petsObs.isEmpty()) {
cbPet.setDisable(true);
cbPet.setPromptText("No available pets for this store");
} else {
cbPet.setDisable(false);
}
}
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.loadAdoptionPetsForStore", e, "Loading adoption pets for store");
cbPet.setDisable(true);
cbPet.setPromptText("Unable to load pets");
});
}
}).start();
}
private void applyStatusFieldRules(String status) {
if ("Cancelled".equalsIgnoreCase(status) || "Completed".equalsIgnoreCase(status) || "Missed".equalsIgnoreCase(status)) {
cbEmployee.setDisable(true);
@@ -212,7 +308,7 @@ public class AdoptionDialogController {
} else {
cbEmployee.setDisable(false);
dpAdoptionDate.setDisable(false);
if (UserSession.getInstance().isAdmin()) cbStore.setDisable(false);
if (UserSession.getInstance().isAdmin() && !isEditing) cbStore.setDisable(false);
}
}
@@ -302,10 +398,19 @@ public class AdoptionDialogController {
public void displayAdoptionDetails(Adoption adoption) {
if (adoption == null) return;
selectedAdoption = adoption;
isEditing = true;
lblAdoptionId.setText("ID: " + adoption.getAdoptionId());
pendingStoreId = adoption.getStoreId();
ensureSelectedEmployeeOption(cbEmployee.getItems());
applySelectedPet();
if (adoption.getPetId() > 0) {
DropdownOption petOption = new DropdownOption();
petOption.setId((long) adoption.getPetId());
petOption.setLabel(adoption.getPetName() != null && !adoption.getPetName().isBlank()
? adoption.getPetName() : "Pet #" + adoption.getPetId());
cbPet.setItems(FXCollections.observableArrayList(petOption));
cbPet.setValue(petOption);
}
applySelectedCustomer();
applySelectedEmployee();
@@ -370,7 +475,7 @@ public class AdoptionDialogController {
dpAdoptionDate.setDisable(false);
cbEmployee.setDisable(false);
cbAdoptionStatus.setDisable(false);
if (UserSession.getInstance().isAdmin()) cbStore.setDisable(false);
cbStore.setDisable(true);
suppressStatusListener = true;
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Pending", "Cancelled"));
if (!cbAdoptionStatus.getItems().contains(cbAdoptionStatus.getValue())) {

View File

@@ -51,6 +51,7 @@ public class AppointmentDialogController {
private Long pendingStoreId = null;
private boolean isOriginallyCancel = false;
private boolean isOriginallyCompletedOrMissed = false;
private boolean isEditing = false;
public void setMode(String mode) {
this.mode = mode;
@@ -83,7 +84,7 @@ public class AppointmentDialogController {
cbHour.setDisable(false);
cbMinute.setDisable(false);
dpAppointmentDate.setDisable(false);
if (UserSession.getInstance().isAdmin()) cbStore.setDisable(false);
if (UserSession.getInstance().isAdmin() && !isEditing) cbStore.setDisable(false);
}
}
});
@@ -203,7 +204,7 @@ public class AppointmentDialogController {
Long customerId = newValue != null ? newValue.getId() : null;
cbPet.setValue(null);
cbPet.setItems(FXCollections.observableArrayList());
cbPet.setDisable(customerId == null || isOriginallyCancel || isOriginallyCompletedOrMissed);
cbPet.setDisable(customerId == null || isOriginallyCancel || isOriginallyCompletedOrMissed || isEditing);
if (customerId != null) {
cbPet.setPromptText("Loading customer pets...");
loadCustomerPets(customerId);
@@ -231,6 +232,8 @@ public class AppointmentDialogController {
if (UserSession.getInstance().isAdmin()) {
vbStore.setVisible(true);
vbStore.setManaged(true);
cbEmployee.setDisable(true);
cbEmployee.setPromptText("Select a store first");
}
btnSave.setOnMouseClicked(this::buttonSaveClicked);
@@ -248,6 +251,7 @@ public class AppointmentDialogController {
public void displayAppointmentDetails(AppointmentDTO appt) {
selectedAppointment = appt;
isEditing = true;
lblAppointmentId.setText("ID: " + appt.getAppointmentId());
pendingPetSelectionId = appt.getPetId() > 0 ? (long) appt.getPetId() : null;
pendingStoreId = appt.getStoreId();
@@ -314,6 +318,7 @@ public class AppointmentDialogController {
cbService.setDisable(true);
cbCustomer.setDisable(true);
cbPet.setDisable(true);
cbStore.setDisable(true);
cbEmployee.setDisable(false);
cbHour.setDisable(false);
cbMinute.setDisable(false);
@@ -468,7 +473,7 @@ public class AppointmentDialogController {
}
}
cbPet.setItems(petOptions);
cbPet.setDisable(petOptions.isEmpty() || isOriginallyCancel || isOriginallyCompletedOrMissed);
cbPet.setDisable(petOptions.isEmpty() || isOriginallyCancel || isOriginallyCompletedOrMissed || isEditing);
cbPet.setPromptText(petOptions.isEmpty() ? "No pets for selected customer" : "Select a pet");
if (pendingPetSelectionId != null) {
for (DropdownOption pet : cbPet.getItems()) {
@@ -522,7 +527,7 @@ public class AppointmentDialogController {
Platform.runLater(() -> {
cbCustomer.setItems(FXCollections.observableArrayList(customers));
boolean hasCustomers = customers != null && !customers.isEmpty();
cbCustomer.setDisable(!hasCustomers || isOriginallyCancel || isOriginallyCompletedOrMissed);
cbCustomer.setDisable(!hasCustomers || isOriginallyCancel || isOriginallyCompletedOrMissed || isEditing);
cbPet.setDisable(true);
cbPet.setItems(FXCollections.observableArrayList());
cbCustomer.setPromptText(hasCustomers ? "Select a customer" : "No customers with pets yet");
@@ -557,7 +562,16 @@ public class AppointmentDialogController {
cbEmployee.setValue(null);
cbEmployee.setItems(FXCollections.observableArrayList());
if (sid != null) {
if (!isOriginallyCancel && !isOriginallyCompletedOrMissed) {
cbEmployee.setDisable(false);
cbEmployee.setPromptText("Select an employee");
}
loadEmployeesForStore(sid);
} else {
if (!isOriginallyCancel && !isOriginallyCompletedOrMissed) {
cbEmployee.setDisable(true);
cbEmployee.setPromptText("Select a store first");
}
}
});
if (pendingStoreId != null) {

View File

@@ -138,6 +138,8 @@ public class CustomerEditDialogController {
UserRequest request = new UserRequest();
request.setUsername(username);
request.setPassword(password.isEmpty() ? null : password);
request.setFirstName(firstName);
request.setLastName(lastName);
request.setFullName(firstName + " " + lastName);
request.setEmail(email);
request.setPhone(phone);

View File

@@ -9,6 +9,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.application.Platform;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
@@ -80,6 +81,21 @@ public class RefundDialogController {
@FXML
private ComboBox<String> cbPaymentMethod;
@FXML
private Label lblRefundSubtotal;
@FXML
private HBox hbRefundCouponDiscount;
@FXML
private Label lblRefundCouponDiscount;
@FXML
private HBox hbRefundLoyaltyDiscount;
@FXML
private Label lblRefundLoyaltyDiscount;
@FXML
private Label lblRefundTotal;
@@ -448,8 +464,68 @@ public class RefundDialogController {
}
private void updateRefundTotal() {
double total = refundItems.stream().mapToDouble(RefundItem::getTotal).sum();
lblRefundTotal.setText(currency.format(total));
if (currentSale == null || refundItems.isEmpty()) {
lblRefundSubtotal.setText(currency.format(0.0));
lblRefundTotal.setText(currency.format(0.0));
hbRefundCouponDiscount.setVisible(false);
hbRefundCouponDiscount.setManaged(false);
hbRefundLoyaltyDiscount.setVisible(false);
hbRefundLoyaltyDiscount.setManaged(false);
return;
}
double originalSubtotal = currentSale.getSubtotalAmount() != null
? currentSale.getSubtotalAmount().doubleValue() : 0;
if (originalSubtotal == 0 && currentSale.getItems() != null) {
for (var item : currentSale.getItems()) {
if (item.getUnitPrice() != null && item.getQuantity() != null) {
originalSubtotal += item.getUnitPrice().doubleValue() * Math.abs(item.getQuantity());
}
}
}
double cartSubtotal = refundItems.stream().mapToDouble(RefundItem::getTotal).sum();
double originalTotal = currentSale.getTotalAmount() != null
? Math.abs(currentSale.getTotalAmount().doubleValue()) : 0;
double refundTotal;
double couponDiscountRefunded = 0;
double loyaltyDiscountRefunded = 0;
if (originalSubtotal > 0) {
double ratio = cartSubtotal / originalSubtotal;
refundTotal = originalTotal * ratio;
if (currentSale.getCouponDiscountAmount() != null) {
couponDiscountRefunded = currentSale.getCouponDiscountAmount().doubleValue() * ratio;
}
if (currentSale.getLoyaltyDiscountAmount() != null) {
loyaltyDiscountRefunded = currentSale.getLoyaltyDiscountAmount().doubleValue() * ratio;
}
} else {
refundTotal = cartSubtotal;
}
lblRefundSubtotal.setText(currency.format(cartSubtotal));
if (couponDiscountRefunded > 0.001) {
lblRefundCouponDiscount.setText("-" + currency.format(couponDiscountRefunded));
hbRefundCouponDiscount.setVisible(true);
hbRefundCouponDiscount.setManaged(true);
} else {
hbRefundCouponDiscount.setVisible(false);
hbRefundCouponDiscount.setManaged(false);
}
if (loyaltyDiscountRefunded > 0.001) {
lblRefundLoyaltyDiscount.setText("-" + currency.format(loyaltyDiscountRefunded));
hbRefundLoyaltyDiscount.setVisible(true);
hbRefundLoyaltyDiscount.setManaged(true);
} else {
hbRefundLoyaltyDiscount.setVisible(false);
hbRefundLoyaltyDiscount.setManaged(false);
}
lblRefundTotal.setText(currency.format(refundTotal));
}
private void closeDialog() {

View File

@@ -27,6 +27,13 @@ public class SaleDetailDialogController {
@FXML private Label lblSaleDate;
@FXML private Label lblEmployee;
@FXML private Label lblPayment;
@FXML private Label lblCustomer;
@FXML private javafx.scene.layout.HBox hbDetailSubtotal;
@FXML private Label lblDetailSubtotal;
@FXML private javafx.scene.layout.HBox hbDetailCouponDiscount;
@FXML private Label lblDetailCouponDiscount;
@FXML private javafx.scene.layout.HBox hbDetailLoyaltyDiscount;
@FXML private Label lblDetailLoyaltyDiscount;
@FXML private Label lblTotal;
@FXML private Button btnRefund;
@FXML private TableView<SaleDetail.SaleDetailItem> tvItems;
@@ -61,6 +68,39 @@ public class SaleDetailDialogController {
lblSaleDate.setText(sale.getSaleDate() != null ? sale.getSaleDate().format(DATE_FORMATTER) : "");
lblEmployee.setText(sale.getEmployeeName() != null ? sale.getEmployeeName() : "");
lblPayment.setText(sale.getPaymentMethod() != null ? sale.getPaymentMethod() : "");
lblCustomer.setText(sale.getCustomerName() != null && !sale.getCustomerName().isEmpty() ? sale.getCustomerName() : "-");
if (!sale.isRefund() && sale.getSubtotalAmount() > 0) {
hbDetailSubtotal.setVisible(true);
hbDetailSubtotal.setManaged(true);
lblDetailSubtotal.setText(currency.format(sale.getSubtotalAmount()));
if (sale.getCouponDiscountAmount() > 0.001) {
lblDetailCouponDiscount.setText("-" + currency.format(sale.getCouponDiscountAmount()));
hbDetailCouponDiscount.setVisible(true);
hbDetailCouponDiscount.setManaged(true);
} else {
hbDetailCouponDiscount.setVisible(false);
hbDetailCouponDiscount.setManaged(false);
}
if (sale.getLoyaltyDiscountAmount() > 0.001) {
lblDetailLoyaltyDiscount.setText("-" + currency.format(sale.getLoyaltyDiscountAmount()));
hbDetailLoyaltyDiscount.setVisible(true);
hbDetailLoyaltyDiscount.setManaged(true);
} else {
hbDetailLoyaltyDiscount.setVisible(false);
hbDetailLoyaltyDiscount.setManaged(false);
}
} else {
hbDetailSubtotal.setVisible(false);
hbDetailSubtotal.setManaged(false);
hbDetailCouponDiscount.setVisible(false);
hbDetailCouponDiscount.setManaged(false);
hbDetailLoyaltyDiscount.setVisible(false);
hbDetailLoyaltyDiscount.setManaged(false);
}
lblTotal.setText(currency.format(sale.getTotalAmount()));
tvItems.setItems(sale.getItems());
if (btnRefund != null) {

View File

@@ -11,8 +11,12 @@ public class SaleDetail {
private final String employeeName;
private final boolean refund;
private final ObservableList<SaleDetailItem> items;
private final String customerName;
private final double subtotalAmount;
private final double couponDiscountAmount;
private final double loyaltyDiscountAmount;
public SaleDetail(int saleId, LocalDateTime saleDate, double totalAmount, String paymentMethod, String employeeName, boolean refund, ObservableList<SaleDetailItem> items) {
public SaleDetail(int saleId, LocalDateTime saleDate, double totalAmount, String paymentMethod, String employeeName, boolean refund, ObservableList<SaleDetailItem> items, String customerName, double subtotalAmount, double couponDiscountAmount, double loyaltyDiscountAmount) {
this.saleId = saleId;
this.saleDate = saleDate;
this.totalAmount = totalAmount;
@@ -20,6 +24,10 @@ public class SaleDetail {
this.employeeName = employeeName;
this.refund = refund;
this.items = items;
this.customerName = customerName != null ? customerName : "";
this.subtotalAmount = subtotalAmount;
this.couponDiscountAmount = couponDiscountAmount;
this.loyaltyDiscountAmount = loyaltyDiscountAmount;
}
public int getSaleId() {
@@ -50,6 +58,22 @@ public class SaleDetail {
return items;
}
public String getCustomerName() {
return customerName;
}
public double getSubtotalAmount() {
return subtotalAmount;
}
public double getCouponDiscountAmount() {
return couponDiscountAmount;
}
public double getLoyaltyDiscountAmount() {
return loyaltyDiscountAmount;
}
public static class SaleDetailItem {
private final int prodId;
private final String productName;

View File

@@ -11,8 +11,9 @@ public class SaleLineItem {
private final String paymentMethod;
private final boolean isRefund;
private final String storeName;
private final String customerName;
public SaleLineItem(int saleId, String saleDate, String employeeName, String itemName, int quantity, double unitPrice, double total, String paymentMethod, boolean isRefund, String storeName) {
public SaleLineItem(int saleId, String saleDate, String employeeName, String itemName, int quantity, double unitPrice, double total, String paymentMethod, boolean isRefund, String storeName, String customerName) {
this.saleId = saleId;
this.saleDate = saleDate;
this.employeeName = employeeName;
@@ -23,6 +24,7 @@ public class SaleLineItem {
this.paymentMethod = paymentMethod;
this.isRefund = isRefund;
this.storeName = storeName != null ? storeName : "";
this.customerName = customerName != null ? customerName : "";
}
public int getSaleId() {
@@ -64,4 +66,8 @@ public class SaleLineItem {
public String getStoreName() {
return storeName;
}
public String getCustomerName() {
return customerName;
}
}

View File

@@ -92,7 +92,7 @@
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Status" />
<ComboBox fx:id="cbActive" maxWidth="1.7976931348623157E308" promptText="Select Status" />
<ComboBox fx:id="cbActive" maxWidth="1.7976931348623157E308" prefWidth="200.0" visibleRowCount="2" promptText="Select Status" />
</children>
</VBox>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">

View File

@@ -165,16 +165,34 @@
</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>
<VBox spacing="2.0" alignment="CENTER_RIGHT">
<children>
<HBox spacing="8.0" alignment="CENTER_RIGHT">
<children>
<Label text="Items Subtotal:" textFill="#2c3e50"><font><Font size="12.0" /></font></Label>
<Label fx:id="lblRefundSubtotal" text="" textFill="#2c3e50"><font><Font size="12.0" /></font></Label>
</children>
</HBox>
<HBox fx:id="hbRefundCouponDiscount" spacing="8.0" alignment="CENTER_RIGHT" visible="false" managed="false">
<children>
<Label text="Coupon Discount:" textFill="#27ae60"><font><Font size="12.0" /></font></Label>
<Label fx:id="lblRefundCouponDiscount" text="" textFill="#27ae60"><font><Font size="12.0" /></font></Label>
</children>
</HBox>
<HBox fx:id="hbRefundLoyaltyDiscount" spacing="8.0" alignment="CENTER_RIGHT" visible="false" managed="false">
<children>
<Label text="Loyalty Discount:" textFill="#27ae60"><font><Font size="12.0" /></font></Label>
<Label fx:id="lblRefundLoyaltyDiscount" text="" textFill="#27ae60"><font><Font size="12.0" /></font></Label>
</children>
</HBox>
<HBox spacing="8.0" alignment="CENTER_RIGHT">
<children>
<Label text="Refund Total:" textFill="#2c3e50"><font><Font name="System Bold" size="14.0" /></font></Label>
<Label fx:id="lblRefundTotal" text="" textFill="#FF6b6b"><font><Font name="System Bold" size="18.0" /></font></Label>
</children>
</HBox>
</children>
</VBox>
<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>

View File

@@ -45,11 +45,45 @@
<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" />
<Label text="Customer" GridPane.columnIndex="0" GridPane.rowIndex="2" />
<Label fx:id="lblCustomer" GridPane.columnIndex="1" GridPane.rowIndex="2" />
</children>
</GridPane>
<HBox alignment="CENTER_RIGHT" style="-fx-background-color: white; -fx-background-radius: 8; -fx-border-color: #e6e6e6; -fx-border-radius: 8; -fx-border-width: 1;">
<padding><Insets bottom="10.0" left="16.0" right="16.0" top="10.0" /></padding>
<children>
<VBox spacing="3.0" alignment="CENTER_RIGHT">
<children>
<HBox fx:id="hbDetailSubtotal" spacing="8.0" alignment="CENTER_RIGHT" visible="false" managed="false">
<children>
<Label text="Subtotal:" textFill="#2c3e50"><font><Font size="12.0" /></font></Label>
<Label fx:id="lblDetailSubtotal" textFill="#2c3e50"><font><Font size="12.0" /></font></Label>
</children>
</HBox>
<HBox fx:id="hbDetailCouponDiscount" spacing="8.0" alignment="CENTER_RIGHT" visible="false" managed="false">
<children>
<Label text="Coupon Discount:" textFill="#27ae60"><font><Font size="12.0" /></font></Label>
<Label fx:id="lblDetailCouponDiscount" textFill="#27ae60"><font><Font size="12.0" /></font></Label>
</children>
</HBox>
<HBox fx:id="hbDetailLoyaltyDiscount" spacing="8.0" alignment="CENTER_RIGHT" visible="false" managed="false">
<children>
<Label text="Loyalty Discount:" textFill="#27ae60"><font><Font size="12.0" /></font></Label>
<Label fx:id="lblDetailLoyaltyDiscount" textFill="#27ae60"><font><Font size="12.0" /></font></Label>
</children>
</HBox>
<HBox spacing="8.0" alignment="CENTER_RIGHT">
<children>
<Label text="Total:" textFill="#2c3e50"><font><Font name="System Bold" size="13.0" /></font></Label>
<Label fx:id="lblTotal" textFill="#2c3e50"><font><Font name="System Bold" size="15.0" /></font></Label>
</children>
</HBox>
</children>
</VBox>
</children>
</HBox>
<TableView fx:id="tvItems" prefHeight="320.0" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colProduct" text="Product" prefWidth="330.0" />

View File

@@ -2,6 +2,7 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
@@ -103,6 +104,27 @@
</Button>
</children>
</FlowPane>
<FlowPane hgap="8.0" maxWidth="Infinity" prefWrapLength="680.0" vgap="8.0">
<children>
<ComboBox fx:id="cbCustomer" minWidth="200.0" prefHeight="24.0" prefWidth="260.0" promptText="Customer (optional)" />
<Label fx:id="lblLoyaltyPoints" text="" textFill="#16a085" visible="false" managed="false">
<font><Font name="System Bold" size="12.0" /></font>
</Label>
<CheckBox fx:id="chkUseLoyaltyPoints" text="Use Loyalty Points" visible="false" managed="false" />
<TextField fx:id="txtCouponCode" prefHeight="24.0" prefWidth="160.0" promptText="Coupon code" />
<Button fx:id="btnApplyCoupon" mnemonicParsing="false" onAction="#btnApplyCoupon" prefHeight="24.0" style="-fx-background-color: #27ae60; -fx-cursor: hand; -fx-background-radius: 8;" text="Apply" textFill="WHITE">
<font><Font name="System Bold" size="13.0" /></font>
<padding><Insets bottom="8.0" left="14.0" right="14.0" top="8.0" /></padding>
</Button>
<Button fx:id="btnClearCoupon" mnemonicParsing="false" onAction="#btnClearCoupon" prefHeight="24.0" style="-fx-background-color: #7f8c8d; -fx-cursor: hand; -fx-background-radius: 8;" text="Clear" textFill="WHITE">
<font><Font name="System Bold" size="13.0" /></font>
<padding><Insets bottom="8.0" left="14.0" right="14.0" top="8.0" /></padding>
</Button>
<Label fx:id="lblCouponStatus" text="" textFill="#27ae60" wrapText="false">
<font><Font size="12.0" /></font>
</Label>
</children>
</FlowPane>
<TableView fx:id="tvCart" prefHeight="120.0" style="-fx-background-color: white; -fx-background-radius: 10;" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colCartProduct" prefWidth="310.0" text="Product" />
@@ -125,20 +147,34 @@
</children>
</HBox>
<Region HBox.hgrow="ALWAYS" />
<HBox alignment="CENTER_LEFT" spacing="8.0">
<VBox spacing="2.0">
<children>
<Label text="Total:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="13.0" />
</font>
</Label>
<Label fx:id="lblCartTotal" text="\$0.00" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<HBox spacing="8.0" alignment="CENTER_LEFT">
<children>
<Label text="Subtotal:" textFill="#2c3e50"><font><Font size="12.0" /></font></Label>
<Label fx:id="lblSubtotal" text="" textFill="#2c3e50"><font><Font size="12.0" /></font></Label>
</children>
</HBox>
<HBox fx:id="hbCouponDiscount" spacing="8.0" alignment="CENTER_LEFT" visible="false" managed="false">
<children>
<Label fx:id="lblCouponDiscountLabel" text="Coupon Discount:" textFill="#27ae60"><font><Font size="12.0" /></font></Label>
<Label fx:id="lblCouponDiscount" text="-$0.00" textFill="#27ae60"><font><Font size="12.0" /></font></Label>
</children>
</HBox>
<HBox fx:id="hbLoyaltyDiscount" spacing="8.0" alignment="CENTER_LEFT" visible="false" managed="false">
<children>
<Label fx:id="lblLoyaltyDiscountLabel" text="Loyalty Discount:" textFill="#27ae60"><font><Font size="12.0" /></font></Label>
<Label fx:id="lblLoyaltyDiscount" text="-$0.00" textFill="#27ae60"><font><Font size="12.0" /></font></Label>
</children>
</HBox>
<HBox spacing="8.0" alignment="CENTER_LEFT">
<children>
<Label text="Total:" textFill="#2c3e50"><font><Font name="System Bold" size="13.0" /></font></Label>
<Label fx:id="lblCartTotal" text="" textFill="#2c3e50"><font><Font name="System Bold" size="16.0" /></font></Label>
</children>
</HBox>
</children>
</HBox>
</VBox>
<FlowPane hgap="8.0" prefWrapLength="220.0" vgap="8.0">
<children>
<Button fx:id="btnClearCart" mnemonicParsing="false" onAction="#btnClearCart" prefHeight="36.0" style="-fx-background-color: transparent; -fx-border-color: #FF6b6b; -fx-border-radius: 8; -fx-cursor: hand;" text="Clear" textFill="#FF6b6b">
@@ -181,6 +217,7 @@
<TableColumn fx:id="colSaleId" minWidth="50.0" prefWidth="60.0" text="ID" />
<TableColumn fx:id="colSaleDate" minWidth="150.0" prefWidth="170.0" text="Date" />
<TableColumn fx:id="colEmployeeName" minWidth="150.0" prefWidth="160.0" text="Employee" />
<TableColumn fx:id="colCustomerName" minWidth="120.0" prefWidth="140.0" text="Customer" />
<TableColumn fx:id="colServiceProduct" minWidth="260.0" prefWidth="320.0" text="Product" />
<TableColumn fx:id="colSaleQuantity" minWidth="55.0" prefWidth="70.0" text="Qty" />
<TableColumn fx:id="colSaleUnitPrice" minWidth="100.0" prefWidth="115.0" text="Unit Price" />