comment desktop controllers
This commit is contained in:
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the activity log screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the pet adoption management screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the analytics and reporting screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the appointment scheduling screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
@@ -132,6 +138,10 @@ public class AppointmentController {
|
|||||||
loadAppointments();
|
loadAppointments();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches appointments from the API (filtered by store and optionally by employee)
|
||||||
|
* and updates both the table and the calendar event dots.
|
||||||
|
*/
|
||||||
private void loadAppointments(){
|
private void loadAppointments(){
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try{
|
try{
|
||||||
@@ -165,6 +175,11 @@ public class AppointmentController {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a server-side search query and replaces the local list with results.
|
||||||
|
* Also refreshes calendar dots to match the filtered set.
|
||||||
|
* @param text the search query text
|
||||||
|
*/
|
||||||
private void applyFilter(String text) {
|
private void applyFilter(String text) {
|
||||||
String query = text == null || text.trim().isEmpty() ? null : text.trim();
|
String query = text == null || text.trim().isEmpty() ? null : text.trim();
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
@@ -334,6 +349,10 @@ public class AppointmentController {
|
|||||||
return (selected != null && selected.getId() != null) ? selected.getId() : null;
|
return (selected != null && selected.getId() != null) ? selected.getId() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the client-side filters (calendar date selection and status dropdown)
|
||||||
|
* on top of whatever the server already returned.
|
||||||
|
*/
|
||||||
private void applyFilterPredicate() {
|
private void applyFilterPredicate() {
|
||||||
String selectedStatus = cbStatusFilter.getValue();
|
String selectedStatus = cbStatusFilter.getValue();
|
||||||
filtered.setPredicate(apt -> {
|
filtered.setPredicate(apt -> {
|
||||||
@@ -372,6 +391,12 @@ public class AppointmentController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes the status string from the API to title case for display.
|
||||||
|
* Handles inconsistencies like "cancelled" vs "canceled".
|
||||||
|
* @param status raw status from the server
|
||||||
|
* @return normalized display string
|
||||||
|
*/
|
||||||
private String normalizeAppointmentStatus(String status) {
|
private String normalizeAppointmentStatus(String status) {
|
||||||
if (status == null) {
|
if (status == null) {
|
||||||
return "Booked";
|
return "Booked";
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the in-app messaging screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
@@ -105,6 +111,8 @@ public class ChatController {
|
|||||||
lvActiveConversations.setItems(activeConversations);
|
lvActiveConversations.setItems(activeConversations);
|
||||||
lvClosedConversations.setItems(closedConversations);
|
lvClosedConversations.setItems(closedConversations);
|
||||||
|
|
||||||
|
// The two lists share a single selection — picking one clears the other.
|
||||||
|
// selectionChanging flag prevents the clear from triggering a feedback loop.
|
||||||
lvActiveConversations.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
|
lvActiveConversations.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
|
||||||
if (newVal != null && !selectionChanging) {
|
if (newVal != null && !selectionChanging) {
|
||||||
selectionChanging = true;
|
selectionChanging = true;
|
||||||
@@ -304,6 +312,11 @@ public class ChatController {
|
|||||||
btnAttachment.setStyle("-fx-background-color: #e2e8f0; -fx-background-radius: 12; -fx-text-fill: #475569; -fx-cursor: hand;");
|
btnAttachment.setStyle("-fx-background-color: #e2e8f0; -fx-background-radius: 12; -fx-text-fill: #475569; -fx-cursor: hand;");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures a conversation list with custom cells showing title, preview, and status.
|
||||||
|
* Highlights conversations that need pickup or reply in red.
|
||||||
|
* @param lv the ListView to configure
|
||||||
|
*/
|
||||||
private void setupConversationListView(ListView<ConversationResponse> lv) {
|
private void setupConversationListView(ListView<ConversationResponse> lv) {
|
||||||
lv.setFixedCellSize(CHAT_CELL_HEIGHT);
|
lv.setFixedCellSize(CHAT_CELL_HEIGHT);
|
||||||
lv.setCellFactory(list -> new ListCell<>() {
|
lv.setCellFactory(list -> new ListCell<>() {
|
||||||
@@ -453,6 +466,12 @@ public class ChatController {
|
|||||||
scrollMessagesToBottom();
|
scrollMessagesToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles an incoming message from the WebSocket.
|
||||||
|
* Only adds it to the view if it belongs to the currently selected conversation
|
||||||
|
* and hasn't already been rendered (dedup by message ID).
|
||||||
|
* @param message the incoming message
|
||||||
|
*/
|
||||||
private void appendMessageIfSelected(MessageResponse message) {
|
private void appendMessageIfSelected(MessageResponse message) {
|
||||||
try {
|
try {
|
||||||
upsertConversationForMessage(message);
|
upsertConversationForMessage(message);
|
||||||
@@ -474,6 +493,11 @@ public class ChatController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts or updates a conversation in the list and re-sorts by last activity.
|
||||||
|
* Called when the WebSocket pushes a conversation update (e.g. status change).
|
||||||
|
* @param conversation the updated conversation from the server
|
||||||
|
*/
|
||||||
private void upsertConversation(ConversationResponse conversation) {
|
private void upsertConversation(ConversationResponse conversation) {
|
||||||
Optional<ConversationResponse> existing = conversations.stream()
|
Optional<ConversationResponse> existing = conversations.stream()
|
||||||
.filter(item -> item.getId().equals(conversation.getId()))
|
.filter(item -> item.getId().equals(conversation.getId()))
|
||||||
@@ -507,6 +531,10 @@ public class ChatController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-selects the previously selected conversation after the list is rebuilt.
|
||||||
|
* Needed because refreshSections() replaces all items, which clears the selection.
|
||||||
|
*/
|
||||||
private void restoreSelection() {
|
private void restoreSelection() {
|
||||||
if (selectedConversation == null) {
|
if (selectedConversation == null) {
|
||||||
return;
|
return;
|
||||||
@@ -528,6 +556,12 @@ public class ChatController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a single chat bubble with avatar, author label, text, optional
|
||||||
|
* image/attachment preview, and timestamp. Own messages go right, others left.
|
||||||
|
* @param message the message to render
|
||||||
|
* @return the bubble wrapped in an HBox
|
||||||
|
*/
|
||||||
private HBox createMessageBubble(MessageResponse message) {
|
private HBox createMessageBubble(MessageResponse message) {
|
||||||
boolean mine = message.getSenderId() != null && message.getSenderId().equals(UserSession.getInstance().getUserId());
|
boolean mine = message.getSenderId() != null && message.getSenderId().equals(UserSession.getInstance().getUserId());
|
||||||
|
|
||||||
@@ -609,6 +643,13 @@ public class ChatController {
|
|||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Figures out the display label for a message sender.
|
||||||
|
* Shows "You" for own messages, "AI Bot" for bot messages, and
|
||||||
|
* "Staff" or "Customer" for everyone else.
|
||||||
|
* @param message the message to resolve
|
||||||
|
* @return the author display name
|
||||||
|
*/
|
||||||
private String resolveAuthorLabel(MessageResponse message) {
|
private String resolveAuthorLabel(MessageResponse message) {
|
||||||
if ("BOT".equalsIgnoreCase(message.getSenderRole())) {
|
if ("BOT".equalsIgnoreCase(message.getSenderRole())) {
|
||||||
return message.getSenderDisplayName() != null && !message.getSenderDisplayName().isBlank()
|
return message.getSenderDisplayName() != null && !message.getSenderDisplayName().isBlank()
|
||||||
@@ -630,6 +671,12 @@ public class ChatController {
|
|||||||
return customerLabel != null ? customerLabel : "Customer #" + conversation.getCustomerId();
|
return customerLabel != null ? customerLabel : "Customer #" + conversation.getCustomerId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the small status line under each conversation in the sidebar.
|
||||||
|
* Shows assignment state (Automated, Assigned, Takeover requested, etc.) and time.
|
||||||
|
* @param conversation the conversation to summarize
|
||||||
|
* @return a short status string like "Assigned · Apr 10, 14:30"
|
||||||
|
*/
|
||||||
private String buildConversationMeta(ConversationResponse conversation) {
|
private String buildConversationMeta(ConversationResponse conversation) {
|
||||||
String updated = conversation.getUpdatedAt() == null ? "" : TIME_FORMATTER.format(conversation.getUpdatedAt());
|
String updated = conversation.getUpdatedAt() == null ? "" : TIME_FORMATTER.format(conversation.getUpdatedAt());
|
||||||
if ("CLOSED".equals(conversation.getStatus())) {
|
if ("CLOSED".equals(conversation.getStatus())) {
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the coupon management screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the customer accounts screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the inventory management screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the login screen and handles authentication.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the main layout with sidebar navigation.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
@@ -339,6 +345,10 @@ public class MainLayoutController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the current user info from the server on a background thread
|
||||||
|
* and updates the sidebar avatar and display name.
|
||||||
|
*/
|
||||||
private void refreshProfileHeader() {
|
private void refreshProfileHeader() {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -376,6 +386,12 @@ public class MainLayoutController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the avatar in the sidebar. Shows the uploaded image if available,
|
||||||
|
* otherwise shows a colored circle with the user's initials.
|
||||||
|
* @param displayName used to generate initials as a fallback
|
||||||
|
* @param avatarImage the user's profile image, or null for the initials fallback
|
||||||
|
*/
|
||||||
private void renderAvatar(String displayName, Image avatarImage) {
|
private void renderAvatar(String displayName, Image avatarImage) {
|
||||||
Circle border = new Circle(29);
|
Circle border = new Circle(29);
|
||||||
border.setFill(Color.web("#dbe4ee"));
|
border.setFill(Color.web("#dbe4ee"));
|
||||||
@@ -427,6 +443,11 @@ public class MainLayoutController {
|
|||||||
alert.showAndWait();
|
alert.showAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows or hides sidebar buttons based on the user's role.
|
||||||
|
* Admin gets inventory, suppliers, staff accounts, etc.
|
||||||
|
* Also kicks off chat WebSocket subscription in the background.
|
||||||
|
*/
|
||||||
private void applyRBAC() {
|
private void applyRBAC() {
|
||||||
UserSession session = UserSession.getInstance();
|
UserSession session = UserSession.getInstance();
|
||||||
|
|
||||||
@@ -498,6 +519,10 @@ public class MainLayoutController {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swaps the main content area to a different view.
|
||||||
|
* @param fxmlFile the FXML filename inside the modelviews folder
|
||||||
|
*/
|
||||||
private void loadView(String fxmlFile) {
|
private void loadView(String fxmlFile) {
|
||||||
try {
|
try {
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/modelviews/" + fxmlFile));
|
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/modelviews/" + fxmlFile));
|
||||||
@@ -551,6 +576,11 @@ public class MainLayoutController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps EXIF orientation tag values to rotation angles.
|
||||||
|
* @param orientation the EXIF orientation value (1-8)
|
||||||
|
* @return degrees to rotate the image
|
||||||
|
*/
|
||||||
private double exifOrientationToAngle(int orientation) {
|
private double exifOrientationToAngle(int orientation) {
|
||||||
return switch (orientation) {
|
return switch (orientation) {
|
||||||
case 3 -> 180;
|
case 3 -> 180;
|
||||||
@@ -560,6 +590,13 @@ public class MainLayoutController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually reads the EXIF orientation tag from raw JPEG bytes.
|
||||||
|
* Walks the JPEG markers to find APP1 (Exif), then parses the TIFF IFD
|
||||||
|
* entries looking for tag 0x0112 (orientation).
|
||||||
|
* @param data raw JPEG image bytes
|
||||||
|
* @return the orientation value (1-8), defaults to 1 if not found
|
||||||
|
*/
|
||||||
private int readExifOrientation(byte[] data) {
|
private int readExifOrientation(byte[] data) {
|
||||||
try {
|
try {
|
||||||
if (data.length < 2 || (data[0] & 0xFF) != 0xFF || (data[1] & 0xFF) != 0xD8) return 1;
|
if (data.length < 2 || (data[0] & 0xFF) != 0xFF || (data[1] & 0xFF) != 0xD8) return 1;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the pet listings screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the product catalog screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.animation.KeyFrame;
|
import javafx.animation.KeyFrame;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the product-supplier linking screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the purchase order management screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the sales and transactions screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
@@ -427,6 +433,10 @@ public class SaleController {
|
|||||||
new Thread(task).start();
|
new Thread(task).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows or hides the create-sale panel based on role.
|
||||||
|
* Admins get read-only view; staff can create sales and process refunds.
|
||||||
|
*/
|
||||||
private void applyRoleMode() {
|
private void applyRoleMode() {
|
||||||
boolean isAdmin = UserSession.getInstance().isAdmin();
|
boolean isAdmin = UserSession.getInstance().isAdmin();
|
||||||
vbCreateSale.setVisible(!isAdmin);
|
vbCreateSale.setVisible(!isAdmin);
|
||||||
@@ -440,6 +450,12 @@ public class SaleController {
|
|||||||
refreshSales(false);
|
refreshSales(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all sales from the API and flattens them into per-item line rows.
|
||||||
|
* Applies any active store filter. Discounts are distributed proportionally
|
||||||
|
* across line items using a ratio of total to subtotal.
|
||||||
|
* @param showErrorDialog whether to show error dialogs on failure
|
||||||
|
*/
|
||||||
private void refreshSales(boolean showErrorDialog) {
|
private void refreshSales(boolean showErrorDialog) {
|
||||||
btnRefresh.setDisable(true);
|
btnRefresh.setDisable(true);
|
||||||
Task<List<SaleLineItem>> task = new Task<List<SaleLineItem>>() {
|
Task<List<SaleLineItem>> task = new Task<List<SaleLineItem>>() {
|
||||||
@@ -456,6 +472,7 @@ public class SaleController {
|
|||||||
: "";
|
: "";
|
||||||
|
|
||||||
if (sale.getItems() != null && !sale.getItems().isEmpty()) {
|
if (sale.getItems() != null && !sale.getItems().isEmpty()) {
|
||||||
|
// Spread the discount evenly across line items via a ratio
|
||||||
double saleSubtotal = sale.getSubtotalAmount() != null ? Math.abs(sale.getSubtotalAmount().doubleValue()) : 0;
|
double saleSubtotal = sale.getSubtotalAmount() != null ? Math.abs(sale.getSubtotalAmount().doubleValue()) : 0;
|
||||||
double saleActualTotal = sale.getTotalAmount() != null ? Math.abs(sale.getTotalAmount().doubleValue()) : 0;
|
double saleActualTotal = sale.getTotalAmount() != null ? Math.abs(sale.getTotalAmount().doubleValue()) : 0;
|
||||||
double discountRatio = saleSubtotal > 0 ? saleActualTotal / saleSubtotal : 1.0;
|
double discountRatio = saleSubtotal > 0 ? saleActualTotal / saleSubtotal : 1.0;
|
||||||
@@ -816,6 +833,12 @@ public class SaleController {
|
|||||||
new Thread(task).start();
|
new Thread(task).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a SaleResponse from the API into a SaleDetail for the detail dialog.
|
||||||
|
* Distributes discounts across line items the same way refreshSales does.
|
||||||
|
* @param sale the API response
|
||||||
|
* @return the mapped detail object
|
||||||
|
*/
|
||||||
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 saleSubtotal = sale.getSubtotalAmount() != null ? Math.abs(sale.getSubtotalAmount().doubleValue()) : 0;
|
||||||
@@ -858,6 +881,10 @@ public class SaleController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recalculates the cart summary: subtotal, coupon discount, loyalty discount, and final total.
|
||||||
|
* Also updates the "points to earn" preview for the selected customer.
|
||||||
|
*/
|
||||||
private void updateCartTotal() {
|
private void updateCartTotal() {
|
||||||
BigDecimal subtotal = BigDecimal.valueOf(cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum());
|
BigDecimal subtotal = BigDecimal.valueOf(cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum());
|
||||||
BigDecimal couponDiscount = calculateCouponDiscount(subtotal);
|
BigDecimal couponDiscount = calculateCouponDiscount(subtotal);
|
||||||
@@ -900,6 +927,12 @@ public class SaleController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates how much the coupon takes off. Percentage coupons use the
|
||||||
|
* subtotal; fixed-amount coupons are capped at the subtotal.
|
||||||
|
* @param subtotal the cart subtotal before discounts
|
||||||
|
* @return the discount amount
|
||||||
|
*/
|
||||||
private BigDecimal calculateCouponDiscount(BigDecimal subtotal) {
|
private BigDecimal calculateCouponDiscount(BigDecimal subtotal) {
|
||||||
if (appliedCoupon == null) {
|
if (appliedCoupon == null) {
|
||||||
return BigDecimal.ZERO;
|
return BigDecimal.ZERO;
|
||||||
@@ -911,6 +944,11 @@ public class SaleController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates loyalty point discount. Each point is worth $0.05.
|
||||||
|
* @param subtotalAfterCoupon the amount after coupon discount
|
||||||
|
* @return the loyalty discount amount
|
||||||
|
*/
|
||||||
private BigDecimal calculateLoyaltyDiscount(BigDecimal subtotalAfterCoupon) {
|
private BigDecimal calculateLoyaltyDiscount(BigDecimal subtotalAfterCoupon) {
|
||||||
if (!chkUseLoyaltyPoints.isSelected() || selectedCustomerData == null) {
|
if (!chkUseLoyaltyPoints.isSelected() || selectedCustomerData == null) {
|
||||||
return BigDecimal.ZERO;
|
return BigDecimal.ZERO;
|
||||||
@@ -919,6 +957,13 @@ public class SaleController {
|
|||||||
return BigDecimal.valueOf(pointsToUse).multiply(BigDecimal.valueOf(0.05));
|
return BigDecimal.valueOf(pointsToUse).multiply(BigDecimal.valueOf(0.05));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Figures out how many loyalty points to redeem. Uses up to the customer's
|
||||||
|
* full balance, but never more than would cover the remaining total.
|
||||||
|
* The multiplier of 20 means 20 points = $1.00 of discount.
|
||||||
|
* @param subtotalAfterCoupon amount left after coupon
|
||||||
|
* @return number of points to use
|
||||||
|
*/
|
||||||
private int calculatePointsToUse(BigDecimal subtotalAfterCoupon) {
|
private int calculatePointsToUse(BigDecimal subtotalAfterCoupon) {
|
||||||
if (selectedCustomerData == null || selectedCustomerData.getLoyaltyPoints() == null) {
|
if (selectedCustomerData == null || selectedCustomerData.getLoyaltyPoints() == null) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -998,6 +1043,10 @@ public class SaleController {
|
|||||||
chkUseLoyaltyPoints.setDisable(disabled);
|
chkUseLoyaltyPoints.setDisable(disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the combined text search, payment type, refund status, and customer filters.
|
||||||
|
* @param filter the text search input
|
||||||
|
*/
|
||||||
private void applySalesFilter(String filter) {
|
private void applySalesFilter(String filter) {
|
||||||
String f = filter == null ? "" : filter.trim().toLowerCase();
|
String f = filter == null ? "" : filter.trim().toLowerCase();
|
||||||
String selectedPayment = cbFilterPayment.getValue();
|
String selectedPayment = cbFilterPayment.getValue();
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the services management screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the staff accounts screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the supplier management screen.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers;
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog for creating or editing an adoption.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog for creating or editing an appointment.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog for creating or editing a coupon.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog for editing customer details.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog for adjusting inventory levels.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog for adding or editing a pet.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog for adding or editing a product.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog for linking a product to a supplier.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog that shows purchase order details.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog for processing a refund.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog that shows sale details.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog for creating or editing a service.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog for editing staff member details.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog for registering a new staff member.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Controls the dialog for adding or editing a supplier.
|
||||||
|
*
|
||||||
|
* Author: Harkamal
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||||
|
|
||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
|
|||||||
Reference in New Issue
Block a user