Update Postman collection
This commit is contained in:
@@ -16,7 +16,6 @@ public class AdoptionDTO {
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
// Constructor for create/update requests
|
||||
public AdoptionDTO(Long petId, Long customerId, String adoptionDate, String adoptionStatus) {
|
||||
this(petId, customerId, null, adoptionDate, adoptionStatus);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public class AppointmentDTO {
|
||||
// Response fields (from server)
|
||||
|
||||
private Long appointmentId;
|
||||
private Long customerId;
|
||||
private String customerName;
|
||||
@@ -22,8 +22,6 @@ public class AppointmentDTO {
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
// Constructor for CREATE/UPDATE request body
|
||||
// Matches AppointmentRequest exactly
|
||||
public AppointmentDTO(Long customerId, Long storeId, Long serviceId,
|
||||
String appointmentDate, String appointmentTime,
|
||||
String appointmentStatus, List<Long> petIds) {
|
||||
@@ -43,7 +41,6 @@ public class AppointmentDTO {
|
||||
this.petIds = petIds;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public Long getAppointmentId() {
|
||||
return appointmentId;
|
||||
}
|
||||
@@ -108,7 +105,6 @@ public class AppointmentDTO {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
// Convenience getters for adapter/list display
|
||||
public String getPetName() {
|
||||
return (petNames != null && !petNames.isEmpty()) ? petNames.get(0) : "";
|
||||
}
|
||||
@@ -121,7 +117,6 @@ public class AppointmentDTO {
|
||||
return getPetID();
|
||||
}
|
||||
|
||||
// Keep old name so adapter doesn't break
|
||||
public String getServiceType() {
|
||||
return serviceName;
|
||||
}
|
||||
@@ -130,7 +125,6 @@ public class AppointmentDTO {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
// Status alias
|
||||
public String getStatus() {
|
||||
return appointmentStatus;
|
||||
}
|
||||
|
||||
@@ -2069,6 +2069,37 @@
|
||||
{
|
||||
"name": "Appointments",
|
||||
"item": [
|
||||
{
|
||||
"name": "Get Appointment Customers Dropdown",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "{{baseUrl}}/api/v1/dropdowns/appointment-customers",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{staffToken}}",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
"pm.test('Status code is 200', function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Check Appointment Availability",
|
||||
"request": {
|
||||
@@ -2180,7 +2211,7 @@
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-12-20\",\n \"appointmentTime\": \"10:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [1]\n}",
|
||||
"raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-12-20\",\n \"appointmentTime\": \"10:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [\n 1\n ],\n \"employeeId\": 1\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
@@ -2222,7 +2253,7 @@
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-12-20\",\n \"appointmentTime\": \"11:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [1]\n}",
|
||||
"raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-12-20\",\n \"appointmentTime\": \"11:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [\n 1\n ],\n \"employeeId\": 1\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
@@ -2315,6 +2346,37 @@
|
||||
{
|
||||
"name": "Adoptions",
|
||||
"item": [
|
||||
{
|
||||
"name": "Get Adoption Pets Dropdown",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "{{baseUrl}}/api/v1/dropdowns/adoption-pets",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{staffToken}}",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
"pm.test('Status code is 200', function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "List Adoptions",
|
||||
"request": {
|
||||
@@ -2395,7 +2457,7 @@
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"petId\": 3,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-12-21\",\n \"adoptionStatus\": \"Pending\"\n}",
|
||||
"raw": "{\n \"petId\": 3,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-12-21\",\n \"adoptionStatus\": \"Pending\",\n \"employeeId\": 1\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
@@ -2437,7 +2499,7 @@
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"petId\": 3,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-12-22\",\n \"adoptionStatus\": \"Completed\"\n}",
|
||||
"raw": "{\n \"petId\": 3,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-12-22\",\n \"adoptionStatus\": \"Completed\",\n \"employeeId\": 1\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
@@ -3719,6 +3781,68 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Get Store Employees Dropdown",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "{{baseUrl}}/api/v1/dropdowns/stores/{{storeId}}/employees",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{staffToken}}",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
"pm.test('Status code is 200', function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Get All Employees Dropdown",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "{{baseUrl}}/api/v1/dropdowns/employees",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{staffToken}}",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
"pm.test('Status code is 200', function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "List Stores",
|
||||
"request": {
|
||||
|
||||
@@ -218,7 +218,6 @@ public class AppointmentService {
|
||||
List<Long> employeeIds = assignableEmployees.stream().map(Employee::getEmployeeId).collect(Collectors.toList());
|
||||
List<Appointment> allAppointments = appointmentRepository.findByEmployeeEmployeeIdInAndAppointmentDate(employeeIds, date);
|
||||
|
||||
// Group by employee for faster lookup in the loop
|
||||
java.util.Map<Long, List<Appointment>> appointmentsByEmployee = allAppointments.stream()
|
||||
.collect(Collectors.groupingBy(a -> a.getEmployee().getEmployeeId()));
|
||||
|
||||
@@ -350,7 +349,6 @@ public class AppointmentService {
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
private void validateAvailability(Employee employee, com.petshop.backend.entity.Service service, LocalDate date, LocalTime time, Long appointmentIdToIgnore) {
|
||||
List<Appointment> existingAppointments = appointmentRepository
|
||||
.findByEmployeeEmployeeIdAndAppointmentDate(employee.getEmployeeId(), date);
|
||||
@@ -359,8 +357,6 @@ public class AppointmentService {
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------
|
||||
|
||||
private boolean isSlotAvailable(List<Appointment> existingAppointments, com.petshop.backend.entity.Service requestedService, LocalTime requestedStart, Long appointmentIdToIgnore) {
|
||||
LocalTime requestedEnd = requestedStart.plusMinutes(requestedService.getServiceDuration());
|
||||
for (Appointment existingAppointment : existingAppointments) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
-- Activate all employees in the users table so they appear in dropdowns
|
||||
UPDATE users u
|
||||
SET u.active = TRUE
|
||||
WHERE u.role IN ('STAFF', 'ADMIN')
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
-- V17: Normalize legacy appointmentPet data into customer_pet and appointment_customer_pet
|
||||
|
||||
-- Step 1: Ensure a customer_pet exists for every pet linked in appointmentPet
|
||||
-- Note: pet species and breed might be null in pet table, but we copy them over if present
|
||||
INSERT INTO customer_pet (customer_id, pet_name, species, breed)
|
||||
SELECT DISTINCT a.customerId, p.petName, p.petSpecies, p.petBreed
|
||||
FROM appointmentPet ap
|
||||
@@ -12,7 +8,6 @@ WHERE NOT EXISTS (
|
||||
WHERE cp.customer_id = a.customerId AND cp.pet_name = p.petName
|
||||
);
|
||||
|
||||
-- Step 2: Link the appointment to the customer_pet
|
||||
INSERT INTO appointment_customer_pet (appointment_id, customer_pet_id)
|
||||
SELECT ap.appointmentId, cp.customer_pet_id
|
||||
FROM appointmentPet ap
|
||||
@@ -24,5 +19,4 @@ WHERE NOT EXISTS (
|
||||
WHERE acp.appointment_id = ap.appointmentId AND acp.customer_pet_id = cp.customer_pet_id
|
||||
);
|
||||
|
||||
-- Step 3: Remove the old legacy relationships so it strictly uses the new ones
|
||||
DELETE FROM appointmentPet;
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
-- V18: Normalize past appointments and resolve initial employee double-bookings
|
||||
|
||||
-- Part 1: Normalize past appointments.
|
||||
-- Any appointment that is still 'Booked' but the date/time has passed should be marked as 'Missed'.
|
||||
UPDATE appointment
|
||||
SET appointmentStatus = 'Missed'
|
||||
WHERE LOWER(appointmentStatus) = 'booked'
|
||||
@@ -10,8 +6,6 @@ WHERE LOWER(appointmentStatus) = 'booked'
|
||||
OR (appointmentDate = CURRENT_DATE AND appointmentTime < CURRENT_TIME)
|
||||
);
|
||||
|
||||
-- Part 2: Resolve potential double-bookings caused by V15's simple backfill.
|
||||
-- MySQL Error 1093 workaround: wrap same-table subqueries in derived tables.
|
||||
UPDATE appointment a1
|
||||
JOIN (
|
||||
SELECT a3.appointmentId
|
||||
|
||||
@@ -90,7 +90,7 @@ class AdoptionServiceTest {
|
||||
void createAdoptionAutoAssignsFirstStaffEmployee() {
|
||||
when(petRepository.findById(1L)).thenReturn(Optional.of(pet));
|
||||
when(customerRepository.findById(1L)).thenReturn(Optional.of(customer));
|
||||
// resolveAdoptionEmployee filters for staff
|
||||
|
||||
when(employeeRepository.findAllByIsActiveTrueOrderByEmployeeIdAsc()).thenReturn(List.of(adminEmployee, staffEmployee));
|
||||
when(adoptionRepository.save(any(Adoption.class))).thenAnswer(invocation -> {
|
||||
Adoption adoption = invocation.getArgument(0);
|
||||
|
||||
@@ -22,7 +22,6 @@ public class AppointmentDTO {
|
||||
private SimpleStringProperty appointmentTime;
|
||||
private SimpleStringProperty appointmentStatus;
|
||||
|
||||
// Constructor
|
||||
public AppointmentDTO(int appointmentId,
|
||||
int customerId, String customerName,
|
||||
int petId, String petName,
|
||||
@@ -47,7 +46,6 @@ public class AppointmentDTO {
|
||||
this.appointmentStatus = new SimpleStringProperty(appointmentStatus);
|
||||
}
|
||||
|
||||
// Getters
|
||||
public int getAppointmentId() { return appointmentId.get(); }
|
||||
|
||||
public int getCustomerId() { return customerId.get(); }
|
||||
|
||||
@@ -68,7 +68,7 @@ public class AdoptionController {
|
||||
void initialize() {
|
||||
btnEdit.setDisable(true);
|
||||
btnDelete.setDisable(true);
|
||||
//Enable multiple selection
|
||||
|
||||
tvAdoptions.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
||||
|
||||
colAdoptionId.setCellValueFactory(new PropertyValueFactory<>("adoptionId"));
|
||||
@@ -91,7 +91,6 @@ public class AdoptionController {
|
||||
displayFilteredAdoptions(newValue);
|
||||
});
|
||||
|
||||
//EventListener for DELETE key
|
||||
tvAdoptions.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||
if (tvAdoptions.getSelectionModel().getSelectedItem() != null) {
|
||||
@@ -109,11 +108,10 @@ public class AdoptionController {
|
||||
|
||||
@FXML
|
||||
void btnDeleteClicked(ActionEvent event) {
|
||||
//get selected adoptions
|
||||
|
||||
var selectedAdoptions = tvAdoptions.getSelectionModel().getSelectedItems();
|
||||
if (selectedAdoptions.isEmpty()) return;
|
||||
|
||||
//ask user to confirm
|
||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
question.setHeaderText("Please confirm delete");
|
||||
String message = selectedAdoptions.size() == 1
|
||||
@@ -123,7 +121,6 @@ public class AdoptionController {
|
||||
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
||||
Optional<ButtonType> result = question.showAndWait();
|
||||
|
||||
//if confirmed, start deletion
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
List<Long> ids = selectedAdoptions.stream()
|
||||
.map(a -> (long) a.getAdoptionId())
|
||||
@@ -146,7 +143,6 @@ public class AdoptionController {
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
//refresh display and reset inputs
|
||||
displayAdoptions();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
|
||||
@@ -47,7 +47,7 @@ public class AppointmentController {
|
||||
|
||||
@FXML
|
||||
public void initialize(){
|
||||
//Enable multiple selection
|
||||
|
||||
tvAppointments.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
||||
|
||||
colAppointmentId.setCellValueFactory(new PropertyValueFactory<>("appointmentId"));
|
||||
@@ -66,7 +66,6 @@ public class AppointmentController {
|
||||
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
|
||||
}
|
||||
|
||||
//EventListener for DELETE key
|
||||
tvAppointments.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||
if (tvAppointments.getSelectionModel().getSelectedItem() != null) {
|
||||
@@ -148,11 +147,10 @@ public class AppointmentController {
|
||||
|
||||
@FXML
|
||||
void btnDeleteClicked(ActionEvent event){
|
||||
//get selected appointments
|
||||
|
||||
var selectedAppointments = tvAppointments.getSelectionModel().getSelectedItems();
|
||||
if (selectedAppointments.isEmpty()) return;
|
||||
|
||||
//ask user to confirm
|
||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
question.setHeaderText("Please confirm delete");
|
||||
String message = selectedAppointments.size() == 1
|
||||
@@ -162,7 +160,6 @@ public class AppointmentController {
|
||||
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
||||
java.util.Optional<ButtonType> result = question.showAndWait();
|
||||
|
||||
//if confirmed, start deletion
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
List<Long> ids = selectedAppointments.stream()
|
||||
.map(a -> (long) a.getAppointmentId())
|
||||
@@ -185,7 +182,6 @@ public class AppointmentController {
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
//refresh display
|
||||
loadAppointments();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import java.util.Objects;
|
||||
|
||||
public class AdoptionDialogController {
|
||||
|
||||
//FXML elements
|
||||
@FXML
|
||||
private Button btnCancel;
|
||||
|
||||
@@ -56,11 +55,9 @@ public class AdoptionDialogController {
|
||||
@FXML
|
||||
private Label lblMode;
|
||||
|
||||
//Stores if the dialog view is in add/edit mode
|
||||
private String mode = null;
|
||||
private Adoption selectedAdoption = null;
|
||||
|
||||
//Adoption statuses
|
||||
private ObservableList<String> statusList = FXCollections.observableArrayList(
|
||||
"Pending", "Completed", "Cancelled"
|
||||
);
|
||||
@@ -234,7 +231,6 @@ public class AdoptionDialogController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void closeStage(MouseEvent mouseEvent) {
|
||||
Node node = (Node) mouseEvent.getSource();
|
||||
Stage stage = (Stage) node.getScene().getWindow();
|
||||
|
||||
@@ -26,10 +26,6 @@ import java.util.Objects;
|
||||
|
||||
public class AppointmentDialogController {
|
||||
|
||||
// ============================
|
||||
// FXML
|
||||
// ============================
|
||||
|
||||
@FXML private Button btnCancel;
|
||||
@FXML private Button btnSave;
|
||||
|
||||
@@ -47,11 +43,7 @@ public class AppointmentDialogController {
|
||||
@FXML private Label lblAppointmentId;
|
||||
@FXML private Label lblMode;
|
||||
|
||||
// ============================
|
||||
// DATA
|
||||
// ============================
|
||||
|
||||
private String mode = null; // Add | Edit
|
||||
private String mode = null;
|
||||
private AppointmentDTO selectedAppointment = null;
|
||||
private Long pendingPetSelectionId = null;
|
||||
|
||||
@@ -60,20 +52,12 @@ public class AppointmentDialogController {
|
||||
"Booked", "Completed", "Cancelled", "Missed"
|
||||
);
|
||||
|
||||
//
|
||||
// MODE
|
||||
//
|
||||
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
lblMode.setText(mode + " Appointment");
|
||||
lblAppointmentId.setVisible(!mode.equals("Add"));
|
||||
}
|
||||
|
||||
//
|
||||
// INITIALIZE
|
||||
//
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
cbAppointmentStatus.setItems(statusList);
|
||||
@@ -85,14 +69,12 @@ public class AppointmentDialogController {
|
||||
dpAppointmentDate.setValue(LocalDate.now().plusDays(1));
|
||||
cbAppointmentStatus.setValue("Booked");
|
||||
|
||||
// Hours 9 AM - 5 PM
|
||||
for (int i = 9; i <= 17; i++) {
|
||||
cbHour.getItems().add(i);
|
||||
}
|
||||
|
||||
cbMinute.getItems().addAll(0, 15, 30, 45);
|
||||
|
||||
// Show dropdown labels
|
||||
cbService.setCellFactory(param -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption option, boolean empty) {
|
||||
@@ -175,10 +157,6 @@ public class AppointmentDialogController {
|
||||
loadEmployees();
|
||||
}
|
||||
|
||||
//
|
||||
// DISPLAY FOR EDIT
|
||||
//
|
||||
|
||||
public void displayAppointmentDetails(AppointmentDTO appt) {
|
||||
|
||||
selectedAppointment = appt;
|
||||
@@ -214,10 +192,6 @@ public class AppointmentDialogController {
|
||||
applySelectedEmployee();
|
||||
}
|
||||
|
||||
//
|
||||
// SAVE
|
||||
//
|
||||
|
||||
private void buttonSaveClicked(MouseEvent e) {
|
||||
|
||||
if (cbService.getValue() == null ||
|
||||
@@ -276,10 +250,6 @@ public class AppointmentDialogController {
|
||||
}).start();
|
||||
}
|
||||
|
||||
//
|
||||
// UTIL
|
||||
//
|
||||
|
||||
private void closeStage(MouseEvent e) {
|
||||
Stage stage = (Stage) ((Node) e.getSource()).getScene().getWindow();
|
||||
stage.close();
|
||||
|
||||
Reference in New Issue
Block a user