From 044e9ba7b2d3e7cd379f2fc53be88156bb0a2be8 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:49:22 -0600 Subject: [PATCH] made it so staff cannot change the status of pets for desktop for adopted or owned --- .../detailfragments/PetDetailFragment.java | 4 + .../PetProfileFragment.java | 4 +- .../backend/service/AdoptionService.java | 1 + .../api/dto/user/UserResponse.java | 9 + .../controllers/StaffAccountsController.java | 177 +++++++++++++----- .../PetDialogController.java | 80 ++++---- .../modelviews/staff-accounts-view.fxml | 140 ++++++++++---- 7 files changed, 292 insertions(+), 123 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java index 69ff8215..a22e2c0d 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java @@ -164,6 +164,10 @@ public class PetDetailFragment extends Fragment { if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Owner")) return; if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return; } + if ("Pending".equalsIgnoreCase(status)) { + if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Owner")) return; + if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return; + } PetDTO petDTO = new PetDTO(); petDTO.setPetName(name); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/listprofilefragments/PetProfileFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/listprofilefragments/PetProfileFragment.java index 73da4613..496304a3 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/listprofilefragments/PetProfileFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/listprofilefragments/PetProfileFragment.java @@ -126,7 +126,7 @@ public class PetProfileFragment extends Fragment { String status = pet.getPetStatus(); - if ("Adopted".equalsIgnoreCase(status) || "Owned".equalsIgnoreCase(status)) { + if ("Adopted".equalsIgnoreCase(status) || "Owned".equalsIgnoreCase(status) || "Pending".equalsIgnoreCase(status)) { binding.layoutPetOwner.setVisibility(View.VISIBLE); if (pet.getCustomerName() != null && !pet.getCustomerName().isEmpty()) { binding.tvPetOwner.setText(pet.getCustomerName()); @@ -137,7 +137,7 @@ public class PetProfileFragment extends Fragment { binding.layoutPetOwner.setVisibility(View.GONE); } - if ("Available".equalsIgnoreCase(status) || "Adopted".equalsIgnoreCase(status)) { + if ("Available".equalsIgnoreCase(status) || "Adopted".equalsIgnoreCase(status) || "Pending".equalsIgnoreCase(status)) { binding.layoutPetStore.setVisibility(View.VISIBLE); if (pet.getStoreName() != null && !pet.getStoreName().isEmpty()) { binding.tvPetStore.setText(pet.getStoreName()); diff --git a/backend/src/main/java/com/petshop/backend/service/AdoptionService.java b/backend/src/main/java/com/petshop/backend/service/AdoptionService.java index 134a78f2..0874aded 100644 --- a/backend/src/main/java/com/petshop/backend/service/AdoptionService.java +++ b/backend/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -271,6 +271,7 @@ public class AdoptionService { pet.setStore(null); } else if (ADOPTION_STATUS_PENDING.equalsIgnoreCase(adoptionStatus)) { pet.setPetStatus(PET_STATUS_PENDING); + pet.setOwner(customer); } else { pet.setPetStatus(PET_STATUS_AVAILABLE); pet.setOwner(null); diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserResponse.java index 3a42f128..f9fc4eac 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserResponse.java @@ -10,6 +10,7 @@ public class UserResponse { private String phone; private String role; private Boolean active; + private Integer loyaltyPoints; private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -72,6 +73,14 @@ public class UserResponse { this.active = active; } + public Integer getLoyaltyPoints() { + return loyaltyPoints; + } + + public void setLoyaltyPoints(Integer loyaltyPoints) { + this.loyaltyPoints = loyaltyPoints; + } + public LocalDateTime getCreatedAt() { return createdAt; } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java index ae749d4c..20dbf6d0 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java @@ -13,21 +13,55 @@ import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; +import javafx.scene.layout.VBox; import javafx.stage.Modality; import javafx.stage.Stage; import org.example.petshopdesktop.api.dto.user.UserResponse; -import org.example.petshopdesktop.api.endpoints.UserApi; import org.example.petshopdesktop.api.endpoints.CustomerApi; +import org.example.petshopdesktop.api.endpoints.UserApi; import org.example.petshopdesktop.auth.UserSession; import org.example.petshopdesktop.util.ActivityLogger; import org.example.petshopdesktop.util.TableViewSupport; -import java.util.List; import java.util.Comparator; +import java.util.List; import java.util.stream.Collectors; public class StaffAccountsController { + @FXML + private VBox staffSection; + + @FXML + private TableView tvCustomers; + + @FXML + private TableColumn colCustomerUsername; + + @FXML + private TableColumn colCustomerName; + + @FXML + private TableColumn colCustomerEmail; + + @FXML + private TableColumn colCustomerPhone; + + @FXML + private TableColumn colCustomerLoyaltyPoints; + + @FXML + private TableColumn colCustomerStatus; + + @FXML + private TableColumn colCustomerCreated; + + @FXML + private TextField txtSearchCustomer; + + @FXML + private Button btnEditCustomer; + @FXML private TableView tvStaff; @@ -55,12 +89,6 @@ public class StaffAccountsController { @FXML private TextField txtSearch; - @FXML - private Label lblError; - - @FXML - private Label lblStatus; - @FXML private Button btnRefresh; @@ -70,11 +98,38 @@ public class StaffAccountsController { @FXML private Button btnEditAccount; + @FXML + private Label lblError; + + @FXML + private Label lblStatus; + + private final ObservableList customerAccounts = FXCollections.observableArrayList(); + private FilteredList filteredCustomers; + private final ObservableList staffAccounts = FXCollections.observableArrayList(); - private FilteredList filtered; + private FilteredList filteredStaff; @FXML public void initialize() { + colCustomerUsername.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getUsername())); + colCustomerName.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getFullName())); + colCustomerEmail.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getEmail())); + colCustomerPhone.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getPhone())); + colCustomerLoyaltyPoints.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getLoyaltyPoints())); + colCustomerStatus.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getActive() != null && data.getValue().getActive() ? "Active" : "Inactive")); + colCustomerCreated.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getCreatedAt())); + + filteredCustomers = new FilteredList<>(customerAccounts, a -> true); + TableViewSupport.bindSortedItems(tvCustomers, filteredCustomers); + TableViewSupport.installDoubleClickAction(tvCustomers, this::openEditDialog); + + tvCustomers.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> + btnEditCustomer.setDisable(newVal == null)); + btnEditCustomer.setDisable(true); + + txtSearchCustomer.textProperty().addListener((obs, o, n) -> applyCustomerFilter(n)); + colUsername.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getUsername())); colName.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getFullName())); colEmail.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getEmail())); @@ -83,33 +138,39 @@ public class StaffAccountsController { colStatus.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getActive() != null && data.getValue().getActive() ? "Active" : "Inactive")); colCreated.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getCreatedAt())); - filtered = new FilteredList<>(staffAccounts, a -> true); - TableViewSupport.bindSortedItems(tvStaff, filtered); + filteredStaff = new FilteredList<>(staffAccounts, a -> true); + TableViewSupport.bindSortedItems(tvStaff, filteredStaff); TableViewSupport.installDoubleClickAction(tvStaff, this::openEditDialog); - txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n)); + tvStaff.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> + btnEditAccount.setDisable(newVal == null)); + btnEditAccount.setDisable(true); - tvStaff.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> { - if (btnEditAccount != null) { - btnEditAccount.setDisable(newValue == null); - } - }); + txtSearch.textProperty().addListener((obs, o, n) -> applyStaffFilter(n)); - if (btnEditAccount != null) { - btnEditAccount.setDisable(true); - } + boolean isAdmin = UserSession.getInstance().isAdmin(); + staffSection.setVisible(isAdmin); + staffSection.setManaged(isAdmin); refresh(); } @FXML void btnRefreshClicked(ActionEvent event) { + txtSearchCustomer.clear(); txtSearch.clear(); + TableViewSupport.clearSort(tvCustomers); TableViewSupport.clearSort(tvStaff); refresh(); TableViewSupport.flashStatus(lblStatus, "Refreshed"); } + @FXML + void btnEditCustomerClicked(ActionEvent event) { + lblError.setText(""); + openEditDialog(tvCustomers.getSelectionModel().getSelectedItem()); + } + @FXML void btnCreateAccountClicked(ActionEvent event) { lblError.setText(""); @@ -132,8 +193,7 @@ public class StaffAccountsController { @FXML void btnEditAccountClicked(ActionEvent event) { lblError.setText(""); - UserResponse selected = tvStaff.getSelectionModel().getSelectedItem(); - openEditDialog(selected); + openEditDialog(tvStaff.getSelectionModel().getSelectedItem()); } private void openEditDialog(UserResponse selected) { @@ -154,7 +214,8 @@ public class StaffAccountsController { try { FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml")); Stage dialog = new Stage(); - dialog.initOwner(tvStaff.getScene().getWindow()); + Stage owner = (tvStaff.getScene() != null) ? (Stage) tvStaff.getScene().getWindow() : (Stage) tvCustomers.getScene().getWindow(); + dialog.initOwner(owner); dialog.initModality(Modality.APPLICATION_MODAL); dialog.setTitle("Edit User Account"); dialog.setScene(new Scene(loader.load())); @@ -164,31 +225,45 @@ public class StaffAccountsController { dialog.showAndWait(); refresh(); } catch (Exception e) { - ActivityLogger.getInstance().logException("StaffAccountsController.btnEditAccountClicked", e, "Opening user edit dialog"); + ActivityLogger.getInstance().logException("StaffAccountsController.openEditDialog", e, "Opening user edit dialog"); lblError.setText("Could not open user account editor."); } } private void refresh() { lblError.setText(""); + tvCustomers.setDisable(true); tvStaff.setDisable(true); new Thread(() -> { try { - UserSession session = UserSession.getInstance(); - List users; - if (session.isAdmin()) { - users = UserApi.getInstance().listUsers(null); + Comparator byCreated = Comparator.comparing( + UserResponse::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder())); + + final List customers; + final List staff; + + if (UserSession.getInstance().isAdmin()) { + List allUsers = UserApi.getInstance().listUsers(null); + customers = allUsers.stream() + .filter(u -> "CUSTOMER".equalsIgnoreCase(u.getRole())) + .sorted(byCreated) + .collect(Collectors.toList()); + staff = allUsers.stream() + .filter(u -> !"CUSTOMER".equalsIgnoreCase(u.getRole())) + .sorted(byCreated) + .collect(Collectors.toList()); } else { - users = CustomerApi.getInstance().listCustomers(null); + customers = CustomerApi.getInstance().listCustomers(null).stream() + .sorted(byCreated) + .collect(Collectors.toList()); + staff = List.of(); } - List sortedUsers = users.stream() - .sorted(Comparator.comparing(UserResponse::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder()))) - .collect(Collectors.toList()); - Platform.runLater(() -> { - staffAccounts.setAll(sortedUsers); + customerAccounts.setAll(customers); + staffAccounts.setAll(staff); + tvCustomers.setDisable(false); tvStaff.setDisable(false); }); } catch (Exception e) { @@ -196,27 +271,41 @@ public class StaffAccountsController { Platform.runLater(() -> { String message = e.getMessage(); lblError.setText(message == null || message.isBlank() - ? "Could not load user accounts." - : "Could not load user accounts: " + message); + ? "Could not load user accounts." + : "Could not load user accounts: " + message); + tvCustomers.setDisable(false); tvStaff.setDisable(false); }); } }).start(); } - private void applyFilter(String text) { + private void applyCustomerFilter(String text) { String q = text == null ? "" : text.trim().toLowerCase(); if (q.isEmpty()) { - filtered.setPredicate(a -> true); + filteredCustomers.setPredicate(a -> true); return; } + filteredCustomers.setPredicate(a -> + safe(a.getUsername()).contains(q) + || safe(a.getFullName()).contains(q) + || safe(a.getEmail()).contains(q) + || safe(a.getPhone()).contains(q) + ); + } - filtered.setPredicate(a -> - safe(a.getUsername()).contains(q) - || safe(a.getFullName()).contains(q) - || safe(a.getEmail()).contains(q) - || safe(a.getPhone()).contains(q) - || safe(a.getRole()).contains(q) + private void applyStaffFilter(String text) { + String q = text == null ? "" : text.trim().toLowerCase(); + if (q.isEmpty()) { + filteredStaff.setPredicate(a -> true); + return; + } + filteredStaff.setPredicate(a -> + safe(a.getUsername()).contains(q) + || safe(a.getFullName()).contains(q) + || safe(a.getEmail()).contains(q) + || safe(a.getPhone()).contains(q) + || safe(a.getRole()).contains(q) ); } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/PetDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/PetDialogController.java index bdb46dab..45b2978d 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/PetDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/PetDialogController.java @@ -132,9 +132,7 @@ public class PetDialogController { } }); - setFieldVisibility(vbCustomerField, false); - setFieldVisibility(vbStoreField, false); - setFieldVisibility(vbPriceField, true); + updateStatusFieldVisibility(null); loadSpecies(); @@ -174,18 +172,18 @@ public class PetDialogController { String speciesValue = cbPetSpecies.getValue() != null ? cbPetSpecies.getValue().trim() : ""; if (speciesValue.isEmpty()) errorMsg += "Species is required\n"; String selectedStatus = cbPetStatus.getValue(); - boolean needsPrice = !("Owned".equalsIgnoreCase(selectedStatus) || "Adopted".equalsIgnoreCase(selectedStatus)); - if (needsPrice) { - errorMsg += Validator.isPresent(txtPetPrice.getText(), "Price"); - } + errorMsg += Validator.isPresent(txtPetPrice.getText(), "Price"); if (cbPetStatus.getSelectionModel().getSelectedItem() == null){ errorMsg += "Status is required"; } - if ("Owned".equalsIgnoreCase(selectedStatus) && cbCustomer.getValue() == null && UserSession.getInstance().isAdmin()) { - errorMsg += "Customer is required for Owned status\n"; + boolean needsCustomer = "Owned".equalsIgnoreCase(selectedStatus) + || "Adopted".equalsIgnoreCase(selectedStatus) + || "Pending".equalsIgnoreCase(selectedStatus); + boolean needsStore = requiresStore(selectedStatus); + if (needsCustomer && cbCustomer.getValue() == null && UserSession.getInstance().isAdmin()) { + errorMsg += "Customer is required for " + selectedStatus + " status\n"; } - boolean storeRequired = requiresStore(selectedStatus) && !"Adopted".equalsIgnoreCase(selectedStatus); - if (storeRequired && cbStore.getValue() == null) { + if (needsStore && cbStore.getValue() == null) { errorMsg += "Store is required for " + selectedStatus + " status\n"; } @@ -193,15 +191,11 @@ public class PetDialogController { errorMsg += Validator.isLessThanVarChars(txtPetName.getText(), "Pet Name", 50); errorMsg += Validator.isLessThanVarChars(speciesValue, "Species", 50); errorMsg += Validator.isLessThanVarChars(txtPetBreed.getText(), "Breed", 50); - if (needsPrice) { - errorMsg += Validator.isLessThanVarChars(txtPetPrice.getText(), "Price", 12); - } + errorMsg += Validator.isLessThanVarChars(txtPetPrice.getText(), "Price", 12); errorMsg += Validator.isLessThanVarChars(txtPetAge.getText(), "Age", 11); //Check validation (format) - if (needsPrice) { - errorMsg += Validator.isNonNegativeDouble(txtPetPrice.getText(), "Price"); - } + errorMsg += Validator.isNonNegativeDouble(txtPetPrice.getText(), "Price"); errorMsg += Validator.isPositiveInteger(txtPetAge.getText(), "Age"); if(errorMsg.isEmpty()){ @@ -263,9 +257,7 @@ public class PetDialogController { request.setPetSpecies(cbPetSpecies.getValue() != null ? cbPetSpecies.getValue().trim() : ""); request.setPetBreed(txtPetBreed.getText()); request.setPetStatus(cbPetStatus.getValue()); - String buildStatus = cbPetStatus.getValue(); - boolean buildNeedsPrice = !("Owned".equalsIgnoreCase(buildStatus) || "Adopted".equalsIgnoreCase(buildStatus)); - if (buildNeedsPrice && txtPetPrice.getText() != null && !txtPetPrice.getText().isBlank()) { + if (txtPetPrice.getText() != null && !txtPetPrice.getText().isBlank()) { try { request.setPetPrice(new BigDecimal(txtPetPrice.getText())); } catch (NumberFormatException e) { @@ -282,7 +274,10 @@ public class PetDialogController { request.setPetAge(age); String status = cbPetStatus.getValue(); - if (("Owned".equalsIgnoreCase(status) || "Adopted".equalsIgnoreCase(status)) && cbCustomer.getValue() != null) { + boolean customerApplicable = "Owned".equalsIgnoreCase(status) + || "Adopted".equalsIgnoreCase(status) + || "Pending".equalsIgnoreCase(status); + if (customerApplicable && cbCustomer.getValue() != null) { request.setCustomerId(cbCustomer.getValue().getId()); } if (requiresStore(status) && cbStore.getValue() != null) { @@ -407,9 +402,17 @@ public class PetDialogController { } } updateStatusFieldVisibility(cbPetStatus.getValue()); + applyStatusLock(); } } + private void applyStatusLock() { + String status = cbPetStatus.getValue(); + boolean isRestricted = "Adopted".equalsIgnoreCase(status) || "Owned".equalsIgnoreCase(status); + boolean isStaff = !UserSession.getInstance().isAdmin(); + cbPetStatus.setDisable(isRestricted && isStaff); + } + public void setMode(String mode) { this.mode = mode; lblMode.setText(mode + " Pet"); @@ -489,28 +492,27 @@ public class PetDialogController { } private void updateStatusFieldVisibility(String status) { - boolean statusNeedsCustomer = "Owned".equalsIgnoreCase(status) || "Adopted".equalsIgnoreCase(status); - boolean needsCustomer = statusNeedsCustomer && UserSession.getInstance().isAdmin(); - boolean storeBased = requiresStore(status); - boolean needsPrice = !statusNeedsCustomer; - setFieldVisibility(vbCustomerField, needsCustomer); - setFieldVisibility(vbStoreField, storeBased); - setFieldVisibility(vbPriceField, needsPrice); + if (status == null) { + vbPriceField.setDisable(false); + vbCustomerField.setDisable(true); + vbStoreField.setDisable(false); + return; + } + boolean isAdmin = UserSession.getInstance().isAdmin(); + boolean customerApplicable = "Owned".equalsIgnoreCase(status) + || "Adopted".equalsIgnoreCase(status) + || "Pending".equalsIgnoreCase(status); + boolean isOwned = "Owned".equalsIgnoreCase(status); + + vbPriceField.setDisable(false); + vbCustomerField.setDisable(!customerApplicable || !isAdmin); + vbStoreField.setDisable(isOwned); } private boolean requiresStore(String status) { return "Available".equalsIgnoreCase(status) - || "Pending".equalsIgnoreCase(status) - || "Unadopted".equalsIgnoreCase(status) - || "Adopted".equalsIgnoreCase(status); - } - - private void setFieldVisibility(VBox field, boolean visible) { - if (field == null) { - return; - } - field.setVisible(visible); - field.setManaged(visible); + || "Pending".equalsIgnoreCase(status) + || "Adopted".equalsIgnoreCase(status); } } diff --git a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/staff-accounts-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/staff-accounts-view.fxml index 7aab7ad4..0e5dddf1 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/staff-accounts-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/staff-accounts-view.fxml @@ -11,7 +11,7 @@ - + @@ -25,22 +25,6 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +