diff --git a/backend/src/main/java/com/petshop/backend/controller/DropdownController.java b/backend/src/main/java/com/petshop/backend/controller/DropdownController.java index 261a56a3..6ad1ad36 100644 --- a/backend/src/main/java/com/petshop/backend/controller/DropdownController.java +++ b/backend/src/main/java/com/petshop/backend/controller/DropdownController.java @@ -11,7 +11,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; @RestController @@ -25,12 +27,14 @@ public class DropdownController { private final StoreRepository storeRepository; private final SupplierRepository supplierRepository; private final UserRepository userRepository; + private final AdoptionRepository adoptionRepository; public DropdownController(PetRepository petRepository, ServiceRepository serviceRepository, ProductRepository productRepository, CategoryRepository categoryRepository, StoreRepository storeRepository, SupplierRepository supplierRepository, - UserRepository userRepository) { + UserRepository userRepository, + AdoptionRepository adoptionRepository) { this.petRepository = petRepository; this.serviceRepository = serviceRepository; this.productRepository = productRepository; @@ -38,6 +42,7 @@ public class DropdownController { this.storeRepository = storeRepository; this.supplierRepository = supplierRepository; this.userRepository = userRepository; + this.adoptionRepository = adoptionRepository; } @GetMapping("/pets") @@ -74,8 +79,19 @@ public class DropdownController { @GetMapping("/appointment-customers") @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity> getAppointmentCustomers() { + Set ownersWithPets = petRepository.findAll().stream() + .filter(p -> p.getOwner() != null) + .map(p -> p.getOwner().getId()) + .collect(Collectors.toSet()); + Set customersWithAdoptions = adoptionRepository.findAll().stream() + .filter(a -> "Completed".equalsIgnoreCase(a.getAdoptionStatus())) + .map(a -> a.getCustomer().getId()) + .collect(Collectors.toSet()); + Set customersWithPets = new HashSet<>(ownersWithPets); + customersWithPets.addAll(customersWithAdoptions); return ResponseEntity.ok( userRepository.findByRoleAndActiveTrue(User.Role.CUSTOMER).stream() + .filter(u -> customersWithPets.contains(u.getId())) .map(u -> new DropdownOption(u.getId(), u.getFirstName() + " " + u.getLastName())) .sorted(Comparator.comparing(DropdownOption::getLabel, String.CASE_INSENSITIVE_ORDER)) .collect(Collectors.toList()) @@ -166,11 +182,16 @@ public class DropdownController { @GetMapping("/customers/{customerId}/pets") @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity> getCustomerPets(@PathVariable Long customerId) { - return ResponseEntity.ok( - petRepository.findAllByOwner_IdOrderByPetNameAsc(customerId).stream() + Set seen = new HashSet<>(); + List pets = new java.util.ArrayList<>(); + petRepository.findAllByOwner_IdOrderByPetNameAsc(customerId).stream() .map(p -> new DropdownOption(p.getPetId(), p.getPetName())) - .collect(Collectors.toList()) - ); + .forEach(o -> { if (seen.add(o.getId())) pets.add(o); }); + adoptionRepository.findByCustomer_IdAndAdoptionStatusIgnoreCase(customerId, "Completed").stream() + .map(a -> new DropdownOption(a.getPet().getPetId(), a.getPet().getPetName())) + .forEach(o -> { if (seen.add(o.getId())) pets.add(o); }); + pets.sort(java.util.Comparator.comparing(DropdownOption::getLabel, String.CASE_INSENSITIVE_ORDER)); + return ResponseEntity.ok(pets); } @GetMapping("/suppliers") diff --git a/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java b/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java index d3700bfc..7fb9ceac 100644 --- a/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java @@ -22,6 +22,8 @@ public class AdoptionRequest { private Long sourceStoreId; + private String paymentMethod; + public Long getPetId() { return petId; } @@ -70,6 +72,14 @@ public class AdoptionRequest { this.sourceStoreId = sourceStoreId; } + public String getPaymentMethod() { + return paymentMethod; + } + + public void setPaymentMethod(String paymentMethod) { + this.paymentMethod = paymentMethod; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java b/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java index f4594a26..beb8e0e8 100644 --- a/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java @@ -9,6 +9,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.time.LocalDate; +import java.util.List; import java.util.Optional; @Repository @@ -37,4 +38,6 @@ public interface AdoptionRepository extends JpaRepository { boolean existsByPet_IdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(Long petId, String adoptionStatus, Long adoptionId); boolean existsByPet_IdAndAdoptionStatusIgnoreCase(Long petId, String adoptionStatus); + + List findByCustomer_IdAndAdoptionStatusIgnoreCase(Long customerId, String adoptionStatus); } 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 0e3c7a95..d7dc0590 100644 --- a/backend/src/main/java/com/petshop/backend/service/AdoptionService.java +++ b/backend/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -5,11 +5,13 @@ import com.petshop.backend.dto.adoption.AdoptionResponse; import com.petshop.backend.dto.common.BulkDeleteRequest; import com.petshop.backend.entity.Adoption; import com.petshop.backend.entity.Pet; +import com.petshop.backend.entity.Sale; import com.petshop.backend.entity.StoreLocation; import com.petshop.backend.entity.User; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.AdoptionRepository; import com.petshop.backend.repository.PetRepository; +import com.petshop.backend.repository.SaleRepository; import com.petshop.backend.repository.StoreRepository; import com.petshop.backend.repository.UserRepository; import org.springframework.data.domain.Page; @@ -17,7 +19,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; import java.time.LocalDate; +import java.time.LocalDateTime; @Service public class AdoptionService { @@ -32,12 +36,14 @@ public class AdoptionService { private final PetRepository petRepository; private final UserRepository userRepository; private final StoreRepository storeRepository; + private final SaleRepository saleRepository; - public AdoptionService(AdoptionRepository adoptionRepository, PetRepository petRepository, UserRepository userRepository, StoreRepository storeRepository) { + public AdoptionService(AdoptionRepository adoptionRepository, PetRepository petRepository, UserRepository userRepository, StoreRepository storeRepository, SaleRepository saleRepository) { this.adoptionRepository = adoptionRepository; this.petRepository = petRepository; this.userRepository = userRepository; this.storeRepository = storeRepository; + this.saleRepository = saleRepository; } public Page getAllAdoptions(String query, Long customerId, String status, Long storeId, LocalDate date, Pageable pageable) { @@ -81,6 +87,9 @@ public class AdoptionService { : null; String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus()); validatePetAvailability(pet, null, null); + if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) && request.getAdoptionDate() != null && request.getAdoptionDate().isAfter(LocalDate.now())) { + throw new IllegalArgumentException("Cannot mark a future-dated adoption as Completed"); + } Adoption adoption = new Adoption(); adoption.setPet(pet); @@ -91,7 +100,10 @@ public class AdoptionService { adoption.setAdoptionStatus(adoptionStatus); adoption = adoptionRepository.save(adoption); - syncPetStatus(pet, adoptionStatus, adoption.getAdoptionId()); + syncPetStatus(pet, adoptionStatus, adoption.getAdoptionId(), customer); + if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus)) { + createSaleForAdoption(adoption, request.getPaymentMethod()); + } return mapToResponse(adoption); } @@ -110,9 +122,13 @@ public class AdoptionService { ? storeRepository.findById(request.getSourceStoreId()) .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getSourceStoreId())) : null; + boolean wasCompleted = ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoption.getAdoptionStatus()); String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus()); Long currentPetId = adoption.getPet() != null ? adoption.getPet().getPetId() : null; validatePetAvailability(pet, adoption.getAdoptionId(), currentPetId); + if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) && request.getAdoptionDate() != null && request.getAdoptionDate().isAfter(LocalDate.now())) { + throw new IllegalArgumentException("Cannot mark a future-dated adoption as Completed"); + } adoption.setPet(pet); adoption.setCustomer(customer); @@ -122,7 +138,10 @@ public class AdoptionService { adoption.setAdoptionStatus(adoptionStatus); adoption = adoptionRepository.save(adoption); - syncPetStatus(pet, adoptionStatus, adoption.getAdoptionId()); + syncPetStatus(pet, adoptionStatus, adoption.getAdoptionId(), customer); + if (!wasCompleted && ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus)) { + createSaleForAdoption(adoption, request.getPaymentMethod()); + } return mapToResponse(adoption); } @@ -216,13 +235,37 @@ public class AdoptionService { } } - private void syncPetStatus(Pet pet, String adoptionStatus, Long adoptionId) { + private void createSaleForAdoption(Adoption adoption, String paymentMethod) { + if (adoption.getEmployee() == null || adoption.getSourceStore() == null) { + return; + } + BigDecimal amount = adoption.getPet().getPetPrice(); + if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { + amount = BigDecimal.ZERO; + } + Sale sale = new Sale(); + sale.setSaleDate(LocalDateTime.now()); + sale.setEmployee(adoption.getEmployee()); + sale.setStore(adoption.getSourceStore()); + sale.setCustomer(adoption.getCustomer()); + sale.setTotalAmount(amount); + sale.setSubtotalAmount(amount); + sale.setPaymentMethod(paymentMethod != null && !paymentMethod.isBlank() ? paymentMethod : "Cash"); + sale.setIsRefund(false); + sale.setChannel("ADOPTION"); + saleRepository.save(sale); + } + + private void syncPetStatus(Pet pet, String adoptionStatus, Long adoptionId, User customer) { boolean completedElsewhere = adoptionId != null && adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(pet.getPetId(), ADOPTION_STATUS_COMPLETED, adoptionId); if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) || completedElsewhere) { pet.setPetStatus(PET_STATUS_ADOPTED); + pet.setOwner(customer); + pet.setStore(null); } else { pet.setPetStatus(PET_STATUS_AVAILABLE); + pet.setOwner(null); } petRepository.save(pet); } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionRequest.java index af596ade..75ece4d9 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionRequest.java @@ -9,6 +9,7 @@ public class AdoptionRequest { private Long sourceStoreId; private LocalDate adoptionDate; private String adoptionStatus; + private String paymentMethod; public AdoptionRequest() { } @@ -60,4 +61,12 @@ public class AdoptionRequest { public void setAdoptionStatus(String adoptionStatus) { this.adoptionStatus = adoptionStatus; } + + public String getPaymentMethod() { + return paymentMethod; + } + + public void setPaymentMethod(String paymentMethod) { + this.paymentMethod = paymentMethod; + } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java index ecc44bc6..e6eb87b5 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java @@ -241,16 +241,16 @@ public class AppointmentController { return new AppointmentDTO( response.getAppointmentId().intValue(), response.getCustomerId() != null ? response.getCustomerId().intValue() : 0, - response.getCustomerName(), + response.getCustomerName() != null ? response.getCustomerName() : "", response.getPetId() != null ? response.getPetId().intValue() : 0, - response.getPetName(), + response.getPetName() != null ? response.getPetName() : "", response.getServiceId() != null ? response.getServiceId().intValue() : 0, - response.getServiceName(), + response.getServiceName() != null ? response.getServiceName() : "", response.getEmployeeId() != null ? response.getEmployeeId().intValue() : 0, - response.getEmployeeName(), - response.getAppointmentDate().toString(), - response.getAppointmentTime().toString(), - response.getAppointmentStatus() + response.getEmployeeName() != null ? response.getEmployeeName() : "", + response.getAppointmentDate() != null ? response.getAppointmentDate().toString() : "", + response.getAppointmentTime() != null ? response.getAppointmentTime().toString() : "", + response.getAppointmentStatus() != null ? response.getAppointmentStatus() : "" ); } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java index 7ac193a5..ed60335d 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java @@ -105,7 +105,12 @@ public class ChatController { }); realtimeClient.setConversationListener(conversation -> Platform.runLater(() -> upsertConversation(conversation))); - realtimeClient.setMessageListener(message -> Platform.runLater(() -> appendMessageIfSelected(message))); + realtimeClient.setMessageListener(message -> Platform.runLater(() -> { + Long myId = UserSession.getInstance().getUserId(); + if (message.getSenderId() == null || !message.getSenderId().equals(myId)) { + appendMessageIfSelected(message); + } + })); realtimeClient.setStatusListener(status -> Platform.runLater(() -> lblChatStatus.setText(status))); realtimeClient.subscribeToConversations(); @@ -208,9 +213,7 @@ public class ChatController { MessageResponse response = ChatApi.getInstance().sendMessage(conversationId, new MessageRequest(content)); Platform.runLater(() -> { btnSend.setDisable(false); - if (!realtimeClient.isConnected()) { - appendMessageIfSelected(response); - } + appendMessageIfSelected(response); if (selectedConversation != null && selectedConversation.getId().equals(conversationId)) { lblChatStatus.setText("Message sent"); } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java index e12c13c8..3e9052e4 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java @@ -327,12 +327,12 @@ public class PetController { private Pet mapToPet(PetResponse response) { Pet pet = new Pet( response.getPetId().intValue(), - response.getPetName(), - response.getPetSpecies(), - response.getPetBreed(), + response.getPetName() != null ? response.getPetName() : "", + response.getPetSpecies() != null ? response.getPetSpecies() : "", + response.getPetBreed() != null ? response.getPetBreed() : "", response.getPetAge() != null ? response.getPetAge() : 0, - response.getPetStatus(), - response.getPetPrice().doubleValue(), + response.getPetStatus() != null ? response.getPetStatus() : "", + response.getPetPrice() != null ? response.getPetPrice().doubleValue() : 0.0, response.getImageUrl() ); pet.setCustomerName(response.getCustomerName()); diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AdoptionDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AdoptionDialogController.java index 534d9feb..015c02ac 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AdoptionDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AdoptionDialogController.java @@ -8,6 +8,7 @@ import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.Button; +import javafx.scene.control.ChoiceDialog; import javafx.scene.control.ComboBox; import javafx.scene.control.DatePicker; import javafx.scene.control.Label; @@ -57,6 +58,8 @@ public class AdoptionDialogController { private String mode = null; private Adoption selectedAdoption = null; + private String selectedPaymentMethod = null; + private boolean suppressPaymentDialog = false; private ObservableList statusList = FXCollections.observableArrayList( "Pending", "Completed", "Cancelled" @@ -66,6 +69,23 @@ public class AdoptionDialogController { void initialize() { cbAdoptionStatus.setItems(statusList); + cbAdoptionStatus.valueProperty().addListener((obs, oldVal, newVal) -> { + if ("Completed".equals(newVal) && !"Completed".equals(oldVal) && !suppressPaymentDialog) { + ChoiceDialog dialog = new ChoiceDialog<>("Cash", "Cash", "Credit Card", "Debit Card", "E-Transfer"); + dialog.setTitle("Payment Method"); + dialog.setHeaderText("Confirm payment received"); + dialog.setContentText("Select payment method:"); + dialog.showAndWait().ifPresentOrElse( + method -> selectedPaymentMethod = method, + () -> { + selectedPaymentMethod = null; + cbAdoptionStatus.setValue(oldVal); + } + ); + } else if (!"Completed".equals(newVal)) { + selectedPaymentMethod = null; + } + }); cbEmployee.setPromptText("Select an employee"); new Thread(() -> { @@ -92,15 +112,11 @@ public class AdoptionDialogController { new Thread(() -> { try { - Long storeId = UserSession.getInstance().getStoreId(); - List employees; - if (storeId != null && storeId > 0) { - employees = DropdownApi.getInstance().getStoreEmployees(storeId); - } else { - employees = DropdownApi.getInstance().getEmployees(); - } + List employees = DropdownApi.getInstance().getEmployees(); Platform.runLater(() -> { - cbEmployee.setItems(FXCollections.observableArrayList(employees)); + ObservableList employeesObs = FXCollections.observableArrayList(employees); + ensureSelectedEmployeeOption(employeesObs); + cbEmployee.setItems(employeesObs); applySelectedEmployee(); }); } catch (Exception e) { @@ -203,6 +219,7 @@ public class AdoptionDialogController { request.setSourceStoreId(storeId); request.setAdoptionDate(dpAdoptionDate.getValue()); request.setAdoptionStatus(cbAdoptionStatus.getValue()); + request.setPaymentMethod(selectedPaymentMethod); if (mode.equals("Add")) { AdoptionApi.getInstance().createAdoption(request); @@ -263,12 +280,14 @@ public class AdoptionDialogController { } } + suppressPaymentDialog = true; for (String status : cbAdoptionStatus.getItems()) { if (status.equals(adoption.getAdoptionStatus())) { cbAdoptionStatus.getSelectionModel().select(status); break; } } + suppressPaymentDialog = false; } } @@ -320,6 +339,19 @@ public class AdoptionDialogController { return null; } + private void ensureSelectedEmployeeOption(ObservableList options) { + if (selectedAdoption == null || selectedAdoption.getEmployeeId() <= 0 || options == null) { + return; + } + DropdownOption existing = findOptionById(options, (long) selectedAdoption.getEmployeeId()); + if (existing == null) { + DropdownOption option = new DropdownOption(); + option.setId((long) selectedAdoption.getEmployeeId()); + option.setLabel(selectedAdoption.getEmployeeName() != null ? selectedAdoption.getEmployeeName() : "Employee #" + selectedAdoption.getEmployeeId()); + options.add(0, option); + } + } + private void ensureSelectedPetOption(ObservableList options) { if (selectedAdoption == null || selectedAdoption.getPetId() <= 0 || options == null) { return; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java index 139255c6..495813ac 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java @@ -18,6 +18,7 @@ import org.example.petshopdesktop.api.endpoints.DropdownApi; import org.example.petshopdesktop.auth.UserSession; import org.example.petshopdesktop.util.ActivityLogger; +import java.time.DayOfWeek; import java.time.LocalTime; import java.time.LocalDate; import java.util.List; @@ -65,7 +66,15 @@ public class AppointmentDialogController { cbPet.setPromptText("Select a customer first"); cbCustomer.setPromptText("Select a customer"); cbService.setPromptText("Select a service"); - dpAppointmentDate.setValue(LocalDate.now().plusDays(1)); + LocalDate minDate = minAppointmentDate(); + dpAppointmentDate.setValue(minDate); + dpAppointmentDate.setDayCellFactory(picker -> new javafx.scene.control.DateCell() { + @Override + public void updateItem(LocalDate item, boolean empty) { + super.updateItem(item, empty); + setDisable(empty || item.isBefore(minDate)); + } + }); cbAppointmentStatus.setValue("Booked"); for (int i = 9; i <= 17; i++) { @@ -315,9 +324,20 @@ public class AppointmentDialogController { try { List pets = DropdownApi.getInstance().getCustomerPets(customerId); Platform.runLater(() -> { - cbPet.setItems(FXCollections.observableArrayList(pets)); - cbPet.setDisable(pets == null || pets.isEmpty()); - cbPet.setPromptText(pets == null || pets.isEmpty() ? "No pets for selected customer" : "Select a pet"); + ObservableList petOptions = FXCollections.observableArrayList(pets != null ? pets : List.of()); + if (pendingPetSelectionId != null) { + boolean found = petOptions.stream().anyMatch(p -> pendingPetSelectionId.equals(p.getId())); + if (!found && selectedAppointment != null && selectedAppointment.getPetId() > 0) { + DropdownOption placeholder = new DropdownOption(); + placeholder.setId((long) selectedAppointment.getPetId()); + placeholder.setLabel(selectedAppointment.getPetName() != null && !selectedAppointment.getPetName().isBlank() + ? selectedAppointment.getPetName() : "Pet #" + selectedAppointment.getPetId()); + petOptions.add(0, placeholder); + } + } + cbPet.setItems(petOptions); + cbPet.setDisable(petOptions.isEmpty()); + cbPet.setPromptText(petOptions.isEmpty() ? "No pets for selected customer" : "Select a pet"); if (pendingPetSelectionId != null) { for (DropdownOption pet : cbPet.getItems()) { if (pet.getId() != null && pet.getId().equals(pendingPetSelectionId)) { @@ -392,6 +412,19 @@ public class AppointmentDialogController { }).start(); } + private LocalDate minAppointmentDate() { + LocalDate date = LocalDate.now(); + int businessDaysAdded = 0; + while (businessDaysAdded < 2) { + date = date.plusDays(1); + DayOfWeek dow = date.getDayOfWeek(); + if (dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY) { + businessDaysAdded++; + } + } + return date; + } + private void loadEmployees() { new Thread(() -> { try { 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 5e4835e2..ae010a32 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 @@ -55,6 +55,9 @@ public class PetDialogController { @FXML private VBox vbStoreField; + @FXML + private VBox vbPriceField; + @FXML private Label lblMode; @@ -80,7 +83,7 @@ public class PetDialogController { private TextField txtPetPrice; @FXML - private TextField txtPetSpecies; + private ComboBox cbPetSpecies; private String mode = null; private File selectedImageFile; @@ -127,6 +130,9 @@ public class PetDialogController { setFieldVisibility(vbCustomerField, false); setFieldVisibility(vbStoreField, false); + setFieldVisibility(vbPriceField, true); + + loadSpecies(); cbPetStatus.valueProperty().addListener((obs, oldVal, newVal) -> { updateStatusFieldVisibility(newVal); @@ -161,28 +167,37 @@ public class PetDialogController { errorMsg += Validator.isPresent(txtPetName.getText(), "Pet Name"); errorMsg += Validator.isPresent(txtPetAge.getText(), "Age"); errorMsg += Validator.isPresent(txtPetBreed.getText(), "Breed"); - errorMsg += Validator.isPresent(txtPetSpecies.getText(), "Species"); - errorMsg += Validator.isPresent(txtPetPrice.getText(), "Price"); + 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"); + } if (cbPetStatus.getSelectionModel().getSelectedItem() == null){ errorMsg += "Status is required"; } - String selectedStatus = cbPetStatus.getValue(); if ("Owned".equalsIgnoreCase(selectedStatus) && cbCustomer.getValue() == null) { errorMsg += "Customer is required for Owned status\n"; } - if (requiresStore(selectedStatus) && cbStore.getValue() == null) { + boolean storeRequired = requiresStore(selectedStatus) && !"Adopted".equalsIgnoreCase(selectedStatus); + if (storeRequired && cbStore.getValue() == null) { errorMsg += "Store is required for " + selectedStatus + " status\n"; } //Check validation (length size) errorMsg += Validator.isLessThanVarChars(txtPetName.getText(), "Pet Name", 50); - errorMsg += Validator.isLessThanVarChars(txtPetSpecies.getText(), "Species", 50); + errorMsg += Validator.isLessThanVarChars(speciesValue, "Species", 50); errorMsg += Validator.isLessThanVarChars(txtPetBreed.getText(), "Breed", 50); - errorMsg += Validator.isLessThanVarChars(txtPetPrice.getText(), "Price", 12); + if (needsPrice) { + errorMsg += Validator.isLessThanVarChars(txtPetPrice.getText(), "Price", 12); + } errorMsg += Validator.isLessThanVarChars(txtPetAge.getText(), "Age", 11); //Check validation (format) - errorMsg += Validator.isNonNegativeDouble(txtPetPrice.getText(), "Price"); + if (needsPrice) { + errorMsg += Validator.isNonNegativeDouble(txtPetPrice.getText(), "Price"); + } errorMsg += Validator.isPositiveInteger(txtPetAge.getText(), "Age"); if(errorMsg.isEmpty()){ @@ -229,13 +244,17 @@ public class PetDialogController { private PetRequest buildPetRequest() { PetRequest request = new PetRequest(); request.setPetName(txtPetName.getText()); - request.setPetSpecies(txtPetSpecies.getText()); + request.setPetSpecies(cbPetSpecies.getValue() != null ? cbPetSpecies.getValue().trim() : ""); request.setPetBreed(txtPetBreed.getText()); request.setPetStatus(cbPetStatus.getValue()); - try { - request.setPetPrice(new BigDecimal(txtPetPrice.getText())); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid price format"); + String buildStatus = cbPetStatus.getValue(); + boolean buildNeedsPrice = !("Owned".equalsIgnoreCase(buildStatus) || "Adopted".equalsIgnoreCase(buildStatus)); + if (buildNeedsPrice && txtPetPrice.getText() != null && !txtPetPrice.getText().isBlank()) { + try { + request.setPetPrice(new BigDecimal(txtPetPrice.getText())); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid price format"); + } } int age; @@ -247,7 +266,7 @@ public class PetDialogController { request.setPetAge(age); String status = cbPetStatus.getValue(); - if ("Owned".equalsIgnoreCase(status) && cbCustomer.getValue() != null) { + if (("Owned".equalsIgnoreCase(status) || "Adopted".equalsIgnoreCase(status)) && cbCustomer.getValue() != null) { request.setCustomerId(cbCustomer.getValue().getId()); } if (requiresStore(status) && cbStore.getValue() != null) { @@ -257,6 +276,27 @@ public class PetDialogController { return request; } + private void loadSpecies() { + new Thread(() -> { + try { + List options = DropdownApi.getInstance().getPetSpecies(); + List species = options.stream() + .map(DropdownOption::getLabel) + .collect(java.util.stream.Collectors.toList()); + Platform.runLater(() -> { + String current = cbPetSpecies.getValue(); + cbPetSpecies.setItems(FXCollections.observableArrayList(species)); + if (current != null && !current.isBlank()) { + cbPetSpecies.setValue(current); + } + }); + } catch (Exception e) { + Platform.runLater(() -> ActivityLogger.getInstance().logException( + "PetDialogController.loadSpecies", e, "Loading species dropdown")); + } + }).start(); + } + private void loadCustomers() { new Thread(() -> { try { @@ -331,7 +371,7 @@ public class PetDialogController { if (pet!=null){ lblPetId.setText("ID: " + pet.getPetId()); txtPetName.setText(pet.getPetName()); - txtPetSpecies.setText(pet.getPetSpecies()); + cbPetSpecies.setValue(pet.getPetSpecies()); txtPetBreed.setText(pet.getPetBreed()); txtPetAge.setText(pet.getPetAge() + ""); txtPetPrice.setText(pet.getPetPrice() + ""); @@ -432,16 +472,19 @@ public class PetDialogController { } private void updateStatusFieldVisibility(String status) { - boolean owned = "Owned".equalsIgnoreCase(status); + boolean needsCustomer = "Owned".equalsIgnoreCase(status) || "Adopted".equalsIgnoreCase(status); boolean storeBased = requiresStore(status); - setFieldVisibility(vbCustomerField, owned); + boolean needsPrice = !needsCustomer; + setFieldVisibility(vbCustomerField, needsCustomer); setFieldVisibility(vbStoreField, storeBased); + setFieldVisibility(vbPriceField, needsPrice); } private boolean requiresStore(String status) { return "Available".equalsIgnoreCase(status) || "Pending".equalsIgnoreCase(status) - || "Unadopted".equalsIgnoreCase(status); + || "Unadopted".equalsIgnoreCase(status) + || "Adopted".equalsIgnoreCase(status); } private void setFieldVisibility(VBox field, boolean visible) { diff --git a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml index 9130513a..6f8bf1e0 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml @@ -91,11 +91,11 @@ - + - + - + @@ -139,7 +139,7 @@ - + - - - - - - - - - - - -