updated sales on desktop, and fixed sales with points again on back end
This commit is contained in:
@@ -28,6 +28,8 @@ public class SaleRequest {
|
|||||||
|
|
||||||
private Long cartId;
|
private Long cartId;
|
||||||
|
|
||||||
|
private Integer pointsUsed;
|
||||||
|
|
||||||
|
|
||||||
public Long getStoreId() {
|
public Long getStoreId() {
|
||||||
return storeId;
|
return storeId;
|
||||||
@@ -101,6 +103,14 @@ public class SaleRequest {
|
|||||||
this.cartId = cartId;
|
this.cartId = cartId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getPointsUsed() {
|
||||||
|
return pointsUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPointsUsed(Integer pointsUsed) {
|
||||||
|
this.pointsUsed = pointsUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
@@ -115,12 +125,13 @@ public class SaleRequest {
|
|||||||
Objects.equals(customerId, that.customerId) &&
|
Objects.equals(customerId, that.customerId) &&
|
||||||
Objects.equals(channel, that.channel) &&
|
Objects.equals(channel, that.channel) &&
|
||||||
Objects.equals(couponId, that.couponId) &&
|
Objects.equals(couponId, that.couponId) &&
|
||||||
Objects.equals(cartId, that.cartId);
|
Objects.equals(cartId, that.cartId) &&
|
||||||
|
Objects.equals(pointsUsed, that.pointsUsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(storeId, paymentMethod, items, isRefund, originalSaleId, customerId, channel, couponId, cartId);
|
return Objects.hash(storeId, paymentMethod, items, isRefund, originalSaleId, customerId, channel, couponId, cartId, pointsUsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -135,6 +146,7 @@ public class SaleRequest {
|
|||||||
", channel='" + channel + '\'' +
|
", channel='" + channel + '\'' +
|
||||||
", couponId=" + couponId +
|
", couponId=" + couponId +
|
||||||
", cartId=" + cartId +
|
", cartId=" + cartId +
|
||||||
|
", pointsUsed=" + pointsUsed +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,10 +162,41 @@ public class SaleService {
|
|||||||
}
|
}
|
||||||
subtotalAmount = subtotalAmount.negate();
|
subtotalAmount = subtotalAmount.negate();
|
||||||
sale.setSubtotalAmount(subtotalAmount);
|
sale.setSubtotalAmount(subtotalAmount);
|
||||||
sale.setTotalAmount(subtotalAmount);
|
|
||||||
sale.setCouponDiscountAmount(BigDecimal.ZERO);
|
Sale originalSale = sale.getOriginalSale();
|
||||||
|
BigDecimal originalSubtotal = originalSale.getSubtotalAmount();
|
||||||
|
BigDecimal loyaltyDiscountRefunded = BigDecimal.ZERO;
|
||||||
|
BigDecimal couponDiscountRefunded = BigDecimal.ZERO;
|
||||||
|
BigDecimal refundTotal;
|
||||||
|
|
||||||
|
if (originalSubtotal != null && originalSubtotal.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
BigDecimal ratio = subtotalAmount.divide(originalSubtotal, 10, RoundingMode.HALF_UP);
|
||||||
|
refundTotal = originalSale.getTotalAmount().abs().multiply(ratio).negate().setScale(2, RoundingMode.HALF_UP);
|
||||||
|
if (originalSale.getLoyaltyDiscountAmount() != null) {
|
||||||
|
loyaltyDiscountRefunded = originalSale.getLoyaltyDiscountAmount().multiply(ratio).setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
if (originalSale.getCouponDiscountAmount() != null) {
|
||||||
|
couponDiscountRefunded = originalSale.getCouponDiscountAmount().multiply(ratio).setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
User refundCustomer = originalSale.getCustomer();
|
||||||
|
if (refundCustomer != null) {
|
||||||
|
sale.setCustomer(refundCustomer);
|
||||||
|
int pointsToRestore = toPointsUsed(loyaltyDiscountRefunded);
|
||||||
|
int pointsEarnedToReverse = originalSale.getPointsEarned() != null
|
||||||
|
? ratio.multiply(BigDecimal.valueOf(originalSale.getPointsEarned())).setScale(0, RoundingMode.FLOOR).intValue()
|
||||||
|
: 0;
|
||||||
|
int currentPoints = refundCustomer.getLoyaltyPoints() != null ? refundCustomer.getLoyaltyPoints() : 0;
|
||||||
|
refundCustomer.setLoyaltyPoints(Math.max(0, currentPoints + pointsToRestore - pointsEarnedToReverse));
|
||||||
|
userRepository.save(refundCustomer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
refundTotal = subtotalAmount.negate();
|
||||||
|
}
|
||||||
|
|
||||||
|
sale.setTotalAmount(refundTotal);
|
||||||
|
sale.setCouponDiscountAmount(couponDiscountRefunded);
|
||||||
sale.setEmployeeDiscountAmount(BigDecimal.ZERO);
|
sale.setEmployeeDiscountAmount(BigDecimal.ZERO);
|
||||||
sale.setLoyaltyDiscountAmount(BigDecimal.ZERO);
|
sale.setLoyaltyDiscountAmount(loyaltyDiscountRefunded);
|
||||||
sale.setPointsEarned(0);
|
sale.setPointsEarned(0);
|
||||||
} else {
|
} else {
|
||||||
if (request.getItems() == null || request.getItems().isEmpty()) {
|
if (request.getItems() == null || request.getItems().isEmpty()) {
|
||||||
@@ -206,18 +237,29 @@ public class SaleService {
|
|||||||
BigDecimal employeeDiscount = calculateEmployeeDiscount(customer, subtotalAmount.subtract(couponDiscount));
|
BigDecimal employeeDiscount = calculateEmployeeDiscount(customer, subtotalAmount.subtract(couponDiscount));
|
||||||
sale.setEmployeeDiscountAmount(employeeDiscount);
|
sale.setEmployeeDiscountAmount(employeeDiscount);
|
||||||
|
|
||||||
boolean useLoyaltyPoints = sale.getCart() != null && Boolean.TRUE.equals(sale.getCart().getPointsApplied());
|
BigDecimal remainingAfterDiscounts = subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount);
|
||||||
BigDecimal loyaltyDiscount = calculateLoyaltyDiscount(customer, subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount), useLoyaltyPoints);
|
BigDecimal loyaltyDiscount;
|
||||||
|
int pointsDeducted;
|
||||||
|
if (request.getPointsUsed() != null && request.getPointsUsed() > 0) {
|
||||||
|
loyaltyDiscount = BigDecimal.valueOf(request.getPointsUsed())
|
||||||
|
.divide(BigDecimal.valueOf(LOYALTY_POINTS_PER_DOLLAR), 2, RoundingMode.HALF_UP)
|
||||||
|
.min(remainingAfterDiscounts.max(BigDecimal.ZERO))
|
||||||
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
pointsDeducted = request.getPointsUsed();
|
||||||
|
} else {
|
||||||
|
boolean useLoyaltyPoints = sale.getCart() != null && Boolean.TRUE.equals(sale.getCart().getPointsApplied());
|
||||||
|
loyaltyDiscount = calculateLoyaltyDiscount(customer, remainingAfterDiscounts, useLoyaltyPoints);
|
||||||
|
pointsDeducted = toPointsUsed(loyaltyDiscount);
|
||||||
|
}
|
||||||
sale.setLoyaltyDiscountAmount(loyaltyDiscount);
|
sale.setLoyaltyDiscountAmount(loyaltyDiscount);
|
||||||
|
|
||||||
BigDecimal finalTotal = subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount).subtract(loyaltyDiscount);
|
BigDecimal finalTotal = subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount).subtract(loyaltyDiscount);
|
||||||
sale.setTotalAmount(finalTotal.max(BigDecimal.ZERO));
|
sale.setTotalAmount(finalTotal.max(BigDecimal.ZERO));
|
||||||
|
|
||||||
int pointsUsed = toPointsUsed(loyaltyDiscount);
|
|
||||||
sale.setPointsEarned(sale.getTotalAmount().setScale(0, RoundingMode.FLOOR).intValue());
|
sale.setPointsEarned(sale.getTotalAmount().setScale(0, RoundingMode.FLOOR).intValue());
|
||||||
if (customer != null) {
|
if (customer != null) {
|
||||||
int currentPoints = customer.getLoyaltyPoints() != null ? customer.getLoyaltyPoints() : 0;
|
int currentPoints = customer.getLoyaltyPoints() != null ? customer.getLoyaltyPoints() : 0;
|
||||||
int updatedPoints = currentPoints - pointsUsed + sale.getPointsEarned();
|
int updatedPoints = currentPoints - pointsDeducted + sale.getPointsEarned();
|
||||||
customer.setLoyaltyPoints(Math.max(updatedPoints, 0));
|
customer.setLoyaltyPoints(Math.max(updatedPoints, 0));
|
||||||
userRepository.save(customer);
|
userRepository.save(customer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ public class SaleRequest {
|
|||||||
private List<SaleItemRequest> items;
|
private List<SaleItemRequest> items;
|
||||||
private Boolean isRefund;
|
private Boolean isRefund;
|
||||||
private Long originalSaleId;
|
private Long originalSaleId;
|
||||||
|
private Long customerId;
|
||||||
|
private Long couponId;
|
||||||
|
private Integer pointsUsed;
|
||||||
|
|
||||||
public SaleRequest() {
|
public SaleRequest() {
|
||||||
}
|
}
|
||||||
@@ -51,4 +54,28 @@ public class SaleRequest {
|
|||||||
public void setOriginalSaleId(Long originalSaleId) {
|
public void setOriginalSaleId(Long originalSaleId) {
|
||||||
this.originalSaleId = 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 Boolean isRefund;
|
||||||
private Long originalSaleId;
|
private Long originalSaleId;
|
||||||
private List<SaleItemResponse> items;
|
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() {
|
public SaleResponse() {
|
||||||
}
|
}
|
||||||
@@ -89,4 +96,60 @@ public class SaleResponse {
|
|||||||
public void setItems(List<SaleItemResponse> items) {
|
public void setItems(List<SaleItemResponse> items) {
|
||||||
this.items = 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 {
|
public class UserRequest {
|
||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
|
private String firstName;
|
||||||
|
private String lastName;
|
||||||
private String fullName;
|
private String fullName;
|
||||||
private String email;
|
private String email;
|
||||||
private String phone;
|
private String phone;
|
||||||
@@ -29,6 +31,22 @@ public class UserRequest {
|
|||||||
this.password = password;
|
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() {
|
public String getFullName() {
|
||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,4 +39,8 @@ public class CouponApi {
|
|||||||
public void deleteCoupon(Long id) throws Exception {
|
public void deleteCoupon(Long id) throws Exception {
|
||||||
apiClient.delete("/api/v1/coupons/" + id);
|
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 {
|
public UserResponse createCustomer(UserRequest request) throws Exception {
|
||||||
return apiClient.post("/api/v1/customers", request, UserResponse.class);
|
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>>() {});
|
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 {
|
public List<DropdownOption> getCustomerPets(Long customerId) throws Exception {
|
||||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/customers/" + customerId + "/pets");
|
String response = apiClient.getRawResponse("/api/v1/dropdowns/customers/" + customerId + "/pets");
|
||||||
if (response == null || response.isEmpty()) {
|
if (response == null || response.isEmpty()) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import javafx.application.Platform;
|
|||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.SelectionMode;
|
import javafx.scene.control.SelectionMode;
|
||||||
@@ -19,18 +20,25 @@ import javafx.scene.control.TableColumn;
|
|||||||
import javafx.scene.control.TableView;
|
import javafx.scene.control.TableView;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.control.cell.PropertyValueFactory;
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.stage.Modality;
|
import javafx.stage.Modality;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.example.petshopdesktop.auth.UserSession;
|
import org.example.petshopdesktop.auth.UserSession;
|
||||||
import javafx.concurrent.Task;
|
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.ProductApi;
|
||||||
import org.example.petshopdesktop.api.endpoints.SaleApi;
|
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.product.ProductResponse;
|
||||||
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
|
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
|
||||||
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
|
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
|
||||||
import org.example.petshopdesktop.api.dto.sale.SaleRequest;
|
import org.example.petshopdesktop.api.dto.sale.SaleRequest;
|
||||||
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
|
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.Product;
|
||||||
import org.example.petshopdesktop.models.SaleCartItem;
|
import org.example.petshopdesktop.models.SaleCartItem;
|
||||||
import org.example.petshopdesktop.models.SaleDetail;
|
import org.example.petshopdesktop.models.SaleDetail;
|
||||||
@@ -111,6 +119,9 @@ public class SaleController {
|
|||||||
@FXML
|
@FXML
|
||||||
private TableColumn<SaleLineItem, String> colEmployeeName;
|
private TableColumn<SaleLineItem, String> colEmployeeName;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<SaleLineItem, String> colCustomerName;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableColumn<SaleLineItem, String> colServiceProduct;
|
private TableColumn<SaleLineItem, String> colServiceProduct;
|
||||||
|
|
||||||
@@ -135,11 +146,53 @@ public class SaleController {
|
|||||||
@FXML
|
@FXML
|
||||||
private TextField txtSearch;
|
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<SaleCartItem> cartItems = FXCollections.observableArrayList();
|
||||||
private final ObservableList<SaleLineItem> saleItems = FXCollections.observableArrayList();
|
private final ObservableList<SaleLineItem> saleItems = FXCollections.observableArrayList();
|
||||||
private FilteredList<SaleLineItem> filteredSales;
|
private FilteredList<SaleLineItem> filteredSales;
|
||||||
private boolean saleSaveInProgress;
|
private boolean saleSaveInProgress;
|
||||||
|
|
||||||
|
private CouponResponse appliedCoupon = null;
|
||||||
|
private UserResponse selectedCustomerData = null;
|
||||||
|
|
||||||
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
|
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
|
||||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
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"));
|
colSaleId.setCellValueFactory(new PropertyValueFactory<>("saleId"));
|
||||||
colSaleDate.setCellValueFactory(new PropertyValueFactory<>("saleDate"));
|
colSaleDate.setCellValueFactory(new PropertyValueFactory<>("saleDate"));
|
||||||
colEmployeeName.setCellValueFactory(new PropertyValueFactory<>("employeeName"));
|
colEmployeeName.setCellValueFactory(new PropertyValueFactory<>("employeeName"));
|
||||||
|
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
|
||||||
|
colCustomerName.setMinWidth(120);
|
||||||
colServiceProduct.setCellValueFactory(new PropertyValueFactory<>("itemName"));
|
colServiceProduct.setCellValueFactory(new PropertyValueFactory<>("itemName"));
|
||||||
colSaleQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
colSaleQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||||
colSaleUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
colSaleUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
||||||
@@ -214,7 +269,23 @@ public class SaleController {
|
|||||||
|
|
||||||
setCreateSaleControlsDisabled(true);
|
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
|
@Override
|
||||||
protected ObservableList<Product> call() throws Exception {
|
protected ObservableList<Product> call() throws Exception {
|
||||||
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
|
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
|
||||||
@@ -232,13 +303,13 @@ public class SaleController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
task.setOnSucceeded(event -> {
|
productsTask.setOnSucceeded(event -> {
|
||||||
cbProduct.setItems(task.getValue());
|
cbProduct.setItems(productsTask.getValue());
|
||||||
setCreateSaleControlsDisabled(false);
|
setCreateSaleControlsDisabled(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
task.setOnFailed(event -> {
|
productsTask.setOnFailed(event -> {
|
||||||
Throwable e = task.getException();
|
Throwable e = productsTask.getException();
|
||||||
ActivityLogger.getInstance().logException(
|
ActivityLogger.getInstance().logException(
|
||||||
"SaleController.setupCreateSale",
|
"SaleController.setupCreateSale",
|
||||||
e instanceof Exception ? (Exception) e : new RuntimeException(e),
|
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.");
|
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();
|
new Thread(task).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,6 +416,9 @@ public class SaleController {
|
|||||||
: "";
|
: "";
|
||||||
|
|
||||||
if (sale.getItems() != null && !sale.getItems().isEmpty()) {
|
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()) {
|
for (SaleItemResponse item : sale.getItems()) {
|
||||||
boolean isRefund = sale.getIsRefund() != null && sale.getIsRefund();
|
boolean isRefund = sale.getIsRefund() != null && sale.getIsRefund();
|
||||||
double unitPrice = item.getUnitPrice() != null ? item.getUnitPrice().doubleValue() : 0.0;
|
double unitPrice = item.getUnitPrice() != null ? item.getUnitPrice().doubleValue() : 0.0;
|
||||||
@@ -288,7 +426,7 @@ public class SaleController {
|
|||||||
if (isRefund && quantity > 0) {
|
if (isRefund && quantity > 0) {
|
||||||
quantity = -quantity;
|
quantity = -quantity;
|
||||||
}
|
}
|
||||||
double lineTotal = unitPrice * quantity;
|
double lineTotal = unitPrice * quantity * discountRatio;
|
||||||
lineItems.add(new SaleLineItem(
|
lineItems.add(new SaleLineItem(
|
||||||
sale.getSaleId().intValue(),
|
sale.getSaleId().intValue(),
|
||||||
saleDate,
|
saleDate,
|
||||||
@@ -299,7 +437,8 @@ public class SaleController {
|
|||||||
lineTotal,
|
lineTotal,
|
||||||
sale.getPaymentMethod(),
|
sale.getPaymentMethod(),
|
||||||
isRefund,
|
isRefund,
|
||||||
sale.getStoreName()
|
sale.getStoreName(),
|
||||||
|
sale.getCustomerName()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,6 +518,73 @@ public class SaleController {
|
|||||||
updateCartTotal();
|
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
|
@FXML
|
||||||
void btnSaveSale(ActionEvent event) {
|
void btnSaveSale(ActionEvent event) {
|
||||||
if (saleSaveInProgress) {
|
if (saleSaveInProgress) {
|
||||||
@@ -409,6 +615,7 @@ public class SaleController {
|
|||||||
SaleRequest request = new SaleRequest();
|
SaleRequest request = new SaleRequest();
|
||||||
request.setStoreId(storeId);
|
request.setStoreId(storeId);
|
||||||
request.setPaymentMethod(payment);
|
request.setPaymentMethod(payment);
|
||||||
|
request.setIsRefund(false);
|
||||||
|
|
||||||
List<SaleItemRequest> itemRequests = new ArrayList<>();
|
List<SaleItemRequest> itemRequests = new ArrayList<>();
|
||||||
for (SaleCartItem cartItem : cartItems) {
|
for (SaleCartItem cartItem : cartItems) {
|
||||||
@@ -419,6 +626,20 @@ public class SaleController {
|
|||||||
}
|
}
|
||||||
request.setItems(itemRequests);
|
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;
|
saleSaveInProgress = true;
|
||||||
setCreateSaleControlsDisabled(true);
|
setCreateSaleControlsDisabled(true);
|
||||||
btnRefund.setDisable(true);
|
btnRefund.setDisable(true);
|
||||||
@@ -437,6 +658,21 @@ public class SaleController {
|
|||||||
SaleResponse response = task.getValue();
|
SaleResponse response = task.getValue();
|
||||||
showInfo("Sale saved", "Sale ID " + response.getSaleId() + " was created.");
|
showInfo("Sale saved", "Sale ID " + response.getSaleId() + " was created.");
|
||||||
cartItems.clear();
|
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();
|
updateCartTotal();
|
||||||
refreshSales(true);
|
refreshSales(true);
|
||||||
});
|
});
|
||||||
@@ -536,6 +772,9 @@ public class SaleController {
|
|||||||
|
|
||||||
private SaleDetail mapToSaleDetail(SaleResponse sale) {
|
private SaleDetail mapToSaleDetail(SaleResponse sale) {
|
||||||
ObservableList<SaleDetail.SaleDetailItem> items = FXCollections.observableArrayList();
|
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) {
|
if (sale.getItems() != null) {
|
||||||
boolean isRefund = sale.getIsRefund() != null && sale.getIsRefund();
|
boolean isRefund = sale.getIsRefund() != null && sale.getIsRefund();
|
||||||
for (SaleItemResponse item : sale.getItems()) {
|
for (SaleItemResponse item : sale.getItems()) {
|
||||||
@@ -549,10 +788,13 @@ public class SaleController {
|
|||||||
item.getProductName(),
|
item.getProductName(),
|
||||||
quantity,
|
quantity,
|
||||||
unitPrice,
|
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(
|
return new SaleDetail(
|
||||||
sale.getSaleId().intValue(),
|
sale.getSaleId().intValue(),
|
||||||
sale.getSaleDate(),
|
sale.getSaleDate(),
|
||||||
@@ -560,18 +802,76 @@ public class SaleController {
|
|||||||
sale.getPaymentMethod(),
|
sale.getPaymentMethod(),
|
||||||
sale.getEmployeeName(),
|
sale.getEmployeeName(),
|
||||||
Boolean.TRUE.equals(sale.getIsRefund()),
|
Boolean.TRUE.equals(sale.getIsRefund()),
|
||||||
items
|
items,
|
||||||
|
sale.getCustomerName(),
|
||||||
|
subtotal,
|
||||||
|
couponDiscount,
|
||||||
|
loyaltyDiscount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCartTotal() {
|
private void updateCartTotal() {
|
||||||
double total = cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum();
|
BigDecimal subtotal = BigDecimal.valueOf(cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum());
|
||||||
lblCartTotal.setText(currency.format(total));
|
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) {
|
private void updateSalesColumnWidths(double tableWidth) {
|
||||||
double available = Math.max(tableWidth - 28.0, 0.0);
|
double available = Math.max(tableWidth - 28.0, 0.0);
|
||||||
double baseWidth = 1255.0;
|
double baseWidth = 1395.0;
|
||||||
if (available <= 0) {
|
if (available <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -580,6 +880,7 @@ public class SaleController {
|
|||||||
colSaleId.setPrefWidth(60.0);
|
colSaleId.setPrefWidth(60.0);
|
||||||
colSaleDate.setPrefWidth(170.0);
|
colSaleDate.setPrefWidth(170.0);
|
||||||
colEmployeeName.setPrefWidth(160.0);
|
colEmployeeName.setPrefWidth(160.0);
|
||||||
|
colCustomerName.setPrefWidth(140.0);
|
||||||
colServiceProduct.setPrefWidth(320.0);
|
colServiceProduct.setPrefWidth(320.0);
|
||||||
colSaleQuantity.setPrefWidth(70.0);
|
colSaleQuantity.setPrefWidth(70.0);
|
||||||
colSaleUnitPrice.setPrefWidth(115.0);
|
colSaleUnitPrice.setPrefWidth(115.0);
|
||||||
@@ -591,14 +892,15 @@ public class SaleController {
|
|||||||
|
|
||||||
double extra = available - baseWidth;
|
double extra = available - baseWidth;
|
||||||
colSaleId.setPrefWidth(60.0);
|
colSaleId.setPrefWidth(60.0);
|
||||||
colSaleDate.setPrefWidth(170.0 + extra * 0.16);
|
colSaleDate.setPrefWidth(170.0 + extra * 0.14);
|
||||||
colEmployeeName.setPrefWidth(160.0 + extra * 0.16);
|
colEmployeeName.setPrefWidth(160.0 + extra * 0.14);
|
||||||
colServiceProduct.setPrefWidth(320.0 + extra * 0.38);
|
colCustomerName.setPrefWidth(140.0 + extra * 0.12);
|
||||||
|
colServiceProduct.setPrefWidth(320.0 + extra * 0.34);
|
||||||
colSaleQuantity.setPrefWidth(70.0);
|
colSaleQuantity.setPrefWidth(70.0);
|
||||||
colSaleUnitPrice.setPrefWidth(115.0 + extra * 0.08);
|
colSaleUnitPrice.setPrefWidth(115.0 + extra * 0.08);
|
||||||
colSaleTotal.setPrefWidth(120.0 + extra * 0.08);
|
colSaleTotal.setPrefWidth(120.0 + extra * 0.08);
|
||||||
colSalePaymentType.setPrefWidth(110.0 + extra * 0.06);
|
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) {
|
private void updateCartColumnWidths(double tableWidth) {
|
||||||
@@ -631,6 +933,11 @@ public class SaleController {
|
|||||||
cbPaymentMethod.setDisable(disabled);
|
cbPaymentMethod.setDisable(disabled);
|
||||||
btnClearCart.setDisable(disabled);
|
btnClearCart.setDisable(disabled);
|
||||||
btnSaveSale.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) {
|
private void applySalesFilter(String filter) {
|
||||||
@@ -644,6 +951,7 @@ public class SaleController {
|
|||||||
String.valueOf(s.getSaleId()).contains(f)
|
String.valueOf(s.getSaleId()).contains(f)
|
||||||
|| safe(s.getSaleDate()).contains(f)
|
|| safe(s.getSaleDate()).contains(f)
|
||||||
|| safe(s.getEmployeeName()).contains(f)
|
|| safe(s.getEmployeeName()).contains(f)
|
||||||
|
|| safe(s.getCustomerName()).contains(f)
|
||||||
|| safe(s.getItemName()).contains(f)
|
|| safe(s.getItemName()).contains(f)
|
||||||
|| safe(s.getPaymentMethod()).contains(f)
|
|| safe(s.getPaymentMethod()).contains(f)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ public class AdoptionDialogController {
|
|||||||
private Adoption selectedAdoption = null;
|
private Adoption selectedAdoption = null;
|
||||||
private boolean suppressStatusListener = false;
|
private boolean suppressStatusListener = false;
|
||||||
private Long pendingStoreId = null;
|
private Long pendingStoreId = null;
|
||||||
|
private boolean isEditing = false;
|
||||||
|
|
||||||
private final ObservableList<String> statusList = FXCollections.observableArrayList(
|
private final ObservableList<String> statusList = FXCollections.observableArrayList(
|
||||||
"Pending", "Completed", "Missed", "Cancelled"
|
"Pending", "Completed", "Missed", "Cancelled"
|
||||||
@@ -63,7 +64,6 @@ public class AdoptionDialogController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cbEmployee.setPromptText("Select an employee");
|
|
||||||
txtAdoptionFee.setDisable(true);
|
txtAdoptionFee.setDisable(true);
|
||||||
|
|
||||||
LocalDate today = LocalDate.now();
|
LocalDate today = LocalDate.now();
|
||||||
@@ -91,6 +91,12 @@ public class AdoptionDialogController {
|
|||||||
if (UserSession.getInstance().isAdmin()) {
|
if (UserSession.getInstance().isAdmin()) {
|
||||||
vbStore.setVisible(true);
|
vbStore.setVisible(true);
|
||||||
vbStore.setManaged(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();
|
loadDropdownsAsync();
|
||||||
@@ -126,49 +132,12 @@ public class AdoptionDialogController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadDropdownsAsync() {
|
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(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
|
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
if (customers != null) {
|
if (customers != null) {
|
||||||
ObservableList<DropdownOption> customersObs = FXCollections.observableArrayList(customers);
|
cbCustomer.setItems(FXCollections.observableArrayList(customers));
|
||||||
cbCustomer.setItems(customersObs);
|
|
||||||
applySelectedCustomer();
|
applySelectedCustomer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -185,6 +154,44 @@ public class AdoptionDialogController {
|
|||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
if (stores != null) {
|
if (stores != null) {
|
||||||
cbStore.setItems(FXCollections.observableArrayList(stores));
|
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) {
|
if (pendingStoreId != null) {
|
||||||
for (DropdownOption store : cbStore.getItems()) {
|
for (DropdownOption store : cbStore.getItems()) {
|
||||||
if (pendingStoreId.equals(store.getId())) {
|
if (pendingStoreId.equals(store.getId())) {
|
||||||
@@ -201,9 +208,98 @@ public class AdoptionDialogController {
|
|||||||
"AdoptionDialogController.loadDropdownsAsync", e, "Loading stores"));
|
"AdoptionDialogController.loadDropdownsAsync", e, "Loading stores"));
|
||||||
}
|
}
|
||||||
}).start();
|
}).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) {
|
private void applyStatusFieldRules(String status) {
|
||||||
if ("Cancelled".equalsIgnoreCase(status) || "Completed".equalsIgnoreCase(status) || "Missed".equalsIgnoreCase(status)) {
|
if ("Cancelled".equalsIgnoreCase(status) || "Completed".equalsIgnoreCase(status) || "Missed".equalsIgnoreCase(status)) {
|
||||||
cbEmployee.setDisable(true);
|
cbEmployee.setDisable(true);
|
||||||
@@ -212,7 +308,7 @@ public class AdoptionDialogController {
|
|||||||
} else {
|
} else {
|
||||||
cbEmployee.setDisable(false);
|
cbEmployee.setDisable(false);
|
||||||
dpAdoptionDate.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) {
|
public void displayAdoptionDetails(Adoption adoption) {
|
||||||
if (adoption == null) return;
|
if (adoption == null) return;
|
||||||
selectedAdoption = adoption;
|
selectedAdoption = adoption;
|
||||||
|
isEditing = true;
|
||||||
lblAdoptionId.setText("ID: " + adoption.getAdoptionId());
|
lblAdoptionId.setText("ID: " + adoption.getAdoptionId());
|
||||||
pendingStoreId = adoption.getStoreId();
|
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();
|
applySelectedCustomer();
|
||||||
applySelectedEmployee();
|
applySelectedEmployee();
|
||||||
|
|
||||||
@@ -370,7 +475,7 @@ public class AdoptionDialogController {
|
|||||||
dpAdoptionDate.setDisable(false);
|
dpAdoptionDate.setDisable(false);
|
||||||
cbEmployee.setDisable(false);
|
cbEmployee.setDisable(false);
|
||||||
cbAdoptionStatus.setDisable(false);
|
cbAdoptionStatus.setDisable(false);
|
||||||
if (UserSession.getInstance().isAdmin()) cbStore.setDisable(false);
|
cbStore.setDisable(true);
|
||||||
suppressStatusListener = true;
|
suppressStatusListener = true;
|
||||||
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Pending", "Cancelled"));
|
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Pending", "Cancelled"));
|
||||||
if (!cbAdoptionStatus.getItems().contains(cbAdoptionStatus.getValue())) {
|
if (!cbAdoptionStatus.getItems().contains(cbAdoptionStatus.getValue())) {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public class AppointmentDialogController {
|
|||||||
private Long pendingStoreId = null;
|
private Long pendingStoreId = null;
|
||||||
private boolean isOriginallyCancel = false;
|
private boolean isOriginallyCancel = false;
|
||||||
private boolean isOriginallyCompletedOrMissed = false;
|
private boolean isOriginallyCompletedOrMissed = false;
|
||||||
|
private boolean isEditing = false;
|
||||||
|
|
||||||
public void setMode(String mode) {
|
public void setMode(String mode) {
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
@@ -83,7 +84,7 @@ public class AppointmentDialogController {
|
|||||||
cbHour.setDisable(false);
|
cbHour.setDisable(false);
|
||||||
cbMinute.setDisable(false);
|
cbMinute.setDisable(false);
|
||||||
dpAppointmentDate.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;
|
Long customerId = newValue != null ? newValue.getId() : null;
|
||||||
cbPet.setValue(null);
|
cbPet.setValue(null);
|
||||||
cbPet.setItems(FXCollections.observableArrayList());
|
cbPet.setItems(FXCollections.observableArrayList());
|
||||||
cbPet.setDisable(customerId == null || isOriginallyCancel || isOriginallyCompletedOrMissed);
|
cbPet.setDisable(customerId == null || isOriginallyCancel || isOriginallyCompletedOrMissed || isEditing);
|
||||||
if (customerId != null) {
|
if (customerId != null) {
|
||||||
cbPet.setPromptText("Loading customer pets...");
|
cbPet.setPromptText("Loading customer pets...");
|
||||||
loadCustomerPets(customerId);
|
loadCustomerPets(customerId);
|
||||||
@@ -231,6 +232,8 @@ public class AppointmentDialogController {
|
|||||||
if (UserSession.getInstance().isAdmin()) {
|
if (UserSession.getInstance().isAdmin()) {
|
||||||
vbStore.setVisible(true);
|
vbStore.setVisible(true);
|
||||||
vbStore.setManaged(true);
|
vbStore.setManaged(true);
|
||||||
|
cbEmployee.setDisable(true);
|
||||||
|
cbEmployee.setPromptText("Select a store first");
|
||||||
}
|
}
|
||||||
|
|
||||||
btnSave.setOnMouseClicked(this::buttonSaveClicked);
|
btnSave.setOnMouseClicked(this::buttonSaveClicked);
|
||||||
@@ -248,6 +251,7 @@ public class AppointmentDialogController {
|
|||||||
public void displayAppointmentDetails(AppointmentDTO appt) {
|
public void displayAppointmentDetails(AppointmentDTO appt) {
|
||||||
|
|
||||||
selectedAppointment = appt;
|
selectedAppointment = appt;
|
||||||
|
isEditing = true;
|
||||||
lblAppointmentId.setText("ID: " + appt.getAppointmentId());
|
lblAppointmentId.setText("ID: " + appt.getAppointmentId());
|
||||||
pendingPetSelectionId = appt.getPetId() > 0 ? (long) appt.getPetId() : null;
|
pendingPetSelectionId = appt.getPetId() > 0 ? (long) appt.getPetId() : null;
|
||||||
pendingStoreId = appt.getStoreId();
|
pendingStoreId = appt.getStoreId();
|
||||||
@@ -314,6 +318,7 @@ public class AppointmentDialogController {
|
|||||||
cbService.setDisable(true);
|
cbService.setDisable(true);
|
||||||
cbCustomer.setDisable(true);
|
cbCustomer.setDisable(true);
|
||||||
cbPet.setDisable(true);
|
cbPet.setDisable(true);
|
||||||
|
cbStore.setDisable(true);
|
||||||
cbEmployee.setDisable(false);
|
cbEmployee.setDisable(false);
|
||||||
cbHour.setDisable(false);
|
cbHour.setDisable(false);
|
||||||
cbMinute.setDisable(false);
|
cbMinute.setDisable(false);
|
||||||
@@ -468,7 +473,7 @@ public class AppointmentDialogController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cbPet.setItems(petOptions);
|
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");
|
cbPet.setPromptText(petOptions.isEmpty() ? "No pets for selected customer" : "Select a pet");
|
||||||
if (pendingPetSelectionId != null) {
|
if (pendingPetSelectionId != null) {
|
||||||
for (DropdownOption pet : cbPet.getItems()) {
|
for (DropdownOption pet : cbPet.getItems()) {
|
||||||
@@ -522,7 +527,7 @@ public class AppointmentDialogController {
|
|||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
cbCustomer.setItems(FXCollections.observableArrayList(customers));
|
cbCustomer.setItems(FXCollections.observableArrayList(customers));
|
||||||
boolean hasCustomers = customers != null && !customers.isEmpty();
|
boolean hasCustomers = customers != null && !customers.isEmpty();
|
||||||
cbCustomer.setDisable(!hasCustomers || isOriginallyCancel || isOriginallyCompletedOrMissed);
|
cbCustomer.setDisable(!hasCustomers || isOriginallyCancel || isOriginallyCompletedOrMissed || isEditing);
|
||||||
cbPet.setDisable(true);
|
cbPet.setDisable(true);
|
||||||
cbPet.setItems(FXCollections.observableArrayList());
|
cbPet.setItems(FXCollections.observableArrayList());
|
||||||
cbCustomer.setPromptText(hasCustomers ? "Select a customer" : "No customers with pets yet");
|
cbCustomer.setPromptText(hasCustomers ? "Select a customer" : "No customers with pets yet");
|
||||||
@@ -557,7 +562,16 @@ public class AppointmentDialogController {
|
|||||||
cbEmployee.setValue(null);
|
cbEmployee.setValue(null);
|
||||||
cbEmployee.setItems(FXCollections.observableArrayList());
|
cbEmployee.setItems(FXCollections.observableArrayList());
|
||||||
if (sid != null) {
|
if (sid != null) {
|
||||||
|
if (!isOriginallyCancel && !isOriginallyCompletedOrMissed) {
|
||||||
|
cbEmployee.setDisable(false);
|
||||||
|
cbEmployee.setPromptText("Select an employee");
|
||||||
|
}
|
||||||
loadEmployeesForStore(sid);
|
loadEmployeesForStore(sid);
|
||||||
|
} else {
|
||||||
|
if (!isOriginallyCancel && !isOriginallyCompletedOrMissed) {
|
||||||
|
cbEmployee.setDisable(true);
|
||||||
|
cbEmployee.setPromptText("Select a store first");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (pendingStoreId != null) {
|
if (pendingStoreId != null) {
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ public class CustomerEditDialogController {
|
|||||||
UserRequest request = new UserRequest();
|
UserRequest request = new UserRequest();
|
||||||
request.setUsername(username);
|
request.setUsername(username);
|
||||||
request.setPassword(password.isEmpty() ? null : password);
|
request.setPassword(password.isEmpty() ? null : password);
|
||||||
|
request.setFirstName(firstName);
|
||||||
|
request.setLastName(lastName);
|
||||||
request.setFullName(firstName + " " + lastName);
|
request.setFullName(firstName + " " + lastName);
|
||||||
request.setEmail(email);
|
request.setEmail(email);
|
||||||
request.setPhone(phone);
|
request.setPhone(phone);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
|
|||||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.cell.PropertyValueFactory;
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
|
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
|
||||||
@@ -80,6 +81,21 @@ public class RefundDialogController {
|
|||||||
@FXML
|
@FXML
|
||||||
private ComboBox<String> cbPaymentMethod;
|
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
|
@FXML
|
||||||
private Label lblRefundTotal;
|
private Label lblRefundTotal;
|
||||||
|
|
||||||
@@ -448,8 +464,68 @@ public class RefundDialogController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateRefundTotal() {
|
private void updateRefundTotal() {
|
||||||
double total = refundItems.stream().mapToDouble(RefundItem::getTotal).sum();
|
if (currentSale == null || refundItems.isEmpty()) {
|
||||||
lblRefundTotal.setText(currency.format(total));
|
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() {
|
private void closeDialog() {
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ public class SaleDetailDialogController {
|
|||||||
@FXML private Label lblSaleDate;
|
@FXML private Label lblSaleDate;
|
||||||
@FXML private Label lblEmployee;
|
@FXML private Label lblEmployee;
|
||||||
@FXML private Label lblPayment;
|
@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 Label lblTotal;
|
||||||
@FXML private Button btnRefund;
|
@FXML private Button btnRefund;
|
||||||
@FXML private TableView<SaleDetail.SaleDetailItem> tvItems;
|
@FXML private TableView<SaleDetail.SaleDetailItem> tvItems;
|
||||||
@@ -61,6 +68,39 @@ public class SaleDetailDialogController {
|
|||||||
lblSaleDate.setText(sale.getSaleDate() != null ? sale.getSaleDate().format(DATE_FORMATTER) : "");
|
lblSaleDate.setText(sale.getSaleDate() != null ? sale.getSaleDate().format(DATE_FORMATTER) : "");
|
||||||
lblEmployee.setText(sale.getEmployeeName() != null ? sale.getEmployeeName() : "");
|
lblEmployee.setText(sale.getEmployeeName() != null ? sale.getEmployeeName() : "");
|
||||||
lblPayment.setText(sale.getPaymentMethod() != null ? sale.getPaymentMethod() : "");
|
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()));
|
lblTotal.setText(currency.format(sale.getTotalAmount()));
|
||||||
tvItems.setItems(sale.getItems());
|
tvItems.setItems(sale.getItems());
|
||||||
if (btnRefund != null) {
|
if (btnRefund != null) {
|
||||||
|
|||||||
@@ -11,8 +11,12 @@ public class SaleDetail {
|
|||||||
private final String employeeName;
|
private final String employeeName;
|
||||||
private final boolean refund;
|
private final boolean refund;
|
||||||
private final ObservableList<SaleDetailItem> items;
|
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.saleId = saleId;
|
||||||
this.saleDate = saleDate;
|
this.saleDate = saleDate;
|
||||||
this.totalAmount = totalAmount;
|
this.totalAmount = totalAmount;
|
||||||
@@ -20,6 +24,10 @@ public class SaleDetail {
|
|||||||
this.employeeName = employeeName;
|
this.employeeName = employeeName;
|
||||||
this.refund = refund;
|
this.refund = refund;
|
||||||
this.items = items;
|
this.items = items;
|
||||||
|
this.customerName = customerName != null ? customerName : "";
|
||||||
|
this.subtotalAmount = subtotalAmount;
|
||||||
|
this.couponDiscountAmount = couponDiscountAmount;
|
||||||
|
this.loyaltyDiscountAmount = loyaltyDiscountAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSaleId() {
|
public int getSaleId() {
|
||||||
@@ -50,6 +58,22 @@ public class SaleDetail {
|
|||||||
return items;
|
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 {
|
public static class SaleDetailItem {
|
||||||
private final int prodId;
|
private final int prodId;
|
||||||
private final String productName;
|
private final String productName;
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ public class SaleLineItem {
|
|||||||
private final String paymentMethod;
|
private final String paymentMethod;
|
||||||
private final boolean isRefund;
|
private final boolean isRefund;
|
||||||
private final String storeName;
|
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.saleId = saleId;
|
||||||
this.saleDate = saleDate;
|
this.saleDate = saleDate;
|
||||||
this.employeeName = employeeName;
|
this.employeeName = employeeName;
|
||||||
@@ -23,6 +24,7 @@ public class SaleLineItem {
|
|||||||
this.paymentMethod = paymentMethod;
|
this.paymentMethod = paymentMethod;
|
||||||
this.isRefund = isRefund;
|
this.isRefund = isRefund;
|
||||||
this.storeName = storeName != null ? storeName : "";
|
this.storeName = storeName != null ? storeName : "";
|
||||||
|
this.customerName = customerName != null ? customerName : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSaleId() {
|
public int getSaleId() {
|
||||||
@@ -64,4 +66,8 @@ public class SaleLineItem {
|
|||||||
public String getStoreName() {
|
public String getStoreName() {
|
||||||
return storeName;
|
return storeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCustomerName() {
|
||||||
|
return customerName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
|
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
|
||||||
<children>
|
<children>
|
||||||
<Label text="Status" />
|
<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>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
|
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
|
||||||
|
|||||||
@@ -165,16 +165,34 @@
|
|||||||
</padding>
|
</padding>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<Region HBox.hgrow="ALWAYS" />
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
<Label text="Refund Total:" textFill="#2c3e50">
|
<VBox spacing="2.0" alignment="CENTER_RIGHT">
|
||||||
<font>
|
<children>
|
||||||
<Font name="System Bold" size="14.0" />
|
<HBox spacing="8.0" alignment="CENTER_RIGHT">
|
||||||
</font>
|
<children>
|
||||||
</Label>
|
<Label text="Items Subtotal:" textFill="#2c3e50"><font><Font size="12.0" /></font></Label>
|
||||||
<Label fx:id="lblRefundTotal" text="\$0.00" textFill="#FF6b6b">
|
<Label fx:id="lblRefundSubtotal" text="" textFill="#2c3e50"><font><Font size="12.0" /></font></Label>
|
||||||
<font>
|
</children>
|
||||||
<Font name="System Bold" size="18.0" />
|
</HBox>
|
||||||
</font>
|
<HBox fx:id="hbRefundCouponDiscount" spacing="8.0" alignment="CENTER_RIGHT" visible="false" managed="false">
|
||||||
</Label>
|
<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" />
|
<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">
|
<Button fx:id="btnProcessRefund" mnemonicParsing="false" onAction="#btnProcessRefundClicked" style="-fx-background-color: #FF6b6b; -fx-cursor: hand; -fx-background-radius: 8;" text="Process Refund" textFill="WHITE">
|
||||||
<font>
|
<font>
|
||||||
|
|||||||
@@ -45,11 +45,45 @@
|
|||||||
<Label fx:id="lblEmployee" GridPane.columnIndex="1" GridPane.rowIndex="1" />
|
<Label fx:id="lblEmployee" GridPane.columnIndex="1" GridPane.rowIndex="1" />
|
||||||
<Label text="Payment" GridPane.columnIndex="2" GridPane.rowIndex="1" />
|
<Label text="Payment" GridPane.columnIndex="2" GridPane.rowIndex="1" />
|
||||||
<Label fx:id="lblPayment" GridPane.columnIndex="3" GridPane.rowIndex="1" />
|
<Label fx:id="lblPayment" GridPane.columnIndex="3" GridPane.rowIndex="1" />
|
||||||
<Label text="Total" GridPane.columnIndex="0" GridPane.rowIndex="2" />
|
<Label text="Customer" GridPane.columnIndex="0" GridPane.rowIndex="2" />
|
||||||
<Label fx:id="lblTotal" GridPane.columnIndex="1" GridPane.rowIndex="2" />
|
<Label fx:id="lblCustomer" GridPane.columnIndex="1" GridPane.rowIndex="2" />
|
||||||
</children>
|
</children>
|
||||||
</GridPane>
|
</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">
|
<TableView fx:id="tvItems" prefHeight="320.0" VBox.vgrow="ALWAYS">
|
||||||
<columns>
|
<columns>
|
||||||
<TableColumn fx:id="colProduct" text="Product" prefWidth="330.0" />
|
<TableColumn fx:id="colProduct" text="Product" prefWidth="330.0" />
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.CheckBox?>
|
||||||
<?import javafx.scene.control.ComboBox?>
|
<?import javafx.scene.control.ComboBox?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.Separator?>
|
<?import javafx.scene.control.Separator?>
|
||||||
@@ -103,6 +104,27 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</children>
|
</children>
|
||||||
</FlowPane>
|
</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">
|
<TableView fx:id="tvCart" prefHeight="120.0" style="-fx-background-color: white; -fx-background-radius: 10;" VBox.vgrow="ALWAYS">
|
||||||
<columns>
|
<columns>
|
||||||
<TableColumn fx:id="colCartProduct" prefWidth="310.0" text="Product" />
|
<TableColumn fx:id="colCartProduct" prefWidth="310.0" text="Product" />
|
||||||
@@ -125,20 +147,34 @@
|
|||||||
</children>
|
</children>
|
||||||
</HBox>
|
</HBox>
|
||||||
<Region HBox.hgrow="ALWAYS" />
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
<HBox alignment="CENTER_LEFT" spacing="8.0">
|
<VBox spacing="2.0">
|
||||||
<children>
|
<children>
|
||||||
<Label text="Total:" textFill="#2c3e50">
|
<HBox spacing="8.0" alignment="CENTER_LEFT">
|
||||||
<font>
|
<children>
|
||||||
<Font name="System Bold" size="13.0" />
|
<Label text="Subtotal:" textFill="#2c3e50"><font><Font size="12.0" /></font></Label>
|
||||||
</font>
|
<Label fx:id="lblSubtotal" text="" textFill="#2c3e50"><font><Font size="12.0" /></font></Label>
|
||||||
</Label>
|
</children>
|
||||||
<Label fx:id="lblCartTotal" text="\$0.00" textFill="#2c3e50">
|
</HBox>
|
||||||
<font>
|
<HBox fx:id="hbCouponDiscount" spacing="8.0" alignment="CENTER_LEFT" visible="false" managed="false">
|
||||||
<Font name="System Bold" size="16.0" />
|
<children>
|
||||||
</font>
|
<Label fx:id="lblCouponDiscountLabel" text="Coupon Discount:" textFill="#27ae60"><font><Font size="12.0" /></font></Label>
|
||||||
</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>
|
</children>
|
||||||
</HBox>
|
</VBox>
|
||||||
<FlowPane hgap="8.0" prefWrapLength="220.0" vgap="8.0">
|
<FlowPane hgap="8.0" prefWrapLength="220.0" vgap="8.0">
|
||||||
<children>
|
<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">
|
<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="colSaleId" minWidth="50.0" prefWidth="60.0" text="ID" />
|
||||||
<TableColumn fx:id="colSaleDate" minWidth="150.0" prefWidth="170.0" text="Date" />
|
<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="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="colServiceProduct" minWidth="260.0" prefWidth="320.0" text="Product" />
|
||||||
<TableColumn fx:id="colSaleQuantity" minWidth="55.0" prefWidth="70.0" text="Qty" />
|
<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" />
|
<TableColumn fx:id="colSaleUnitPrice" minWidth="100.0" prefWidth="115.0" text="Unit Price" />
|
||||||
|
|||||||
Reference in New Issue
Block a user