updated sales on desktop, and fixed sales with points again on back end
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user